diff --git a/.gitignore b/.gitignore index 5761abc..78be072 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ *.o +Makefile.POSIX +cmake-build-debug +build + diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..05aee6c --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +DarwinStreamingServer \ No newline at end of file diff --git a/.idea/DSS6.0.3.iml b/.idea/DSS6.0.3.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/DSS6.0.3.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..da50b0d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/APIModules/OSMemory_Modules/OSMemory_Modules.cpp b/APIModules/OSMemory_Modules/OSMemory_Modules.cpp new file mode 100644 index 0000000..e9fcc48 --- /dev/null +++ b/APIModules/OSMemory_Modules/OSMemory_Modules.cpp @@ -0,0 +1,72 @@ +/* + * + * @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: OSMemory_Modules.cpp + + Contains: + + +*/ + +#include "OSMemory.h" +#include "QTSS.h" + +#if MEMORY_DEBUGGING + +void* operator new(size_t s, char* /*inFile*/, int /*inLine*/) +{ + return QTSS_New (FOUR_CHARS_TO_INT('0', '0', '0', '0'), s); +} + +void* operator new[](size_t s, char* /*inFile*/, int /*inLine*/) +{ + return QTSS_New (FOUR_CHARS_TO_INT('0', '0', '0', '0'), s); +} + +#else + +void* operator new (size_t s) +{ + return QTSS_New (FOUR_CHARS_TO_INT('0', '0', '0', '0'), s); +} + +void* operator new[](size_t s) +{ + return QTSS_New (FOUR_CHARS_TO_INT('0', '0', '0', '0'), s); +} + +void operator delete(void* mem) +{ + QTSS_Delete(mem); +} + +void operator delete[](void* mem) +{ + QTSS_Delete(mem); +} + + +#endif //__OS_MEMORY_H__ + diff --git a/APIModules/QTSSAccessLogModule/QTSSAccessLogModule.cpp b/APIModules/QTSSAccessLogModule/QTSSAccessLogModule.cpp new file mode 100644 index 0000000..1e49a3f --- /dev/null +++ b/APIModules/QTSSAccessLogModule/QTSSAccessLogModule.cpp @@ -0,0 +1,1020 @@ +/* + * + * @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: QTSSAccessLogModule.cpp + + Contains: Implementation of an RTP access log module. + + +*/ + +#include "QTSSAccessLogModule.h" +#include "QTSSModuleUtils.h" +#include "QTSSRollingLog.h" +#include "OSMutex.h" +#include "MyAssert.h" +#include "OSMemory.h" +#include +#include "StringParser.h" +#include "StringFormatter.h" +#include "StringTranslator.h" +#include "StrPtrLen.h" +#include "UserAgentParser.h" +#include "Task.h" + +#define TESTUNIXTIME 0 + +class QTSSAccessLog; +class LogCheckTask; + +// STATIC DATA + +// Default values for preferences +static Bool16 sDefaultLogEnabled = true; +static char* sDefaultLogName = "StreamingServer"; +static char* sDefaultLogDir = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; + +static UInt32 sDefaultMaxLogBytes = 10240000; +static UInt32 sDefaultRollInterval = 7; +static char* sVoidField = "-"; +static Bool16 sStartedUp = false; +static Bool16 sDefaultLogTimeInGMT = true; + +static QTSS_AttributeID sLoggedAuthorizationAttrID = qtssIllegalAttrID; + +// Current values for preferences +static Bool16 sLogEnabled = true; +static UInt32 sMaxLogBytes = 51200000; +static UInt32 sRollInterval = 7; +static Bool16 sLogTimeInGMT = true; + +static OSMutex* sLogMutex = NULL;//Log module isn't reentrant +static QTSSAccessLog* sAccessLog = NULL; +static QTSS_ServerObject sServer = NULL; +static QTSS_ModulePrefsObject sPrefs = NULL; +static LogCheckTask* sLogCheckTask = NULL; + +// This header conforms to the W3C "Extended Log File Format". +// (See "http://www.w3.org/TR/WD-logfile.html" for details.) +// The final remark filed of the log header tells us if the logged times are in GMT or in system local time. +static char* sLogHeader = "#Software: %s\n" + "#Version: %s\n" //%s == version + "#Date: %s\n" //%s == date/time + "#Remark: all time values are in %s.\n" //%s == qtss_localtime or GMT + "#Fields: c-ip date time c-dns cs-uri-stem c-starttime x-duration c-rate c-status c-playerid" + " c-playerversion c-playerlanguage cs(User-Agent) c-os" + " c-osversion c-cpu filelength filesize avgbandwidth protocol transport audiocodec videocodec" + " sc-bytes cs-bytes c-bytes s-pkts-sent c-pkts-received c-pkts-lost-client c-buffercount" + " c-totalbuffertime c-quality s-ip s-dns s-totalclients s-cpu-util cs-uri-query c-username sc(Realm) \n"; + + + +//************************************************** +// CLASS DECLARATIONS +//************************************************** + +class LogCheckTask : public Task +{ + public: + LogCheckTask() : Task() {this->SetTaskName("LogCheckTask"); this->Signal(Task::kStartEvent); } + virtual ~LogCheckTask() {} + + private: + virtual SInt64 Run(); +}; + +class QTSSAccessLog : public QTSSRollingLog +{ + public: + + QTSSAccessLog() : QTSSRollingLog() { this->SetTaskName("QTSSAccessLog"); } + virtual ~QTSSAccessLog() {} + + virtual char* GetLogName() { return QTSSModuleUtils::GetStringAttribute(sPrefs, "request_logfile_name", sDefaultLogName); } + virtual char* GetLogDir() { return QTSSModuleUtils::GetStringAttribute(sPrefs, "request_logfile_dir", sDefaultLogDir); } + virtual UInt32 GetRollIntervalInDays() { return sRollInterval; } + virtual UInt32 GetMaxLogBytes() { return sMaxLogBytes; } + virtual time_t WriteLogHeader(FILE *inFile); + +}; + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSAccessLogModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error RereadPrefs(); +static QTSS_Error Shutdown(); +static QTSS_Error PostProcess(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error ClientSessionClosing(QTSS_ClientSessionClosing_Params* inParams); +static QTSS_Error LogRequest( QTSS_ClientSessionObject inClientSession, + QTSS_RTSPSessionObject inRTSPSession,QTSS_CliSesClosingReason *inCloseReasonPtr); +static void CheckAccessLogState(Bool16 forceEnabled); +static QTSS_Error RollAccessLog(QTSS_ServiceFunctionArgsPtr inArgs); +static void ReplaceSpaces(StrPtrLen *sourcePtr, StrPtrLen *destPtr, char *replaceStr); + +static QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams); +static void WriteStartupMessage(); +static void WriteShutdownMessage(); + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSAccessLogModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSAccessLogModuleDispatch); +} + +QTSS_Error QTSSAccessLogModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParamBlock->regParams); + case QTSS_StateChange_Role: + return StateChange(&inParamBlock->stateChangeParams); + case QTSS_Initialize_Role: + return Initialize(&inParamBlock->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPPostProcessor_Role: + return PostProcess(&inParamBlock->rtspPostProcessorParams); + case QTSS_ClientSessionClosing_Role: + return ClientSessionClosing(&inParamBlock->clientSessionClosingParams); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + sLogMutex = NEW OSMutex(); + + // Do role & service setup + + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RTSPPostProcessor_Role); + (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_Shutdown_Role); + (void)QTSS_AddRole(QTSS_StateChange_Role); + + (void)QTSS_AddService("RollAccessLog", &RollAccessLog); + + // Tell the server our name! + static char* sModuleName = "QTSSAccessLogModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + static char* sLoggedAuthorizationName = "QTSSAccessLogModuleLoggedAuthorization"; + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sLoggedAuthorizationName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sLoggedAuthorizationName, &sLoggedAuthorizationAttrID); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sServer = inParams->inServer; + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + sServerPrefs = inParams->inPrefs; + + RereadPrefs(); + WriteStartupMessage(); + sLogCheckTask = NEW LogCheckTask(); + return QTSS_NoErr; +} + + +QTSS_Error RereadPrefs() +{ + delete [] sDefaultLogDir; + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorLogDir, 0, &sDefaultLogDir); + + QTSSModuleUtils::GetAttribute(sPrefs, "request_logging", qtssAttrDataTypeBool16, + &sLogEnabled, &sDefaultLogEnabled, sizeof(sLogEnabled)); + QTSSModuleUtils::GetAttribute(sPrefs, "request_logfile_size", qtssAttrDataTypeUInt32, + &sMaxLogBytes, &sDefaultMaxLogBytes, sizeof(sMaxLogBytes)); + QTSSModuleUtils::GetAttribute(sPrefs, "request_logfile_interval", qtssAttrDataTypeUInt32, + &sRollInterval, &sDefaultRollInterval, sizeof(sRollInterval)); + QTSSModuleUtils::GetAttribute(sPrefs, "request_logtime_in_gmt", qtssAttrDataTypeBool16, + &sLogTimeInGMT, &sDefaultLogTimeInGMT, sizeof(sLogTimeInGMT)); + + CheckAccessLogState(false); + + return QTSS_NoErr; +} + + +QTSS_Error Shutdown() +{ + WriteShutdownMessage(); + if (sLogCheckTask != NULL) + { + //sLogCheckTask is a task object, so don't delete it directly + // instead we signal it to kill itself. + sLogCheckTask->Signal(Task::kKillEvent); + sLogCheckTask = NULL; + } + return QTSS_NoErr; +} + +QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams) +{ + if (stateChangeParams->inNewState == qtssIdleState) + WriteShutdownMessage(); + else if (stateChangeParams->inNewState == qtssRunningState) + WriteStartupMessage(); + + return QTSS_NoErr; +} + + + +QTSS_Error PostProcess(QTSS_StandardRTSP_Params* inParams) +{ + static UInt32 sZero = 0; + + UInt32* theStatus = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqRealStatusCode, 0, (void**)&theStatus, &theLen); + if (theErr != QTSS_NoErr) + return theErr; + + QTSS_CliSesClosingReason theReason = qtssCliSesCloseClientTeardown; + + if ((*theStatus == 401) || (*theStatus == 403)) + { + LogRequest(inParams->inClientSession, NULL, &theReason); + (void)QTSS_SetValue(inParams->inClientSession, sLoggedAuthorizationAttrID, 0, theStatus, sizeof(*theStatus)); + } + else + (void)QTSS_SetValue(inParams->inClientSession, sLoggedAuthorizationAttrID, 0, &sZero, sizeof(sZero)); + + return theErr; +} + +#if TESTUNIXTIME +void TestUnixTime(time_t theTime, char *ioDateBuffer); +void TestUnixTime(time_t theTime, char *ioDateBuffer) +{ + Assert(NULL != ioDateBuffer); + + //use ansi routines for getting the date. + time_t calendarTime = theTime; + Assert(-1 != calendarTime); + if (-1 == calendarTime) + return; + + struct tm timeResult; + struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult); + Assert(NULL != theLocalTime); + if (NULL == theLocalTime) + return; + + //date needs to look like this for common log format: 29/Sep/1998:11:34:54 -0700 + //this wonderful ANSI routine just does it for you. + //qtss_strftime(ioDateBuffer, kMaxDateBufferSize, "%d/%b/%Y:%H:%M:%S", theLocalTime); + qtss_strftime(ioDateBuffer,QTSSRollingLog::kMaxDateBufferSizeInBytes, "%Y-%m-%d %H:%M:%S", theLocalTime); + return; +} +#endif + + +QTSS_Error ClientSessionClosing(QTSS_ClientSessionClosing_Params* inParams) +{ + return LogRequest(inParams->inClientSession, NULL, &inParams->inReason); +} + +void ReplaceSpaces(StrPtrLen *sourcePtr, StrPtrLen *destPtr, char *replaceStr) +{ + + if ( (NULL != destPtr) && (NULL != destPtr->Ptr) && (0 < destPtr->Len) ) destPtr->Ptr[0] = 0; + do + { + if ( (NULL == sourcePtr) + || (NULL == destPtr) + || (NULL == sourcePtr->Ptr) + || (NULL == destPtr->Ptr) + || (0 == sourcePtr->Len) + || (0 == destPtr->Len) + ) break; + + if (0 == sourcePtr->Ptr[0]) + { + destPtr->Len = 0; + break; + } + + const StrPtrLen replaceValue(replaceStr); + StringFormatter formattedString(destPtr->Ptr, destPtr->Len); + StringParser sourceStringParser(sourcePtr); + StrPtrLen preStopChars; + + do + { sourceStringParser.ConsumeUntil(&preStopChars, StringParser::sEOLWhitespaceMask); + if (preStopChars.Len > 0) + { formattedString.Put(preStopChars);// copy the string up to the space or eol. it will be truncated if there's not enough room. + if ( sourceStringParser.Expect(' ') && (formattedString.GetSpaceLeft() > replaceValue.Len) ) + { formattedString.Put(replaceValue.Ptr, replaceValue.Len); + } + else //no space character or no room for replacement + { break; + } + } + + } while ( preStopChars.Len != 0); + + destPtr->Set(formattedString.GetBufPtr(), formattedString.GetBytesWritten() ); + + } while (false); +} + + +QTSS_Error LogRequest( QTSS_ClientSessionObject inClientSession, + QTSS_RTSPSessionObject inRTSPSession, QTSS_CliSesClosingReason *inCloseReasonPtr) +{ + static StrPtrLen sUnknownStr(sVoidField); + static StrPtrLen sTCPStr("TCP"); + static StrPtrLen sUDPStr("UDP"); + + //Fetch the URL, user agent, movielength & movie bytes to log out of the RTP session + enum { + eTempLogItemSize = 256, // must be same or larger than others + eURLSize = 256, + eUserAgentSize = 256, + ePlayerIDSize = 32, + ePlayerVersionSize = 32, + ePlayerLangSize = 32, + ePlayerOSSize = 32, + ePlayerOSVersSize = 32, + ePlayerCPUSize = 32 + }; + + char tempLogItemBuf[eTempLogItemSize] = { 0 }; + StrPtrLen tempLogStr(tempLogItemBuf, eTempLogItemSize -1); + + // + // Check to see if this session is closing because authorization failed. If that's + // the case, we've logged that already, let's not log it twice + + UInt32 theLen = 0; + UInt32* authorizationFailed = NULL; + (void)QTSS_GetValuePtr(inClientSession, sLoggedAuthorizationAttrID, 0, (void**)&authorizationFailed, &theLen); + if ((authorizationFailed != NULL) && (*authorizationFailed > 0)) + return QTSS_NoErr; + + ///inClientSession should never be NULL + //inRTSPRequest may be NULL if this is a timeout + + OSMutexLocker locker(sLogMutex); + CheckAccessLogState(false); + if (sAccessLog == NULL) + return QTSS_NoErr; + + //if logging is on, then log the request... first construct a timestamp + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, sLogTimeInGMT); + + + //for now, just ignore the error. + if (!result) + theDateBuffer[0] = '\0'; + + theLen = sizeof(QTSS_RTSPSessionObject); + QTSS_RTSPSessionObject theRTSPSession = inRTSPSession; + if (theRTSPSession == NULL) + (void)QTSS_GetValue(inClientSession, qtssCliSesLastRTSPSession, 0, (void*) &theRTSPSession, &theLen); + + // Get lots of neat info to log from the various dictionaries + + // Each attribute must be copied out to ensure that it is NULL terminated. + // To ensure NULL termination, just memset the buffers to 0, and make sure that + // the last byte of each array is untouched. + + Float32* packetLossPercent = NULL; + Float64* movieDuration = NULL; + UInt64* movieSizeInBytes = NULL; + UInt32* movieAverageBitRatePtr = 0; + UInt32 clientPacketsReceived = 0; + UInt32 clientPacketsLost = 0; + StrPtrLen* theTransportType = &sUnknownStr; + SInt64* theCreateTime = NULL; + SInt64* thePlayTime = NULL; + + UInt32 startPlayTimeInSecs = 0; + + char localIPAddrBuf[20] = { 0 }; + StrPtrLen localIPAddr(localIPAddrBuf, 19); + + char localDNSBuf[70] = { 0 }; + StrPtrLen localDNS(localDNSBuf, 69); + + char remoteDNSBuf[70] = { 0 }; + StrPtrLen remoteDNS(remoteDNSBuf, 69); + + char remoteAddrBuf[20] = { 0 }; + StrPtrLen remoteAddr(remoteAddrBuf, 19); + + char playerIDBuf[ePlayerIDSize] = { 0 }; + StrPtrLen playerID(playerIDBuf, ePlayerIDSize -1) ; + + // First, get networking info from the RTSP session + (void)QTSS_GetValue(inClientSession, qtssCliRTSPSessLocalAddrStr, 0, localIPAddr.Ptr, &localIPAddr.Len); + (void)QTSS_GetValue(inClientSession, qtssCliRTSPSessLocalDNS, 0, localDNS.Ptr, &localDNS.Len); + (void)QTSS_GetValue(inClientSession, qtssCliSesHostName, 0, remoteDNS.Ptr, &remoteDNS.Len); + (void)QTSS_GetValue(inClientSession, qtssCliRTSPSessRemoteAddrStr, 0, remoteAddr.Ptr, &remoteAddr.Len); + (void)QTSS_GetValue(inClientSession, qtssCliRTSPSessRemoteAddrStr, 0, playerID.Ptr, &playerID.Len); + + UInt32* rtpBytesSent = NULL; + UInt32* rtcpBytesRecv = NULL; + UInt32* rtpPacketsSent = NULL; + UInt32 clientBytesRecv = 0; + + // Second, get networking info from the Client's session. + // (Including the stats for incoming RTCP packets.) + char urlBuf[eURLSize] = { 0 }; + StrPtrLen url(urlBuf, eURLSize -1); + (void)QTSS_GetValue(inClientSession, qtssCliSesPresentationURL, 0, url.Ptr, &url.Len); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesPacketLossPercent, 0, (void**)&packetLossPercent, &theLen); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesMovieDurationInSecs, 0, (void**)&movieDuration, &theLen); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesMovieSizeInBytes, 0, (void**)&movieSizeInBytes, &theLen); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesMovieAverageBitRate, 0, (void**)&movieAverageBitRatePtr, &theLen); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesCreateTimeInMsec, 0, (void**)&theCreateTime, &theLen); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesFirstPlayTimeInMsec, 0, (void**)&thePlayTime, &theLen); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesRTPBytesSent, 0, (void**)&rtpBytesSent, &theLen); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesRTPPacketsSent, 0, (void**)&rtpPacketsSent, &theLen); + (void)QTSS_GetValuePtr(inClientSession, qtssCliSesRTCPBytesRecv, 0, (void**)&rtcpBytesRecv, &theLen); + + if (theCreateTime != NULL && thePlayTime != NULL) + startPlayTimeInSecs = (UInt32)(((*theCreateTime - *thePlayTime)/1000)+0.5); + + + // We need a value of 'c-bytes' to report as a log entry. This is supposed to be the total number + // of bytes the client has received during the session. Unfortunately, the QT client does not give + // us this number. We will use the following heuristic formula to estimate the number of bytes the + // client has received during the session: + // + // client-bytes-received = bytes-sent * (100.0 - percent-packet-lost) / 100.0 + // + // The 'percent-packet-lost' value has been calculated internally by QTSS based on the RTCP packets + // sent to the server from the client. If those values are accurate then the above formula will not + // be exactly correct but it will be nearly correct. + + clientBytesRecv = (UInt32)((*rtpBytesSent * (100.0 - *packetLossPercent))/100.0); + + tempLogStr.Ptr[0] = 0; tempLogStr.Len = eUserAgentSize; + (void)QTSS_GetValue(inClientSession, qtssCliSesFirstUserAgent, 0, tempLogStr.Ptr, &tempLogStr.Len); + + char userAgentBuf[eUserAgentSize] = { 0 }; + StrPtrLen userAgent(userAgentBuf, eUserAgentSize -1); + ReplaceSpaces(&tempLogStr, &userAgent, "%20"); + + UserAgentParser userAgentParser(&userAgent); + +// StrPtrLen* playerID = userAgentParser.GetUserID() ; + StrPtrLen* playerVersion = userAgentParser.GetUserVersion() ; + StrPtrLen* playerLang = userAgentParser.GetUserLanguage() ; + StrPtrLen* playerOS = userAgentParser.GetrUserOS() ; + StrPtrLen* playerOSVers = userAgentParser.GetUserOSVersion() ; + StrPtrLen* playerCPU = userAgentParser.GetUserCPU() ; + +// char playerIDBuf[ePlayerIDSize] = {}; + char playerVersionBuf[ePlayerVersionSize] = { 0 }; + char playerLangBuf[ePlayerLangSize] = { 0 }; + char playerOSBuf[ePlayerOSSize] = { 0 }; + char playerOSVersBuf[ePlayerOSVersSize] = { 0 }; + char playerCPUBuf[ePlayerCPUSize] = { 0 }; + + UInt32 size; +// (ePlayerIDSize < playerID->Len ) ? size = ePlayerIDSize -1 : size = playerID->Len; +// if (playerID->Ptr != NULL) memcpy (playerIDBuf, playerID->Ptr, size); + + (ePlayerVersionSize < playerVersion->Len ) ? size = ePlayerVersionSize -1 : size = playerVersion->Len; + if (playerVersion->Ptr != NULL) memcpy (playerVersionBuf, playerVersion->Ptr, size); + + (ePlayerLangSize < playerLang->Len ) ? size = ePlayerLangSize -1 : size = playerLang->Len; + if (playerLang->Ptr != NULL) memcpy (playerLangBuf, playerLang->Ptr, size); + + (ePlayerOSSize < playerOS->Len ) ? size = ePlayerOSSize -1 : size = playerOS->Len; + if (playerOS->Ptr != NULL) memcpy (playerOSBuf, playerOS->Ptr, size); + + (ePlayerOSVersSize < playerOSVers->Len ) ? size = ePlayerOSVersSize -1 : size = playerOSVers->Len; + if (playerOSVers->Ptr != NULL) memcpy (playerOSVersBuf, playerOSVers->Ptr, size); + + (ePlayerCPUSize < playerCPU->Len ) ? size = ePlayerCPUSize -1 : size = playerCPU->Len; + if (playerCPU->Ptr != NULL) memcpy (playerCPUBuf, playerCPU->Ptr, size); + + + // clientPacketsReceived, clientPacketsLost, videoPayloadName and audioPayloadName + // are all stored on a per-stream basis, so let's iterate through all the streams, + // finding this information + + char videoPayloadNameBuf[32] = { 0 }; + StrPtrLen videoPayloadName(videoPayloadNameBuf, 31); + + char audioPayloadNameBuf[32] = { 0 }; + StrPtrLen audioPayloadName(audioPayloadNameBuf, 31); + + UInt32 qualityLevel = 0; + UInt32 clientBufferTime = 0; + UInt32 theStreamIndex = 0; + Bool16* isTCPPtr = NULL; + QTSS_RTPStreamObject theRTPStreamObject = NULL; + + for ( UInt32 theStreamObjectLen = sizeof(theRTPStreamObject); + QTSS_GetValue(inClientSession, qtssCliSesStreamObjects, theStreamIndex, (void*)&theRTPStreamObject, &theStreamObjectLen) == QTSS_NoErr; + theStreamIndex++, theStreamObjectLen = sizeof(theRTPStreamObject)) + { + + UInt32* streamPacketsReceived = NULL; + UInt32* streamPacketsLost = NULL; + (void)QTSS_GetValuePtr(theRTPStreamObject, qtssRTPStrTotPacketsRecv, 0, (void**)&streamPacketsReceived, &theLen); + (void)QTSS_GetValuePtr(theRTPStreamObject, qtssRTPStrTotalLostPackets, 0, (void**)&streamPacketsLost, &theLen); + + // Add up packets received and packets lost to come up with a session wide total + if (streamPacketsReceived != NULL) + clientPacketsReceived += *streamPacketsReceived; + if (streamPacketsLost != NULL) + clientPacketsLost += *streamPacketsLost; + + // Identify the video and audio codec types + QTSS_RTPPayloadType* thePayloadType = NULL; + (void)QTSS_GetValuePtr(theRTPStreamObject, qtssRTPStrPayloadType, 0, (void**)&thePayloadType, &theLen); + if (thePayloadType != NULL) + { + if (*thePayloadType == qtssVideoPayloadType) + (void)QTSS_GetValue(theRTPStreamObject, qtssRTPStrPayloadName, 0, videoPayloadName.Ptr, &videoPayloadName.Len); + else if (*thePayloadType == qtssAudioPayloadType) + (void)QTSS_GetValue(theRTPStreamObject, qtssRTPStrPayloadName, 0, audioPayloadName.Ptr, &audioPayloadName.Len); + } + + // If any one of the streams is being delivered over UDP instead of TCP, + // report in the log that the transport type for this session was UDP. + if (isTCPPtr == NULL) + { + (void)QTSS_GetValuePtr(theRTPStreamObject, qtssRTPStrIsTCP, 0, (void**)&isTCPPtr, &theLen); + if (isTCPPtr != NULL) + { if (*isTCPPtr == false) + theTransportType = &sUDPStr; + else + theTransportType = &sTCPStr; + } + } + + Float32* clientBufferTimePtr = NULL; + (void)QTSS_GetValuePtr(theRTPStreamObject, qtssRTPStrBufferDelayInSecs, 0, (void**)&clientBufferTimePtr, &theLen); + if ( (clientBufferTimePtr != NULL) && (*clientBufferTimePtr != 0) ) + { if ( *clientBufferTimePtr > clientBufferTime) + clientBufferTime = (UInt32) (*clientBufferTimePtr + .5); // round up to full seconds + } + + } + + // Add the client buffer time to our client start latency (in whole seconds). + startPlayTimeInSecs += clientBufferTime; + + if (*rtpPacketsSent == 0) // no packets sent + qualityLevel = 0; // no quality + else + { + if ( (clientPacketsReceived == 0) && (clientPacketsLost == 0) ) // no info from client + qualityLevel = 100; //so assume 100 + else + { + float qualityPercent = (float) clientPacketsReceived / (float) (clientPacketsReceived + clientPacketsLost); + qualityPercent += (float).005; // round up + qualityLevel = (UInt32) ( (float) 100.0 * qualityPercent); // average of sum of packet counts for all streams + } + } + + //we may not have an RTSP request. Assume that the status code is 504 timeout, if there is an RTSP + //request, though, we can find out what the real status code of the response is + static UInt32 sTimeoutCode = 504; + UInt32* theStatusCode = &sTimeoutCode; + theLen = sizeof(UInt32); + (void)QTSS_GetValuePtr(inClientSession, qtssCliRTSPReqRealStatusCode, 0, (void **) &theStatusCode, &theLen); +// qtss_printf("qtssCliRTSPReqRealStatusCode = %"_U32BITARG_" \n", *theStatusCode); + + + if (inCloseReasonPtr) do + { + if (*theStatusCode < 300) // it was a succesful RTSP request but... + { + if (*inCloseReasonPtr == qtssCliSesCloseTimeout) // there was a timeout + { + *theStatusCode = sTimeoutCode; +// qtss_printf(" log timeout "); + break; + } + + if (*inCloseReasonPtr == qtssCliSesCloseClientTeardown) // there was a teardown + { + + static QTSS_CliSesClosingReason sReason = qtssCliSesCloseClientTeardown; + QTSS_CliSesClosingReason* theReasonPtr = &sReason; + theLen = sizeof(QTSS_CliSesTeardownReason); + (void)QTSS_GetValuePtr(inClientSession, qtssCliTeardownReason, 0, (void **) &theReasonPtr, &theLen); +// qtss_printf("qtssCliTeardownReason = %"_U32BITARG_" \n", *theReasonPtr); + + if (*theReasonPtr == qtssCliSesTearDownClientRequest) // the client asked for a tear down + { +// qtss_printf(" client requests teardown "); + break; + } + + if (*theReasonPtr == qtssCliSesTearDownUnsupportedMedia) // An error occured while streaming the file. + { + *theStatusCode = 415; +// qtss_printf(" log UnsupportedMedia "); + break; + } + if (*theReasonPtr == qtssCliSesTearDownBroadcastEnded) // a broadcaster stopped broadcasting + { + *theStatusCode = 452; +// qtss_printf(" log broadcast removed "); + break; + } + + *theStatusCode = 500; // some unknown reason for cancelling the connection + } + +// qtss_printf("return status "); + // just use the qtssCliRTSPReqRealStatusCode for the reason + } + + } while (false); + +// qtss_printf(" = %"_U32BITARG_" \n", *theStatusCode); + + + // Find out what time it is + SInt64 curTime = QTSS_Milliseconds(); + + UInt32 numCurClients = 0; + theLen = sizeof(numCurClients); + (void)QTSS_GetValue(sServer, qtssRTPSvrCurConn, 0, &numCurClients, &theLen); + +/* + IMPORTANT!!!! + + Some values such as cpu, #conns, need to be grabbed as the session starts, not when the teardown happened (I think) + +*/ + +#if TESTUNIXTIME + char thetestDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + TestUnixTime(QTSS_MilliSecsTo1970Secs(*theCreateTime), thetestDateBuffer); + qtss_printf("%s\n",thetestDateBuffer); +#endif + + float zeroFloat = 0; + UInt64 zeroUInt64 = 0; + Float32 fcpuUtilized = 0; + UInt32 cpuUtilized = 0; // percent + + theLen = sizeof(fcpuUtilized); + (void)QTSS_GetValue(sServer, qtssSvrCPULoadPercent, 0, &fcpuUtilized, &theLen); + cpuUtilized = (UInt32)fcpuUtilized; + + char lastUserName[eTempLogItemSize] ={ 0 }; + StrPtrLen lastUserNameStr(lastUserName,eTempLogItemSize); + + char lastURLRealm[eTempLogItemSize] ={ 0 }; + StrPtrLen lastURLRealmStr(lastURLRealm,eTempLogItemSize); + + //qtss_printf("logging of saved params are in dictionary \n"); + + tempLogStr.Ptr[0] = 0; tempLogStr.Len = eTempLogItemSize; + (void)QTSS_GetValue(inClientSession, qtssCliRTSPSesUserName, 0, tempLogStr.Ptr, &tempLogStr.Len); + ReplaceSpaces(&tempLogStr, &lastUserNameStr, "%20"); + //qtss_printf("qtssRTSPSesLastUserName dictionary item = %s len = %"_S32BITARG_"\n",lastUserNameStr.Ptr,lastUserNameStr.Len); + + tempLogStr.Ptr[0] = 0; tempLogStr.Len = eTempLogItemSize; + (void)QTSS_GetValue(inClientSession, qtssCliRTSPSesURLRealm, 0, tempLogStr.Ptr, &tempLogStr.Len); + ReplaceSpaces(&tempLogStr, &lastURLRealmStr, "%20"); + //qtss_printf("qtssRTSPSesLastURLRealm dictionary item = %s len = %"_S32BITARG_"\n",lastURLRealmStr.Ptr,lastURLRealmStr.Len); + + char respMsgBuffer[1024] = { 0 }; + StrPtrLen theRespMsg; + (void)QTSS_GetValuePtr(inClientSession, qtssCliRTSPReqRespMsg, 0, (void**)&theRespMsg.Ptr, &theRespMsg.Len); + StrPtrLen respMsgEncoded(respMsgBuffer, 1024 -1); + SInt32 theErr = StringTranslator::EncodeURL(theRespMsg.Ptr, theRespMsg.Len, respMsgEncoded.Ptr, respMsgEncoded.Len); + if (theErr <= 0) + respMsgEncoded.Ptr[0] = '\0'; + else + { + respMsgEncoded.Len = theErr; + respMsgEncoded.Ptr[respMsgEncoded.Len] = '\0'; + } + + //cs-uri-query + char urlQryBuf[eURLSize] = { 0 }; + StrPtrLen urlQry(urlQryBuf, eURLSize -1); + (void)QTSS_GetValue(inClientSession, qtssCliSesReqQueryString, 0, urlQry.Ptr, &urlQry.Len); + + char tempLogBuffer[1024]; + char logBuffer[2048]; + // compatible fields (no respMsgEncoded field) + ::memset(logBuffer, 0, 2048); + qtss_sprintf(tempLogBuffer, "%s ", (remoteAddr.Ptr[0] == '\0') ? sVoidField : remoteAddr.Ptr); //c-ip* + ::strcpy(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (theDateBuffer[0] == '\0') ? sVoidField : theDateBuffer); //date* time* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (remoteDNS.Ptr[0] == '\0') ? sVoidField : remoteDNS.Ptr); //c-dns + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (url.Ptr[0] == '\0') ? sVoidField : url.Ptr); //cs-uri-stem* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", startPlayTimeInSecs); //c-starttime + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", theCreateTime == NULL ? (UInt32) 0 : (UInt32) (QTSS_MilliSecsTo1970Secs(curTime) + - QTSS_MilliSecsTo1970Secs(*theCreateTime))); //x-duration* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_S32BITARG_" ", (UInt32) 1); //c-rate + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_S32BITARG_" ", *theStatusCode); //c-status* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (playerIDBuf[0] == '\0') ? sVoidField : playerIDBuf); //c-playerid* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (playerVersionBuf[0] == '\0') ? sVoidField : playerVersionBuf); //c-playerversion + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (playerLangBuf[0] == '\0') ? sVoidField : playerLangBuf); //c-playerlanguage* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (userAgent.Ptr[0] == '\0') ? sVoidField : userAgent.Ptr); //cs(User-Agent) + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (playerOSBuf[0] == '\0') ? sVoidField : playerOSBuf); //c-os* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (playerOSVersBuf[0] == '\0') ? sVoidField : playerOSVersBuf); //c-osversion + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (playerCPUBuf[0] == '\0') ? sVoidField : playerCPUBuf); //c-cpu* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%0.0f ", movieDuration == NULL ? zeroFloat : *movieDuration); //filelength in secs* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_64BITARG_"d ", movieSizeInBytes == NULL ? zeroUInt64 : *movieSizeInBytes); //filesize in bytes* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", movieAverageBitRatePtr == NULL ? (UInt32) 0 : *movieAverageBitRatePtr); //avgbandwidth in bits per second + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", "RTP"); //protocol + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (theTransportType->Ptr[0] == '\0') ? sVoidField : theTransportType->Ptr); //transport + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (audioPayloadName.Ptr[0] == '\0') ? sVoidField : audioPayloadName.Ptr); //audiocodec* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (videoPayloadName.Ptr[0] == '\0') ? sVoidField : videoPayloadName.Ptr); //videocodec* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ",rtpBytesSent == NULL ? (UInt32) 0 : *rtpBytesSent); //sc-bytes* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ",rtcpBytesRecv == NULL ? (UInt32) 0 : *rtcpBytesRecv); //cs-bytes* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", clientBytesRecv); //c-bytes + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", rtpPacketsSent == NULL ? (UInt32)0 : *rtpPacketsSent); //s-pkts-sent* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", clientPacketsReceived); //c-pkts-recieved + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", clientPacketsLost); //c-pkts-lost-client* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", (UInt32)1); //c-buffercount + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", clientBufferTime); //c-totalbuffertime* + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", qualityLevel); //c-quality + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (localIPAddr.Ptr[0] == '\0') ? sVoidField : localIPAddr.Ptr); //s-ip + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (localDNS.Ptr[0] == '\0') ? sVoidField : localDNS.Ptr); //s-dns + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", numCurClients); //s-totalclients + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%"_U32BITARG_" ", cpuUtilized); //s-cpu-util + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (urlQry.Ptr[0] == '\0') ? sVoidField : urlQry.Ptr); //cs-uri-query + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (lastUserName[0] == '\0') ? sVoidField : lastUserName); //c-username + ::strcat(logBuffer, tempLogBuffer); + qtss_sprintf(tempLogBuffer, "%s ", (lastURLRealm[0] == '\0') ? sVoidField : lastURLRealm); //sc(Realm) + ::strcat(logBuffer, tempLogBuffer); + + ::strcat(logBuffer, "\n"); + + Assert(::strlen(logBuffer) < 2048); + + //finally, write the log message + sAccessLog->WriteToLog(logBuffer, kAllowLogToRoll); + + return QTSS_NoErr; +} + + +void CheckAccessLogState(Bool16 forceEnabled) +{ + //this function makes sure the logging state is in synch with the preferences. + //extern variable declared in QTSSPreferences.h + //check error log. + if ((NULL == sAccessLog) && (forceEnabled || sLogEnabled)) + { + sAccessLog = NEW QTSSAccessLog(); + sAccessLog->EnableLog(); + } + + if ((NULL != sAccessLog) && ((!forceEnabled) && (!sLogEnabled))) + { + sAccessLog->Delete(); //sAccessLog is a task object, so don't delete it directly + sAccessLog = NULL; + } +} + +// SERVICE ROUTINES + +QTSS_Error RollAccessLog(QTSS_ServiceFunctionArgsPtr /*inArgs*/) +{ + const Bool16 kForceEnable = true; + + OSMutexLocker locker(sLogMutex); + //calling CheckLogState is a kludge to allow logs + //to be rolled while logging is disabled. + + CheckAccessLogState(kForceEnable); + + if (sAccessLog != NULL ) + sAccessLog->RollLog(); + + CheckAccessLogState(!kForceEnable); + return QTSS_NoErr; +} + +// This task runs once an hour to check and see if the log needs to roll. +SInt64 LogCheckTask::Run() +{ + static Bool16 firstTime = true; + + // don't check the log for rolling the first time we run. + if (firstTime) + { + firstTime = false; + } + else + { + Bool16 success = false; + + if (sAccessLog != NULL && sAccessLog->IsLogEnabled()) + success = sAccessLog->CheckRollLog(); + Assert(success); + } + // execute this task again in one hour. + return (60*60*1000); +} + +time_t QTSSAccessLog::WriteLogHeader(FILE *inFile) +{ + time_t calendarTime = QTSSRollingLog::WriteLogHeader(inFile); + + //format a date for the startup time + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes] = { 0 }; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + char tempBuffer[1024] = { 0 }; + if (result) + { + StrPtrLen serverName; + (void)QTSS_GetValuePtr(sServer, qtssSvrServerName, 0, (void**)&serverName.Ptr, &serverName.Len); + StrPtrLen serverVersion; + (void)QTSS_GetValuePtr(sServer, qtssSvrServerVersion, 0, (void**)&serverVersion.Ptr, &serverVersion.Len); + qtss_sprintf(tempBuffer, sLogHeader, serverName.Ptr , serverVersion.Ptr, theDateBuffer, sLogTimeInGMT ? "GMT" : "local time"); + this->WriteToLog(tempBuffer, !kAllowLogToRoll); + } + + return calendarTime; +} + + +void WriteStartupMessage() +{ + if (sStartedUp) + return; + + sStartedUp = true; + + //format a date for the startup time + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + char tempBuffer[1024]; + if (result) + qtss_sprintf(tempBuffer, "#Remark: Streaming beginning STARTUP %s\n", theDateBuffer); + + // log startup message to error log as well. + if ((result) && (sAccessLog != NULL)) + sAccessLog->WriteToLog(tempBuffer, kAllowLogToRoll); +} + +void WriteShutdownMessage() +{ + if (!sStartedUp) + return; + + sStartedUp = false; + + //log shutdown message + //format a date for the shutdown time + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + char tempBuffer[1024]; + if (result) + qtss_sprintf(tempBuffer, "#Remark: Streaming beginning SHUTDOWN %s\n", theDateBuffer); + + if ( result && sAccessLog != NULL ) + sAccessLog->WriteToLog(tempBuffer, kAllowLogToRoll); +} + + +/* + +Log file format recognized by Lariat Stats + +#Fields: c-ip date time c-dns cs-uri-stem c-starttime x-duration c-rate c-status c-playerid c-playerversion c-playerlanguage cs(User-Agent) cs(Referer) c-hostexe c-hostexever c-os c-osversion c-cpu filelength filesize avgbandwidth protocol transport audiocodec videocodec channelURL sc-bytes c-bytes s-pkts-sent c-pkts-received c-pkts-lost-client c-pkts-lost-net c-pkts-lost-cont-net c-resendreqs c-pkts-recovered-ECC c-pkts-recovered-resent c-buffercount c-totalbuffertime c-quality s-ip s-dns s-totalclients s-cpu-util +e.g. 157.56.87.123 1998-01-19 22:53:59 foo.bar.com rtsp://ddelval1/56k_5min_1.mov 1 16 1 200 - 5.1.51.119 0409 - - dshow.exe 5.1.51.119 Windows_NT 4.0.0.1381 Pentium 78 505349 11000 RTSP UDP MPEG_Layer-3 MPEG-4_Video_High_Speed_Compressor_(MT) - 188387 188387 281 281 0 0 0 0 0 0 1 5 100 157.56.87.123 foo.bar.com 1 0 + +Notes: +In the table below, W3C/Custom - refers to whether the fields are supported by the W3C file format or if it is a Custom field +All fields are space-delimited +Fields not used by Lariat Stats should be present in the log file in some form, preferably a "-" +Log files should be named according to the format: Filename.YYMMDDNNN.log, where Filename is specific to the server type, YYMMDD is the date of creation, and NNN is a 3 digit number used when there are multiple logs from the same date (e.g. 000, 001, 002, etc.) + +Field Name Value W3C/Custom Example value Used by Stats + +c-ip IP address of client W3C 157.100.200.300 y +date Date of the access W3C 11-16-98 y +time Time of the access (HH:MM:SS) W3C 15:30:30 y +c-dns Resolved dns of the client W3C fredj.ford.com n +cs-uri-stem Requested file W3C rtsp://server/sample.asf y +c-starttime Start time W3C 0 [in seconds, no fractions] n +x-duration Duration of the session (s) W3C 31 [in seconds, no fractions] y +c-rate Rate file was played by client Custom 1 [1= play, -5=rewind, +5=fforward] n +c-status http return code Custom 200 [mapped to http/rtsp status codes; 200 is success, 404 file not found...] y +c-playerid unique player ID Custom [a GUID value] y +c-playerversion player version Custom 3.0.0.1212 +c-playerlanguage player language Custom EN [two letter country code] y +cs(User-Agent) user agent W3C Mozilla/2.0+(compatible;+MSIE+3.0;+Windows 95) - this is a sample user-agent string n +cs(Referer) referring URL W3C http://www.gte.com n +c-hostexe host program Custom iexplore.exe [iexplore.exe, netscape.exe, dshow.exe, nsplay.exe, vb.exe, etcä] n +c-hostexever version Custom 4.70.1215 y +c-os os Custom Windows [Windows, Windows NT, Unix-[flavor], Mac-[flavor]] y +c-osversion os version Custom 4.0.0.1212 n +c-cpu cpu type Custom Pentium [486, Pentium, Alpha %d, Mac?, Unix?] y +filelength file length (s) Custom 60 [in seconds, no fractions] y +filesize file size (bytes) Custom 86000 [ie: 86kbytes] y +avgbandwidth Custom 24300 [ie: 24.3kbps] n +protocol Custom RTSP [rtsp, http] n +transport Custom UDP [udp, tcp, or mc] n +audiocodec Custom MPEG-Layer-3 y +videocodec Custom MPEG4 y +channelURL Custom http://server/channel.nsc n +sc-bytes bytes sent by server W3C 30000 [30k bytes sent from the server to the client] y +cs-bytes bytes received by client W3C 28000 [bytes received] n +s-pkts-sent packets sent Custom 55 y +c-pkts-recieved packets received Custom 50 n +c-pkts-lost-client packets lost Custom 5 y +c-pkts-lost-net Custom 2 [renamed from erasures; refers to packets lost at the network layer] n +c-pkts-lost-cont-net Custom 2 [continuous packets lost at the network layer] n +c-resendreqs packets resent Custom 5 y +c-pkts-recovered-ECC packets resent successfully Custom 1 [this refers to packets recovered in the client layer] y +c-pkts-recovered-resent Custom 5 [this refers to packets recovered via udp resend] n +c-buffercount Custom 1 n +c-totalbuffertime seconds buffered Custom 20 [in seconds] y +c-quality quality measurement Custom 89 [in percent] n +s-ip server ip W3C 155.12.1.234 [entered by the unicast server] n +s-dns server dns W3C foo.company.com n +s-totalclients total connections at time of access Custom 201 [total clients] n +s-cpu-util cpu utilization at time of access Custom 40 [in percent] n +cs-uri-query W3C language=EN&rate=1&CPU=486&protocol=RTSP&transport=UDP&quality=89&avgbandwidth=24300 n + +*/ diff --git a/APIModules/QTSSAccessLogModule/QTSSAccessLogModule.h b/APIModules/QTSSAccessLogModule/QTSSAccessLogModule.h new file mode 100644 index 0000000..fc28024 --- /dev/null +++ b/APIModules/QTSSAccessLogModule/QTSSAccessLogModule.h @@ -0,0 +1,44 @@ +/* + * + * @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: QTSSAccessLogModule.h + + Contains: A QTSS API module that runs as an RTSP Post Processor and an RTP + timeout processor. It generates access log files compatible with + the Lariat Logging tool. + +*/ + +#ifndef _QTSSACCESSLOGMODULE_H_ +#define _QTSSACCESSLOGMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSAccessLogModule_Main(void* inPrivateArgs); +} + +#endif //_QTSSACCESSLOGMODULE_H_ diff --git a/APIModules/QTSSAccessModule/AccessChecker.cpp b/APIModules/QTSSAccessModule/AccessChecker.cpp new file mode 100644 index 0000000..f10b5ae --- /dev/null +++ b/APIModules/QTSSAccessModule/AccessChecker.cpp @@ -0,0 +1,426 @@ +/* + * + * @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: AccessChecker.cpp + + Contains: + +*/ + +#include + +#ifndef __Win32__ + #ifndef __USE_XOPEN + #define __USE_XOPEN 1 + #endif + #include +#endif + +#include +#include +#include +#include "SafeStdLib.h" +#include "StringParser.h" +#include "OSFileSource.h" +#include "base64.h" +#include "OSMemory.h" +#include "OSHeaders.h" +#include "AccessChecker.h" +#include "QTSSModuleUtils.h" +#include "OSArrayObjectDeleter.h" + +static StrPtrLen sAuthWord("realm", 5); + +// Constructor +// Allocates no memory +AccessChecker::AccessChecker() : + fGroupsFilePath(NULL), + fUsersFilePath(NULL), + fUsersFileModDate(-1), + fGroupsFileModDate(-1), + fProfiles(NULL), + fNumUsers(0), + fCurrentSize(0) +{ +} + +// Destructor +// Deletes the fUsersFilePath, fGroupsFilePath, +// and calls the function to delete the authRealm and all the profiles +AccessChecker::~AccessChecker() +{ + delete[] fGroupsFilePath; + delete[] fUsersFilePath; + + DeleteProfilesAndRealm(); +} + + +// Allocates memory for the fUsersFilePath and fGroupsFilePath +// Before this call is made, make sure that the previous memory allocated is deleted +// or that memory will be orphaned! +void AccessChecker::UpdateFilePaths(const char* inUsersFilePath, const char* inGroupsFilePath) { + // Assert input arguments are not null + Assert(inUsersFilePath != NULL); + Assert(inGroupsFilePath != NULL); + + // Before reassigning, delete old paths + delete[] fGroupsFilePath; + delete[] fUsersFilePath; + + fUsersFilePath = NEW char[strlen(inUsersFilePath)+1]; + ::strcpy(fUsersFilePath, inUsersFilePath); + + fGroupsFilePath = NEW char[strlen(inGroupsFilePath)+1]; + ::strcpy(fGroupsFilePath, inGroupsFilePath); +} + +// Function to delete memory allocated for all the profiles, and the authRealm +// For each profile, memory is allocated for +// username +// cryptPassword +// digestPassword +// each group that the user belongs to (array of group names) +// All of the above are deleted +void AccessChecker::DeleteProfilesAndRealm() +{ + UInt32 i, j; + + // delete the profiles + if(fProfiles != NULL) + { + // For each profile + for(i = 0; i < fNumUsers; i++) + { + UserProfile* profile = fProfiles[i]; + + // delete the username + if((profile->username).Len != 0) + { + delete (profile->username).Ptr; + (profile->username).Len = 0; + } + + // delete cryptPassword + if((profile->cryptPassword).Len != 0) + { + delete (profile->cryptPassword).Ptr; + (profile->cryptPassword).Len = 0; + } + + // delete digestPassword + if((profile->digestPassword).Len != 0) + { + delete (profile->digestPassword).Ptr; + (profile->digestPassword).Len = 0; + } + + // delete each group name + for(j = 0; j < profile->numGroups; j++) + { + delete [] profile->groups[j]; + } + // delete the array of pointers to the group names + delete [] profile->groups; + + profile->groups = NULL; + profile->maxGroupNameLen = 0; + profile->numGroups = 0; + profile->groupsSize = 0; + delete profile; + } + + // delete the array of profile pointers + delete [] fProfiles; + fProfiles = NULL; + } + + // delete the fAuthRealm field + if(fAuthRealm.Len != 0) { + delete fAuthRealm.Ptr; + fAuthRealm.Len = 0; + } + + fNumUsers = 0; + fCurrentSize = 0; +} + +// Memory is allocated for each username record found in the users file +// Memory is also allocated for each group name found in the groups file per user +// All this memory must be deleted if the profiles are deleted, before parsing +// the file again +UInt32 AccessChecker::UpdateUserProfiles() { + + UInt32 index = 0; + UInt32 i = 0, j = 0; + UInt32 resultErr = kNoErr; + Bool16 groupFileErrors = true; + Bool16 userFileErrors = true; + + StrPtrLen line; + + QTSS_TimeVal oldUsersFileModDate = fUsersFileModDate; + QTSS_TimeVal oldGroupsFileModDate = fGroupsFileModDate; + + // Read the users file into a buffer + StrPtrLen userData; + QTSS_TimeVal newModDate = -1; + QTSS_Error err = QTSS_NoErr; + // QTSSModuleUtils::ReadEntireFile allocates memory for userData + err = QTSSModuleUtils::ReadEntireFile(fUsersFilePath, &userData, fUsersFileModDate, &newModDate); + if(err == QTSS_FileNotFound) + resultErr |= kUsersFileNotFoundErr; + else if(err != QTSS_NoErr) + resultErr |= kUsersFileUnknownErr; + else + userFileErrors = false; + + if(userFileErrors) + fUsersFileModDate = -1; + else if(userData.Len != 0) + fUsersFileModDate = newModDate; + + newModDate = -1; + + // Read the groups file into a buffer + StrPtrLen groupData; + // QTSSModuleUtils::ReadEntireFile allocates memory for groupData + err = QTSSModuleUtils::ReadEntireFile(fGroupsFilePath, &groupData, fGroupsFileModDate, &newModDate); + if(err == QTSS_FileNotFound) + resultErr |= kGroupsFileNotFoundErr; + else if(err != QTSS_NoErr) + resultErr |= kGroupsFileUnknownErr; + else + groupFileErrors = false; + + if(groupFileErrors) + fGroupsFileModDate = -1; + else if(groupData.Len != 0) + fGroupsFileModDate = newModDate; + + if(userFileErrors) + { + // delete user profiles and exit + DeleteProfilesAndRealm(); + return resultErr; + } + + if((fUsersFileModDate == oldUsersFileModDate) && (fGroupsFileModDate == oldGroupsFileModDate)) + return resultErr; + + // If either the users or groups file has changed + // the old profiles and the old realm should be deleted + // before a new array of profiles is created and a new realm from the users file is read + DeleteProfilesAndRealm(); + + // Since one or both of the files has changed, reread the files and create user profiles + if(userData.Len == 0) + (void)QTSSModuleUtils::ReadEntireFile(fUsersFilePath, &userData, -1, NULL); + if(groupData.Len == 0 && !groupFileErrors) + (void)QTSSModuleUtils::ReadEntireFile(fGroupsFilePath, &groupData, -1, NULL); + + + // This will delete the memory allocated for userData when we return from this function + OSCharArrayDeleter userDataPtrDeleter(userData.Ptr); + // This will delete the memory allocated for groupData when we return from this function + OSCharArrayDeleter groupDataPtrDeleter(groupData.Ptr); + + // Create the fProfiles array of size kDefaultNumProfiles + fProfiles = NEW UserProfile*[kDefaultNumProfiles]; + fCurrentSize = kDefaultNumProfiles; + + StringParser userDataParser(&userData); + // check if the first line is "realm" + while(true) { + StrPtrLen word; + userDataParser.GetThruEOL(&line); + StringParser authLineParser(&line); + // Skip over leading whitespace + authLineParser.ConsumeUntil(NULL, StringParser::sWhitespaceMask); + // Skip over comments and blank lines + if ((authLineParser.GetDataRemaining() == 0) || (authLineParser[0] == '#') || (authLineParser[0] == '\0') ) + continue; + authLineParser.ConsumeWord(&word); + if(sAuthWord.Equal(word)) { + authLineParser.ConsumeWhitespace(); + authLineParser.ConsumeUntil(&word, StringParser::sEOLMask); + fAuthRealm.Set(word.GetAsCString(), word.Len); + } + else { + // This shouldn't happen because it means that the realm line + // is not the first non-commented out line in the file + // Implies the users file is corrupted! + resultErr |= kBadUsersFileErr; + + // Create a new user profile for the first username + UserProfile* profile = NEW UserProfile; + profile->groups = NEW char*[kDefaultNumGroups]; + profile->maxGroupNameLen = 0; + profile->numGroups = 0; + profile->groupsSize = kDefaultNumGroups; + (profile->username).Set(word.GetAsCString(), word.Len); + // Get the crypted password + if ( authLineParser.Expect(':') ) + { + authLineParser.ConsumeUntil(&word, ':'); + (profile->cryptPassword).Set(word.GetAsCString(), word.Len); + // Get the digest password + authLineParser.GetThruEOL(&word); + (profile->digestPassword).Set(word.GetAsCString(), word.Len); + } + + fProfiles[index] = profile; + index ++; + } + break; + } + + while(userDataParser.GetDataRemaining() != 0) { + // Read each line + userDataParser.GetThruEOL(&line); + StringParser userLineParser(&line); + //parse the line + //skip over leading whitespace + userLineParser.ConsumeUntil(NULL, StringParser::sWhitespaceMask); + + //skip over comments and blank lines + if ((userLineParser.GetDataRemaining() == 0) || (userLineParser[0] == '#') || (userLineParser[0] == '\0') ) + continue; + + // Create a new user profile for each username found + UserProfile* profile = NEW UserProfile; + profile->groups = NEW char*[kDefaultNumGroups]; + profile->maxGroupNameLen = 0; + profile->numGroups = 0; + profile->groupsSize = kDefaultNumGroups; + StrPtrLen word; + userLineParser.ConsumeUntil(&word, ':'); + (profile->username).Set(word.GetAsCString(), word.Len); + // Get the crypted password + if ( userLineParser.Expect(':') ) + { + userLineParser.ConsumeUntil(&word, ':'); + (profile->cryptPassword).Set(word.GetAsCString(), word.Len); + if(userLineParser.Expect(':')) { + // Get the digest password + userLineParser.GetThruEOL(&word); + (profile->digestPassword).Set(word.GetAsCString(), word.Len); + } + } + + if(index >= fCurrentSize) { + UserProfile** oldProfiles = fProfiles; + fProfiles = NEW UserProfile*[fCurrentSize * 2]; + for(i = 0; i < fCurrentSize; i++) + { + fProfiles[i] = oldProfiles[i]; + } + fCurrentSize *= 2; + delete [] oldProfiles; + } + + fProfiles[index] = profile; + index ++; + } + fNumUsers = index; + + if(!groupFileErrors) + { + StringParser groupDataParser(&groupData); + while(groupDataParser.GetDataRemaining() != 0) { + // Read each line + groupDataParser.GetThruEOL(&line); + StringParser groupLineParser(&line); + //parse the line + //skip over leading whitespace + groupLineParser.ConsumeUntil(NULL, StringParser::sWhitespaceMask); + + //skip over comments and blank lines + if ((groupLineParser.GetDataRemaining() == 0) || (groupLineParser[0] == '#') || (groupLineParser[0] == '\0') ) + continue; + + //parse the groupname + StrPtrLen groupName; + groupLineParser.ConsumeUntil(&groupName, ':'); + + if (groupLineParser.Expect(':')) { + StrPtrLen groupUser; + UInt32 nameLen = groupName.Len + 1; + + while(groupLineParser.GetDataRemaining() != 0) + { + groupLineParser.ConsumeWhitespace(); + groupLineParser.ConsumeUntilWhitespace(&groupUser); + for(i = 0; i < fNumUsers; i++) { + if(fProfiles[i]->username.Equal(groupUser)) + { + UInt32 grpSize = fProfiles[i]->groupsSize; + if(fProfiles[i]->numGroups >= grpSize) { + char** oldGroups = fProfiles[i]->groups; + fProfiles[i]->groups = NEW char*[grpSize * 2]; + for(j = 0; j < grpSize; j++) { + fProfiles[i]->groups[j] = oldGroups[j]; + } + fProfiles[i]->groupsSize *= 2; + delete [] oldGroups; + } + + fProfiles[i]->groups[fProfiles[i]->numGroups] = groupName.GetAsCString(); + if(nameLen > fProfiles[i]->maxGroupNameLen) + fProfiles[i]->maxGroupNameLen = nameLen; + fProfiles[i]->numGroups++; + break; + } + } + } + } + } + } + + return resultErr; +} + +// No memory is allocated +Bool16 AccessChecker::HaveFilePathsChanged(const char* inUsersFilePath, const char* inGroupsFilePath) +{ + Bool16 changed = true; + if((inUsersFilePath != NULL) && (inGroupsFilePath != NULL) && (fUsersFilePath != NULL) && (fGroupsFilePath != NULL)) { + if((strcmp(inUsersFilePath, fUsersFilePath) == 0) && (strcmp(inGroupsFilePath, fGroupsFilePath) == 0)) + changed = false; + } + return changed; +} + +// No memory is allocated +AccessChecker::UserProfile* AccessChecker::RetrieveUserProfile(const StrPtrLen* inUserName) +{ + UInt32 index = 0; + for(index = 0; index < fNumUsers; index++) { + if(fProfiles[index]->username.Equal(*inUserName)) + return fProfiles[index]; + } + return NULL; +} + diff --git a/APIModules/QTSSAccessModule/AccessChecker.h b/APIModules/QTSSAccessModule/AccessChecker.h new file mode 100644 index 0000000..08a89a4 --- /dev/null +++ b/APIModules/QTSSAccessModule/AccessChecker.h @@ -0,0 +1,115 @@ +/* + * + * @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: AccessChecker.h + + Contains: + +*/ + +#ifndef _QTSSACCESSCHECKER_H_ +#define _QTSSACCESSCHECKER_H_ + +#include "QTSS.h" +#include "StrPtrLen.h" +#include "OSHeaders.h" + +class AccessChecker +{ +/* + Access check logic: + + If "modAccess_enabled" == "enabled, + Starting at URL dir, walk up directories to Movie Folder until a "qtaccess" file is found + If not found, + allow access + If found, + send a challenge to the client + verify user against QTSSPasswd + verify that user or member group is in the lowest ".qtacess" + walk up directories until a ".qtaccess" is found + If found, + allow access + If not found, + deny access + + ToDo: + would probably be a good idea to do some caching of ".qtaccess" data to avoid + multiple directory walks +*/ + +public: + struct UserProfile + { + StrPtrLen username; + StrPtrLen cryptPassword; + StrPtrLen digestPassword; + char** groups; + UInt32 maxGroupNameLen; + UInt32 numGroups; + UInt32 groupsSize; + }; + + AccessChecker(); + virtual ~AccessChecker(); + + void UpdateFilePaths(const char* inUsersFilePath, const char* inGroupsFilePath); + UInt32 UpdateUserProfiles(); + + Bool16 HaveFilePathsChanged(const char* inUsersFilePath, const char* inGroupsFilePath); + UserProfile* RetrieveUserProfile(const StrPtrLen* inUserName); + inline StrPtrLen* GetAuthRealm() {return &fAuthRealm;} + inline char* GetUsersFilePathPtr() {return fUsersFilePath;} + inline char* GetGroupsFilePathPtr() {return fGroupsFilePath;} + + enum { kDefaultNumProfiles = 10, kDefaultNumGroups = 2 }; + enum { kNoErr = 0x00000000, + kUsersFileNotFoundErr = 0x00000001, + kGroupsFileNotFoundErr = 0x00000002, + kBadUsersFileErr = 0x00000004, + kBadGroupsFileErr = 0x00000008, + kUsersFileUnknownErr = 0x00000010, + kGroupsFileUnknownErr = 0x00000020 + }; + +protected: + char* fGroupsFilePath; + char* fUsersFilePath; + QTSS_TimeVal fUsersFileModDate; + QTSS_TimeVal fGroupsFileModDate; + StrPtrLen fAuthRealm; + + UserProfile** fProfiles; + UInt32 fNumUsers; + UInt32 fCurrentSize; + + static const char* kDefaultUsersFilePath; + static const char* kDefaultGroupsFilePath; + +private: + void DeleteProfilesAndRealm(); +}; + +#endif //_QTSSACCESSCHECKER_H_ diff --git a/APIModules/QTSSAccessModule/QTSSAccessModule.cpp b/APIModules/QTSSAccessModule/QTSSAccessModule.cpp new file mode 100644 index 0000000..7682843 --- /dev/null +++ b/APIModules/QTSSAccessModule/QTSSAccessModule.cpp @@ -0,0 +1,536 @@ +/* + * + * @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: QTSSAccessModule.cpp + + Contains: Implementation of QTSSAccessModule. + + + +*/ + +#include "QTSSAccessModule.h" + +#include "../defaultPaths.h" + + +#include "OSArrayObjectDeleter.h" +#include "QTSS_Private.h" +#include "StrPtrLen.h" +#include "OSMemory.h" +#include "MyAssert.h" +#include "StringFormatter.h" +#include "StrPtrLen.h" +#include "StringParser.h" +#include "base64.h" +#include "OS.h" +#include "AccessChecker.h" +#include "QTAccessFile.h" +#include "QTSSModuleUtils.h" + +#ifndef __Win32__ +#include +#endif + +#include +#include + + + +// ATTRIBUTES + +// STATIC DATA + + +#define MODPREFIX_ "modAccess_" + +static StrPtrLen sSDPSuffix(".sdp"); +static OSMutex* sUserMutex = NULL; + +static Bool16 sDefaultAuthenticationEnabled = true; +static Bool16 sAuthenticationEnabled = true; + +static char* sDefaultUsersFilePath = DEFAULTPATHS_ETC_DIR "qtusers"; +static char* sUsersFilePath = NULL; + +static char* sDefaultGroupsFilePath = DEFAULTPATHS_ETC_DIR "qtgroups"; +static char* sGroupsFilePath = NULL; + +static char* sDefaultAccessFileName = "qtaccess"; + +static QTSS_AttributeID sBadNameMessageAttrID = qtssIllegalAttrID; +static QTSS_AttributeID sUsersFileNotFoundMessageAttrID = qtssIllegalAttrID; +static QTSS_AttributeID sGroupsFileNotFoundMessageAttrID = qtssIllegalAttrID; +static QTSS_AttributeID sBadUsersFileMessageAttrID = qtssIllegalAttrID; +static QTSS_AttributeID sBadGroupsFileMessageAttrID = qtssIllegalAttrID; + +static QTSS_StreamRef sErrorLogStream = NULL; +static QTSS_TextMessagesObject sMessages = NULL; +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; + +static AccessChecker** sAccessCheckers; +static UInt32 sNumAccessCheckers = 0; +static UInt32 sAccessCheckerArraySize = 0; + +static Bool16 sAllowGuestDefaultEnabled = true; +static Bool16 sDefaultGuestEnabled = true; + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSAccessModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error Shutdown(); +static QTSS_Error RereadPrefs(); +static QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams); +static QTSS_Error AccessAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams); +static char* GetCheckedFileName(); + +// FUNCTION IMPLEMENTATIONS + + +QTSS_Error QTSSAccessModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSAccessModuleDispatch); +} + + +QTSS_Error QTSSAccessModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(); + break; + + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + break; + + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + break; + + case QTSS_RTSPAuthenticate_Role: + if (sAuthenticationEnabled) + return AuthenticateRTSPRequest(&inParams->rtspAthnParams); + break; + + case QTSS_RTSPAuthorize_Role: + if (sAuthenticationEnabled) + return AccessAuthorizeRTSPRequest(&inParams->rtspRequestParams); + break; + + case QTSS_Shutdown_Role: + return Shutdown(); + break; + } + + return QTSS_NoErr; +} + +QTSS_Error Register() +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthenticate_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); + + // Add AuthenticateName and Password attributes + static char* sBadAccessFileName = "QTSSAccessModuleBadAccessFileName"; + static char* sUsersFileNotFound = "QTSSAccessModuleUsersFileNotFound"; + static char* sGroupsFileNotFound = "QTSSAccessModuleGroupsFileNotFound"; + static char* sBadUsersFile = "QTSSAccessModuleBadUsersFile"; + static char* sBadGroupsFile = "QTSSAccessModuleBadGroupsFile"; + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadAccessFileName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadAccessFileName, &sBadNameMessageAttrID); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sUsersFileNotFound, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sUsersFileNotFound, &sUsersFileNotFoundMessageAttrID); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sGroupsFileNotFound, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sGroupsFileNotFound, &sGroupsFileNotFoundMessageAttrID); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadUsersFile, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadUsersFile, &sBadUsersFileMessageAttrID); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadGroupsFile, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadGroupsFile, &sBadGroupsFileMessageAttrID); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Create an array of AccessCheckers + sAccessCheckers = NEW AccessChecker*[2]; + sAccessCheckers[0] = NEW AccessChecker(); + sNumAccessCheckers = 1; + sAccessCheckerArraySize = 2; + + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sErrorLogStream = inParams->inErrorLogStream; + sMessages = inParams->inMessages; + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + sServerPrefs = inParams->inPrefs; + sUserMutex = NEW OSMutex(); + RereadPrefs(); + QTAccessFile::Initialize(); + + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + //cleanup + + // delete all the AccessCheckers + UInt32 index; + for(index = 0; index < sNumAccessCheckers; index++) + delete sAccessCheckers[index]; + delete[] sAccessCheckers; + sNumAccessCheckers = 0; + + // delete the main users and groups path + + //if(sUsersFilePath != sDefaultUsersFilePath) + // sUsersFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always + // allocates memory even if it just returns the default value + delete[] sUsersFilePath; + sUsersFilePath = NULL; + + //if(sGroupsFilePath != sDefaultGroupsFilePath) + // sGroupsFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always + // allocates memory even if it just returns the default value + delete[] sGroupsFilePath; + sGroupsFilePath = NULL; + + return QTSS_NoErr; +} + +char* GetCheckedFileName() +{ + char *result = NULL; + static char *badChars = "/'\""; + char theBadCharMessage[] = "' '"; + char *theBadChar = NULL; + result = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"qtaccessfilename", sDefaultAccessFileName); + StrPtrLen searchStr(result); + + theBadChar = strpbrk(searchStr.Ptr, badChars); + if ( theBadChar!= NULL) + { + theBadCharMessage[1] = theBadChar[0]; + QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadNameMessageAttrID, 0, theBadCharMessage, result); + + delete[] result; + result = NEW char[::strlen(sDefaultAccessFileName) + 2]; + ::strcpy(result, sDefaultAccessFileName); + } + return result; +} + +QTSS_Error RereadPrefs() +{ + OSMutexLocker locker(sUserMutex); + + // + // Use the standard GetAttribute routine to retrieve the correct values for our preferences + QTSSModuleUtils::GetAttribute(sPrefs, MODPREFIX_"enabled", qtssAttrDataTypeBool16, + &sAuthenticationEnabled, &sDefaultAuthenticationEnabled, sizeof(sAuthenticationEnabled)); + + //if(sUsersFilePath != sDefaultUsersFilePath) + // sUsersFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always + // allocates memory even if it just returns the default value + // delete this old memory before reassigning it to new memory + delete[] sUsersFilePath; + sUsersFilePath = NULL; + + //if(sGroupsFilePath != sDefaultGroupsFilePath) + // sGroupsFilePath is assigned by a call to QTSSModuleUtils::GetStringAttribute which always + // allocates memory even if it just returns the default value + // delete this old memory before reassigning it to new memory + delete[] sGroupsFilePath; + sGroupsFilePath = NULL; + + sUsersFilePath = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"usersfilepath", sDefaultUsersFilePath); + sGroupsFilePath = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"groupsfilepath", sDefaultGroupsFilePath); + // GetCheckedFileName always allocates memory + char* accessFile = GetCheckedFileName(); + // QTAccessFile::SetAccessFileName makes its own copy, + // so delete the previous allocated memory after this call + QTAccessFile::SetAccessFileName(accessFile); + delete [] accessFile; + + if(sAccessCheckers[0]->HaveFilePathsChanged(sUsersFilePath, sGroupsFilePath)) + { + sAccessCheckers[0]->UpdateFilePaths(sUsersFilePath, sGroupsFilePath); + UInt32 err; + err = sAccessCheckers[0]->UpdateUserProfiles(); + if(err & AccessChecker::kUsersFileNotFoundErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, sUsersFilePath, NULL); + else if(err & AccessChecker::kBadUsersFileErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, sUsersFilePath, NULL); + if(err & AccessChecker::kGroupsFileNotFoundErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, sGroupsFilePath, NULL); + else if(err & AccessChecker::kBadGroupsFileErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, sGroupsFilePath, NULL); + } + + QTSSModuleUtils::GetAttribute(sServerPrefs,"enable_allow_guest_default", qtssAttrDataTypeBool16, + &sAllowGuestDefaultEnabled,(void *)&sDefaultGuestEnabled, sizeof(sAllowGuestDefaultEnabled)); + + + return QTSS_NoErr; +} + +QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams) +{ + QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; + UInt32 fileErr; + + OSMutexLocker locker(sUserMutex); + + if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) ) + return QTSS_RequestFailed; + + // Get the user profile object from the request object + QTSS_UserProfileObject theUserProfile = NULL; + UInt32 len = sizeof(QTSS_UserProfileObject); + QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqUserProfile, 0, (void*)&theUserProfile, &len); + Assert(len == sizeof(QTSS_UserProfileObject)); + if (theErr != QTSS_NoErr) + return theErr; + + Bool16 defaultPaths = true; + // Check for a users and groups file in the access file + // For this, first get local file path and root movie directory + //get the local file path + char* pathBuffStr = QTSSModuleUtils::GetLocalPath_Copy(theRTSPRequest); + OSCharArrayDeleter pathBuffDeleter(pathBuffStr); + if (NULL == pathBuffStr) + return QTSS_RequestFailed; + //get the root movie directory + char* movieRootDirStr = QTSSModuleUtils::GetMoviesRootDir_Copy(theRTSPRequest); + OSCharArrayDeleter movieRootDeleter(movieRootDirStr); + if (NULL == movieRootDirStr) + return QTSS_RequestFailed; + // Now get the access file path + char* accessFilePath = QTAccessFile::GetAccessFile_Copy(movieRootDirStr, pathBuffStr); + OSCharArrayDeleter accessFilePathDeleter(accessFilePath); + // Parse the access file for the AuthUserFile and AuthGroupFile keywords + char* usersFilePath = NULL; + char* groupsFilePath = NULL; + + // Get the request action from the request object + QTSS_ActionFlags action = qtssActionFlagsNoFlags; + len = sizeof(action); + theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAction, 0, (void*)&action, &len); + Assert(len == sizeof(action)); + if (theErr != QTSS_NoErr) + return theErr; + + // Allocates memory for usersFilePath and groupsFilePath + QTSS_AuthScheme authScheme = QTAccessFile::FindUsersAndGroupsFilesAndAuthScheme(accessFilePath, action, &usersFilePath, &groupsFilePath); + + if((usersFilePath != NULL) || (groupsFilePath != NULL)) + defaultPaths = false; + + if(usersFilePath == NULL) + usersFilePath = strdup(sUsersFilePath); + + if(groupsFilePath == NULL) + groupsFilePath = strdup(sGroupsFilePath); + + OSCharArrayDeleter userPathDeleter(usersFilePath); + OSCharArrayDeleter groupPathDeleter(groupsFilePath); + + AccessChecker* currentChecker = NULL; + UInt32 index; + + // If the default users and groups file are not the ones we need + if(!defaultPaths) + { + // check if there is one AccessChecker that matches the needed paths + // Don't have to check for the first one (or element zero) because it has the default paths + for(index = 1; index < sNumAccessCheckers; index++) + { + // If an access checker that matches the users and groups file paths is found + if(!sAccessCheckers[index]->HaveFilePathsChanged(usersFilePath, groupsFilePath)) + { + currentChecker = sAccessCheckers[index]; + break; + } + } + // If an existing AccessChecker for the needed paths isn't found + if(currentChecker == NULL) + { + // Grow the AccessChecker array if needed + if(sNumAccessCheckers == sAccessCheckerArraySize) + { + AccessChecker** oldAccessCheckers = sAccessCheckers; + sAccessCheckers = NEW AccessChecker*[sAccessCheckerArraySize * 2]; + for(index = 0; index < sNumAccessCheckers; index++) + { + sAccessCheckers[index] = oldAccessCheckers[index]; + } + sAccessCheckerArraySize *= 2; + delete [] oldAccessCheckers; + } + + // And create a new AccessChecker for the paths + sAccessCheckers[sNumAccessCheckers] = NEW AccessChecker(); + sAccessCheckers[sNumAccessCheckers]->UpdateFilePaths(usersFilePath, groupsFilePath); + fileErr = sAccessCheckers[sNumAccessCheckers]->UpdateUserProfiles(); + + if(fileErr & AccessChecker::kUsersFileNotFoundErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, usersFilePath, NULL); + else if(fileErr & AccessChecker::kBadUsersFileErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, usersFilePath, NULL); + if(fileErr & AccessChecker::kGroupsFileNotFoundErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, groupsFilePath, NULL); + else if(fileErr & AccessChecker::kBadGroupsFileErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, groupsFilePath, NULL); + + currentChecker = sAccessCheckers[sNumAccessCheckers]; + sNumAccessCheckers++; + } + + } + else + { + currentChecker = sAccessCheckers[0]; + } + + // Before retrieving the user profile information + // check if the groups/users files have been modified and update them otherwise + fileErr = currentChecker->UpdateUserProfiles(); + + /* + // This is for logging the errors if users file and/or the groups file is not found or corrupted + char* usersFile = currentChecker->GetUsersFilePathPtr(); + char* groupsFile = currentChecker->GetGroupsFilePathPtr(); + + if(fileErr & AccessChecker::kUsersFileNotFoundErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sUsersFileNotFoundMessageAttrID, 0, usersFile, NULL); + else if(fileErr & AccessChecker::kBadUsersFileErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadUsersFileMessageAttrID, 0, usersFile, NULL); + if(fileErr & AccessChecker::kGroupsFileNotFoundErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sGroupsFileNotFoundMessageAttrID, 0, groupsFile, NULL); + else if(fileErr & AccessChecker::kBadGroupsFileErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity,sBadGroupsFileMessageAttrID, 0, groupsFile, NULL); + */ + + // Retrieve the password data and group information for the user and set them + // in the qtssRTSPReqUserProfile attr + // The password data is crypt of the real password for Basic authentication + // and it is MD5(username:realm:password) for Digest authentication + + // It the access file didn't contain an auth scheme, then get the auth scheme out of the request object + // else, set the qtssRTSPReqAuthScheme to that found in the access file + + if (authScheme == qtssAuthNone) + { + // Get the authentication scheme from the request object + len = sizeof(authScheme); + theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&authScheme, &len); + Assert(len == sizeof(authScheme)); + if (theErr != QTSS_NoErr) + return theErr; + } + else + { + theErr = QTSS_SetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&authScheme, sizeof(authScheme)); + if (theErr != QTSS_NoErr) + return theErr; + } + + // Set the qtssUserRealm to the realm value retrieved from the users file + // This should be used for digest auth scheme, and if no realm is found in the qtaccess file, then + // it should be used for basic auth scheme. + // No memory is allocated; just a pointer is returned + StrPtrLen* authRealm = currentChecker->GetAuthRealm(); + (void)QTSS_SetValue(theUserProfile, qtssUserRealm, 0, (void*)(authRealm->Ptr), (authRealm->Len)); + + + // Get the username from the user profile object + char* usernameBuf = NULL; + theErr = QTSS_GetValueAsString(theUserProfile, qtssUserName, 0, &usernameBuf); + OSCharArrayDeleter usernameBufDeleter(usernameBuf); + StrPtrLen username(usernameBuf); + if (theErr != QTSS_NoErr) + return theErr; + + // No memory is allocated; just a pointer to the profile is returned + AccessChecker::UserProfile* profile = currentChecker->RetrieveUserProfile(&username); + + if(profile == NULL) + return QTSS_NoErr; + + // Set the qtssUserPassword attribute to either the crypted password or the digest password + // based on the authentication scheme + if (authScheme == qtssAuthBasic) + (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)((profile->cryptPassword).Ptr), (profile->cryptPassword).Len); + else if (authScheme == qtssAuthDigest) + (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)((profile->digestPassword).Ptr), (profile->digestPassword).Len); + + + // Set the multivalued qtssUserGroups attr to the groups the user belongs to, if any + UInt32 maxLen = profile->maxGroupNameLen; + for(index = 0; index < profile->numGroups; index++) + { + UInt32 curLen = ::strlen(profile->groups[index]); + if(curLen < maxLen) + { + char* groupWithPaddedZeros = NEW char[maxLen]; // memory allocated + ::memcpy(groupWithPaddedZeros, profile->groups[index], curLen); + ::memset(groupWithPaddedZeros+curLen, '\0', maxLen-curLen); + (void)QTSS_SetValue(theUserProfile, qtssUserGroups, index, (void*)groupWithPaddedZeros, maxLen); + delete [] groupWithPaddedZeros; // memory deleted + } + else + { + (void)QTSS_SetValue(theUserProfile, qtssUserGroups, index, (void*)(profile->groups[index]), maxLen); + } + } + + return QTSS_NoErr; +} + +QTSS_Error AccessAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams) +{ + Bool16 allowNoAccessFiles = sAllowGuestDefaultEnabled; //no access files allowed means allowing guest access (unknown users) + QTSS_ActionFlags noAction = ~qtssActionFlagsRead; // allow any action + QTSS_ActionFlags authorizeAction = QTSSModuleUtils::GetRequestActions(inParams->inRTSPRequest); + Bool16 authorized =false; + Bool16 allowAnyUser = false; + QTAccessFile accessFile; + return accessFile.AuthorizeRequest(inParams,allowNoAccessFiles, noAction, authorizeAction, &authorized, &allowAnyUser); +} diff --git a/APIModules/QTSSAccessModule/QTSSAccessModule.h b/APIModules/QTSSAccessModule/QTSSAccessModule.h new file mode 100644 index 0000000..16ecf0b --- /dev/null +++ b/APIModules/QTSSAccessModule/QTSSAccessModule.h @@ -0,0 +1,46 @@ +/* + * + * @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: QTSSAccessModule.h + + Contains: Module that handles authentication and authorization independent of the file system + + + +*/ + +#ifndef _QTSSACCESSMODULE_H_ +#define _QTSSACCESSMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSAccessModule_Main(void* inPrivateArgs); +} + +#endif //_QTSSACCESSMODULE_H_ + + diff --git a/APIModules/QTSSAdminModule/AdminElementNode.cpp b/APIModules/QTSSAdminModule/AdminElementNode.cpp new file mode 100644 index 0000000..4e5ba7f --- /dev/null +++ b/APIModules/QTSSAdminModule/AdminElementNode.cpp @@ -0,0 +1,2298 @@ +/* + * + * @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: AdminElementNode.cpp + + Contains: Implements Admin Elements + + + +*/ + + +#ifndef __Win32__ + #include /* for getopt() et al */ +#endif + +#include /* for //qtss_printf */ +#include /* for getloadavg & other useful stuff */ +#include +#include "QTSS.h" +#include "QTSSAdminModule.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" +#include "OSHashTable.h" +#include "OSMutex.h" +#include "StrPtrLen.h" +#include "OSRef.h" +#include "AdminElementNode.h" +#include "OSMemory.h" +//#include "OSHeaders.h" + +static char* sParameterDelimeter = ";"; +static char* sListDelimeter = ","; +static char* sAccess = "a="; +static char* sType = "t="; +static StrPtrLen sDoAllSPL("*"); +static StrPtrLen sDoAllIndexIteratorSPL(":"); + +#define MEMORYDEBUGGING 0 +#if MEMORYDEBUGGING +static SInt32 sMaxPtrs = 10000; +static void * sPtrArray[10000]; +static char * sSourceArray[10000]; +#endif + +Bool16 ElementNode_DoAll(StrPtrLen* str) +{ + Assert(str); + Bool16 isIterator = false; + + if ( str->Equal(sDoAllSPL) || str->Equal(sDoAllIndexIteratorSPL)) + isIterator = true; + + return isIterator; +} + +void ElementNode_InitPtrArray() +{ +#if MEMORYDEBUGGING + memset(sPtrArray, 0, sizeof(sPtrArray)); + memset(sSourceArray, 0, sizeof(sSourceArray)); +#endif +} + +void ElementNode_InsertPtr(void *ptr, char * src) +{ +#if MEMORYDEBUGGING + if (ptr == NULL) + return; + + for (SInt32 index = 0; index < sMaxPtrs; index ++) + { + if (sPtrArray[index] == NULL) + { sPtrArray[index] =ptr; + sSourceArray[index] = src; + //qtss_printf("%s INSERTED ptr=%p countPtrs=%"_S32BITARG_"\n",src, ptr, ElementNode_CountPtrs()); + return; + } + } + + qtss_printf("ElementNode_InsertPtr no space in ptr array\n"); + Assert(0); +#endif +} + +Bool16 ElementNode_FindPtr(void *ptr, char * src) +{ // use for validating duplicates at some point +#if MEMORYDEBUGGING + if (ptr == NULL) + return false; + + for (SInt32 index = 0; index < sMaxPtrs; index ++) + { if (sPtrArray[index] == ptr) + return true; + } + +#endif + return false; +} + +void ElementNode_RemovePtr(void *ptr, char * src) +{ +#if MEMORYDEBUGGING + if (ptr == NULL) + return; + + SInt16 foundCount = 0; + for (SInt32 index = 0; index < sMaxPtrs; index ++) + { + if (sPtrArray[index] == ptr) + { + sPtrArray[index] = NULL; + sSourceArray[index] = NULL; + //qtss_printf("%s REMOVED ptr countPtrs=%"_S32BITARG_"\n",src,ElementNode_CountPtrs()); + foundCount ++; // use for validating duplicates at some point + return; + } + } + + if (foundCount == 0) + { qtss_printf("PTR NOT FOUND ElementNode_RemovePtr %s ptr=%p countPtrs=%"_S32BITARG_"\n",src,ptr,ElementNode_CountPtrs()); + Assert(0); + } +#endif +} + +SInt32 ElementNode_CountPtrs() +{ +#if MEMORYDEBUGGING + SInt32 count = 0; + for (SInt32 index = 0; index < sMaxPtrs; index ++) + { + if (sPtrArray[index] != NULL) + count ++; + } + + return count; +#else + return 0; +#endif +} + +void ElementNode_ShowPtrs() +{ +#if MEMORYDEBUGGING + for (SInt32 index = 0; index < sMaxPtrs; index ++) + { + if (sPtrArray[index] != NULL) + qtss_printf("ShowPtrs ptr=%p source=%s\n", sPtrArray[index],sSourceArray[index]); + } +#endif +} + +void PRINT_STR(StrPtrLen *spl) +{ + + if (spl && spl->Ptr && spl->Ptr[0] != 0) + { char buff[1024] = {0}; + memcpy (buff,spl->Ptr, spl->Len); + qtss_printf("%s len=%"_U32BITARG_"\n",buff,spl->Len); + } + else + { qtss_printf("(null)\n"); + } +} + +void COPYBUFFER(char *dest,char *src,SInt8 size) +{ + if ( (dest != NULL) && (src != NULL) && (size > 0) ) + memcpy (dest, src, size); + else + Assert(0); +}; + +char* NewCharArrayCopy(StrPtrLen *theStringPtr) +{ + char* newArray = NULL; + if (theStringPtr != NULL) + { + newArray = NEW char[theStringPtr->Len + 1]; + if (newArray != NULL) + { memcpy(newArray, theStringPtr->Ptr,theStringPtr->Len); + newArray[theStringPtr->Len] = 0; + } + } + return newArray; +} + + + +ElementNode::ElementNode() +{ + fDataSource = NULL; + fNumFields = 0; + fPathLen = 0; + fInitialized = false; + + fFieldIDs = NULL; + fFieldDataPtrs = NULL; + fFieldOSRefPtrs = NULL; + fParentNodePtr = NULL; + fElementMap = NULL; + fSelfPtr = NULL; + fPathBuffer[0]=0; + fPathSPL.Set(fPathBuffer,0); + fIsTop = false; + fDataFieldsType = eDynamic; + +}; + +void ElementNode::Initialize(SInt32 index, ElementNode *parentPtr, QueryURI *queryPtr, StrPtrLen *currentSegmentPtr,QTSS_Initialize_Params *initParams,QTSS_Object nodeSource, DataFieldsType dataFieldsType) +{ + //qtss_printf("------ ElementNode::Initialize ---------\n"); + + if (!fInitialized) + { + SetParentNode(parentPtr); + SetSource(nodeSource); + + SetNodeInfo(parentPtr->GetNodeInfoPtr(index)); + SetNodeName(parentPtr->GetName(index)); + SetMyElementDataPtr(parentPtr->GetElementDataPtr(index)); + + fDataFieldsType = dataFieldsType; + + StrPtrLen nextSegment; + StrPtrLen nextnextSegment; + (void) queryPtr->NextSegment(currentSegmentPtr, &nextSegment); + (void) queryPtr->NextSegment(&nextSegment, &nextnextSegment); + Bool16 forceAll = nextSegment.Equal(sDoAllIndexIteratorSPL) | nextnextSegment.Equal(sDoAllIndexIteratorSPL); + + if (GetFields() == NULL) + InitializeAllFields(true, NULL, nodeSource,queryPtr,currentSegmentPtr,forceAll); + + fInitialized = true; + } + SetupNodes(queryPtr,currentSegmentPtr, initParams); + +}; + + +ElementNode::~ElementNode() +{ + + //qtss_printf("ElementNode::~ElementNode delete %s Element Node # fields = %"_U32BITARG_"\n",GetNodeName(), fNumFields); + + for(SInt32 index = 0; !IsStopItem(index) ; index ++) + { + OSRef *theRefPtr = GetOSRef(index); + if (theRefPtr != NULL ) + { + //qtss_printf("deleting hash entry of %s \n", GetName(index)); + SetOSRef(index,NULL); + (fElementMap->GetHashTable())->Remove(theRefPtr); + delete (OSRef*) theRefPtr; ElementNode_RemovePtr(theRefPtr,"ElementNode::~ElementNode OSRef *"); + } + + char *dataPtr = GetElementDataPtr(index); + if (dataPtr != NULL) + { + SetElementDataPtr(index,NULL,IsNodeElement(index)); + } + } + + delete fElementMap; ElementNode_RemovePtr(fElementMap,"ElementNode::~ElementNode fElementMap"); + + fElementMap = NULL; + + UInt32 i = 0; + for (i = 0; i < GetNumFields(); i ++) + { SetElementDataPtr(i,NULL, IsNodeElement(i)) ; + fFieldDataPtrs[i] = NULL; + } + delete fFieldDataPtrs; ElementNode_RemovePtr(fFieldDataPtrs,"ElementNode::~ElementNode fFieldDataPtrs"); + fFieldDataPtrs = NULL; + + for (i = 0; i < GetNumFields(); i ++) + { delete fFieldOSRefPtrs[i]; ElementNode_RemovePtr(fFieldOSRefPtrs[i],"ElementNode::~ElementNode fFieldOSRefPtrs"); + fFieldOSRefPtrs[i] = NULL; + } + delete fFieldOSRefPtrs; ElementNode_RemovePtr(fFieldOSRefPtrs,"ElementNode::~ElementNode fFieldOSRefPtrs"); + fFieldOSRefPtrs = NULL; + + if (fDataFieldsType == eDynamic) + { delete fFieldIDs; ElementNode_RemovePtr(fFieldIDs,"ElementNode::~ElementNode fFieldIDs"); + fFieldIDs = NULL; + } + + SetNodeName(NULL); + +}; + +QTSS_Error ElementNode::AllocateFields(UInt32 numFields) +{ + //qtss_printf("-------- ElementNode::AllocateFields ----------\n"); + //qtss_printf("ElementNode::AllocateFields numFields=%"_U32BITARG_"\n",numFields); + + QTSS_Error err = QTSS_NotEnoughSpace; + + Assert(GetNumFields() == 0); + SetNumFields(numFields); + + if (numFields > 0) do + { + Assert(fFieldIDs == NULL); + fFieldIDs = NEW ElementNode::ElementDataFields[numFields]; ElementNode_InsertPtr(fFieldIDs,"ElementNode::AllocateFields fFieldIDs array"); + Assert(fFieldIDs != NULL); + if (fFieldIDs == NULL) break; + memset(fFieldIDs, 0, numFields * sizeof(ElementNode::ElementDataFields)); + + Assert(fElementMap == NULL); + fElementMap = NEW OSRefTable(); ElementNode_InsertPtr(fElementMap,"ElementNode::AllocateFields fElementMap OSRefTable"); + Assert(fElementMap != NULL); + if (fElementMap == NULL) break; + + Assert(fFieldDataPtrs == NULL); + fFieldDataPtrs = NEW char*[numFields]; ElementNode_InsertPtr(fFieldDataPtrs,"ElementNode::AllocateFields fFieldDataPtrs array"); + Assert(fFieldDataPtrs != NULL); + if (fFieldDataPtrs == NULL) break; + memset(fFieldDataPtrs, 0, numFields * sizeof(char*)); + + Assert(fFieldOSRefPtrs == NULL); + fFieldOSRefPtrs = NEW OSRef*[numFields]; ElementNode_InsertPtr(fFieldOSRefPtrs,"ElementNode::AllocateFields fFieldDataPtrs array"); + Assert(fFieldOSRefPtrs != NULL); + if (fFieldOSRefPtrs == NULL) break; + memset(fFieldOSRefPtrs, 0, numFields * sizeof(OSRef*)); + + err = QTSS_NoErr; + } while (false); + + return err; +}; + + + + +void ElementNode::SetFields(UInt32 i, QTSS_Object attrInfoObject) +{ + //qtss_printf("------- ElementNode::SetFields -------- \n"); + + UInt32 ioLen = 0; + QTSS_Error err = QTSS_NoErr; + if(fFieldIDs[i].fFieldName[0] != 0) + return; + + if(fFieldIDs[i].fFieldName[0] == 0) + { + fFieldIDs[i].fFieldLen = eMaxAttributeNameSize; + err = QTSS_GetValue (attrInfoObject, qtssAttrName,0, &fFieldIDs[i].fFieldName,&fFieldIDs[i].fFieldLen); + Assert(err == QTSS_NoErr); + if (fFieldIDs[i].fFieldName != NULL) + fFieldIDs[i].fFieldName[fFieldIDs[i].fFieldLen] = 0; + } + + ioLen = sizeof(fFieldIDs[i].fAPI_ID); + err = QTSS_GetValue (attrInfoObject, qtssAttrID,0, &fFieldIDs[i].fAPI_ID, &ioLen); + Assert(err == QTSS_NoErr); + + ioLen = sizeof(fFieldIDs[i].fAPI_Type); + err = QTSS_GetValue (attrInfoObject, qtssAttrDataType,0, &fFieldIDs[i].fAPI_Type, &ioLen); + Assert(err == QTSS_NoErr); + if (fFieldIDs[i].fAPI_Type == 0 || err != QTSS_NoErr) + { + //qtss_printf("QTSS_GetValue err = %"_S32BITARG_" attrInfoObject=%"_U32BITARG_" qtssAttrDataType = %"_U32BITARG_" \n",err, (UInt32) attrInfoObject, (UInt32) fFieldIDs[i].fAPI_Type); + } + + if (fFieldIDs[i].fAPI_Type == qtssAttrDataTypeQTSS_Object) + fFieldIDs[i].fFieldType = eNode; + + ioLen = sizeof(fFieldIDs[i].fAccessPermissions); + err = QTSS_GetValue (attrInfoObject, qtssAttrPermissions,0, &fFieldIDs[i].fAccessPermissions, &ioLen); + Assert(err == QTSS_NoErr); + + fFieldIDs[i].fAccessData[0] = 0; + if (fFieldIDs[i].fAccessPermissions & qtssAttrModeRead) + { strcat(fFieldIDs[i].fAccessData, "r"); + } + + if (fFieldIDs[i].fAccessPermissions & qtssAttrModeWrite && fFieldIDs[i].fAPI_Type != qtssAttrDataTypeQTSS_Object) + { strcat(fFieldIDs[i].fAccessData, "w"); + } + + if (fFieldIDs[i].fAccessPermissions & qtssAttrModeInstanceAttrAllowed && fFieldIDs[i].fAPI_Type == qtssAttrDataTypeQTSS_Object) + { strcat(fFieldIDs[i].fAccessData, "w"); + } + + if (GetMyFieldType() != eNode && GetNumFields() > 1) + { strcat(fFieldIDs[i].fAccessData, "d"); + } + if (fFieldIDs[i].fAccessPermissions & qtssAttrModeDelete) + { strcat(fFieldIDs[i].fAccessData, "d"); + } + + + if (fFieldIDs[i].fAccessPermissions & qtssAttrModePreempSafe) + { strcat(fFieldIDs[i].fAccessData, "p"); + } + + fFieldIDs[i].fAccessLen = strlen(fFieldIDs[i].fAccessData); + + //qtss_printf("ElementNode::SetFields name=%s api_id=%"_S32BITARG_" \n",fFieldIDs[i].fFieldName, fFieldIDs[i].fAPI_ID); + //DebugShowFieldDataType(i); +}; + + +ElementNode* ElementNode::CreateArrayAttributeNode(UInt32 index, QTSS_Object source, QTSS_Object attributeInfo, UInt32 arraySize ) +{ + //qtss_printf("------- ElementNode::CreateArrayAttributeNode --------\n"); + //qtss_printf("ElementNode::CreateArrayAttributeNode name = %s index = %"_U32BITARG_" arraySize =%"_U32BITARG_" \n",fFieldIDs[index].fFieldName, index,arraySize); + + ElementDataFields* fieldPtr = NULL; + SetFields(index, attributeInfo); + fFieldIDs[index].fFieldType = eArrayNode; + + ElementNode* nodePtr = NEW ElementNode(); ElementNode_InsertPtr(nodePtr,"ElementNode::CreateArrayAttributeNode ElementNode*"); + this->SetElementDataPtr(index,(char *) nodePtr, true); + Assert(nodePtr!=NULL); + if (NULL == nodePtr) return NULL; + + nodePtr->SetSource(source); // the node's API source + nodePtr->AllocateFields(arraySize); + + if (this->GetNodeInfoPtr(index) == NULL) + { //qtss_printf("ElementNode::CreateArrayAttributeNode index = %"_U32BITARG_" this->GetNodeInfoPtr(index) == NULL \n",index); + } + nodePtr->SetNodeInfo(this->GetNodeInfoPtr(index)); + + for (UInt32 i = 0; !nodePtr->IsStopItem(i); i++) + { + fieldPtr = nodePtr->GetElementFieldPtr(i); + Assert(fieldPtr != NULL); + + nodePtr->SetFields(i, attributeInfo); + + fieldPtr->fIndex = i; // set the API attribute index + + // set the name of the field to the array index + fieldPtr->fFieldName[0]= 0; + qtss_sprintf(fieldPtr->fFieldName,"%"_U32BITARG_,i); + fieldPtr->fFieldLen = ::strlen(fieldPtr->fFieldName); + + if (fieldPtr->fAPI_Type != qtssAttrDataTypeQTSS_Object) + { + //qtss_printf("ElementNode::CreateArrayAttributeNode array field index = %"_U32BITARG_" name = %s api Source = %"_U32BITARG_" \n", (UInt32) i,fieldPtr->fFieldName, (UInt32) source); + fieldPtr->fAPISource = source; // the attribute's source is the same as node source + } + else + { fieldPtr->fFieldType = eNode; + // this is an array of objects so record each object as the source for a new node + UInt32 sourceLen = sizeof(fieldPtr->fAPISource); + QTSS_Error err = QTSS_GetValue (source,fieldPtr->fAPI_ID,fieldPtr->fIndex, &fieldPtr->fAPISource, &sourceLen); + Warn(err == QTSS_NoErr); + if (err != QTSS_NoErr) + { //qtss_printf("Error Getting Value for %s type = qtssAttrDataTypeQTSS_Object err = %"_U32BITARG_"\n", fieldPtr->fFieldName,err); + fieldPtr->fAPISource = NULL; + } + + QTSS_AttributeID id; + Bool16 foundFilteredAttribute = GetFilteredAttributeID(GetMyName(),nodePtr->GetMyName(), &id); + if (foundFilteredAttribute) + { GetFilteredAttributeName(fieldPtr,id); + } + + //qtss_printf("ElementNode::CreateArrayAttributeNode array field index = %"_U32BITARG_" name = %s api Source = %"_U32BITARG_" \n", i,fieldPtr->fFieldName, (UInt32) fieldPtr->fAPISource); + } + + nodePtr->fElementMap->Register(nodePtr->GetOSRef(i)); + } + nodePtr->SetNodeName(GetName(index)); + nodePtr->SetParentNode(this); + nodePtr->SetSource(source); + nodePtr->fInitialized = true; + + return nodePtr; + +} + +void ElementNode::InitializeAllFields(Bool16 allocateFields, QTSS_Object defaultAttributeInfo, QTSS_Object source, QueryURI *queryPtr, StrPtrLen *currentSegmentPtr , Bool16 forceAll =false) +{ + //qtss_printf("------- ElementNode::InitializeAllFields -------- \n"); + + QTSS_Error err = QTSS_NoErr; + QTSS_Object theAttributeInfo; + + if (allocateFields) + { + UInt32 numFields = this->CountAttributes(source); + err = AllocateFields( numFields); + //qtss_printf("ElementNode::InitializeAllFields AllocateFields numFields = %"_U32BITARG_" error = %"_S32BITARG_" \n",numFields, err); + } + + if (err == QTSS_NoErr) + { + UInt32 numValues = 0; + + for (UInt32 i = 0; !IsStopItem(i); i++) + { + if (defaultAttributeInfo == NULL) + { err = QTSS_GetAttrInfoByIndex(source, i, &theAttributeInfo); + Assert(err == QTSS_NoErr); + if (err != QTSS_NoErr) + { //qtss_printf("QTSS_GetAttrInfoByIndex returned err = %"_U32BITARG_" \n",err); + } + } + else + { theAttributeInfo = defaultAttributeInfo; + } + + SetFields(i, theAttributeInfo); + + if ((SInt32) fFieldIDs[i].fAPI_ID < 0) + { //qtss_printf("ElementNode::InitializeAllFields name = %s index = %"_S32BITARG_" numValues =%"_U32BITARG_" \n",fFieldIDs[i].fFieldName, (SInt32) fFieldIDs[i].fAPI_ID,numValues); + } + numValues = this->CountValues(source, fFieldIDs[i].fAPI_ID); + //qtss_printf("ElementNode::InitializeAllFields name = %s index = %"_U32BITARG_" numValues =%"_U32BITARG_" \n",fFieldIDs[i].fFieldName, fFieldIDs[i].fAPI_ID,numValues); + + QTSS_AttributeID id; + Bool16 foundFilteredAttribute = GetFilteredAttributeID(GetMyName(),GetName(i), &id); + + StrPtrLen nextSegment; + (void) queryPtr->NextSegment(currentSegmentPtr, &nextSegment); + + if (forceAll || nextSegment.Equal(sDoAllIndexIteratorSPL) || queryPtr->IndexParam() || numValues > 1 || foundFilteredAttribute) + { + ElementNode *nodePtr = CreateArrayAttributeNode(i, source, theAttributeInfo,numValues); + Assert(nodePtr != NULL); + /* + if (NULL == nodePtr) + { //qtss_printf("ElementNode::InitializeAllFields(NULL == CreateArrayAttributeNode nodePtr\n"); + } + if (NULL == GetElementDataPtr(i)) + { //qtss_printf("ElementNode::InitializeAllFields(NULL == GetElementDataPtr (i=%"_U32BITARG_") nodePtr=%"_U32BITARG_" \n",i, (UInt32) nodePtr); + } + */ + + } + else + { + //qtss_printf("ElementNode::InitializeAllFields field index = %"_U32BITARG_" name = %s api Source = %"_U32BITARG_" \n", i,fFieldIDs[i].fFieldName, (UInt32) source); + } + + err = fElementMap->Register(GetOSRef(i)); + if (err != QTSS_NoErr) + { //qtss_printf("ElementNode::InitializeAllFields Register returned err = %"_U32BITARG_" field = %s node=%s \n",err,GetName(i),GetMyName()); + } + Assert(err == QTSS_NoErr); + } + } +}; + + +void ElementNode::SetNodeInfo(ElementDataFields *nodeInfoPtr) +{ + if (nodeInfoPtr == NULL) + { + //qtss_printf("---- SetNodeInfo nodeInfoPtr = NULL \n"); + } + else + { + //qtss_printf("---- SetNodeInfo nodeInfoPtr name = %s \n",nodeInfoPtr->fFieldName); + fSelfPtr = nodeInfoPtr; + } +}; + + +void ElementNode::SetNodeName(char *namePtr) +{ + if (namePtr == NULL) + { delete fNodeNameSPL.Ptr; ElementNode_RemovePtr(fNodeNameSPL.Ptr,"ElementNode::SetNodeName char* "); + fNodeNameSPL.Set(NULL, 0); + return; + } + + if (fNodeNameSPL.Ptr != NULL) + { delete fNodeNameSPL.Ptr; ElementNode_RemovePtr(fNodeNameSPL.Ptr,"ElementNode::SetNodeName char* "); + fNodeNameSPL.Set(NULL, 0); + } + //qtss_printf(" ElementNode::SetNodeName new NodeName = %s \n",namePtr); + int len = ::strlen(namePtr); + fNodeNameSPL.Ptr = NEW char[len + 1]; ElementNode_InsertPtr(fNodeNameSPL.Ptr,"ElementNode::SetNodeName ElementNode* chars"); + fNodeNameSPL.Len = len; + memcpy(fNodeNameSPL.Ptr,namePtr,len); + fNodeNameSPL.Ptr[len] = 0; +}; + +ElementNode::ElementDataFields *ElementNode::GetElementFieldPtr(SInt32 index) +{ + ElementNode::ElementDataFields *resultPtr = NULL; + Assert (fFieldIDs != NULL); + Assert ((index >= 0) && (index < (SInt32) fNumFields)); + if ((index >= 0) && (index < (SInt32) fNumFields)) + resultPtr = &fFieldIDs[index]; + return resultPtr; +} + +char *ElementNode::GetElementDataPtr(SInt32 index) +{ + char *resultPtr = NULL; + Assert((index >= 0) && (index < (SInt32) fNumFields)); + if (fInitialized && (fFieldDataPtrs != NULL) && (index >= 0) && (index < (SInt32) fNumFields)) + { resultPtr = fFieldDataPtrs[index]; + } + return resultPtr; +} + +void ElementNode::SetElementDataPtr(SInt32 index,char *data, Bool16 isNode) +{ + //qtss_printf("------ElementNode::SetElementDataPtr----- \n"); + //qtss_printf("ElementNode::SetElementDataPtr index = %"_S32BITARG_" fNumFields = %"_S32BITARG_" \n", index,fNumFields); + Assert ((index >= 0) && (index < (SInt32) fNumFields)); + if ((index >= 0) && (index < (SInt32) fNumFields)) + { //Assert(fFieldDataPtrs[index] == NULL); + if (fDataFieldsType != eStatic) + { + if (isNode) + { delete (ElementNode*) fFieldDataPtrs[index];ElementNode_RemovePtr(fFieldDataPtrs[index],"ElementNode::SetElementDataPtr ElementNode* fFieldDataPtrs"); + } + else + { delete fFieldDataPtrs[index]; ElementNode_RemovePtr(fFieldDataPtrs[index],"ElementNode::SetElementDataPtr char* fFieldDataPtrs"); + } + } + fFieldDataPtrs[index] = data; + //qtss_printf("ElementNode::SetElementDataPtr index = %"_S32BITARG_" \n", index); + } +} + + + +inline void ElementNode::DebugShowFieldDataType(SInt32 /*index*/) +{ + //char field[100]; + //field[0] = ' '; + //char* typeStringPtr = GetAPI_TypeStr(index); + //qtss_printf("debug: %s=%s\n",GetName(index),typeStringPtr); + +} + +inline void ElementNode::DebugShowFieldValue(SInt32 /*index*/) +{ + //qtss_printf("debug: %s=%s\n",GetName(index),GetElementDataPtr(index)); +} + +ElementNode::ElementDataFields *ElementNode::GetNodeInfoPtr(SInt32 index) +{ + ElementNode::ElementDataFields *resultPtr = GetElementFieldPtr(index); + Assert (resultPtr != NULL); + + if ((resultPtr != NULL) && ((eNode != resultPtr->fFieldType) && (eArrayNode != resultPtr->fFieldType) )) + resultPtr = NULL; + return resultPtr; +} + + +void ElementNode::SetUpSingleNode(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr, StrPtrLen *nextSegmentPtr, SInt32 index, QTSS_Initialize_Params *initParams) +{ + //qtss_printf("--------ElementNode::SetUpSingleNode ------------\n"); + if (queryPtr && currentSegmentPtr && nextSegmentPtr&& initParams) do + { + if (!queryPtr->RecurseParam() && (nextSegmentPtr->Len == 0) ) break; + + ElementNode::ElementDataFields *theNodePtr = GetNodeInfoPtr(index); + if (NULL == theNodePtr) + { + //qtss_printf(" ElementNode::SetUpSingleNode (NULL == theNodePtr(%"_S32BITARG_")) name=%s \n",index,GetName(index)); + break; + } + + if (!IsNodeElement(index) ) + { + //qtss_printf(" ElementNode::SetUpSingleNode (apiType != qtssAttrDataTypeQTSS_Object) \n"); + break; + } + + // filter unnecessary nodes + char *nodeName = GetName(index); + if (nodeName != NULL) + { StrPtrLen nodeNameSPL(nodeName); + if ( (!nodeNameSPL.Equal(*nextSegmentPtr) && !ElementNode_DoAll(nextSegmentPtr)) + && + !(queryPtr->RecurseParam() && (nextSegmentPtr->Len == 0)) + ) + { + //qtss_printf(" ElementNode::SetUpSingleNode SPL TEST SKIP NodeElement= %s\n",GetName(index)); + //qtss_printf("ElementNode::SetUpAllNodes skip nextSegmentPtr=");PRINT_STR(nextSegmentPtr); + break; + } + + } + + ElementNode *nodePtr = NULL; + nodePtr = (ElementNode *) GetElementDataPtr(index); + if (nodePtr == NULL) + { + //qtss_printf("ElementNode::SetUpSingleNode %s nodePtr == NULL make NEW nodePtr index = %"_S32BITARG_"\n", GetMyName(),index); + nodePtr = NEW ElementNode(); ElementNode_InsertPtr(nodePtr,"ElementNode::SetUpSingleNode ElementNode* NEW ElementNode() "); + SetElementDataPtr(index,(char *) nodePtr, true); + } + + if (nodePtr != NULL) + { + StrPtrLen tempSegment; + ( void)queryPtr->NextSegment(nextSegmentPtr, &tempSegment); + currentSegmentPtr = nextSegmentPtr; + nextSegmentPtr = &tempSegment; + + + if (!nodePtr->fInitialized) + { + //qtss_printf("ElementNode::SetUpSingleNode Node !fInitialized -- Initialize %s\n",GetName(index)); + //qtss_printf("ElementNode::SetUpSingleNode GetValue source = %"_U32BITARG_" name = %s id = %"_U32BITARG_" \n",(UInt32) GetSource(),(UInt32) GetName(index),(UInt32) GetAPI_ID(index)); + + ElementDataFields* fieldPtr = GetElementFieldPtr(index); + if (fieldPtr != NULL && fieldPtr->fAPI_Type == qtssAttrDataTypeQTSS_Object) + { UInt32 sourceLen = sizeof(fieldPtr->fAPISource); + (void) QTSS_GetValue (GetSource(),fieldPtr->fAPI_ID,fieldPtr->fIndex, &fieldPtr->fAPISource, &sourceLen); + } + + QTSS_Object theSourceObject = GetAPISource(index); + //Warn(theSourceObject != NULL); + + nodePtr->Initialize(index, this, queryPtr,nextSegmentPtr,initParams, theSourceObject, eDynamic); + nodePtr->SetUpAllElements(queryPtr, currentSegmentPtr,nextSegmentPtr, initParams); + fInitialized = true; + + break; + + } + else + { + nodePtr->SetUpAllElements(queryPtr, currentSegmentPtr,nextSegmentPtr, initParams); + } + } + + } while (false); + + return; +} + +Bool16 ElementNode::SetUpOneDataField( UInt32 index) +{ + //qtss_printf("----ElementNode::SetUpOneDataField----\n"); + //qtss_printf(" ElementNode::SetUpOneDataField parent = %s field name=%s\n",GetNodeName(), GetName(index)); + + QTSS_AttributeID inID = GetAPI_ID(index); + Bool16 isNodeResult = IsNodeElement(index); + char *testPtr = GetElementDataPtr(index); + //Warn(NULL == testPtr); + if (NULL != testPtr) + { //qtss_printf(" ElementNode::SetUpOneDataField skip field already setup parent = %s field name=%s\n",GetNodeName(), GetName(index)); + return isNodeResult; + } + + if (!isNodeResult) + { + //qtss_printf("ElementNode::SetUpOneDataField %s Source=%"_U32BITARG_" Field index=%"_U32BITARG_" API_ID=%"_U32BITARG_" value index=%"_U32BITARG_"\n",GetName(index),GetSource(), index,inID,GetAttributeIndex(index)); + SetElementDataPtr(index, NewIndexElement (GetSource() , inID, GetAttributeIndex(index)), false); + } + else + { + //qtss_printf("ElementNode::SetUpOneDataField %s Source=%"_U32BITARG_" Field index=%"_U32BITARG_" API_ID=%"_U32BITARG_" value index=%"_U32BITARG_"\n",GetName(index),(UInt32) GetSource(),(UInt32) index,(UInt32) inID,(UInt32) GetAttributeIndex(index)); + //qtss_printf("ElementNode::SetUpOneDataField %s IsNodeElement index = %"_U32BITARG_"\n",GetName(index),(UInt32) index); + //DebugShowFieldDataType(index); + } + + DebugShowFieldValue( index); + + return isNodeResult; +} + +void ElementNode::SetUpAllElements(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr,StrPtrLen *nextSegmentPtr, QTSS_Initialize_Params *initParams) +{ + //qtss_printf("---------ElementNode::SetUpAllElements------- \n"); + + for(SInt32 index = 0; !IsStopItem(index); index ++) + { + SetUpSingleElement(queryPtr, currentSegmentPtr,nextSegmentPtr, index,initParams); + } +} + + + + +void ElementNode::SetUpSingleElement(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr,StrPtrLen *nextSegmentPtr, SInt32 index, QTSS_Initialize_Params *initParams) +{ + //qtss_printf("---------ElementNode::SetUpSingleElement------- \n"); + StrPtrLen indexNodeNameSPL; + GetNameSPL(index,&indexNodeNameSPL); + if ( (queryPtr->RecurseParam() && (nextSegmentPtr->Len == 0)) + || + (indexNodeNameSPL.Equal(*nextSegmentPtr) || ElementNode_DoAll(nextSegmentPtr)) + ) // filter unnecessary elements + { + + Bool16 isNode = SetUpOneDataField((UInt32) index); + if (isNode) + { + //qtss_printf("ElementNode::SetUpSingleElement isNode=true calling SetUpSingleNode \n"); + SetUpSingleNode(queryPtr,currentSegmentPtr, nextSegmentPtr, index, initParams); + } + } + else + { //qtss_printf("ElementNode::SetUpSingleElement filter element=%s\n",GetName(index)); + } +} + + +void ElementNode::SetUpAllNodes(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr, StrPtrLen *nextSegmentPtr, QTSS_Initialize_Params *initParams) +{ + //qtss_printf("--------ElementNode::SetUpAllNodes------- \n"); + for(SInt32 index = 0; !IsStopItem(index); index ++) + { + if (!queryPtr->RecurseParam() && (nextSegmentPtr->Len == 0) ) break; + + //qtss_printf("ElementNode::SetUpAllNodes index = %"_S32BITARG_" nextSegmentPtr=", index);PRINT_STR(nextSegmentPtr); + StrPtrLen indexNodeNameSPL; + GetNameSPL(index,&indexNodeNameSPL); + if ( IsNodeElement(index) + && + ( (queryPtr->RecurseParam() && (nextSegmentPtr->Len == 0)) + || + (indexNodeNameSPL.Equal(*nextSegmentPtr) || ElementNode_DoAll(nextSegmentPtr)) + ) + ) // filter unnecessary nodes + SetUpSingleNode(queryPtr, currentSegmentPtr, nextSegmentPtr, index, initParams); + else + { + //qtss_printf("ElementNode::SetUpAllNodes skip index = %"_S32BITARG_" indexNodeName=", index);PRINT_STR(&indexNodeNameSPL); + //qtss_printf("ElementNode::SetUpAllNodes skip nextSegmentPtr=");PRINT_STR(nextSegmentPtr); + } + } +} + +QTSS_Error ElementNode::GetAttributeSize (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex, UInt32* outLenPtr) +{ + return QTSS_GetValue (inObject, inID, inIndex, NULL, outLenPtr); +} + +char *ElementNode::NewIndexElement (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex) +{ + QTSS_Error err = QTSS_NoErr; + char *resultPtr = NULL; + + Assert(inObject != NULL); + + if (inObject != NULL) + { err = QTSS_GetValueAsString (inObject, inID, inIndex, &resultPtr); ElementNode_InsertPtr(resultPtr,"ElementNode::NewIndexElement QTSS_GetValueAsString "); + if (err != QTSS_NoErr) + { //qtss_printf("ElementNode::NewIndexElement QTSS_GetValueAsString object= %p id=%"_U32BITARG_" index=%"_U32BITARG_" err= %"_S32BITARG_" \n",inObject,inID, inIndex, err); + } + } + return resultPtr; +} + + +inline SInt32 ElementNode::ResolveSPLKeyToIndex(StrPtrLen *keyPtr) +{ + SInt32 index = -1; + OSRef* osrefptr = NULL; + PointerSizedInt object = 0; + + if (fElementMap != NULL && keyPtr != NULL && keyPtr->Len > 0) + { osrefptr = fElementMap->Resolve(keyPtr); + if (osrefptr != NULL) + { object = (PointerSizedInt) osrefptr->GetObject(); + index = (SInt32) object; + } + } + + return index; +} + + +UInt32 ElementNode::CountAttributes(QTSS_Object source) +{ + //qtss_printf("------ElementNode::CountAttributes-------\n"); + //qtss_printf("ElementNode::CountAttributes SOURCE = %"_U32BITARG_" \n", (UInt32) source); + + UInt32 numFields = 0; + + (void) QTSS_GetNumAttributes (source, &numFields); + + //qtss_printf("ElementNode::CountAttributes %s = %"_U32BITARG_" \n",GetNodeName() ,numFields); + + return numFields; +} + +UInt32 ElementNode::CountValues(QTSS_Object source, UInt32 apiID) +{ + //qtss_printf("------ElementNode::CountValues-------\n"); + UInt32 numFields = 0; + + (void) QTSS_GetNumValues (source, apiID, &numFields); + + //qtss_printf("ElementNode::CountValues %s = %"_U32BITARG_" \n",GetNodeName() ,numFields); + + return numFields; +} + + + +OSRef* ElementNode::GetOSRef(SInt32 index) +{ + StrPtrLen theName; + OSRef* resultPtr = NULL; + + resultPtr = fFieldOSRefPtrs[index]; +// Assert(resultPtr != NULL); + if (resultPtr == NULL) + { + fFieldOSRefPtrs[index] = NEW OSRef(); Assert(fFieldOSRefPtrs[index] != NULL); ElementNode_InsertPtr(fFieldOSRefPtrs[index],"ElementNode::GetOSRef NEW OSRef() fFieldOSRefPtrs "); + GetNameSPL(index,&theName); Assert(theName.Len != 0); + //qtss_printf("ElementNode::GetOSRef index = %"_S32BITARG_" name = %s \n", index, theName.Ptr); + fFieldOSRefPtrs[index]->Set(theName,(void *) index); + if (0 != theName.Len && NULL != theName.Ptr) //return the ptr else NULL + resultPtr = fFieldOSRefPtrs[index]; + } + + + return resultPtr; +} + +void ElementNode::SetOSRef(SInt32 index, OSRef* refPtr) +{ + Assert ((index >= 0) && (index < (SInt32) fNumFields)); + if (fInitialized && (index >= 0) && (index < (SInt32) fNumFields)) + fFieldOSRefPtrs[index] = refPtr; +} + +void ElementNode::GetFullPath(StrPtrLen *resultPtr) +{ + //qtss_printf("ElementNode::GetFullPath this node name %s \n",GetNodeName()); + + Assert(fPathSPL.Ptr != NULL); + + if (fPathSPL.Len != 0) + { + resultPtr->Set(fPathSPL.Ptr,fPathSPL.Len); + //qtss_printf("ElementNode::GetFullPath has path=%s\n",resultPtr->Ptr); + return; + } + + ElementNode *parentPtr = GetParentNode(); + if (parentPtr != NULL) + { + StrPtrLen parentPath; + parentPtr->GetFullPath(&parentPath); + memcpy(fPathSPL.Ptr,parentPath.Ptr,parentPath.Len); + fPathSPL.Ptr[parentPath.Len] = 0; + fPathSPL.Len = parentPath.Len; + } + + UInt32 nodeNameLen = GetNodeNameLen(); + if (nodeNameLen > 0) + { + fPathSPL.Len += nodeNameLen + 1; + Assert(fPathSPL.Len < kmaxPathlen); + if (fPathSPL.Len < kmaxPathlen) + { strcat(fPathSPL.Ptr, GetNodeName()); + strcat(fPathSPL.Ptr,"/"); + fPathSPL.Len = strlen(fPathSPL.Ptr); + } + } + + resultPtr->Set(fPathSPL.Ptr,fPathSPL.Len); + //qtss_printf("ElementNode::GetFullPath element=%s received full path=%s \n",GetMyName(),resultPtr->Ptr); +} + +void ElementNode::RespondWithSelfAdd(QTSS_StreamRef inStream, QueryURI *queryPtr) +{ + static char *nullErr = "(null)"; + Bool16 nullData = false; + QTSS_Error err = QTSS_NoErr; + char messageBuffer[1024] = ""; + StrPtrLen bufferSPL(messageBuffer); + + //qtss_printf("ElementNode::RespondWithSelfAdd NODE = %s index = %"_S32BITARG_" \n",GetNodeName(), (SInt32) index); + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondWithSelfAdd not Initialized EXIT\n"); + return; + } + if (NULL == queryPtr) + { //qtss_printf("ElementNode::RespondWithSelfAdd NULL == queryPtr EXIT\n"); + return; + } + + if (NULL == inStream) + { //qtss_printf("ElementNode::RespondWithSelfAdd NULL == inStream EXIT\n"); + return; + } + + char *dataPtr = GetMyElementDataPtr(); + if (NULL == dataPtr) + { //qtss_printf("ElementNode::RespondWithSelfAdd NULL == dataPtr EXIT\n"); + dataPtr = nullErr; + nullData = true; + } + + queryPtr->SetQueryHasResponse(); + + + +#if CHECKACCESS +/* + StrPtrLen *accessParamsPtr=queryPtr->GetAccess(); + if (accessParamsPtr == NULL) + { + UInt32 result = 400; + qtss_sprintf(messageBuffer, "Attribute Access is required"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + accessFlags = queryPtr->GetAccessFlags(); + if (0 == (accessFlags & qtssAttrModeWrite)) + { + UInt32 result = 400; + qtss_sprintf(messageBuffer, "Attribute must have write access"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } +*/ +#endif + + + StrPtrLen* valuePtr = queryPtr->GetValue(); + OSCharArrayDeleter value(NewCharArrayCopy(valuePtr)); + if (!valuePtr || !valuePtr->Ptr) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "Attribute value is required"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + + StrPtrLen *typePtr = queryPtr->GetType(); + OSCharArrayDeleter dataType(NewCharArrayCopy(typePtr)); + if (!typePtr || !typePtr->Ptr) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "Attribute type is required"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + QTSS_AttrDataType attrDataType = qtssAttrDataTypeUnknown; + if (typePtr && typePtr->Len > 0) + { + err = QTSS_TypeStringToType(dataType.GetObject(), &attrDataType); + Assert(err == QTSS_NoErr); + //qtss_printf("ElementNode::RespondWithSelfAdd theType=%s typeID=%"_U32BITARG_" \n",dataType.GetObject(), attrDataType); + } + + //qtss_printf("ElementNode::RespondWithSelfAdd theValue= %s theType=%s typeID=%"_U32BITARG_" \n",value.GetObject(), typePtr->Ptr, attrDataType); + char valueBuff[2048] = ""; + UInt32 len = 2048; + err = QTSS_StringToValue(value.GetObject(),attrDataType, valueBuff, &len); + if (err) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from ElementNode::RespondWithSelfAdd QTSS_ConvertStringToType",err); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + if (GetMyFieldType() != eNode) + { UInt32 result = 500; + qtss_sprintf(messageBuffer, "Internal error"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + StrPtrLen *namePtr = queryPtr->GetName(); + OSCharArrayDeleter nameDeleter(NewCharArrayCopy(namePtr)); + if (!namePtr || !namePtr->Ptr || namePtr->Len == 0) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "Missing name for attribute"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + err = QTSS_AddInstanceAttribute(GetSource(),nameDeleter.GetObject(), NULL, attrDataType); + //qtss_printf("QTSS_AddInstanceAttribute(source=%"_U32BITARG_", name=%s, NULL, %d, %"_U32BITARG_")\n",GetSource(),nameDeleter.GetObject(),attrDataType,accessFlags); + if (err) + { UInt32 result = 400; + if (err == QTSS_AttrNameExists) + { qtss_sprintf(messageBuffer, "The name %s already exists QTSS_Error=%"_S32BITARG_" from QTSS_AddInstanceAttribute",nameDeleter.GetObject(),err); + } + else + { qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_AddInstanceAttribute",err); + } + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + QTSS_Object attrInfoObject; + err = QTSS_GetAttrInfoByName(GetSource(), nameDeleter.GetObject(), &attrInfoObject); + if (err) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_GetAttrInfoByName",err); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + QTSS_AttributeID attributeID = 0; + UInt32 attributeLen = sizeof(attributeID); + err = QTSS_GetValue (attrInfoObject, qtssAttrID,0, &attributeID, &attributeLen); + if (err) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_GetValue",err); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + err = QTSS_SetValue (GetSource(), attributeID, 0, valueBuff, len); + if (err) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_SetValue",err); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + +} + +void ElementNode::RespondWithSelf(QTSS_StreamRef inStream, QueryURI *queryPtr) +{ + //qtss_printf("ElementNode::RespondWithSelf = %s \n",GetNodeName()); + + static char *nullErr = "(null)"; + if (QueryURI::kADDCommand == queryPtr->GetCommandID()) + { + if (GetMyFieldType() == eArrayNode) + { RespondToAdd(inStream, 0,queryPtr); + } + else + { RespondWithSelfAdd(inStream,queryPtr); + } + + return; + + } + + if (QueryURI::kDELCommand == queryPtr->GetCommandID()) + { GetParentNode()->RespondToDel(inStream, GetMyKey(),queryPtr,true); + return; + } + + + if (GetNodeName() == NULL) + { //qtss_printf("ElementNode::RespondWithSelf Node = %s is Uninitialized no name so LEAVE\n",GetNodeName() ); + return; + } + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondWithSelf not Initialized EXIT\n"); + return; + } + + if (NULL == queryPtr) + { //qtss_printf("ElementNode::RespondWithSelf NULL == queryPtr EXIT\n"); + return; + } + + if (queryPtr->fNumFilters > 0) + { + Bool16 foundFilter = false; + StrPtrLen* theFilterPtr; + for (SInt32 count = 0; count < queryPtr->fNumFilters; count ++) + { + theFilterPtr = queryPtr->GetFilter(count); + if (theFilterPtr && theFilterPtr->Equal(StrPtrLen(GetMyName())) ) + { foundFilter = true; + //qtss_printf("ElementNode::RespondWithSelf found filter = ");PRINT_STR(theFilterPtr); + break; + } + } + if (!foundFilter) return; + } + + StrPtrLen bufferSPL; + + UInt32 parameters = queryPtr->GetParamBits(); + parameters &= ~QueryURI::kRecurseParam; // clear recurse flag + parameters &= ~QueryURI::kDebugParam; // clear verbose flag + parameters &= ~QueryURI::kIndexParam; // clear index flag + + + Bool16 isVerbosePath = 0 != (parameters & QueryURI::kVerboseParam); + if (isVerbosePath) + { + parameters &= ~QueryURI::kVerboseParam; // clear verbose flag + GetFullPath(&bufferSPL); + (void)QTSS_Write(inStream, bufferSPL.Ptr, ::strlen(bufferSPL.Ptr), NULL, 0); + //qtss_printf("ElementNode::RespondWithSelf Path=%s \n",bufferSPL.Ptr); + } + + if (IsNodeElement()) + { if (!isVerbosePath) // this node name already in path + { + (void)QTSS_Write(inStream, GetNodeName(), GetNodeNameLen(), NULL, 0); + (void)QTSS_Write(inStream, "/", 1, NULL, 0); + //qtss_printf("ElementNode::RespondWithSelf %s/ \n",GetNodeName()); + } + } + else + { //qtss_printf(" ****** ElementNode::RespondWithSelf NOT a node **** \n"); + (void)QTSS_Write(inStream, GetNodeName(), GetNodeNameLen(), NULL, 0); + (void)QTSS_Write(inStream, "=", 1, NULL, 0); + //qtss_printf(" %s=",GetNodeName()); + + char *dataPtr = GetMyElementDataPtr(); + if (dataPtr == NULL) + { + (void)QTSS_Write(inStream, nullErr, ::strlen(nullErr), NULL, 0); + } + else + { + (void)QTSS_Write(inStream, dataPtr, ::strlen(dataPtr), NULL, 0); + } + //qtss_printf(" %s",buffer); + + } + + if (parameters) + { (void)QTSS_Write(inStream, sParameterDelimeter, 1, NULL, 0); + //qtss_printf(" %s",sParameterDelimeter); + } + + if (parameters & QueryURI::kAccessParam) + { + (void)QTSS_Write(inStream, sAccess, 2, NULL, 0); + //qtss_printf(" %s",sAccess); + (void)QTSS_Write(inStream, GetMyAccessData(), GetMyAccessLen(), NULL, 0); + //qtss_printf("%s",GetMyAccessData()); + parameters &= ~QueryURI::kAccessParam; // clear access flag + + if (parameters) + { (void)QTSS_Write(inStream, sListDelimeter, 1, NULL, 0); + //qtss_printf("%s",sListDelimeter); + } + } + + if (parameters & QueryURI::kTypeParam) + { + (void)QTSS_Write(inStream, sType, 2, NULL, 0); + //qtss_printf(" %s",sType); + char *theTypeString = GetMyAPI_TypeStr(); + if (theTypeString == NULL) + { + (void)QTSS_Write(inStream, nullErr, ::strlen(nullErr), NULL, 0); + } + else + { + (void)QTSS_Write(inStream,theTypeString,strlen(theTypeString), NULL, 0); + //qtss_printf("%s",theTypeString); + } + + parameters &= ~QueryURI::kTypeParam; // clear type flag + + if (parameters) + { (void)QTSS_Write(inStream, sListDelimeter, 1, NULL, 0); + //qtss_printf("%s",sListDelimeter); + } + } + queryPtr->SetQueryHasResponse(); + (void)QTSS_Write(inStream, "\n", 1, NULL, 0); + //qtss_printf("\n"); + +} + +void ElementNode::RespondToAdd(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr) +{ + char messageBuffer[1024] = ""; + + //qtss_printf("ElementNode::RespondToAdd NODE = %s index = %"_S32BITARG_" \n",GetNodeName(), (SInt32) index); + if (GetNumFields() == 0) + { + UInt32 result = 405; + qtss_sprintf(messageBuffer, "Attribute does not allow adding. Action not allowed"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + //qtss_printf("ElementNode::RespondToAdd error = %s \n",messageBuffer); + return; + } + + if (GetFieldType(index) == eNode) + { RespondWithSelfAdd(inStream, queryPtr); + return; + } + + static char *nullErr = "(null)"; + Bool16 nullData = false; + QTSS_Error err = QTSS_NoErr; + StrPtrLen bufferSPL(messageBuffer); + + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondToAdd not Initialized EXIT\n"); + return; + } + if (NULL == queryPtr) + { //qtss_printf("ElementNode::RespondToAdd NULL == queryPtr EXIT\n"); + return; + } + + if (NULL == inStream) + { //qtss_printf("ElementNode::RespondToAdd NULL == inStream EXIT\n"); + return; + } + + char *dataPtr = GetElementDataPtr(index); + if (NULL == dataPtr) + { //qtss_printf("ElementNode::RespondToAdd NULL == dataPtr EXIT\n"); + // return; + dataPtr = nullErr; + nullData = true; + } + + queryPtr->SetQueryHasResponse(); + + + UInt32 accessFlags = 0; + StrPtrLen *accessParamsPtr=queryPtr->GetAccess(); + if (accessParamsPtr != NULL) + accessFlags = queryPtr->GetAccessFlags(); + else + accessFlags = GetAccessPermissions(index); + + + + StrPtrLen* valuePtr = queryPtr->GetValue(); + OSCharArrayDeleter value(NewCharArrayCopy(valuePtr)); + if (!valuePtr || !valuePtr->Ptr) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "No value found"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + //qtss_printf("ElementNode::RespondToAdd theValue= %s theType=%s typeID=%"_U32BITARG_" \n",value.GetObject(), GetAPI_TypeStr(index), GetAPI_Type(index)); + char valueBuff[2048] = ""; + UInt32 len = 2048; + err = QTSS_StringToValue(value.GetObject(),GetAPI_Type(index), valueBuff, &len); + if (err) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_ConvertStringToType",err); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + if (GetFieldType(index) != eNode) + { + OSCharArrayDeleter typeDeleter(NewCharArrayCopy(queryPtr->GetType())); + StrPtrLen theQueryType(typeDeleter.GetObject()); + + if (typeDeleter.GetObject()) + { + StrPtrLen attributeString(GetAPI_TypeStr(index)); + if (!attributeString.Equal(theQueryType)) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "Type %s does not match attribute type %s",typeDeleter.GetObject(), attributeString.Ptr); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + } + + QTSS_Object source = GetSource(); + + UInt32 tempBuff; + UInt32 attributeLen = sizeof(tempBuff); + (void) QTSS_GetValue (source, GetAPI_ID(index), 0, &tempBuff, &attributeLen); + if (attributeLen != len) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "Data length %"_U32BITARG_" does not match attribute len %"_U32BITARG_"",len, attributeLen); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + + UInt32 numValues = 0; + err = QTSS_GetNumValues (source, GetAPI_ID(index), &numValues); + if (err) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_GetNumValues",err); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + //qtss_printf("ElementNode::RespondToAdd QTSS_SetValue object=%"_U32BITARG_" attrID=%"_U32BITARG_", index = %"_U32BITARG_" valuePtr=%"_U32BITARG_" valuelen =%"_U32BITARG_" \n",GetSource(),GetAPI_ID(index), GetAttributeIndex(index), valueBuff,len); + err = QTSS_SetValue (source, GetAPI_ID(index), numValues, valueBuff, len); + if (err) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_SetValue",err); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + } + +} + +void ElementNode::RespondToSet(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr) +{ + static char *nullErr = "(null)"; + Bool16 nullData = false; + QTSS_Error err = QTSS_NoErr; + char messageBuffer[1024] = ""; + StrPtrLen bufferSPL(messageBuffer); + + //qtss_printf("ElementNode::RespondToSet NODE = %s index = %"_S32BITARG_" \n",GetNodeName(), (SInt32) index); + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondToSet not Initialized EXIT\n"); + return; + } + if (NULL == queryPtr) + { //qtss_printf("ElementNode::RespondToSet NULL == queryPtr EXIT\n"); + return; + } + + if (NULL == inStream) + { //qtss_printf("ElementNode::RespondToSet NULL == inStream EXIT\n"); + return; + } + + char *dataPtr = GetElementDataPtr(index); + if (NULL == dataPtr) + { //qtss_printf("ElementNode::RespondToSet NULL == dataPtr EXIT\n"); + // return; + dataPtr = nullErr; + nullData = true; + } + + queryPtr->SetQueryHasResponse(); + + OSCharArrayDeleter typeDeleter(NewCharArrayCopy(queryPtr->GetType())); + StrPtrLen theQueryType(typeDeleter.GetObject()); + + if (theQueryType.Len > 0) + { StrPtrLen attributeString(GetAPI_TypeStr(index)); + if (!attributeString.Equal(theQueryType)) + { UInt32 result = 400; + qtss_sprintf(messageBuffer, "Type %s does not match attribute type %s",typeDeleter.GetObject(), attributeString.Ptr); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + } + + if (0 == (GetAccessPermissions(index) & qtssAttrModeWrite)) + { + UInt32 result = 400; + qtss_sprintf(messageBuffer, "Attribute is read only. Action not allowed"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + + if (GetFieldType(index) == eNode) + { + UInt32 result = 400; + qtss_sprintf(messageBuffer, "Set of type %s not allowed",typeDeleter.GetObject()); + //qtss_printf("ElementNode::RespondToSet (GetFieldType(index) == eNode) %s\n",messageBuffer); + (void) queryPtr->EvalQuery(&result, messageBuffer); + return; + } + else do + { + + StrPtrLen* valuePtr = queryPtr->GetValue(); + if (!valuePtr || !valuePtr->Ptr) break; + + char valueBuff[2048] = ""; + UInt32 len = 2048; + OSCharArrayDeleter value(NewCharArrayCopy(valuePtr)); + + //qtss_printf("ElementNode::RespondToSet valuePtr->Ptr= %s\n",value.GetObject()); + + err = QTSS_StringToValue(value.GetObject(),GetAPI_Type(index), valueBuff, &len); + if (err) + { //qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_ConvertStringToType",err); + break; + } + + //qtss_printf("ElementNode::RespondToSet QTSS_SetValue object=%"_U32BITARG_" attrID=%"_U32BITARG_", index = %"_U32BITARG_" valuePtr=%"_U32BITARG_" valuelen =%"_U32BITARG_" \n",GetSource(),GetAPI_ID(index), GetAttributeIndex(index), valueBuff,len); + err = QTSS_SetValue (GetSource(), GetAPI_ID(index), GetAttributeIndex(index), valueBuff, len); + if (err) + { //qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_SetValue",err); + break; + } + + } while (false); + + if (err != QTSS_NoErr) + { UInt32 result = 400; + (void) queryPtr->EvalQuery(&result, messageBuffer); + //qtss_printf("ElementNode::RespondToSet %s len = %"_U32BITARG_" ",messageBuffer, result); + return; + } + +} + +void ElementNode::RespondToDel(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr,Bool16 delAttribute) +{ + static char *nullErr = "(null)"; + Bool16 nullData = false; + QTSS_Error err = QTSS_NoErr; + char messageBuffer[1024] = ""; + StrPtrLen bufferSPL(messageBuffer); + + //qtss_printf("ElementNode::RespondToDel NODE = %s index = %"_S32BITARG_" \n",GetNodeName(), (SInt32) index); + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondToDel not Initialized EXIT\n"); + return; + } + if (NULL == queryPtr) + { //qtss_printf("ElementNode::RespondToDel NULL == queryPtr EXIT\n"); + return; + } + + if (NULL == inStream) + { //qtss_printf("ElementNode::RespondToDel NULL == inStream EXIT\n"); + return; + } + + //qtss_printf("ElementNode::RespondToDel NODE = %s index = %"_S32BITARG_" \n",GetNodeName(), (SInt32) index); + if ( GetNumFields() == 0 + || ( 0 == (GetAccessPermissions(index) & qtssAttrModeDelete) && GetMyFieldType() == eArrayNode && GetNumFields() == 1) + || ( 0 == (GetAccessPermissions(index) & qtssAttrModeDelete) && GetMyFieldType() != eArrayNode) + ) + { + UInt32 result = 405; + qtss_sprintf(messageBuffer, "Attribute does not allow deleting. Action not allowed"); + (void) queryPtr->EvalQuery(&result, messageBuffer); + //qtss_printf("ElementNode::RespondToDel error = %s \n",messageBuffer); + return; + } + + char *dataPtr = GetElementDataPtr(index); + if (NULL == dataPtr) + { //qtss_printf("ElementNode::RespondToDel NULL == dataPtr EXIT\n"); + // return; + dataPtr = nullErr; + nullData = true; + } + + queryPtr->SetQueryHasResponse(); + + // DMS - Removeable is no longer a permission bit + // + //if (GetMyFieldType() != eArrayNode && 0 == (GetAccessPermissions(index) & qtssAttrModeRemoveable)) + //{ + // UInt32 result = 405; + // qtss_sprintf(messageBuffer, "Attribute is not removable. Action not allowed"); + // (void) queryPtr->EvalQuery(&result, messageBuffer); + // return; + //} + + if (GetMyFieldType() == eArrayNode && !delAttribute) + { + UInt32 result = 500; + err = QTSS_RemoveValue (GetSource(),GetAPI_ID(index), GetAttributeIndex(index)); + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_RemoveValue", err); + //qtss_printf("ElementNode::RespondToDel QTSS_RemoveValue object=%"_U32BITARG_" attrID=%"_U32BITARG_" index=%"_U32BITARG_" %s\n",GetSource(),GetAPI_ID(index),GetAttributeIndex(index),messageBuffer); + if (err) + { (void) queryPtr->EvalQuery(&result, messageBuffer); + } + } + else + { + //qtss_printf("ElementNode::RespondToDel QTSS_RemoveInstanceAttribute object=%"_U32BITARG_" attrID=%"_U32BITARG_" \n",GetSource(),GetAPI_ID(index)); + err = QTSS_RemoveInstanceAttribute(GetSource(),GetAPI_ID(index)); + if (err) + { + qtss_sprintf(messageBuffer, "QTSS_Error=%"_S32BITARG_" from QTSS_RemoveInstanceAttribute",err); + } + + } + + if (err != QTSS_NoErr) + { UInt32 result = 400; + (void) queryPtr->EvalQuery(&result, messageBuffer); + //qtss_printf("ElementNode::RespondToDel %s len = %"_U32BITARG_" ",messageBuffer, result); + return; + } + +} + +Bool16 ElementNode::IsFiltered(SInt32 index,QueryURI *queryPtr) +{ + Bool16 foundFilter = false; + StrPtrLen* theFilterPtr; + for (SInt32 count = 0; count < queryPtr->fNumFilters; count ++) + { + theFilterPtr = queryPtr->GetFilter(count); + if (theFilterPtr && theFilterPtr->Equal(StrPtrLen(GetName(index))) ) + { foundFilter = true; + break; + } + } + return foundFilter; +} + +void ElementNode::RespondToGet(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr) +{ + static char *nullErr = "(null)"; + Bool16 nullData = false; + + //qtss_printf("ElementNode::RespondToGet NODE = %s index = %"_S32BITARG_" \n",GetNodeName(), (SInt32) index); + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondToGet not Initialized EXIT\n"); + return; + } + if (NULL == queryPtr) + { //qtss_printf("ElementNode::RespondToGet NULL == queryPtr EXIT\n"); + return; + } + + if (NULL == inStream) + { //qtss_printf("ElementNode::RespondToGet NULL == inStream EXIT\n"); + return; + } + + char *dataPtr = GetElementDataPtr(index); + if (NULL == dataPtr) + { //qtss_printf("ElementNode::RespondToGet NULL == dataPtr EXIT\n"); + // return; + dataPtr = nullErr; + nullData = true; + } + + StrPtrLen bufferSPL; + + UInt32 parameters = queryPtr->GetParamBits(); + parameters &= ~QueryURI::kRecurseParam; // clear verbose flag + parameters &= ~QueryURI::kDebugParam; // clear debug flag + parameters &= ~QueryURI::kIndexParam; // clear index flag + + //qtss_printf("ElementNode::RespondToGet QTSS_SetValue object=%"_U32BITARG_" attrID=%"_U32BITARG_", index = %"_U32BITARG_" \n",GetSource(),GetAPI_ID(index), GetAttributeIndex(index)); + + + if ((parameters & QueryURI::kVerboseParam) ) + { + parameters &= ~QueryURI::kVerboseParam; // clear verbose flag + GetFullPath(&bufferSPL); + (void)QTSS_Write(inStream, bufferSPL.Ptr, ::strlen(bufferSPL.Ptr), NULL, 0); + //qtss_printf("ElementNode::RespondToGet Path=%s \n",bufferSPL.Ptr); + } + + + (void)QTSS_Write(inStream, GetName(index), GetNameLen(index), NULL, 0); + //qtss_printf("ElementNode::RespondToGet %s:len = %"_U32BITARG_"",GetName(index),(UInt32) GetNameLen(index)); + + if (IsNodeElement(index)) + { + (void)QTSS_Write(inStream, "/\"", 1, NULL, 0); + //qtss_printf(" %s/\"",GetNodeName()); + } + else + { + if (nullData) + { + (void)QTSS_Write(inStream, "=", 1, NULL, 0); + (void)QTSS_Write(inStream, dataPtr, ::strlen(dataPtr), NULL, 0); + } + else + { + (void)QTSS_Write(inStream, "=\"", 2, NULL, 0); + (void)QTSS_Write(inStream, dataPtr, ::strlen(dataPtr), NULL, 0); + (void)QTSS_Write(inStream, "\"", 1, NULL, 0); + } + } + + //qtss_printf(" %s len = %d ",buffer, ::strlen(buffer)); + //DebugShowFieldDataType(index); + + if (parameters) + { (void)QTSS_Write(inStream, sParameterDelimeter, 1, NULL, 0); + //qtss_printf(" %s",sParameterDelimeter); + } + + if (parameters & QueryURI::kAccessParam) + { + (void)QTSS_Write(inStream, sAccess, 2, NULL, 0); + //qtss_printf(" %s",sAccess); + (void)QTSS_Write(inStream, GetAccessData(index), GetAccessLen(index), NULL, 0); + //qtss_printf("%s",GetAccessData(index)); + parameters &= ~QueryURI::kAccessParam; // clear access flag + + if (parameters) + { (void)QTSS_Write(inStream, sListDelimeter, 1, NULL, 0); + //qtss_printf("%s",sListDelimeter); + } + } + + if (parameters & QueryURI::kTypeParam) + { + (void)QTSS_Write(inStream, sType, 2, NULL, 0); + //qtss_printf(" %s",sType); + char* typeStringPtr = GetAPI_TypeStr(index); + if (typeStringPtr == NULL) + { + //qtss_printf("ElementNode::RespondToGet typeStringPtr is NULL for type = %s \n", typeStringPtr); + (void)QTSS_Write(inStream, nullErr, ::strlen(nullErr), NULL, 0); + } + else + { + //qtss_printf("ElementNode::RespondToGet type = %s \n", typeStringPtr); + (void)QTSS_Write(inStream,typeStringPtr,strlen(typeStringPtr), NULL, 0); + } + + parameters &= ~QueryURI::kTypeParam; // clear type flag + + if (parameters) + { (void)QTSS_Write(inStream, sListDelimeter, 1, NULL, 0); + //qtss_printf("%s",sListDelimeter); + } + } + + + (void)QTSS_Write(inStream, "\n", 1, NULL, 0); + //qtss_printf(" %s","\n"); + + queryPtr->SetQueryHasResponse(); + +} + +void ElementNode::RespondToKey(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr) +{ + SInt32 command = queryPtr->GetCommandID(); + //qtss_printf("ElementNode::RespondToKey command = %"_S32BITARG_" node =%s index=%"_S32BITARG_"\n",command, GetNodeName(),index); + + switch (command) + { + case QueryURI::kGETCommand: RespondToGet(inStream,index,queryPtr); + break; + + case QueryURI::kSETCommand: RespondToSet(inStream,index,queryPtr); + break; + + case QueryURI::kADDCommand: RespondToAdd(inStream,index,queryPtr); + break; + + case QueryURI::kDELCommand: RespondToDel(inStream,index,queryPtr,false); + break; + } + +} + +void ElementNode::RespondWithNodeName(QTSS_StreamRef inStream, QueryURI * /*unused queryPtr*/) +{ + + //qtss_printf("ElementNode::RespondWithNodeName NODE = %s \n",GetNodeName()); + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondWithNodeName not Initialized EXIT\n"); + return; + } + + StrPtrLen fullPathSPL; + GetFullPath(&fullPathSPL); + + (void)QTSS_Write(inStream, "Container=\"/", ::strlen("Container=\"/"), NULL, 0); + + (void)QTSS_Write(inStream, fPathSPL.Ptr, ::strlen(fPathSPL.Ptr), NULL, 0); + //qtss_printf("ElementNode::RespondWithNodeName Path=%s \n",fPathSPL.Ptr); + + (void)QTSS_Write(inStream, "\"", 1, NULL, 0); + //qtss_printf("\""); + + (void)QTSS_Write(inStream, "\n", 1, NULL, 0); + //qtss_printf("\n"); + +} + +void ElementNode::RespondWithSingleElement(QTSS_StreamRef inStream, QueryURI *queryPtr, StrPtrLen *currentSegmentPtr) +{ + + //qtss_printf("ElementNode::RespondWithSingleElement Current Node = %s\n",GetNodeName() ); + + if (!fInitialized) + { + //qtss_printf("ElementNode::RespondWithSingleElement failed Not Initialized %s\n",GetNodeName() ); + return; + } + + if (GetNodeName() == NULL) + { + //qtss_printf("ElementNode::RespondWithSingleElement Node = %s is Uninitialized LEAVE\n",GetNodeName() ); + return; + } + + Assert(queryPtr != NULL); + Assert(currentSegmentPtr != NULL); + Assert(currentSegmentPtr->Ptr != 0); + Assert(currentSegmentPtr->Len != 0); + + SInt32 key = ResolveSPLKeyToIndex(currentSegmentPtr); + //qtss_printf("ElementNode::RespondWithSingleElement key = %"_S32BITARG_"\n",key); + //qtss_printf("currentSegmentPtr="); PRINT_STR(currentSegmentPtr); + + if (key < 0) + { + //qtss_printf("ElementNode::RespondWithSingleElement key = %"_S32BITARG_" NOT FOUND no ELEMENT\n",key); + return; + } + + if ((queryPtr == NULL) || (currentSegmentPtr == NULL) || (currentSegmentPtr->Ptr == NULL) || (currentSegmentPtr->Len == 0)) + { + //qtss_printf("ElementNode::RespondWithSingleElement currentSegmentPtr || queryPtr = NULL\n"); + return; + } + + //add here + + StrPtrLen nextSegment; + ( void)queryPtr->NextSegment(currentSegmentPtr, &nextSegment); + + if ( (nextSegment.Len == 0) && !queryPtr->RecurseParam() ) // only respond if we are at the end of the path + { + //qtss_printf("currentSegmentPtr="); PRINT_STR(currentSegmentPtr); + //qtss_printf("nextSegment="); PRINT_STR(&nextSegment); + //qtss_printf("ElementNode::RespondWithSingleElement Current Node = %s Call RespondWithNodeName\n",GetNodeName() ); + if (QueryURI::kGETCommand == queryPtr->GetCommandID()) + RespondWithNodeName( inStream,queryPtr); + } + + if (IsNodeElement(key)) + { + ElementNode *theNodePtr = (ElementNode *)GetElementDataPtr(key); + if (theNodePtr) + { + //qtss_printf("ElementNode::RespondWithSingleElement Current Node = %s Call RespondToQuery\n",GetNodeName() ); + theNodePtr->RespondToQuery(inStream, queryPtr,currentSegmentPtr); + } + } + else + { + //qtss_printf("ElementNode::RespondWithSingleElement call RespondToKey\n"); + if ( (queryPtr->fNumFilters > 0) && (QueryURI::kGETCommand == queryPtr->GetCommandID()) ) + { + StrPtrLen* theFilterPtr; + SInt32 index; + for (SInt32 count = 0; count < queryPtr->fNumFilters; count ++) + { + theFilterPtr = queryPtr->GetFilter(count); + index = ResolveSPLKeyToIndex(theFilterPtr); + if (index < 0) continue; + RespondToKey(inStream, index, queryPtr); + //qtss_printf("ElementNode::RespondWithSingleElement found filter = ");PRINT_STR(theFilterPtr); + break; + } + //qtss_printf("ElementNode::RespondWithSingleElement found filter = ?");PRINT_STR(theFilterPtr); + } + else + { RespondToKey(inStream, key, queryPtr); + } + } + +} + + +void ElementNode::RespondWithAllElements(QTSS_StreamRef inStream, QueryURI *queryPtr, StrPtrLen *currentSegmentPtr) +{ + //qtss_printf("ElementNode::RespondWithAllElements %s\n",GetNodeName()); + //qtss_printf("ElementNode::RespondWithAllElements fDataFieldsStop = %d \n",fDataFieldsStop); + + if (GetNodeName() == NULL) + { //qtss_printf("ElementNode::RespondWithAllElements %s is Uninitialized LEAVE\n",GetNodeName()); + return; + } + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondWithAllElements %s is Uninitialized LEAVE\n",GetNodeName()); + return; + } + + StrPtrLen nextSegment; + ( void)queryPtr->NextSegment(currentSegmentPtr, &nextSegment); + + if ( (nextSegment.Len == 0 || nextSegment.Ptr == 0) ) // only respond if we are at the end of the path + if (QueryURI::kGETCommand == queryPtr->GetCommandID()) + RespondWithNodeName( inStream,queryPtr); + + if ( (queryPtr->fNumFilters > 0) && (QueryURI::kGETCommand == queryPtr->GetCommandID()) ) + { + StrPtrLen* theFilterPtr; + SInt32 index; + for (SInt32 count = 0; count < queryPtr->fNumFilters; count ++) + { + theFilterPtr = queryPtr->GetFilter(count); + index = ResolveSPLKeyToIndex(theFilterPtr); + if (index < 0) continue; + + if ( (nextSegment.Len == 0 || nextSegment.Ptr == 0) ) + { if ( (!IsNodeElement(index)) || (IsNodeElement(index) && queryPtr->RecurseParam() )) // only respond if we are at the end of the path + RespondToKey(inStream, index, queryPtr); + } + + //qtss_printf("ElementNode::RespondWithAllElements found filter = ");PRINT_STR(theFilterPtr); + } + } + else + { + UInt32 index = 0; + for ( index = 0; !IsStopItem(index) ;index++) + { + //qtss_printf("RespondWithAllElements = %d \n",index); + //qtss_printf("ElementNode::RespondWithAllElements nextSegment="); PRINT_STR(&nextSegment); + + if ( (nextSegment.Len == 0 || nextSegment.Ptr == 0) ) + { if ( (!IsNodeElement(index)) || (IsNodeElement(index) && queryPtr->RecurseParam() ) ) // only respond if we are at the end of the path + RespondToKey(inStream, index, queryPtr); + } + + } + } + + UInt32 index = 0; + for ( index = 0; !IsStopItem(index);index++) + { + + if (IsNodeElement(index)) + { + //qtss_printf("ElementNode::RespondWithAllElements FoundNode\n"); + //qtss_printf("ElementNode::RespondWithAllElements currentSegmentPtr="); PRINT_STR(currentSegmentPtr); + //qtss_printf("ElementNode::RespondWithAllElements nextSegment="); PRINT_STR(&nextSegment); + ElementNode *theNodePtr = (ElementNode *)GetElementDataPtr(index); + + if (theNodePtr ) + { + //qtss_printf("ElementNode::RespondWithAllElements Current Node = %s Call RespondToQuery\n",GetNodeName() ); + theNodePtr->RespondToQuery(inStream, queryPtr,&nextSegment); + } + else + { + //qtss_printf("ElementNode::RespondWithAllElements Current Node index= %"_U32BITARG_" NULL = %s\n",index,GetName(index)); + + } + } + } +} + + + +void ElementNode::RespondWithAllNodes(QTSS_StreamRef inStream, QueryURI *queryPtr, StrPtrLen *currentSegmentPtr) +{ + + //qtss_printf("ElementNode::RespondWithAllNodes %s\n",GetNodeName()); + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondWithAllNodes %s is Uninitialized LEAVE\n",GetNodeName()); + return; + } + + if (GetNodeName() == NULL) + { //qtss_printf("ElementNode::RespondWithAllNodes %s is Uninitialized LEAVE\n",GetNodeName()); + return; + } + + StrPtrLen nextSegment; + ( void)queryPtr->NextSegment(currentSegmentPtr, &nextSegment); + + for(SInt32 index = 0; !IsStopItem(index); index ++) + { + if (!queryPtr->RecurseParam() && (currentSegmentPtr->Len == 0) ) + { Assert(0); + break; + } + + + if (IsNodeElement(index)) + { + //qtss_printf("ElementNode::RespondWithAllNodes FoundNode\n"); + //qtss_printf("ElementNode::RespondWithAllNodes currentSegmentPtr="); PRINT_STR(currentSegmentPtr); + ElementNode *theNodePtr = (ElementNode *)GetElementDataPtr(index); + //qtss_printf("ElementNode::RespondWithAllNodes nextSegment="); PRINT_STR(&nextSegment); + if (theNodePtr) + { + //qtss_printf("ElementNode::RespondWithAllNodes Current Node = %s Call RespondToQuery\n",GetNodeName() ); + theNodePtr->RespondToQuery(inStream, queryPtr,currentSegmentPtr); + } + } + } +} + +void ElementNode::RespondToQuery(QTSS_StreamRef inStream, QueryURI *queryPtr,StrPtrLen *currentPathPtr) +{ + //qtss_printf("----- ElementNode::RespondToQuery ------\n"); + //qtss_printf("ElementNode::RespondToQuery NODE = %s\n",GetNodeName()); + + Assert(NULL != queryPtr); + Assert(NULL != currentPathPtr); + + if (!fInitialized) + { //qtss_printf("ElementNode::RespondToQuery %s is Uninitialized LEAVE\n",GetNodeName()); + return; + + } + + if (GetNodeName() == NULL) + { //qtss_printf("ElementNode::RespondToQuery %s is Uninitialized LEAVE\n",GetNodeName()); + return; + } + + + Bool16 recurse = queryPtr->RecurseParam() ; + Bool16 doAllNext = false; + Bool16 doAllNextNext = false; + StrPtrLen nextSegment; + StrPtrLen nextnextSegment; + StrPtrLen nextnextnextSegment; + + + if (queryPtr && currentPathPtr) do + { + ( void)queryPtr->NextSegment(currentPathPtr, &nextSegment); + ( void)queryPtr->NextSegment(&nextSegment, &nextnextSegment); + ( void)queryPtr->NextSegment(&nextnextSegment, &nextnextnextSegment); + + //qtss_printf("ElementNode::RespondToQuery currentPathPtr="); PRINT_STR( currentPathPtr); + //qtss_printf("ElementNode::RespondToQuery nextSegment="); PRINT_STR( &nextSegment); + //qtss_printf("ElementNode::RespondToQuery nextnextSegment="); PRINT_STR( &nextnextSegment); + + // recurse param is set and this is the end of the path + if (recurse && ( (0 == currentPathPtr->Len) || (0 == nextSegment.Len) ) ) + { // admin + //qtss_printf("ElementNode::RespondToQuery 1)RespondToQuery -> RespondWithAllElements ") ;PRINT_STR( GetNodeNameSPL()); + RespondWithAllElements(inStream, queryPtr, &nextSegment); + break; + } + + // recurse param is not set and this is the end of the path + if ( (!recurse && ( (0 == currentPathPtr->Len) || (0 == nextSegment.Len) ) ) + ) + { // admin + //qtss_printf("ElementNode::RespondToQuery 2)RespondToQuery -> RespondWithSelf ") ;PRINT_STR( GetNodeNameSPL()); + if (fIsTop) + (void)QTSS_Write(inStream, "Container=\"/\"\n", ::strlen("Container=\"/\"\n"), NULL, 0); + + RespondWithSelf(inStream, queryPtr); + break; + } + + + doAllNext = ElementNode_DoAll(&nextSegment); + doAllNextNext = ElementNode_DoAll(&nextnextSegment); + + if ( doAllNext && (0 == nextnextSegment.Len) ) + { // admin/* + //qtss_printf("ElementNode::RespondToQuery 3)RespondToQuery -> RespondWithAllElements ");PRINT_STR( &nextSegment); + RespondWithAllElements(inStream, queryPtr, &nextSegment); + break; + } + + if ( doAllNext && doAllNextNext) + { // admin/*/* + //qtss_printf("ElementNode::RespondToQuery 4)RespondToQuery -> RespondWithAllNodes ");PRINT_STR( currentPathPtr); + RespondWithAllNodes(inStream, queryPtr, &nextSegment); + break; + } + + + if ( doAllNext && (nextnextSegment.Len > 0) ) + { // admin/*/attribute + //qtss_printf("ElementNode::RespondToQuery 5)RespondToQuery -> RespondWithAllNodes ");PRINT_STR( &nextSegment); + RespondWithAllNodes(inStream, queryPtr, &nextSegment); + break; + } + + // admin/attribute + //qtss_printf("ElementNode::RespondToQuery 6)RespondToQuery -> RespondWithSingleElement ");PRINT_STR( &nextSegment); + RespondWithSingleElement(inStream, queryPtr,&nextSegment); + + } while (false); + + if (QueryURI::kGETCommand != queryPtr->GetCommandID() && (!queryPtr->fIsPref)) + { queryPtr->fIsPref = IsPreferenceContainer(GetMyName(),NULL); + } + //qtss_printf("ElementNode::RespondToQuery LEAVE\n"); +} + + +void ElementNode::SetupNodes(QueryURI *queryPtr,StrPtrLen *currentPathPtr,QTSS_Initialize_Params *initParams) +{ + //qtss_printf("----- ElementNode::SetupNodes ------ NODE = %s\n", GetNodeName()); + //qtss_printf("ElementNode::SetupNodes currentPathPtr ="); PRINT_STR(currentPathPtr); + if (fSelfPtr == NULL) + { //qtss_printf("******* ElementNode::SetupNodes (fSelfPtr == NULLL) \n"); + } + Assert(NULL != queryPtr); + Assert(NULL != currentPathPtr); + + if (queryPtr && currentPathPtr) do + { + Bool16 doAll = false; + StrPtrLen nextSegment; + + ( void)queryPtr->NextSegment(currentPathPtr, &nextSegment); + doAll = ElementNode_DoAll(&nextSegment); + + StrPtrLen *thisNamePtr = GetNodeNameSPL(); + //qtss_printf("ElementNode::SetupNodes thisNamePtr="); PRINT_STR(thisNamePtr); + + if ( ( (doAll) && (currentPathPtr->Equal(*thisNamePtr) || ElementNode_DoAll(currentPathPtr)) ) + || (queryPtr->RecurseParam()) + ) + { + SetUpAllElements(queryPtr,currentPathPtr, &nextSegment, initParams); + break; + } + + SInt32 index = ResolveSPLKeyToIndex(&nextSegment); + if (index < 0) + { + //qtss_printf("ElementNode::SetupNodes FAILURE ResolveSPLKeyToIndex = %d NODE = %s\n", index, GetNodeName()); + break; + } + + SetUpAllNodes(queryPtr, currentPathPtr, &nextSegment, initParams); + + if (NULL == GetElementDataPtr(index)) + { //qtss_printf("ElementNode::SetupNodes call SetUpSingleElement index=%"_U32BITARG_" nextSegment=");PRINT_STR( &nextSegment); + SetUpSingleElement(queryPtr,currentPathPtr, &nextSegment, index, initParams); + } + + + } while (false); + +} + +void ElementNode::GetFilteredAttributeName(ElementDataFields* fieldPtr, QTSS_AttributeID theID) +{ + fieldPtr->fFieldLen = 0; + char *theName = NULL; + (void) QTSS_GetValueAsString (fieldPtr->fAPISource, theID,0, &theName); + OSCharArrayDeleter nameDeleter(theName); + if (theName != NULL ) + { UInt32 len = strlen(theName); + if (len < eMaxAttributeNameSize) + { memcpy(fieldPtr->fFieldName, theName, len); + fieldPtr->fFieldName[len] = 0; + fieldPtr->fFieldLen = len; + } + } +} + +Bool16 ElementNode::GetFilteredAttributeID(char *parentName, char *nodeName, QTSS_AttributeID* foundID) +{ + Bool16 found = false; + + if (0 == strcmp("server", parentName)) + { + if (0 == strcmp("qtssSvrClientSessions", nodeName) ) + { if (foundID) + *foundID = qtssCliSesCounterID; + found = true; + } + + if (0 == strcmp("qtssSvrModuleObjects", nodeName)) + { if (foundID) + *foundID = qtssModName; + found = true; + } + } + return found; +}; + +Bool16 ElementNode::IsPreferenceContainer(char *nodeName, QTSS_AttributeID* foundID) +{ + Bool16 found = false; + if (foundID) *foundID = 0; + //qtss_printf(" ElementNode::IsPreferenceContainer name = %s \n",nodeName); + if (0 == strcmp("qtssSvrPreferences", nodeName) ) + { if (foundID) *foundID = qtssCliSesCounterID; + found = true; + } + + if (0 == strcmp("qtssModPrefs", nodeName)) + { if (foundID) *foundID = qtssModName; + found = true; + } + + return found; +}; + +ElementNode::ElementDataFields AdminClass::sAdminSelf[] = // special case of top of tree +{ // key, API_ID, fIndex, Name, Name_Len, fAccessPermissions, Access, access_Len, fAPI_Type, fFieldType ,fAPISource + {0, 0, 0, "admin", strlen("admin"), qtssAttrModeRead, "r", strlen("r"),0, ElementNode::eNode, NULL } +}; + +ElementNode::ElementDataFields AdminClass::sAdminFieldIDs[] = +{ // key, API_ID, fIndex, Name, Name_Len, fAccessPermissions, Access, access_Len, fAPI_Type, fFieldType ,fAPISource + {0, 0, 0, "server", strlen("server"),qtssAttrModeRead, "r", strlen("r"),qtssAttrDataTypeQTSS_Object, ElementNode::eNode, NULL } +}; + + + +void AdminClass::Initialize(QTSS_Initialize_Params *initParams, QueryURI *queryPtr) +{ + + //qtss_printf("----- Initialize AdminClass ------\n"); + + SetParentNode(NULL); + SetNodeInfo(&sAdminSelf[0]);// special case of this node as top of tree so it sets self + Assert(NULL != GetMyName()); + SetNodeName(GetMyName()); + SetSource(NULL); + StrPtrLen *currentPathPtr = queryPtr->GetRootID(); + UInt32 numFields = 1; + SetNumFields(numFields); + fFieldIDs = sAdminFieldIDs; + fDataFieldsType = eStatic; + fPathBuffer[0]=0; + fPathSPL.Set(fPathBuffer,0); + fIsTop = true; + fInitialized = true; + do + { + Assert(fElementMap == NULL); + fElementMap = NEW OSRefTable(); ElementNode_InsertPtr(fElementMap,"AdminClass::Initialize ElementNode* fElementMap "); + Assert(fElementMap != NULL); + if (fElementMap == NULL) break; + + Assert(fFieldDataPtrs == NULL); + fFieldDataPtrs = NEW char*[numFields]; ElementNode_InsertPtr(fFieldDataPtrs,"AdminClass::Initialize ElementNode* fFieldDataPtrs array "); + Assert(fFieldDataPtrs != NULL); + if (fFieldDataPtrs == NULL) break; + memset(fFieldDataPtrs, 0, numFields * sizeof(char*)); + + Assert(fFieldOSRefPtrs == NULL); + fFieldOSRefPtrs = NEW OSRef *[numFields]; ElementNode_InsertPtr(fFieldOSRefPtrs,"AdminClass::Initialize ElementNode* fFieldOSRefPtrs array "); + Assert(fFieldOSRefPtrs != NULL); + if (fFieldOSRefPtrs == NULL) break; + memset(fFieldOSRefPtrs, 0, numFields * sizeof(OSRef*)); + + QTSS_Error err = fElementMap->Register(GetOSRef(0)); + Assert(err == QTSS_NoErr); + } while (false); + + if (queryPtr && currentPathPtr) do + { + StrPtrLen nextSegment; + if (!queryPtr->NextSegment(currentPathPtr, &nextSegment)) break; + + SetupNodes(queryPtr,currentPathPtr,initParams); + } while(false); + + +}; + +void AdminClass::SetUpSingleNode(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr, StrPtrLen *nextSegmentPtr, SInt32 index, QTSS_Initialize_Params *initParams) +{ + //qtss_printf("-------- AdminClass::SetUpSingleNode ---------- \n"); + switch (index) + { + case eServer: + //qtss_printf("AdminClass::SetUpSingleNode case eServer\n"); + fNodePtr = NEW ElementNode(); ElementNode_InsertPtr(fNodePtr, "AdminClass::SetUpSingleNode ElementNode * NEW ElementNode()"); + SetElementDataPtr(index,(char *) fNodePtr, true); + if (fNodePtr) + { fNodePtr->Initialize(index, this, queryPtr,nextSegmentPtr,initParams, initParams->inServer, eDynamic); + } + break; + }; + +} +void AdminClass::SetUpSingleElement(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr,StrPtrLen *nextSegmentPtr, SInt32 index, QTSS_Initialize_Params *initParams) +{ + //qtss_printf("---------AdminClass::SetUpSingleElement------- \n"); + SetUpSingleNode(queryPtr,currentSegmentPtr, nextSegmentPtr, index, initParams); +} + +AdminClass::~AdminClass() +{ //qtss_printf("AdminClass::~AdminClass() \n"); + delete (ElementNode*) fNodePtr;ElementNode_RemovePtr(fNodePtr,"AdminClass::~AdminClass ElementNode* fNodePtr"); + fNodePtr = NULL; +} + diff --git a/APIModules/QTSSAdminModule/AdminElementNode.h b/APIModules/QTSSAdminModule/AdminElementNode.h new file mode 100644 index 0000000..6650f9e --- /dev/null +++ b/APIModules/QTSSAdminModule/AdminElementNode.h @@ -0,0 +1,311 @@ +/* + * + * @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: AdminElements.h + + Contains: implements various Admin Elements class + + +*/ +#ifndef _ADMINELEMENTNODE_H_ +#define _ADMINELEMENTNODE_H_ + + + +#ifndef __Win32__ + #include /* for getopt() et al */ +#endif + +#include +#include /* for //qtss_printf */ +#include /* for getloadavg & other useful stuff */ +#include "QTSSAdminModule.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" +#include "OSHashTable.h" +#include "OSMutex.h" +#include "StrPtrLen.h" +#include "OSRef.h" +#include "AdminQuery.h" + +void PRINT_STR(StrPtrLen *spl); +void COPYBUFFER(char *dest,char *src,SInt8 size); + +void ElementNode_InitPtrArray(); +void ElementNode_InsertPtr(void *ptr, char * src); +void ElementNode_RemovePtr(void *ptr, char * src); +SInt32 ElementNode_CountPtrs(); +void ElementNode_ShowPtrs(); + +class ClientSession { + public: + ClientSession(void) : fRTSPSessionID(0), fBitrate(0), fPacketLossPercent(0), fBytesSent(0),fTimeConnected(0) {}; + ~ClientSession() { } ; + UInt32 fRTSPSessionID; + char fIPAddressStr[32]; + char fURLBuffer[512]; + UInt32 fBitrate; + Float32 fPacketLossPercent; + UInt64 fBytesSent; + UInt64 fTimeConnected; + +}; + + +class ElementNode +{ + public: + enum { eMaxAccessSize = 32, eMaxAttributeNameSize = 63, eMaxAPITypeSize = 63, eMaxAttrIDSize = sizeof(UInt32) }; + + enum { eData = 0, eArrayNode, eNode}; + + + #define kEmptyRef (OSRef *)NULL + #define kEmptyData (char *)NULL + + + enum { kFirstIndexItem = 0 }; + + + typedef enum + { eStatic = 0, + eDynamic = 1, + } DataFieldsType; + + struct ElementDataFields + { + UInt32 fKey; + UInt32 fAPI_ID; + UInt32 fIndex; + + char fFieldName[eMaxAttributeNameSize + 1]; + UInt32 fFieldLen; + + QTSS_AttrPermission fAccessPermissions; + char fAccessData[eMaxAccessSize + 1]; + UInt32 fAccessLen; + + UInt32 fAPI_Type; + UInt32 fFieldType; + + QTSS_Object fAPISource; + + }; + + SInt32 fDataFieldsStop; + + UInt32 CountElements(); + + SInt32 GetMyStopItem() { Assert (fSelfPtr); return fDataFieldsStop; }; + UInt32 GetMyKey() { Assert (fSelfPtr); return fSelfPtr->fKey; }; + char* GetMyName() { Assert (fSelfPtr); return fSelfPtr->fFieldName; }; + UInt32 GetMyNameLen() { Assert (fSelfPtr); return fSelfPtr->fFieldLen; }; + UInt32 GetMyAPI_ID() { Assert (fSelfPtr); return fSelfPtr->fAPI_ID; }; + UInt32 GetMyIndex() { Assert (fSelfPtr); return fSelfPtr->fIndex; }; + + UInt32 GetMyAPI_Type() { Assert (fSelfPtr); return fSelfPtr->fAPI_Type; }; + char* GetMyAPI_TypeStr() { Assert (fSelfPtr); char* theTypeString = NULL; (void)QTSS_TypeToTypeString(GetMyAPI_Type(), &theTypeString); return theTypeString; }; + UInt32 GetMyFieldType() { Assert (fSelfPtr); return fSelfPtr->fFieldType; }; + + char* GetMyAccessData() { Assert (fSelfPtr); return fSelfPtr->fAccessData; }; + UInt32 GetMyAccessLen() { Assert (fSelfPtr); return fSelfPtr->fAccessLen; }; + UInt32 GetMyAccessPermissions() { Assert (fSelfPtr); return fSelfPtr->fAccessPermissions; }; + + void GetMyNameSPL(StrPtrLen* str) { Assert(str); if (str != NULL) str->Set(fSelfPtr->fFieldName,fSelfPtr->fFieldLen); }; + void GetMyAccess(StrPtrLen* str) { Assert(str); if (str != NULL) str->Set(fSelfPtr->fAccessData,fSelfPtr->fAccessLen);}; + QTSS_Object GetMySource() { + Assert(fSelfPtr != NULL); + //qtss_printf("GetMySource fSelfPtr->fAPISource = %"_U32BITARG_" \n", fSelfPtr->fAPISource); + return fSelfPtr->fAPISource; + }; + + Bool16 IsNodeElement() { Assert(this); return (this->GetMyFieldType() == eNode || this->GetMyFieldType() == eArrayNode); } + + + Bool16 IsStopItem(SInt32 index) { return index == GetMyStopItem(); }; + UInt32 GetKey(SInt32 index) { return fFieldIDs[index].fKey; }; + char* GetName(SInt32 index) { return fFieldIDs[index].fFieldName; }; + UInt32 GetNameLen(SInt32 index) { return fFieldIDs[index].fFieldLen; }; + UInt32 GetAPI_ID(SInt32 index) { return fFieldIDs[index].fAPI_ID; }; + UInt32 GetAttributeIndex(SInt32 index) { return fFieldIDs[index].fIndex; }; + UInt32 GetAPI_Type(SInt32 index) { return fFieldIDs[index].fAPI_Type; }; + char* GetAPI_TypeStr(SInt32 index) { char* theTypeStr = NULL; (void)QTSS_TypeToTypeString(GetAPI_Type(index), &theTypeStr); return theTypeStr; }; + UInt32 GetFieldType(SInt32 index) { return fFieldIDs[index].fFieldType; }; + char* GetAccessData(SInt32 index) { return fFieldIDs[index].fAccessData; }; + UInt32 GetAccessLen(SInt32 index) { return fFieldIDs[index].fAccessLen; }; + UInt32 GetAccessPermissions(SInt32 index) { return fFieldIDs[index].fAccessPermissions; }; + void GetNameSPL(SInt32 index,StrPtrLen* str) { if (str != NULL) str->Set(fFieldIDs[index].fFieldName,fFieldIDs[index].fFieldLen); }; + void GetAccess(SInt32 index,StrPtrLen* str) { if (str != NULL) str->Set(fFieldIDs[index].fAccessData,fFieldIDs[index].fAccessLen); }; + QTSS_Object GetAPISource(SInt32 index) { return fFieldIDs[index].fAPISource; }; + Bool16 IsNodeElement(SInt32 index) { return (GetFieldType(index) == eNode || GetFieldType(index) == eArrayNode); } + + enum + { eAPI_ID = 0, + eAPI_Name = 1, + eAccess = 2, + ePath = 3, + eType = 4, + eNumAttributes = 5 + }; + + ElementNode(); + void Initialize(SInt32 index, ElementNode *parentPtr, QueryURI *queryPtr, StrPtrLen *currentSegmentPtr,QTSS_Initialize_Params *initParams,QTSS_Object nodeSource, DataFieldsType dataFieldsType); + virtual ~ElementNode(); + + void SetNodeName(char *namePtr); + char * GetNodeName() { return fNodeNameSPL.Ptr;}; + UInt32 GetNodeNameLen() { return fNodeNameSPL.Len;}; + StrPtrLen* GetNodeNameSPL() { return &fNodeNameSPL;}; + + void SetParentNode(ElementNode *parentPtr) { fParentNodePtr = parentPtr;}; + ElementNode* GetParentNode() { return fParentNodePtr;}; + void GetFullPath(StrPtrLen *resultPtr); + + OSRef* GetOSRef(SInt32 index); + void SetOSRef(SInt32 index, OSRef* refPtr); + SInt32 ResolveSPLKeyToIndex(StrPtrLen *keyPtr); + virtual Bool16 SetUpOneDataField(UInt32 index); + + ElementDataFields *GetElementFieldPtr(SInt32 index); + char *GetElementDataPtr(SInt32 index); + void SetElementDataPtr(SInt32 index, char * data, Bool16 isNode); + void SetMyElementDataPtr(char * data) { fSelfDataPtr = data; } + char* GetMyElementDataPtr() { return fSelfDataPtr; } + Bool16 IsFiltered(SInt32 index,QueryURI *queryPtr); + + ElementDataFields *GetNodeInfoPtr(SInt32 index); + + void SetNodeInfo(ElementDataFields *nodeInfo); + void SetSource(void * dataSource) { fDataSource = dataSource;}; + void * GetSource() { + QTSS_Object source = GetMySource(); + if (source != NULL) + return source; + else + { //qtss_printf("GetSource return fDataSource = %"_U32BITARG_" \n",fDataSource); + return fDataSource; + } + }; + + virtual void SetUpSingleNode(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr, StrPtrLen *nextSegmentPtr, SInt32 index, QTSS_Initialize_Params *initParams); + virtual void SetUpAllNodes(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr,StrPtrLen *nextSegmentPtr, QTSS_Initialize_Params *initParams); + + virtual void SetUpSingleElement(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr, StrPtrLen *nextSegmentPtr, SInt32 index, QTSS_Initialize_Params *initParams); + virtual void SetUpAllElements(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr, StrPtrLen *nextSegmentPtr, QTSS_Initialize_Params *initParams); + virtual void SetupNodes(QueryURI *queryPtr,StrPtrLen *currentPathPtr,QTSS_Initialize_Params *initParams); + + + void RespondWithSelfAdd(QTSS_StreamRef inStream, QueryURI *queryPtr); + void RespondToAdd(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr); + void RespondToSet(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr); + void RespondToGet(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr); + void RespondToDel(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr,Bool16 delAttribute); + void RespondToKey(QTSS_StreamRef inStream, SInt32 index,QueryURI *queryPtr); + + void RespondWithNodeName(QTSS_StreamRef inStream, QueryURI *queryPtr); + void RespondWithSelf(QTSS_StreamRef inStream, QueryURI *queryPtr); + void RespondWithSingleElement(QTSS_StreamRef inStream,QueryURI *queryPtr, StrPtrLen *currentSegmentPtr); + void RespondWithAllElements(QTSS_StreamRef inStream,QueryURI *queryPtr, StrPtrLen *currentSegmentPtr); + void RespondWithAllNodes(QTSS_StreamRef inStream,QueryURI *queryPtr, StrPtrLen *currentSegmentPtr); + void RespondToQuery(QTSS_StreamRef inStream, QueryURI *queryPtr,StrPtrLen *currentPathPtr); + + UInt32 CountAttributes(QTSS_Object source); + UInt32 CountValues(QTSS_Object source, UInt32 apiID); + + QTSS_Error AllocateFields(UInt32 numFields); + void InitializeAllFields(Bool16 allocateFields, QTSS_Object defaultAttributeInfo, QTSS_Object source, QueryURI *queryPtr, StrPtrLen *currentSegmentPtr,Bool16 forceAll); + void InitializeSingleField(StrPtrLen *currentSegmentPtr); + void SetFields(UInt32 i, QTSS_Object attrInfoObject); + ElementNode* CreateArrayAttributeNode(UInt32 index, QTSS_Object source, QTSS_Object attributeInfo, UInt32 arraySize); + + QTSS_Error GetAttributeSize (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex, UInt32* outLenPtr); + char* NewIndexElement (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex); + UInt32 GetNumFields() { return fNumFields; }; + void SetNumFields(UInt32 numFields) { fNumFields = numFields; fDataFieldsStop = numFields; }; + + ElementDataFields* GetFields() {return fFieldIDs;}; + void SetFields(ElementDataFields *fieldsPtr) {fFieldIDs = fieldsPtr;}; + void SetFieldsType(DataFieldsType fDataFieldsType){fDataFieldsType = fDataFieldsType;}; + + static void GetFilteredAttributeName(ElementDataFields* fieldPtr, QTSS_AttributeID theID); + static Bool16 GetFilteredAttributeID(char *parentName, char *nodeName, QTSS_AttributeID* foundID); + static Bool16 IsPreferenceContainer(char *nodeName, QTSS_AttributeID* foundID); + + enum { kmaxPathlen = 1048 }; + char fPathBuffer[kmaxPathlen]; + StrPtrLen fPathSPL; + StrPtrLen fNodeNameSPL; + + QTSS_Object fDataSource; + SInt32 fNumFields; + SInt32 fPathLen; + Bool16 fInitialized; + + ElementDataFields* fFieldIDs; + ElementDataFields* fSelfPtr; + DataFieldsType fDataFieldsType; + char* fSelfDataPtr; + + + char** fFieldDataPtrs; + OSRef** fFieldOSRefPtrs; + ElementNode* fParentNodePtr; + OSRefTable* fElementMap; + + Bool16 fIsTop; + + private: + + inline void DebugShowFieldDataType(SInt32 index); + inline void DebugShowFieldValue(SInt32 index); + + +}; + +class AdminClass : public ElementNode +{ + public: + QueryURI *fQueryPtr; + ElementNode *fNodePtr; + + void SetUpSingleElement(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr,StrPtrLen *nextSegmentPtr, SInt32 index, QTSS_Initialize_Params *initParams); + void SetUpSingleNode(QueryURI *queryPtr, StrPtrLen *currentSegmentPtr, StrPtrLen *nextSegmentPtr, SInt32 index, QTSS_Initialize_Params *initParams); + void Initialize(QTSS_Initialize_Params *initParams, QueryURI *queryPtr); + AdminClass():fQueryPtr(NULL), fNodePtr(NULL) {}; + ~AdminClass(); + static ElementNode::ElementDataFields sAdminSelf[]; + static ElementNode::ElementDataFields sAdminFieldIDs[]; + enum + { eServer = 0, + eNumAttributes + }; +}; + + + + +#endif // _ADMINELEMENTNODE_H_ diff --git a/APIModules/QTSSAdminModule/AdminQuery.cpp b/APIModules/QTSSAdminModule/AdminQuery.cpp new file mode 100644 index 0000000..b16acd3 --- /dev/null +++ b/APIModules/QTSSAdminModule/AdminQuery.cpp @@ -0,0 +1,849 @@ +/* + * + * @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: AdminQuery.cpp + + Contains: Implements Admin Query + + + +*/ + +#ifndef __Win32__ + #include /* for getopt() et al */ +#endif + +#include +#include /* for //qtss_printf */ +#include /* for getloadavg & other useful stuff */ +#include "QTSSAdminModule.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" +#include "OSHashTable.h" +#include "OSMutex.h" +#include "StrPtrLen.h" +#include "OSRef.h" +#include "AdminElementNode.h" +#include "AdminQuery.h" +#include "OSMemory.h" +#include "StringTranslator.h" + +/* +r = recurse -> walk downward in hierarchy +v = verbose -> return full path in name +a = access -> return read/write access +t = type -> return type of value ** perhaps better not to support +f = filter -> return filter +p = path -> return path +i = indexed -> return indexed representation of attributes +d = debug -> return debug info on errors +*/ + + + +StrPtrLen * QueryURI::NextSegment(StrPtrLen *currentPathPtr, StrPtrLen *outNextPtr) +{ + StrPtrLen *result = NULL; + StrPtrLen *theURLPtr = GetURL(); + if (outNextPtr) + outNextPtr->Set(NULL,0); + + if (currentPathPtr && outNextPtr && theURLPtr && currentPathPtr->Len > 0) + { + if ( (currentPathPtr->Ptr >= theURLPtr->Ptr) + && (currentPathPtr->Ptr < &theURLPtr->Ptr[theURLPtr->Len] ) + ) + { + //qtss_printf("theURLPtr="); PRINT_STR(theURLPtr); + //qtss_printf("QueryURI::NextSegment currentPathPtr="); PRINT_STR(currentPathPtr); + + UInt32 len = (PointerSizedInt)&(theURLPtr->Ptr[theURLPtr->Len -1]) - ((PointerSizedInt) currentPathPtr->Ptr + (currentPathPtr->Len -1)); + char *startPtr = (char *) ( (PointerSizedInt) currentPathPtr->Ptr + currentPathPtr->Len) ; + StrPtrLen tempPath(startPtr,len); + + + StringParser URLparser(&tempPath); + URLparser.ConsumeLength(NULL, 1); + URLparser.ConsumeUntil(outNextPtr,(UInt8*) sNotQueryData); + result = outNextPtr; + + //qtss_printf("QueryURI::NextSegment nextPathPtr=");PRINT_STR(outNextPtr); + } + + } + + return result; +}; + + +QueryURI::URIField QueryURI::sURIFields[] = +{ /* fAttrName, len, id, fDataptr*/ + { "modules", strlen("modules"), eModuleID, NULL }, + { "admin", strlen("admin"), eRootID, NULL }, + { "URL", strlen("URL"), eURL, NULL }, + { "QUERY", strlen("QUERY"), eQuery, NULL }, + { "parameters", strlen("parameters"), eParameters,NULL }, + { "snapshot", strlen("snapshot"), eSnapshot, NULL }, + { "command", strlen("command"), eCommand, NULL }, + { "value", strlen("value"), eValue, NULL }, + { "type", strlen("type"), eType, NULL }, + { "access", strlen("access"), eAccess, NULL }, + { "name", strlen("name"), eName, NULL }, + { "filter1", strlen("filter1"), eFilter1, NULL }, + { "filter2", strlen("filter2"), eFilter2, NULL }, + { "filter3", strlen("filter3"), eFilter3, NULL }, + { "filter4", strlen("filter4"), eFilter4, NULL }, + { "filter5", strlen("filter5"), eFilter5, NULL }, + { "filter6", strlen("filter6"), eFilter6, NULL }, + { "filter7", strlen("filter7"), eFilter7, NULL }, + { "filter8", strlen("filter8"), eFilter8, NULL }, + { "filter9", strlen("filter9"), eFilter9, NULL }, + { "filter10", strlen("filter10"), eFilter10, NULL }, + { "", 0, -1, NULL } +}; + +char *QueryURI::sCommandDefs[] = +{ + "GET", + "SET", + "ADD", + "DEL" +}; + + +UInt8 QueryURI::sNotQueryData[] = // query stops +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //0-9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //10-19 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //20-29 + 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, //30-39 + 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, //40-49 allow * . and - and all numbers + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //50-59 allow : + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, //60-69 //stop on every character except a letter + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, //90-99 _ is a word + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, //120-129 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //130-139 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //140-149 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //150-159 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //160-169 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //170-179 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //180-189 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //190-199 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //200-209 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //210-219 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //220-229 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //230-239 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //240-249 + 1, 1, 1, 1, 1, 0 //250-255 +}; + + +UInt8 QueryURI::sWhiteQuoteOrEOL[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 // \t is a stop + 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, //30-39 ' ' , '"' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + +UInt8 QueryURI::sWhitespaceOrQuoteMask[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 // \t is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //10-19 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, //30-39 ' ' , '"' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + + +QueryURI::QueryURI (StrPtrLen *inStream) +{ + fIsAdminQuery = false; + fUseSnapShot = false; + fURIFieldsPtr = &sURIFields[0]; + fTheCommand = -1; + + for (short testField = 0; testField < eNumAttributes; testField++) + fURIFieldsPtr[testField].fData = NULL; + + memset(fURIBuffer,0,QueryURI::eMaxBufferSize); + memset(fQueryBuffer,0,QueryURI::eMaxBufferSize); + fParamBits = 0; + fSnapshotID = 0; + fAccessFlags = 0; + fQueryHasResponse = false; + fLastPath[0] = 0; + fIsPref = false; + fNumFilters = 0; + fHasQuery = false; + URLParse(inStream); + SetQueryData(); + +} + + +void QueryURI::SetSnapShot() +{ + StrPtrLen *snapshotSPL = GetSnapshot(); + if (snapshotSPL != NULL) + { + StringParser parser(snapshotSPL); + fSnapshotID = parser.ConsumeInteger(NULL); + fUseSnapShot = true; + } +}; + +void QueryURI::SetCommand() +{ + StrPtrLen commandDef; + StrPtrLen *queryCommandPtr; + Bool16 foundCommand = false; + SInt16 commandIndex; + + queryCommandPtr = this->GetCommand(); + if (queryCommandPtr == NULL || queryCommandPtr->Len == 0) // Default command + { + fTheCommand = kGETCommand; + return; + } + + for (commandIndex = 0; commandIndex < kLastCommand; commandIndex ++) + { + foundCommand = queryCommandPtr->EqualIgnoreCase(sCommandDefs[commandIndex],strlen(sCommandDefs[commandIndex])); + if (foundCommand) + { fTheCommand = commandIndex; + } + } +}; + + +void QueryURI::SetAccessFlags() +{ + + StrPtrLen tempStr; + SInt16 theChar; + StringParser parser(GetAccess()); + fAccessFlags = 0; + while (parser.GetDataRemaining() != 0) + { + parser.ConsumeLength(&tempStr, 1); + if(tempStr.Len > 0) + { + theChar = *tempStr.Ptr; + switch(theChar) + { + case 'r': fAccessFlags |= qtssAttrModeRead; + break; + + case 'w': fAccessFlags |= qtssAttrModeWrite; + break; + + case 'p': fAccessFlags |= qtssAttrModePreempSafe; + break; + + //case 'd': fAccessFlags |= qtssAttrModeRemoveable; + //break; + } + + } + tempStr.Len = 0; + } +} + +void QueryURI::SetParamBits(UInt32 forcebits) +{ + StrPtrLen tempStr; + SInt16 theChar; + + StringParser parser(GetParameters()); + fParamBits = forcebits; + while (parser.GetDataRemaining() != 0) + { + parser.ConsumeLength(&tempStr, 1); + if(tempStr.Len > 0) + { + theChar = *tempStr.Ptr; + switch(theChar) + { + case 'r': fParamBits |= kRecurseParam; + break; + + case 'v': fParamBits |= kVerboseParam; + break; + + case 'a': fParamBits |= kAccessParam; + break; + + case 't': fParamBits |= kTypeParam; + break; + + case 'f': fParamBits |= kFilterParam; + break; + + case 'p': fParamBits |= kPathParam; + break; + + case 'd': fParamBits |= kDebugParam; + break; + + case 'i': fParamBits |= kIndexParam; + break; + } + + } + tempStr.Len = 0; + } +} + +QueryURI::~QueryURI() +{ +/* + for (int count = 0; fURIFieldsPtr[count].fID != -1 ; count++) + { //qtss_printf("QueryURI::~QueryURI delete %s=",fURIFieldsPtr[count].fFieldName); PRINT_STR(fURIFieldsPtr[count].fData); + if (fURIFieldsPtr[count].fData && fURIFieldsPtr[count].fData->Ptr) + delete fURIFieldsPtr[count].fData->Ptr; + fURIFieldsPtr[count].fData = NULL; + } +*/ +} + +UInt32 QueryURI::CheckInvalidIterator(char* evalMessageBuff) +{ + UInt32 result = 0; + + StringParser parser(GetURL()); + parser.ConsumeUntil(NULL,'*'); + if (parser.PeekFast() == '*') + { result = 405; + static char *message = "* iterator not valid"; + qtss_sprintf(evalMessageBuff, "%s",message); + } + + return result; + +} + +UInt32 QueryURI::CheckInvalidArrayIterator(char* evalMessageBuff) +{ + UInt32 result = 0; + + StringParser parser(GetURL()); + parser.ConsumeUntil(NULL,':'); + if (parser.PeekFast() == ':') + { result = 405; + static char *message = ": array iterator not valid"; + qtss_sprintf(evalMessageBuff, "%s",message); + } + + return result; + +} + +UInt32 QueryURI::CheckInvalidRecurseParam(char* evalMessageBuff) +{ + UInt32 result = 0; + + if (RecurseParam()) + { result = 405; + static char *message = "(r)ecurse parameter not valid"; + qtss_sprintf(evalMessageBuff, "%s",message); + } + + return result; + +} + + +UInt32 QueryURI::EvalQuery(UInt32 *forceResultPtr, char *forceMessagePtr) +{ + UInt32 result = 0; + const SInt16 messageLen = 512; + char evalMessageBuff[messageLen] = {0}; + StrPtrLen evalMessage; + evalMessage.Set(evalMessageBuff, messageLen); + if (forceResultPtr != NULL) + { + result = *forceResultPtr; + switch(*forceResultPtr) + { + case 404: + qtss_sprintf(fQueryMessageBuff,"reason=\"No data found\"" ); + fQueryEvalMessage.Set(fQueryMessageBuff, strlen(fQueryMessageBuff)); + break; + + default: + { SInt32 theID = GetCommandID(); + StrPtrLen *commandPtr = GetCommand(); + if (theID < 0) + { if (commandPtr) + { qtss_sprintf(fQueryMessageBuff,"reason=\"%s for command %s\"",forceMessagePtr,commandPtr->Ptr); + } + } + else + { qtss_sprintf(fQueryMessageBuff,"reason=\"%s for command %s\"",forceMessagePtr, QueryURI::sCommandDefs[GetCommandID()]); + } + } + + fQueryEvalMessage.Set(fQueryMessageBuff, strlen(fQueryMessageBuff)); + } + } + else + { + + switch(GetCommandID()) + { + case kGETCommand: + { // special case test. A query with no parameters is a get. A query with parameters requires a command. + if (fHasQuery && (this->GetCommand() == NULL || this->GetCommand()->Len == 0)) + { result = 400; + static char *message = "reason=\"command parameter is missing\""; + qtss_sprintf(evalMessageBuff, "%s",message); + fQueryEvalMessage.Set(evalMessageBuff, strlen(evalMessageBuff)); + fQueryEvalResult = result; + return result; + } + } + break; + + case kSETCommand: + { + if (NULL==GetValue()) + { result = 400; + static char *message = "No value"; + qtss_sprintf(evalMessageBuff, "%s",message); + break; + } + + result = CheckInvalidRecurseParam( (char *) evalMessageBuff); + if (result != 0) + break; + + result = CheckInvalidIterator( (char *) evalMessageBuff); + if (result != 0) + break; + + result = CheckInvalidArrayIterator( (char *) evalMessageBuff); + if (result != 0) + break; + + } + break; + + case kADDCommand: + { + + if (0) + { + result = 501; + static char *message="No implementation"; + qtss_sprintf(evalMessageBuff, "%s",message); + break; + } + + if (NULL==GetValue()) + { result = 400; + static char *message = "Attribute value not defined"; + qtss_sprintf(evalMessageBuff, "%s",message); + break; + } + + result = CheckInvalidRecurseParam( (char *) evalMessageBuff); + if (result != 0) + break; + + result = CheckInvalidIterator( (char *) evalMessageBuff); + if (result != 0) + break; + + result = CheckInvalidArrayIterator( (char *) evalMessageBuff); + if (result != 0) + break; + + } + break; + + case kDELCommand: + { + + result = CheckInvalidRecurseParam( (char *) evalMessageBuff); + if (result != 0) + break; + + result = CheckInvalidIterator( (char *) evalMessageBuff); + if (result != 0) + break; + + result = CheckInvalidArrayIterator( (char *) evalMessageBuff); + if (result != 0) + break; + + } + break; + + default: + { + result = 501; + static char *message="No implementation"; + qtss_sprintf(evalMessageBuff, "%s",message); + break; + } + + + } + + if (result != 0) + { SInt32 theID = GetCommandID(); + StrPtrLen *commandPtr = GetCommand(); + if (theID < 0) + { if (commandPtr) + { qtss_sprintf(fQueryMessageBuff,"reason=\"%s for command %s\"",evalMessage.Ptr,commandPtr->Ptr); + } + } + else + { //qtss_printf("Set fQueryMessageBuff=%s\n",evalMessage.Ptr); + qtss_sprintf(fQueryMessageBuff,"reason=\"%s for command %s\"",evalMessage.Ptr, QueryURI::sCommandDefs[GetCommandID()]); + } + } + else + { qtss_sprintf(fQueryMessageBuff,"reason=\"OK\""); + } + + fQueryEvalMessage.Set(fQueryMessageBuff, strlen(fQueryMessageBuff)); + //qtss_printf("fQueryMessageBuff=%s\n",fQueryMessageBuff); + } + fQueryEvalResult = result; + return result; +} + +void QueryURI::ParseQueryString(StringParser *parserPtr,StrPtrLen *urlStreamPtr) +{ + StrPtrLen queryStr; + StringParser tempQueryParse(parserPtr->GetStream());// point to local copy xx + char *stopCharPtr = NULL; + char *startCharPtr = NULL; + UInt32 len = 0; + tempQueryParse.ConsumeUntil(NULL, sWhiteQuoteOrEOL); // stop on whitespace '"' + tempQueryParse.ConsumeWhitespace(); + tempQueryParse.ConsumeUntil(NULL, '?'); // stop at start of query + tempQueryParse.Expect('?'); + startCharPtr = tempQueryParse.GetCurrentPosition(); + //qtss_printf("QueryURI::ParseQueryString start Position = '%s'\n",startCharPtr); + while (tempQueryParse.GetDataRemaining() > 0) + { + + tempQueryParse.ConsumeUntil(NULL, sWhiteQuoteOrEOL); // stop on whitespace '"' + stopCharPtr = tempQueryParse.GetCurrentPosition(); + if (*stopCharPtr == '"') // if quote read to next quote + { + tempQueryParse.ConsumeLength(NULL, 1); + tempQueryParse.ConsumeUntil(NULL, '"'); + tempQueryParse.ConsumeLength(NULL, 1); + //qtss_printf("QueryURI::ParseQueryString is quote GetCurrentPosition = '%s' len = %"_U32BITARG_"\n",stopCharPtr, strlen(stopCharPtr)); + } + else + { + //qtss_printf("QueryURI::ParseQueryString white or EOL GetCurrentPosition = '%s' len = %"_U32BITARG_"\n",stopCharPtr, strlen(stopCharPtr)); + if (*stopCharPtr == ' ') + { tempQueryParse.ConsumeWhitespace(); + continue; + } + + break; + + } + } + len = (UInt32) ((PointerSizedInt)stopCharPtr - (PointerSizedInt) startCharPtr); + if (len < QueryURI::eMaxBufferSize) + { if (len > 0) + fHasQuery = true; + queryStr.Set(fQueryBuffer,len); + memcpy(fQueryBuffer, startCharPtr, len ); + fURIFieldSPL[eQuery].Set(queryStr.Ptr,queryStr.Len); + fURIFieldsPtr[eQuery].fData = &fURIFieldSPL[eQuery]; + } + + //qtss_printf("Query String = '%s' Query len = %"_U32BITARG_" parseLen = %"_U32BITARG_"\n",queryStr.Ptr, queryStr.Len,len); + +}; + +void QueryURI::ParseURLString(StringParser *parserPtr,StrPtrLen *urlStreamPtr) +{ + parserPtr->ConsumeWhitespace(); + parserPtr->ConsumeUntilWhitespace(urlStreamPtr); + + fAdminFullURI.Set(fURIBuffer, urlStreamPtr->Len); + + if (urlStreamPtr->Len < QueryURI::eMaxBufferSize) + memcpy(fURIBuffer,urlStreamPtr->Ptr,urlStreamPtr->Len); // make a local copy in fAdminFullURI + + //qtss_printf("QueryURI::ParseURLString fURIBuffer =%s len = %"_U32BITARG_"\n", fURIBuffer,urlStreamPtr->Len); + + StringParser tempURLParse(&fAdminFullURI);// point to local copy + tempURLParse.ConsumeUntil(&fURIFieldSPL[eURL],'?'); // pull out URL + fURIFieldsPtr[eURL].fData = &fURIFieldSPL[eURL]; + //qtss_printf("QueryURI::ParseURLString fURIFieldsPtr[eURL]="); PRINT_STR(fURIFieldsPtr[eURL].fData); +}; + + +void QueryURI::URLParse(StrPtrLen *inStream) +{ + if (inStream != NULL) + { + char * decodedRequest = NEW char[inStream->Len + 1]; + Assert(decodedRequest != NULL); + decodedRequest[inStream->Len] = 0; + OSCharArrayDeleter decodedRequestDeleter(decodedRequest); + + StringParser tempParser(inStream); + StrPtrLen URLToParse; + SInt32 URLoffset = 0; + + if (inStream->Len > 0) + { // skip past the HTTP command for the StringTranslator::DecodeURL but keep it in our decoded Request buffer + tempParser.ConsumeWhitespace(); + tempParser.ConsumeWord(NULL); + tempParser.ConsumeWhitespace(); + URLToParse.Set(tempParser.GetCurrentPosition(),tempParser.GetDataRemaining()); // this should be a '/' and is required by the DecodeURL routine + URLoffset = tempParser.GetDataParsedLen(); + memcpy(decodedRequest, inStream->Ptr, URLoffset); + } + + SInt32 decodedLen = StringTranslator::DecodeURL(URLToParse.Ptr, URLToParse.Len, &decodedRequest[URLoffset], inStream->Len); + StrPtrLen decodedRequestStr(decodedRequest,decodedLen); + + StringParser parser(&decodedRequestStr); + StrPtrLen startFields; + StrPtrLen adminURI; + StrPtrLen streamURL; + + do // once + { + + if (decodedRequestStr.Len < 1) + { + //qtss_printf("no string to parse \n"); + break; + } + + if (decodedRequestStr.Len > QueryURI::eMaxBufferSize -1) + { + //qtss_printf("URL string bigger than Buffer size=%"_U32BITARG_"\n",decodedRequestStr.Len); + break; + } + + StrPtrLen httpRequest; + parser.ConsumeWord(&httpRequest); + + static StrPtrLen sPost("POST"); + static StrPtrLen sGet("GET"); + if ( false == httpRequest.Equal(sPost) && false == httpRequest.Equal(sGet) ) //bail if not a GET or POST + { + //qtss_printf("not a POST or GET \n"); + break; + } + + ParseURLString(&parser,&streamURL); + ParseQueryString(&parser,&streamURL); + + if (fURIFieldSPL[eURL].Len > 0) + { + StrPtrLen tempStr; + + StringParser URIParser(fURIFieldsPtr[eURL].fData); // parser now pointing to internal buffer root of URL + + if(!URIParser.Expect('/')) + { + //qtss_printf("no starting slash\n"); + break; + } + + URIParser.ConsumeWord(&tempStr); + if ( !(tempStr.Len != 0 && tempStr.Equal(StrPtrLen(fURIFieldsPtr[eModuleID].fFieldName,fURIFieldsPtr[eModuleID].fFieldLen) )) )//check "modules" request + { + //qtss_printf("no %s in URL\n",fURIFieldsPtr[eModuleID].fFieldName); + break; + } + fURIFieldSPL[eModuleID] = tempStr; + fURIFieldsPtr[eModuleID].fData = &fURIFieldSPL[eModuleID]; + + if(!URIParser.Expect('/')) + { + //qtss_printf("no trailing slash for modules\n"); + break; + } + URIParser.ConsumeWord(&tempStr); + if ( !(tempStr.Len != 0 && tempStr.Equal(StrPtrLen(fURIFieldsPtr[eRootID].fFieldName,fURIFieldsPtr[eRootID].fFieldLen) )) )//check "modules" request + { + //qtss_printf("no %s in URL\n", fURIFieldsPtr[eRootID].fFieldName); + break; + } + + fIsAdminQuery = true; // ok it is for us + + fURIFieldSPL[eRootID] = tempStr; + fURIFieldsPtr[eRootID].fData = &fURIFieldSPL[eRootID]; + + } + + + + if (fURIFieldSPL[eQuery].Len > 0) // has query fields (step past ?) + { + StringParser queryParser(fURIFieldsPtr[eQuery].fData); + StrPtrLen tempStr(fURIFieldSPL[eQuery].Ptr,fURIFieldSPL[eQuery].Len); + StrPtrLen tempData; + + //qtss_printf("queryParser=");PRINT_STR(fURIFieldsPtr[eQuery].fData); + while (queryParser.GetDataRemaining() != 0) + { tempData.Set(NULL,0); + if (queryParser.GetDataRemaining())queryParser.ConsumeWhitespace(); + if (queryParser.GetDataRemaining())queryParser.ConsumeUntil(&tempStr,(UInt8*) sNotQueryData); + if (tempStr.Len == 0) + { + //qtss_printf("no query name\n"); + if (queryParser.GetDataRemaining())queryParser.ConsumeLength(NULL, 1); + continue; + } + if (queryParser.GetDataRemaining())queryParser.ConsumeWhitespace(); + if(!queryParser.Expect('=')) + { + //qtss_printf("no '=' for query name ");PRINT_STR(&tempStr); + if (queryParser.GetDataRemaining())queryParser.ConsumeLength(NULL, 1); + continue; + } + if (queryParser.GetDataRemaining())queryParser.ConsumeWhitespace(); + char testQuote = queryParser.PeekFast(); + if (testQuote == '"') + { if (queryParser.GetDataRemaining())queryParser.ConsumeLength(NULL, 1); + if (queryParser.GetDataRemaining())queryParser.ConsumeUntil(&tempData, '"'); + if (queryParser.GetDataRemaining())queryParser.ConsumeLength(NULL, 1); + } + else + { + if (queryParser.GetDataRemaining())queryParser.ConsumeUntil(&tempData,(UInt8*) sNotQueryData); + } + + if (tempData.Len == 0) + { + if (queryParser.GetDataRemaining())queryParser.Expect('+'); + if (queryParser.GetDataRemaining())queryParser.ConsumeWhitespace(); + //qtss_printf("no query data for ");PRINT_STR(&tempStr); + continue; + } + + if (queryParser.GetDataRemaining())queryParser.ConsumeWhitespace(); + if (queryParser.GetDataRemaining())queryParser.Expect('+'); + if (queryParser.GetDataRemaining())queryParser.ConsumeWhitespace(); + + //qtss_printf("set data =%s\n", tempData.Ptr); + + StrPtrLen definedID; + UInt32 fieldID; + for (short testField = 0; testField < eNumAttributes; testField++) + { + definedID.Set(fURIFieldsPtr[testField].fFieldName,fURIFieldsPtr[testField].fFieldLen); + fieldID = fURIFieldsPtr[testField].fID; + if ( definedID.EqualIgnoreCase(tempStr.Ptr, tempStr.Len) ) // test (fURIFieldsPtr[fieldID].fData == NULL) to make first time setting only + { // set the field value always takes the last appearance of the name=value pair + if (fieldID >= eFilter1) + { UInt32 emptyId = eFilter1 + fNumFilters; + if (tempData.Len > 0) + { fNumFilters ++; + fURIFieldSPL[emptyId].Set(tempData.Ptr,tempData.Len); + fURIFieldsPtr[emptyId].fData = &fURIFieldSPL[emptyId]; + } + } + else + { + fURIFieldSPL[fieldID].Set(tempData.Ptr,tempData.Len); + fURIFieldsPtr[testField].fData = &fURIFieldSPL[fieldID]; + } + } + } + + } + + } + + } while (false); + +/* + for (int count = 0; fURIFieldsPtr[count].fID != -1 ; count++) + { //qtss_printf("QueryURI::URLParse %s=",fURIFieldsPtr[count].fFieldName); PRINT_STR(fURIFieldsPtr[count].fData); + } +*/ + + } +} + diff --git a/APIModules/QTSSAdminModule/AdminQuery.h b/APIModules/QTSSAdminModule/AdminQuery.h new file mode 100644 index 0000000..3a6b01c --- /dev/null +++ b/APIModules/QTSSAdminModule/AdminQuery.h @@ -0,0 +1,212 @@ +/* + * + * @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: AdminElements.h + + Contains: implements various Admin Elements class + + +*/ +#ifndef _ADMINQUERY_H_ +#define _ADMINQUERY_H_ + +#ifndef __Win32__ + #include /* for getopt() et al */ +#endif + +#include +#include /* for qtss_printf */ +#include /* for getloadavg & other useful stuff */ +#include "QTSSAdminModule.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" +#include "OSHashTable.h" +#include "OSMutex.h" +#include "StrPtrLen.h" +#include "OSRef.h" + + +/* +r = recurse -> walk downward in hierarchy +v = verbose -> return full path in name +a = access -> return read/write access +t = type -> return type of value ** perhaps better not to support +f = filter -> return filter +p = path -> return path +*/ + + +class QueryURI +{ + public: + + enum{ eMaxAttributeSize = 60 , eMaxBufferSize =2048}; + struct URIField + { + char fFieldName[eMaxAttributeSize + 1]; + UInt32 fFieldLen; + SInt32 fID; + StrPtrLen* fData; + }; + + enum + { + eModuleID = 0, + eRootID = 1, + eURL = 2, + eQuery = 3, + eParameters = 4, + eSnapshot = 5, + eCommand = 6, + eValue = 7, + eType = 8, + eAccess = 9, + eName = 10, + + eFilter1, + eFilter2, + eFilter3, + eFilter4, + eFilter5, + eFilter6, + eFilter7, + eFilter8, + eFilter9, + eFilter10, + + eNumAttributes + }; + + enum //commands + { + kGETCommand = 0, + kSETCommand = 1, + kADDCommand = 2, + kDELCommand = 3, + kLastCommand = 4 + }; + + enum + { + kRecurseParam = 1 << 0, + kVerboseParam = 1 << 1, + kAccessParam = 1 << 2, + kTypeParam = 1 << 3, + kFilterParam = 1 << 4, + kPathParam = 1 << 5, + kDebugParam = 1 << 6, + kIndexParam = 1 << 7 + }; + + static UInt8 sNotQueryData[]; + static UInt8 sWhiteQuoteOrEOL[]; + static UInt8 sWhitespaceOrQuoteMask[]; + + static URIField sURIFields[]; + static char *sCommandDefs[]; + + URIField *fURIFieldsPtr; + + void URLParse(StrPtrLen *inStream); + + void SetQueryData() { if (fIsAdminQuery) { SetSnapShot(); SetParamBits(0); SetCommand(); SetAccessFlags(); } } + + StrPtrLen* GetModuleID() { return fURIFieldsPtr[eModuleID].fData; }; + StrPtrLen* GetRootID() { return fURIFieldsPtr[eRootID].fData; }; + StrPtrLen* GetURL() { return fURIFieldsPtr[eURL].fData; }; + StrPtrLen* GetQuery() { return fURIFieldsPtr[eQuery].fData; }; + StrPtrLen* GetParameters() { return fURIFieldsPtr[eParameters].fData; }; + StrPtrLen* GetSnapshot() { return fURIFieldsPtr[eSnapshot].fData; }; + + StrPtrLen* GetCommand() { return fURIFieldsPtr[eCommand].fData; }; + StrPtrLen* GetValue() { return fURIFieldsPtr[eValue].fData; }; + StrPtrLen* GetType() { return fURIFieldsPtr[eType].fData; }; + StrPtrLen* GetAccess() { return fURIFieldsPtr[eAccess].fData; }; + StrPtrLen* GetName() { return fURIFieldsPtr[eName].fData; }; + StrPtrLen* GetFilter(UInt32 index) { return fURIFieldsPtr[eFilter1 + index].fData; }; + + StrPtrLen* GetEvalMsg() { return &fQueryEvalMessage; }; + + UInt32 GetAccessFlags() { return fAccessFlags; }; + UInt32 GetSnapshotID() { return fSnapshotID; }; + UInt32 GetParamBits() { return fParamBits; }; + Bool16 IsAdminQuery() { return fIsAdminQuery; }; + Bool16 UseSnapShot() { return fUseSnapShot; }; + + Bool16 RecurseParam() { return (Bool16) ( (fParamBits & kRecurseParam) != 0); }; + Bool16 VerboseParam() { return (Bool16) ( (fParamBits & kVerboseParam) != 0); }; + Bool16 AccessParam() { return (Bool16) ( (fParamBits & kAccessParam) != 0); }; + Bool16 TypeParam() { return (Bool16) ( (fParamBits & kTypeParam) != 0); }; + Bool16 FilterParam() { return (Bool16) ( (fParamBits & kFilterParam) != 0); }; + Bool16 PathParam() { return (Bool16) ( (fParamBits & kPathParam) != 0); }; + Bool16 DebugParam() { return (Bool16) ( (fParamBits & kDebugParam) != 0); }; + Bool16 IndexParam() { return (Bool16) ( (fParamBits & kIndexParam) != 0); }; + void SetQueryHasResponse() { fQueryHasResponse =true; }; + Bool16 QueryHasReponse() { if (fQueryEvalResult > 0) return true; else return fQueryHasResponse; }; + UInt32 GetEvaluResult() { return fQueryEvalResult; }; + StrPtrLen* NextSegment(StrPtrLen *currentPathPtr, StrPtrLen *outNextPtr); + void SetAccessFlags(); + void SetParamBits(UInt32 forcebits); + void SetSnapShot(); + SInt32 GetCommandID() { return fTheCommand;}; + + char fLastPath[1024]; + QueryURI (StrPtrLen *inStream); + ~QueryURI(); + UInt32 EvalQuery(UInt32 *forceResultPtr, char *forceMessagePtr); + + char fQueryMessageBuff[1024]; + Bool16 fIsPref; + SInt16 fNumFilters; + Bool16 fHasQuery; + private: + char fURIBuffer[QueryURI::eMaxBufferSize]; + char fQueryBuffer[QueryURI::eMaxBufferSize]; + StrPtrLen fURIFieldSPL[QueryURI::eNumAttributes]; + StrPtrLen fAdminFullURI; + UInt32 fParamBits; + UInt32 fSnapshotID; + UInt32 fAccessFlags; + Bool16 fIsAdminQuery; + Bool16 fUseSnapShot; + StrPtrLen fCurrentPath; + StrPtrLen fNext; + Bool16 fQueryHasResponse; + UInt32 fQueryEvalResult; + StrPtrLen fQueryEvalMessage; + SInt32 fTheCommand; + void SetCommand(); + void ParseURLString(StringParser *parserPtr,StrPtrLen *urlPtr); + void ParseQueryString(StringParser *parserPtr,StrPtrLen *urlPtr); + + UInt32 CheckInvalidIterator(char* evalMessageBuff); + UInt32 CheckInvalidArrayIterator(char* evalMessageBuff); + UInt32 CheckInvalidRecurseParam(char* evalMessageBuff); +}; + + +#endif // _ADMINQUERY_H_ diff --git a/APIModules/QTSSAdminModule/QTSSAdminModule.cpp b/APIModules/QTSSAdminModule/QTSSAdminModule.cpp new file mode 100644 index 0000000..ed683ef --- /dev/null +++ b/APIModules/QTSSAdminModule/QTSSAdminModule.cpp @@ -0,0 +1,1049 @@ +/* + * + * @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: QTSSAdminModule.cpp + + Contains: Implements Admin module + +*/ + + +#ifndef __Win32__ + #include /* for getopt() et al */ +#endif + +#include +#include /* for qtss_printf */ +#include /* for getloadavg & other useful stuff */ +#include "QTSSAdminModule.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" +#include "OSHashTable.h" +#include "OSMutex.h" +#include "StrPtrLen.h" +#include "OSRef.h" +#include "AdminElementNode.h" +#include "base64.h" +#include "OSMemory.h" +#include "md5.h" +#include "md5digest.h" + +#if __MacOSX__ +#include +#include +#endif + +#if __solaris__ || __linux__ || __sgi__ || __hpux__ + #include +#endif + +#define DEBUG_ADMIN_MODULE 0 + + +//************************************************** +#define kAuthNameAndPasswordBuffSize 512 +#define kPasswordBuffSize kAuthNameAndPasswordBuffSize/2 + +// STATIC DATA +//************************************************** +#if DEBUG_ADMIN_MODULE +static UInt32 sRequestCount= 0; +#endif + +static QTSS_Initialize_Params sQTSSparams; + +//static char* sResponseHeader = "HTTP/1.0 200 OK\r\nServer: QTSS\r\nConnection: Close\r\nContent-Type: text/plain\r\n\r\n"; +static char* sResponseHeader = "HTTP/1.0 200 OK"; +static char* sUnauthorizedResponseHeader = "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"QTSS/modules/admin\"\r\nServer: QTSS\r\nConnection: Close\r\nContent-Type: text/plain\r\n\r\n"; +static char* sPermissionDeniedHeader = "HTTP/1.1 403 Forbidden\r\nConnection: Close\r\nContent-Type: text/html\r\n\r\n"; +static char* sHTMLBody = "\n

Your request was denied by the server.

\n\r\n\r\n"; + + +static char* sVersionHeader = NULL; +static char* sConnectionHeader = "Connection: Close"; +static char* kDefaultHeader = "Server: QTSS"; +static char* sContentType = "Content-Type: text/plain"; +static char* sEOL = "\r\n"; +static char* sEOM = "\r\n\r\n"; +static char* sAuthRealm = "QTSS/modules/admin"; +static char* sAuthResourceLocalPath = "/modules/admin/"; + +static QTSS_ServerObject sServer = NULL; +static QTSS_ModuleObject sModule = NULL; +static QTSS_ModulePrefsObject sModulePrefs = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; +static AdminClass *sAdminPtr = NULL; +static QueryURI *sQueryPtr = NULL; +static OSMutex* sAdminMutex = NULL;//admin module isn't reentrant +static UInt32 sVersion=20030306; +static char *sDesc="Implements HTTP based Admin Protocol for accessing server attributes"; +static char decodedLine[kAuthNameAndPasswordBuffSize] = { 0 }; +static char codedLine[kAuthNameAndPasswordBuffSize] = { 0 }; +static QTSS_TimeVal sLastRequestTime = 0; +static UInt32 sSessID = 0; + +static StrPtrLen sAuthRef("AuthRef"); +#if __MacOSX__ + +static char* sSecurityServerAuthKey = "com.apple.server.admin.streaming"; +static AuthorizationItem sRight = { sSecurityServerAuthKey, 0, NULL, 0 }; +static AuthorizationRights sRightSet = { 1, &sRight }; +#endif + + +// ATTRIBUTES +//************************************************** +enum { kMaxRequestTimeIntervalMilli = 1000, kDefaultRequestTimeIntervalMilli = 50 }; +static UInt32 sDefaultRequestTimeIntervalMilli = kDefaultRequestTimeIntervalMilli; +static UInt32 sRequestTimeIntervalMilli = kDefaultRequestTimeIntervalMilli; + +static Bool16 sAuthenticationEnabled = true; +static Bool16 sDefaultAuthenticationEnabled = true; + +static Bool16 sLocalLoopBackOnlyEnabled = true; +static Bool16 sDefaultLocalLoopBackOnlyEnabled = true; + +static Bool16 sEnableRemoteAdmin = true; +static Bool16 sDefaultEnableRemoteAdmin = true; + + +static QTSS_AttributeID sIPAccessListID = qtssIllegalAttrID; +static char* sIPAccessList = NULL; +static char* sLocalLoopBackAddress = "127.0.0.*"; + +static char* sAdministratorGroup = NULL; +static char* sDefaultAdministratorGroup = "admin"; + +static Bool16 sFlushing = false; +static QTSS_AttributeID sFlushingID = qtssIllegalAttrID; +static char* sFlushingName = "QTSSAdminModuleFlushingState"; +static UInt32 sFlushingLen = sizeof(sFlushing); + +static QTSS_AttributeID sAuthenticatedID = qtssIllegalAttrID; +static char* sAuthenticatedName = "QTSSAdminModuleAuthenticatedState"; + +//************************************************** + +static QTSS_Error QTSSAdminModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error FilterRequest(QTSS_Filter_Params* inParams); +static QTSS_Error RereadPrefs(); +static QTSS_Error AuthorizeAdminRequest(QTSS_RTSPRequestObject request); +static Bool16 AcceptSession(QTSS_RTSPSessionObject inRTSPSession); + + +#if !DEBUG_ADMIN_MODULE + #define APITests_DEBUG() + #define ShowQuery_DEBUG() +#else +void ShowQuery_DEBUG() +{ + qtss_printf("======REQUEST #%"_U32BITARG_"======\n",++sRequestCount); + StrPtrLen* aStr; + aStr = sQueryPtr->GetURL(); + qtss_printf("URL="); PRINT_STR(aStr); + + aStr = sQueryPtr->GetQuery(); + qtss_printf("Query="); PRINT_STR(aStr); + + aStr = sQueryPtr->GetParameters(); + qtss_printf("Parameters="); PRINT_STR(aStr); + + aStr = sQueryPtr->GetCommand(); + qtss_printf("Command="); PRINT_STR(aStr); + qtss_printf("CommandID=%"_S32BITARG_" \n",sQueryPtr->GetCommandID()); + aStr = sQueryPtr->GetValue(); + qtss_printf("Value="); PRINT_STR(aStr); + aStr = sQueryPtr->GetType(); + qtss_printf("Type="); PRINT_STR(aStr); + aStr = sQueryPtr->GetAccess(); + qtss_printf("Access="); PRINT_STR(aStr); +} + +void APITests_DEBUG() +{ + if (0) + { qtss_printf("QTSSAdminModule start tests \n"); + + if (0) + { + qtss_printf("admin called locked \n"); + const int ksleeptime = 15; + qtss_printf("sleeping for %d seconds \n",ksleeptime); + sleep(ksleeptime); + qtss_printf("done sleeping \n"); + qtss_printf("QTSS_GlobalUnLock \n"); + (void) QTSS_GlobalUnLock(); + qtss_printf("again sleeping for %d seconds \n",ksleeptime); + sleep(ksleeptime); + } + + if (0) + { + qtss_printf(" GET VALUE PTR TEST \n"); + + QTSS_Object *sessionsPtr = NULL; + UInt32 paramLen = sizeof(sessionsPtr); + UInt32 numValues = 0; + QTSS_Error err = 0; + + err = QTSS_GetNumValues (sServer, qtssSvrClientSessions, &numValues); + err = QTSS_GetValuePtr(sServer, qtssSvrClientSessions, 0, (void**)&sessionsPtr, ¶mLen); + qtss_printf("Admin Module Num Sessions = %"_U32BITARG_" sessions[0] = %"_S32BITARG_" err = %"_S32BITARG_" paramLen =%"_U32BITARG_"\n", numValues, (SInt32) *sessionsPtr,err,paramLen); + + UInt32 numAttr = 0; + if (sessionsPtr) + { err = QTSS_GetNumAttributes (*sessionsPtr, &numAttr); + qtss_printf("Admin Module Num attributes = %"_U32BITARG_" sessions[0] = %"_S32BITARG_" err = %"_S32BITARG_"\n", numAttr, (SInt32) *sessionsPtr,err); + + QTSS_Object theAttributeInfo; + char nameBuff[128]; + UInt32 len = 127; + for (UInt32 i = 0; i < numAttr; i++) + { err = QTSS_GetAttrInfoByIndex(*sessionsPtr, i, &theAttributeInfo); + nameBuff[0] = 0;len = 127; + err = QTSS_GetValue (theAttributeInfo, qtssAttrName,0, nameBuff,&len); + nameBuff[len] = 0; + qtss_printf("found %s \n",nameBuff); + } + } + } + + if (0) + { + qtss_printf(" GET VALUE TEST \n"); + + QTSS_Object sessions = NULL; + UInt32 paramLen = sizeof(sessions); + UInt32 numValues = 0; + QTSS_Error err = 0; + + err = QTSS_GetNumValues (sServer, qtssSvrClientSessions, &numValues); + err = QTSS_GetValue(sServer, qtssSvrClientSessions, 0, (void*)&sessions, ¶mLen); + qtss_printf("Admin Module Num Sessions = %"_U32BITARG_" sessions[0] = %"_S32BITARG_" err = %"_S32BITARG_" paramLen = %"_U32BITARG_"\n", numValues, (SInt32) sessions,err, paramLen); + + if (sessions) + { + UInt32 numAttr = 0; + err = QTSS_GetNumAttributes (sessions, &numAttr); + qtss_printf("Admin Module Num attributes = %"_U32BITARG_" sessions[0] = %"_S32BITARG_" err = %"_S32BITARG_"\n", numAttr,(SInt32) sessions,err); + + QTSS_Object theAttributeInfo; + char nameBuff[128]; + UInt32 len = 127; + for (UInt32 i = 0; i < numAttr; i++) + { err = QTSS_GetAttrInfoByIndex(sessions, i, &theAttributeInfo); + nameBuff[0] = 0;len = 127; + err = QTSS_GetValue (theAttributeInfo, qtssAttrName,0, nameBuff,&len); + nameBuff[len] = 0; + qtss_printf("found %s \n",nameBuff); + } + } + } + + + if (0) + { + qtss_printf("----------------- Start test ----------------- \n"); + qtss_printf(" GET indexed pref TEST \n"); + + QTSS_Error err = 0; + + UInt32 numAttr = 1; + err = QTSS_GetNumAttributes (sModulePrefs, &numAttr); + qtss_printf("Admin Module Num preference attributes = %"_U32BITARG_" err = %"_S32BITARG_"\n", numAttr, err); + + QTSS_Object theAttributeInfo; + char valueBuff[512]; + char nameBuff[128]; + QTSS_AttributeID theID; + UInt32 len = 127; + UInt32 i = 0; + qtss_printf("first pass over preferences\n"); + for ( i = 0; i < numAttr; i++) + { err = QTSS_GetAttrInfoByIndex(sModulePrefs, i, &theAttributeInfo); + nameBuff[0] = 0;len = 127; + err = QTSS_GetValue (theAttributeInfo, qtssAttrName,0, nameBuff,&len); + nameBuff[len]=0; + + theID = qtssIllegalAttrID; len = sizeof(theID); + err = QTSS_GetValue (theAttributeInfo, qtssAttrID,0, &theID,&len); + qtss_printf("found preference=%s \n",nameBuff); + } + valueBuff[0] = 0;len = 512; + err = QTSS_GetValue (sModulePrefs, theID,0, valueBuff,&len);valueBuff[len] = 0; + qtss_printf("Admin Module QTSS_GetValue name = %s id = %"_S32BITARG_" value=%s err = %"_S32BITARG_"\n", nameBuff,theID, valueBuff, err); + err = QTSS_SetValue (sModulePrefs,theID,0, valueBuff,len); + qtss_printf("Admin Module QTSS_SetValue name = %s id = %"_S32BITARG_" value=%s err = %"_S32BITARG_"\n", nameBuff,theID, valueBuff, err); + + { QTSS_ServiceID id; + (void) QTSS_IDForService(QTSS_REREAD_PREFS_SERVICE, &id); + (void) QTSS_DoService(id, NULL); + } + + valueBuff[0] = 0;len = 512; + err = QTSS_GetValue (sModulePrefs, theID,0, valueBuff,&len);valueBuff[len] = 0; + qtss_printf("Admin Module QTSS_GetValue name = %s id = %"_S32BITARG_" value=%s err = %"_S32BITARG_"\n", nameBuff,theID, valueBuff, err); + err = QTSS_SetValue (sModulePrefs,theID,0, valueBuff,len); + qtss_printf("Admin Module QTSS_SetValue name = %s id = %"_S32BITARG_" value=%s err = %"_S32BITARG_"\n", nameBuff,theID, valueBuff, err); + + qtss_printf("second pass over preferences\n"); + for ( i = 0; i < numAttr; i++) + { err = QTSS_GetAttrInfoByIndex(sModulePrefs, i, &theAttributeInfo); + nameBuff[0] = 0;len = 127; + err = QTSS_GetValue (theAttributeInfo, qtssAttrName,0, nameBuff,&len); + nameBuff[len]=0; + + theID = qtssIllegalAttrID; len = sizeof(theID); + err = QTSS_GetValue (theAttributeInfo, qtssAttrID,0, &theID,&len); + qtss_printf("found preference=%s \n",nameBuff); + } + qtss_printf("----------------- Done test ----------------- \n"); + } + + } +} + +#endif + +inline void KeepSession(QTSS_RTSPRequestObject theRequest,Bool16 keep) +{ + (void)QTSS_SetValue(theRequest, qtssRTSPReqRespKeepAlive, 0, &keep, sizeof(keep)); +} + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSAdminModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSAdminModuleDispatch); +} + + +QTSS_Error QTSSAdminModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RTSPFilter_Role: + { if (!sEnableRemoteAdmin) + break; + return FilterRequest(&inParams->rtspFilterParams); + } + case QTSS_RTSPAuthorize_Role: + return AuthorizeAdminRequest(inParams->rtspRequestParams.inRTSPRequest); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RTSPFilter_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); + + (void)QTSS_AddStaticAttribute(qtssRTSPRequestObjectType, sFlushingName, NULL, qtssAttrDataTypeBool16); + (void)QTSS_IDForAttr(qtssRTSPRequestObjectType, sFlushingName, &sFlushingID); + + (void)QTSS_AddStaticAttribute(qtssRTSPRequestObjectType, sAuthenticatedName, NULL, qtssAttrDataTypeBool16); + (void)QTSS_IDForAttr(qtssRTSPRequestObjectType, sAuthenticatedName, &sAuthenticatedID); + + // Tell the server our name! + static char* sModuleName = "QTSSAdminModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error RereadPrefs() +{ + + delete [] sVersionHeader; + sVersionHeader = QTSSModuleUtils::GetStringAttribute(sServer, "qtssSvrRTSPServerHeader", kDefaultHeader); + + delete [] sIPAccessList; + sIPAccessList = QTSSModuleUtils::GetStringAttribute(sModulePrefs, "IPAccessList", sLocalLoopBackAddress); + sIPAccessListID = QTSSModuleUtils::GetAttrID(sModulePrefs, "IPAccessList"); + + QTSSModuleUtils::GetAttribute(sModulePrefs, "Authenticate", qtssAttrDataTypeBool16, &sAuthenticationEnabled, &sDefaultAuthenticationEnabled, sizeof(sAuthenticationEnabled)); + QTSSModuleUtils::GetAttribute(sModulePrefs, "LocalAccessOnly", qtssAttrDataTypeBool16, &sLocalLoopBackOnlyEnabled, &sDefaultLocalLoopBackOnlyEnabled, sizeof(sLocalLoopBackOnlyEnabled)); + QTSSModuleUtils::GetAttribute(sModulePrefs, "RequestTimeIntervalMilli", qtssAttrDataTypeUInt32, &sRequestTimeIntervalMilli, &sDefaultRequestTimeIntervalMilli, sizeof(sRequestTimeIntervalMilli)); + QTSSModuleUtils::GetAttribute(sModulePrefs, "enable_remote_admin", qtssAttrDataTypeBool16, &sEnableRemoteAdmin, &sDefaultEnableRemoteAdmin, sizeof(sDefaultEnableRemoteAdmin)); + + delete [] sAdministratorGroup; + sAdministratorGroup = QTSSModuleUtils::GetStringAttribute(sModulePrefs, "AdministratorGroup", sDefaultAdministratorGroup); + + if (sRequestTimeIntervalMilli > kMaxRequestTimeIntervalMilli) + { sRequestTimeIntervalMilli = kMaxRequestTimeIntervalMilli; + } + + (void)QTSS_SetValue(sModule, qtssModDesc, 0, sDesc, strlen(sDesc)+1); + (void)QTSS_SetValue(sModule, qtssModVersion, 0, &sVersion, sizeof(sVersion)); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + sAdminMutex = NEW OSMutex(); + ElementNode_InitPtrArray(); + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + sQTSSparams = *inParams; + sServer = inParams->inServer; + sModule = inParams->inModule; + sModulePrefs = QTSSModuleUtils::GetModulePrefsObject(sModule); + sServerPrefs = inParams->inPrefs; + + RereadPrefs(); + + return QTSS_NoErr; +} + + +void ReportErr(QTSS_Filter_Params* inParams, UInt32 err) +{ + StrPtrLen* urlPtr = sQueryPtr->GetURL(); + StrPtrLen* evalMessagePtr = sQueryPtr->GetEvalMsg(); + char temp[32]; + + if (urlPtr && evalMessagePtr) + { qtss_sprintf(temp,"(%"_U32BITARG_")",err); + (void)QTSS_Write(inParams->inRTSPRequest, "error:", strlen("error:"), NULL, 0); + (void)QTSS_Write(inParams->inRTSPRequest, temp, strlen(temp), NULL, 0); + if (sQueryPtr->VerboseParam()) + { (void)QTSS_Write(inParams->inRTSPRequest, ";URL=", strlen(";URL="), NULL, 0); + if (urlPtr) (void)QTSS_Write(inParams->inRTSPRequest, urlPtr->Ptr, urlPtr->Len, NULL, 0); + } + if (sQueryPtr->DebugParam()) + { + (void)QTSS_Write(inParams->inRTSPRequest, ";", strlen(";"), NULL, 0); + (void)QTSS_Write(inParams->inRTSPRequest, evalMessagePtr->Ptr, evalMessagePtr->Len, NULL, 0); + } + (void)QTSS_Write(inParams->inRTSPRequest, "\r\n\r\n", 4, NULL, 0); + } +} + + +inline Bool16 AcceptAddress(StrPtrLen *theAddressPtr) +{ + IPComponentStr ipComponentStr(theAddressPtr); + + Bool16 isLocalRequest = ipComponentStr.IsLocal(); + if (sLocalLoopBackOnlyEnabled && isLocalRequest) + return true; + + if (sLocalLoopBackOnlyEnabled && !isLocalRequest) + return false; + + if (QTSSModuleUtils::AddressInList(sModulePrefs, sIPAccessListID, theAddressPtr)) + return true; + + return false; +} + +inline Bool16 IsAdminRequest(StringParser *theFullRequestPtr) +{ + Bool16 handleRequest = false; + if (theFullRequestPtr != NULL) do + { + StrPtrLen strPtr; + theFullRequestPtr->ConsumeWord(&strPtr); + if ( !strPtr.Equal(StrPtrLen("GET")) ) break; //it's a "Get" request + + theFullRequestPtr->ConsumeWhitespace(); + if ( !theFullRequestPtr->Expect('/') ) break; + + theFullRequestPtr->ConsumeWord(&strPtr); + if ( strPtr.Len == 0 || !strPtr.Equal(StrPtrLen("modules") ) ) break; + if (!theFullRequestPtr->Expect('/') ) break; + + theFullRequestPtr->ConsumeWord(&strPtr); + if ( strPtr.Len == 0 || !strPtr.Equal(StrPtrLen("admin") ) ) break; + handleRequest = true; + + } while (false); + + return handleRequest; +} + +inline void ParseAuthNameAndPassword(StrPtrLen *codedStrPtr, StrPtrLen* namePtr, StrPtrLen* passwordPtr) + { + + if (!codedStrPtr || (codedStrPtr->Len >= kAuthNameAndPasswordBuffSize) ) + { return; + } + + StrPtrLen codedLineStr; + StrPtrLen nameAndPassword; + memset(decodedLine,0,kAuthNameAndPasswordBuffSize); + memset(codedLine,0,kAuthNameAndPasswordBuffSize); + + memcpy (codedLine,codedStrPtr->Ptr,codedStrPtr->Len); + codedLineStr.Set((char*) codedLine, codedStrPtr->Len); + (void) Base64decode(decodedLine, codedLineStr.Ptr); + + nameAndPassword.Set((char*) decodedLine, strlen(decodedLine)); + StringParser parsedNameAndPassword(&nameAndPassword); + + parsedNameAndPassword.ConsumeUntil(namePtr,':'); + parsedNameAndPassword.ConsumeLength(NULL, 1); + + // password can have whitespace, so read until the end of the line, not just until whitespace + parsedNameAndPassword.ConsumeUntil(passwordPtr, StringParser::sEOLMask); + + namePtr->Ptr[namePtr->Len]= 0; + passwordPtr->Ptr[passwordPtr->Len]= 0; + + //qtss_printf("decoded nameAndPassword="); PRINT_STR(&nameAndPassword); + //qtss_printf("decoded name="); PRINT_STR(namePtr); + //qtss_printf("decoded password="); PRINT_STR(passwordPtr); + + return; +}; + + +inline Bool16 OSXAuthenticate(StrPtrLen *keyStrPtr) +{ +#if __MacOSX__ +// Authorization: AuthRef QWxhZGRpbjpvcGVuIHNlc2FtZQ== + Bool16 result = false; + + if (keyStrPtr == NULL || keyStrPtr->Len == 0) + return result; + + char *encodedKey = keyStrPtr->GetAsCString(); + OSCharArrayDeleter encodedKeyDeleter(encodedKey); + + char *decodedKey = NEW char[Base64decode_len(encodedKey) + 1]; + OSCharArrayDeleter decodedKeyDeleter(decodedKey); + + (void) Base64decode(decodedKey, encodedKey); + + AuthorizationExternalForm *receivedExtFormPtr = (AuthorizationExternalForm *) decodedKey; + AuthorizationRef receivedAuthorization; + OSStatus status = AuthorizationCreateFromExternalForm(receivedExtFormPtr, &receivedAuthorization); + + if (status != errAuthorizationSuccess) + return result; + + status = AuthorizationCopyRights(receivedAuthorization, &sRightSet, kAuthorizationEmptyEnvironment, kAuthorizationFlagExtendRights , NULL); + if (status == errAuthorizationSuccess) + { + result = true; + } + + AuthorizationFree(receivedAuthorization, kAuthorizationFlagDestroyRights); + + return result; + +#else + + return false; + +#endif + +} + +inline Bool16 HasAuthentication(StringParser *theFullRequestPtr, StrPtrLen* namePtr, StrPtrLen* passwordPtr, StrPtrLen* outAuthTypePtr) +{ +// Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + Bool16 hasAuthentication = false; + StrPtrLen strPtr; + StrPtrLen authType; + StrPtrLen authString; + while (theFullRequestPtr->GetDataRemaining() > 0) + { + theFullRequestPtr->ConsumeWhitespace(); + theFullRequestPtr->ConsumeUntilWhitespace(&strPtr); + if ( strPtr.Len == 0 || !strPtr.Equal(StrPtrLen("Authorization:")) ) + continue; + + theFullRequestPtr->ConsumeWhitespace(); + theFullRequestPtr->ConsumeUntilWhitespace(&authType); + if ( authType.Len == 0 ) + continue; + + theFullRequestPtr->ConsumeWhitespace(); + theFullRequestPtr->ConsumeUntil(&authString, StringParser::sEOLMask); + if ( authString.Len == 0 ) + continue; + + if (outAuthTypePtr != NULL) + outAuthTypePtr->Set(authType.Ptr, authType.Len); + + if (authType.Equal(StrPtrLen("Basic") ) ) + { + (void) ParseAuthNameAndPassword(&authString,namePtr, passwordPtr); + if (namePtr->Len == 0) + continue; + + hasAuthentication = true; + break; + } + else if (authType.Equal(sAuthRef) ) + { + namePtr->Set(NULL,0); + passwordPtr->Set(authString.Ptr, authString.Len); + hasAuthentication = true; + break; + } + + + }; + + return hasAuthentication; +} + +Bool16 Authenticate(QTSS_RTSPRequestObject request, StrPtrLen* namePtr, StrPtrLen* passwordPtr) +{ + Bool16 authenticated = true; + + char* authName = namePtr->GetAsCString(); + OSCharArrayDeleter authNameDeleter(authName); + + QTSS_ActionFlags authAction = qtssActionFlagsAdmin; + + // authenticate callback to retrieve the password + QTSS_Error err = QTSS_Authenticate(authName, sAuthResourceLocalPath, sAuthResourceLocalPath, authAction, qtssAuthBasic, request); + if (err != QTSS_NoErr) { + return false; // Couldn't even call QTSS_Authenticate...abandon! + } + + // Get the user profile object from the request object that was created in the authenticate callback + QTSS_UserProfileObject theUserProfile = NULL; + UInt32 len = sizeof(QTSS_UserProfileObject); + err = QTSS_GetValue(request, qtssRTSPReqUserProfile, 0, (void*)&theUserProfile, &len); + Assert(len == sizeof(QTSS_UserProfileObject)); + if (err != QTSS_NoErr) + authenticated = false; + + if(err == QTSS_NoErr) { + char* reqPassword = passwordPtr->GetAsCString(); + OSCharArrayDeleter reqPasswordDeleter(reqPassword); + char* userPassword = NULL; + (void) QTSS_GetValueAsString(theUserProfile, qtssUserPassword, 0, &userPassword); + OSCharArrayDeleter userPasswordDeleter(userPassword); + + if(userPassword == NULL) { + authenticated = false; + } + else { +#ifdef __Win32__ + // The password is md5 encoded for win32 + char md5EncodeResult[120]; + MD5Encode(reqPassword, userPassword, md5EncodeResult, sizeof(md5EncodeResult)); + if(::strcmp(userPassword, md5EncodeResult) != 0) + authenticated = false; +#else + if(::strcmp(userPassword, (char*)crypt(reqPassword, userPassword)) != 0) + authenticated = false; +#endif + } + } + + char* realm = NULL; + Bool16 allowed = true; + //authorize callback to check authorization + // allocates memory for realm + err = QTSS_Authorize(request, &realm, &allowed); + // QTSS_Authorize allocates memory for the realm string + // we don't use the realm returned by the callback, but instead + // use our own. + // delete the memory allocated for realm because we don't need it! + OSCharArrayDeleter realmDeleter(realm); + + if(err != QTSS_NoErr) { + qtss_printf("QTSSAdminModule::Authenticate: QTSS_Authorize failed\n"); + return false; // Couldn't even call QTSS_Authorize...abandon! + } + + if(authenticated && allowed) + return true; + + return false; +} + + +QTSS_Error AuthorizeAdminRequest(QTSS_RTSPRequestObject request) +{ + Bool16 allowed = false; + + // get the resource path + // if the path does not match the admin path, don't handle the request + char* resourcePath = QTSSModuleUtils::GetLocalPath_Copy(request); + OSCharArrayDeleter resourcePathDeleter(resourcePath); + + if(strcmp(sAuthResourceLocalPath, resourcePath) != 0) + return QTSS_NoErr; + + // get the type of request + QTSS_ActionFlags action = QTSSModuleUtils::GetRequestActions(request); + if(!(action & qtssActionFlagsAdmin)) + return QTSS_RequestFailed; + + QTSS_UserProfileObject theUserProfile = QTSSModuleUtils::GetUserProfileObject(request); + if (NULL == theUserProfile) + return QTSS_RequestFailed; + + (void) QTSS_SetValue(request,qtssRTSPReqURLRealm, 0, sAuthRealm, ::strlen(sAuthRealm)); + + // Authorize the user if the user belongs to the AdministratorGroup (this is an admin module pref) + UInt32 numGroups = 0; + char** groupsArray = QTSSModuleUtils::GetGroupsArray_Copy(theUserProfile, &numGroups); + + if ((groupsArray != NULL) && (numGroups != 0)) + { + UInt32 index = 0; + for (index = 0; index < numGroups; index++) + { + if(strcmp(sAdministratorGroup, groupsArray[index]) == 0) + { + allowed = true; + break; + } + } + + // delete the memory allocated in QTSSModuleUtils::GetGroupsArray_Copy call + delete [] groupsArray; + } + + if(!allowed) + { + if (QTSS_NoErr != QTSS_SetValue(request,qtssRTSPReqUserAllowed, 0, &allowed, sizeof(allowed))) + return QTSS_RequestFailed; // Bail on the request. The Server will handle the error + } + + return QTSS_NoErr; +} + + +Bool16 AcceptSession(QTSS_RTSPSessionObject inRTSPSession) +{ + char remoteAddress[20] = {0}; + StrPtrLen theClientIPAddressStr(remoteAddress,sizeof(remoteAddress)); + QTSS_Error err = QTSS_GetValue(inRTSPSession, qtssRTSPSesRemoteAddrStr, 0, (void*)theClientIPAddressStr.Ptr, &theClientIPAddressStr.Len); + if (err != QTSS_NoErr) return false; + + return AcceptAddress(&theClientIPAddressStr); +} + +Bool16 StillFlushing(QTSS_Filter_Params* inParams,Bool16 flushing) +{ + + QTSS_Error err = QTSS_NoErr; + if (flushing) + { + err = QTSS_Flush(inParams->inRTSPRequest); + //qtss_printf("Flushing session=%"_U32BITARG_" QTSS_Flush err =%"_S32BITARG_"\n",sSessID,err); + } + if (err == QTSS_WouldBlock) // more to flush later + { + sFlushing = true; + (void) QTSS_SetValue(inParams->inRTSPRequest, sFlushingID, 0, (void*)&sFlushing, sFlushingLen); + err = QTSS_RequestEvent(inParams->inRTSPRequest, QTSS_WriteableEvent); + KeepSession(inParams->inRTSPRequest,true); + //qtss_printf("Flushing session=%"_U32BITARG_" QTSS_RequestEvent err =%"_S32BITARG_"\n",sSessID,err); + } + else + { + sFlushing = false; + (void) QTSS_SetValue(inParams->inRTSPRequest, sFlushingID, 0, (void*)&sFlushing, sFlushingLen); + KeepSession(inParams->inRTSPRequest,false); + + if (flushing) // we were flushing so reset the LastRequestTime + { + sLastRequestTime = QTSS_Milliseconds(); + //qtss_printf("Done Flushing session=%"_U32BITARG_"\n",sSessID); + return true; + } + } + + return sFlushing; +} + +Bool16 IsAuthentic(QTSS_Filter_Params* inParams,StringParser *fullRequestPtr) +{ + Bool16 isAuthentic = false; + + if (!sAuthenticationEnabled) // no authentication + { + isAuthentic = true; + } + else // must authenticate + { + StrPtrLen theClientIPAddressStr; + (void) QTSS_GetValuePtr(inParams->inRTSPSession, qtssRTSPSesRemoteAddrStr, 0, (void**)&theClientIPAddressStr.Ptr, &theClientIPAddressStr.Len); + Bool16 isLocal = IPComponentStr(&theClientIPAddressStr).IsLocal(); + + StrPtrLen authenticateName; + StrPtrLen authenticatePassword; + StrPtrLen authType; + Bool16 hasAuthentication = HasAuthentication(fullRequestPtr,&authenticateName,&authenticatePassword, &authType); + if (hasAuthentication) + { + if (authType.Equal(sAuthRef)) + { + if (isLocal) + isAuthentic = OSXAuthenticate(&authenticatePassword); + } + else + isAuthentic = Authenticate(inParams->inRTSPRequest, &authenticateName,&authenticatePassword); + } + } +// if (isAuthentic) +// isAuthentic = AuthorizeAdminRequest(inParams->inRTSPRequest); + (void) QTSS_SetValue(inParams->inRTSPRequest, sAuthenticatedID, 0, (void*)&isAuthentic, sizeof(isAuthentic)); + + return isAuthentic; +} + +inline Bool16 InWaitInterval(QTSS_Filter_Params* inParams) +{ + QTSS_TimeVal nextExecuteTime = sLastRequestTime + sRequestTimeIntervalMilli; + QTSS_TimeVal currentTime = QTSS_Milliseconds(); + SInt32 waitTime = 0; + if (currentTime < nextExecuteTime) + { + waitTime = (SInt32) (nextExecuteTime - currentTime) + 1; + //qtss_printf("(currentTime < nextExecuteTime) sSessID = %"_U32BITARG_" waitTime =%"_S32BITARG_" currentTime = %qd nextExecute = %qd interval=%"_U32BITARG_"\n",sSessID, waitTime, currentTime, nextExecuteTime,sRequestTimeIntervalMilli); + (void)QTSS_SetIdleTimer(waitTime); + KeepSession(inParams->inRTSPRequest,true); + + //qtss_printf("-- call me again after %"_S32BITARG_" millisecs session=%"_U32BITARG_" \n",waitTime,sSessID); + return true; + } + sLastRequestTime = QTSS_Milliseconds(); + //qtss_printf("handle sessID=%"_U32BITARG_" time=%qd \n",sSessID,currentTime); + return false; +} + +inline void GetQueryData(QTSS_RTSPRequestObject theRequest) +{ + sAdminPtr = NEW AdminClass(); + Assert(sAdminPtr != NULL); + if (sAdminPtr == NULL) + { //qtss_printf ("NEW AdminClass() failed!! \n"); + return; + } + if (sAdminPtr != NULL) + { + sAdminPtr->Initialize(&sQTSSparams,sQueryPtr); // Get theData + } +} + +inline void SendHeader(QTSS_StreamRef inStream) +{ + (void)QTSS_Write(inStream, sResponseHeader, ::strlen(sResponseHeader), NULL, 0); + (void)QTSS_Write(inStream, sEOL, ::strlen(sEOL), NULL, 0); + (void)QTSS_Write(inStream, sVersionHeader, ::strlen(sVersionHeader), NULL, 0); + (void)QTSS_Write(inStream, sEOL, ::strlen(sEOL), NULL, 0); + (void)QTSS_Write(inStream, sConnectionHeader, ::strlen(sConnectionHeader), NULL, 0); + (void)QTSS_Write(inStream, sEOL, ::strlen(sEOL), NULL, 0); + (void)QTSS_Write(inStream, sContentType, ::strlen(sContentType), NULL, 0); + (void)QTSS_Write(inStream, sEOM, ::strlen(sEOM), NULL, 0); +} + +inline void SendResult(QTSS_StreamRef inStream) +{ + SendHeader(inStream); + if (sAdminPtr != NULL) + sAdminPtr->RespondToQuery(inStream,sQueryPtr,sQueryPtr->GetRootID()); + +} + +inline Bool16 GetRequestAuthenticatedState(QTSS_Filter_Params* inParams) +{ + Bool16 result = false; + UInt32 paramLen = sizeof(result); + QTSS_Error err = QTSS_GetValue(inParams->inRTSPRequest, sAuthenticatedID, 0, (void*)&result, ¶mLen); + if(err != QTSS_NoErr) + { + paramLen = sizeof(result); + result = false; + err =QTSS_SetValue(inParams->inRTSPRequest, sAuthenticatedID, 0, (void*)&result, paramLen); + } + return result; +} + +inline Bool16 GetRequestFlushState(QTSS_Filter_Params* inParams) +{ Bool16 result = false; + UInt32 paramLen = sizeof(result); + QTSS_Error err = QTSS_GetValue(inParams->inRTSPRequest, sFlushingID, 0, (void*)&result, ¶mLen); + if (err != QTSS_NoErr) + { paramLen = sizeof(result); + result = false; + //qtss_printf("no flush val so set to false session=%"_U32BITARG_" err =%"_S32BITARG_"\n",sSessID, err); + err =QTSS_SetValue(inParams->inRTSPRequest, sFlushingID, 0, (void*)&result, paramLen); + //qtss_printf("QTSS_SetValue flush session=%"_U32BITARG_" err =%"_S32BITARG_"\n",sSessID, err); + } + return result; +} + +QTSS_Error FilterRequest(QTSS_Filter_Params* inParams) +{ + if (NULL == inParams || NULL == inParams->inRTSPSession || NULL == inParams->inRTSPRequest) + { Assert(0); + return QTSS_NoErr; + } + + OSMutexLocker locker(sAdminMutex); + //check to see if we should handle this request. Invokation is triggered + //by a "GET /" request + + QTSS_Error err = QTSS_NoErr; + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + + UInt32 paramLen = sizeof(sSessID); + err = QTSS_GetValue(inParams->inRTSPSession, qtssRTSPSesID, 0, (void*)&sSessID, ¶mLen); + if (err != QTSS_NoErr) + return QTSS_NoErr; + + StrPtrLen theFullRequest; + err = QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len); + if (err != QTSS_NoErr) + return QTSS_NoErr; + + + StringParser fullRequest(&theFullRequest); + + if ( !IsAdminRequest(&fullRequest) ) + return QTSS_NoErr; + + if ( !AcceptSession(inParams->inRTSPSession) ) + { (void)QTSS_Write(inParams->inRTSPRequest, sPermissionDeniedHeader, ::strlen(sPermissionDeniedHeader), NULL, 0); + (void)QTSS_Write(inParams->inRTSPRequest, sHTMLBody, ::strlen(sHTMLBody), NULL, 0); + KeepSession(theRequest,false); + return QTSS_NoErr; + } + + if(!GetRequestAuthenticatedState(inParams)) // must authenticate before handling + { + if(QTSS_IsGlobalLocked()) // must NOT be global locked + return QTSS_RequestFailed; + + if (!IsAuthentic(inParams,&fullRequest)) + { + (void)QTSS_Write(inParams->inRTSPRequest, sUnauthorizedResponseHeader, ::strlen(sUnauthorizedResponseHeader), NULL, 0); + (void)QTSS_Write(inParams->inRTSPRequest, sHTMLBody, ::strlen(sHTMLBody), NULL, 0); + KeepSession(theRequest,false); + return QTSS_NoErr; + } + + } + + if (GetRequestFlushState(inParams)) + { StillFlushing(inParams,true); + return QTSS_NoErr; + } + + if (!QTSS_IsGlobalLocked()) + { + if (InWaitInterval(inParams)) + return QTSS_NoErr; + + //qtss_printf("New Request Wait for GlobalLock session=%"_U32BITARG_"\n",sSessID); + (void)QTSS_RequestGlobalLock(); + KeepSession(theRequest,true); + return QTSS_NoErr; + } + + //qtss_printf("Handle request session=%"_U32BITARG_"\n",sSessID); + APITests_DEBUG(); + + if (sQueryPtr != NULL) + { delete sQueryPtr; + sQueryPtr = NULL; + } + sQueryPtr = NEW QueryURI(&theFullRequest); + if (sQueryPtr == NULL) return QTSS_NoErr; + + ShowQuery_DEBUG(); + + if (sAdminPtr != NULL) + { delete sAdminPtr; + sAdminPtr = NULL; + } + UInt32 result = sQueryPtr->EvalQuery(NULL, NULL); + if (result == 0) do + { + if( ElementNode_CountPtrs() > 0) + { ElementNode_ShowPtrs(); + Assert(0); + } + + GetQueryData(theRequest); + + SendResult(theRequest); + delete sAdminPtr; + sAdminPtr = NULL; + + if (sQueryPtr && !sQueryPtr->QueryHasReponse()) + { UInt32 err = 404; + (void) sQueryPtr->EvalQuery(&err,NULL); + ReportErr(inParams, err); + break; + } + + if (sQueryPtr && sQueryPtr->QueryHasReponse()) + { ReportErr(inParams, sQueryPtr->GetEvaluResult()); + } + + if (sQueryPtr->fIsPref && sQueryPtr->GetEvaluResult() == 0) + { QTSS_ServiceID id; + (void) QTSS_IDForService(QTSS_REREAD_PREFS_SERVICE, &id); + (void) QTSS_DoService(id, NULL); + } + } while(false); + else + { + SendHeader(theRequest); + ReportErr(inParams, sQueryPtr->GetEvaluResult()); + } + + if (sQueryPtr != NULL) + { delete sQueryPtr; + sQueryPtr = NULL; + } + + (void) StillFlushing(inParams,true); + return QTSS_NoErr; + +} + + + + diff --git a/APIModules/QTSSAdminModule/QTSSAdminModule.h b/APIModules/QTSSAdminModule/QTSSAdminModule.h new file mode 100644 index 0000000..44a8323 --- /dev/null +++ b/APIModules/QTSSAdminModule/QTSSAdminModule.h @@ -0,0 +1,47 @@ +/* + * + * @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: QTSSWebStatsModule.h + + Contains: A module that uses the information available in the server + to present a web page containing that information. Uses the Filter + module feature of QTSS API. + + + +*/ + +#ifndef __QTSSADMINMODULE_H__ +#define __QTSSADMINMODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSAdminModule_Main(void* inPrivateArgs); +} + +#endif // __QTSSADMINMODULE_H__ + diff --git a/APIModules/QTSSDSAuthModule/DSAccessChecker.cpp b/APIModules/QTSSDSAuthModule/DSAccessChecker.cpp new file mode 100644 index 0000000..ed1dffd --- /dev/null +++ b/APIModules/QTSSDSAuthModule/DSAccessChecker.cpp @@ -0,0 +1,646 @@ +/* + * + * Copyright (c) 1999-2005 Apple Computer, Inc. All Rights Reserved. + * + * @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: DSAccessChecker.cpp + + Contains: Class definition for access checking via Open Directory + + Created By: Dan Sinema + + Created: Jan 14, 2005 + +*/ + +/* + * Directory Service code added by Dan Sinema + * + * Jan 14, 2005 - Cleaned up code and added more comments. + * Nov 8, 2004 - Finsihed final code. Added group support. + * +*/ + + +// ANSI / POSIX Headers +#include +#include +#include +#include +#include + +// STL Headers +#include +#include +#include + + +// Project Headers +#include "SafeStdLib.h" +#include "StrPtrLen.h" +#include "StringParser.h" +#include "ResizeableStringFormatter.h" + +#include "DSAccessChecker.h" +#include "DSDataList.h" +#include "QTAccessFile.h" + +#define DEBUG_DSACCESS 0 +#define debug_printf if (DEBUG_DSACCESS) ::qtss_printf + +#include + +#ifdef AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER + #if OSX_OD_API + #define OD_API 1 + #define DS_API 0 + #else + #define OD_API 0 + #define DS_API 1 + #endif +#else + #define OD_API 0 + #define DS_API 1 +#endif + +// Framework Headers +#if DS_API +#include +using namespace DirectoryServices; +#endif + +#if OD_API +#include +#endif + +#if __LP64__ + #define ds_API_PTR UInt32* +#else + #define ds_API_PTR long unsigned int* +#endif + + +#pragma mark DSAccessChecker class globals + +const char* DSAccessChecker::kDefaultAccessFileName = "qtaccess"; + + +#pragma mark DSAccessChecker class implementation +#if DS_API + +// Find a list of records that match the given criteria. +static SInt32 _GetRecordList( + tDirReference inDSRef, + const char *inDomain, + const char *inRecName, + const char *inRecType, + tDataList *inAttrType, + tDirNodeReference *outNodeRef, + tDataBuffer **outDataBuff, + UInt32 *outRecCount ) +{ + SInt32 status = eDSNoErr; + tDataBuffer *pDataBuff = NULL; + tDirNodeReference nodeRef = 0; + tContextData context = NULL; + UInt32 nodeCount = 0; + tDataList *nodeName = NULL; + UInt32 recCount = 0; + + *outNodeRef = 0; + *outDataBuff = NULL; + *outRecCount = 0; + + pDataBuff = ::dsDataBufferAllocate( inDSRef, 4096 ); + if (pDataBuff == NULL) + { + // We need the buffer for locating the node for which the user object resides + debug_printf("QTSSODAuthModule: Unable to allocate buffer.\n"); + return eDSAllocationFailed; + } + + // This is the default action with no domain: use the Search node. + status = ::dsFindDirNodes( inDSRef, pDataBuff, NULL, eDSSearchNodeName, (ds_API_PTR) &nodeCount, &context ); + if ( context != NULL ) + { + ::dsReleaseContinueData( inDSRef, context ); + context = NULL; + } + + // Check for failure of the dsFindDirNodes + // Node count less than 1 means no node found...doh! + if ( nodeCount < 1 ) + { + status = eDSNodeNotFound; + } + if ( status != eDSNoErr ) + { + goto cleanupBadGetRecordList; + } + + // Extract the name of the found node. + status = ::dsGetDirNodeName( inDSRef, pDataBuff, 1, &nodeName ); + if (status == eDSNoErr) + { + // Open the node so we can do the DS magic + status = ::dsOpenDirNode( inDSRef, nodeName, &nodeRef ); + ::dsDataListDeallocate( inDSRef, nodeName ); + std::free( nodeName ); + nodeName = NULL; + } + + if (status != eDSNoErr) + { + // Bail if we cannot open the node. + debug_printf("QTSSODAuthModule: Could not open node - error: %"_S32BITARG_"\n", status); + } + else + { + // Specify what we are looking for... + // pRecName: the passed name of the record + // pRecType: the passed name of the record type + // pAttrType: attributes to return to the caller + DSDataList recName( inDSRef, inRecName ); + DSDataList recType( inDSRef, inRecType ); + + recCount = 1; + + // Find the record that matchs the above criteria + status = ::dsGetRecordList( nodeRef, pDataBuff, recName, eDSExact, recType, inAttrType, 0, (ds_API_PTR)&recCount, &context ); + if ( context != NULL ) + { + ::dsReleaseContinueData( inDSRef, context ); + context = NULL; + } + if ( recCount == 0 ) + { + status = eDSRecordNotFound; + debug_printf("QTSSODAuthModule: No records found.\n"); + } + else if ( status != eDSNoErr ) + { + debug_printf("QTSSODAuthModule: No records found - error: %"_S32BITARG_"\n", status); + } + } + + if ( status == eDSNoErr ) + { + *outNodeRef = nodeRef; + *outDataBuff = pDataBuff; + *outRecCount = recCount; + return eDSNoErr; + } + +cleanupBadGetRecordList: + if ( nodeRef != 0 ) + { + ::dsCloseDirNode( nodeRef ); + } + + // This variable is guaranteed to be valid because the function would + // have returned if it was bad. + ::dsDataBufferDeAllocate( inDSRef, pDataBuff ); + + return status; +} + +static SInt32 _FindRecordNode( + tDirReference inDSRef, + const char *inDomain, + const char *inRecName, + const char *inRecType, + tDataList *outHomeNodeName ) +{ + tDataBuffer *pRecBuff = NULL; + tDirNodeReference nodeRef = 0; + SInt32 status = eDSNoErr; + UInt32 attrIndex = 0; + UInt32 recCount = 0; + tRecordEntry *pRecEntry = NULL; + tAttributeListRef attrListRef = 0; + + if ( outHomeNodeName == NULL ) + { + return eDSNullDataList; + } + std::memset( outHomeNodeName, 0, sizeof( *outHomeNodeName) ); + + // A Username and Password is needed, if either one is not present then bail! + if ( inRecName == NULL ) + { + return eDSInvalidRecordName; + } + if ( inRecType == NULL ) + { + return eDSInvalidRecordType; + } + + status = ::_GetRecordList( inDSRef, inDomain, inRecName, inRecType, + DSDataList (inDSRef, kDSNAttrMetaNodeLocation), + &nodeRef, &pRecBuff, &recCount ); + if ( status != eDSNoErr ) + { + return status; + } + + // Get the record entry out of the list, there should only be one record! + status = ::dsGetRecordEntry( nodeRef, pRecBuff, 1, &attrListRef, &pRecEntry ); + if ( status != eDSNoErr ) + { + // These variables are guaranteed to be valid because the function would + // have returned if they were bad. + ::dsCloseDirNode( nodeRef ); + ::dsDataBufferDeAllocate( inDSRef, pRecBuff ); + return status; + } + + // Now loop through attributes of the entry...looking for kDSNAttrMetaNodeLocation and kDSNAttrRecordName + for ( attrIndex = 1; (attrIndex <= pRecEntry->fRecordAttributeCount) && (status == eDSNoErr); attrIndex++ ) + { + tAttributeEntryPtr pAttrEntry = NULL; + tAttributeValueListRef valueRef = 0; + + status = ::dsGetAttributeEntry( nodeRef, pRecBuff, attrListRef, attrIndex, &valueRef, &pAttrEntry ); + if ( ( status != eDSNoErr ) || ( pAttrEntry == NULL ) ) + continue; + // Test for kDSNAttrMetaNodeLocation + if ( std::strcmp( pAttrEntry->fAttributeSignature.fBufferData, kDSNAttrMetaNodeLocation ) == 0 ) + { + tAttributeValueEntry *pValueEntry = NULL; + + // If it matches then get the value of the attribute + status = ::dsGetAttributeValue( nodeRef, pRecBuff, 1, valueRef, &pValueEntry ); + if ( ( status == eDSNoErr ) && ( pValueEntry != NULL ) ) + { + // Store the node location in outHomeNodeName + if ( outHomeNodeName->fDataNodeCount != 0 ) + { + debug_printf("QTSSODAuthModule: Multiple user locations found!?\n"); + } + else + { + status = ::dsBuildListFromPathAlloc( inDSRef, outHomeNodeName, pValueEntry->fAttributeValueData.fBufferData, "/" ); + ::dsDeallocAttributeValueEntry( inDSRef, pValueEntry ); + } + } + } + + ::dsDeallocAttributeEntry( inDSRef, pAttrEntry ); + ::dsCloseAttributeValueList( valueRef ); + } + + // Cleanup dsGetRecordEntry() return values. + ::dsCloseAttributeList( attrListRef ); + ::dsDeallocRecordEntry( inDSRef, pRecEntry ); + ::dsCloseDirNode( nodeRef ); + ::dsDataBufferDeAllocate( inDSRef, pRecBuff ); + return status; +} + +#endif + +#pragma mark - +#pragma mark "Public Methods" +// Now the class proper. +DSAccessChecker::DSAccessChecker() +{ +} + +DSAccessChecker::~DSAccessChecker() +{ +#if DEBUG + debug_printf("QTSSODAuthModule: Access checker object destroyed.\n"); +#endif +} + + + +#if 0 //OD_API notes +/* + This is Leopard or later code so some check before using OD based code is needed. + Implement this api for digest auth. +*/ + +#include +/System/Library/PrivateFrameworks/OpenDirectory.framework/Frameworks/CFOpenDirectory.framework/CFOpenDirectory +CFErrorRef outError = NULL; +ODNodeRef cfNode = ODNodeCreateWithNodeType( kCFAllocatorDefault, kODSessionDefault, kODTypeAuthenticationSearchNode, NULL ); +if (cfNode) +{ + ODRecordRef cfUserRecord = ODNodeCopyRecord( kCFAllocatorDefault, cfNode, CFSTR("username"), NULL ); + + if (cfUserRecord != NULL) + { + CFArrayRef authItems = CFArrayCreate.... ( username, server challenge, client response, http method); + + // for DIGEST_MD5 + if (ODRecordVerifyPasswordExtended( cfUserRecord, CFSTR(kDSStdAuthDIGEST_MD5), authItems, NULL, NULL, &outError )) + { + } + + // this for password + if (ODRecordVerifyPassword( cfUserRecord, CFSTR("password") ) ) + { + } + CFRelease( cfUserRecord ); + CFRelease( autItems ); + } + CFRelease( cfNode ); +} + + +// kDSStdAuthDIGEST_MD5 + * user name in UTF8 encoding, + * server challenge in UTF8 encoding, + * client response data, + * HTTP method in UTF8 encoding + + + } +} +#endif + + +#if OD_API +Bool16 DSAccessChecker::CheckPassword(const char* inUsername, const char* inPassword) +{ + Bool16 checkedResult = false; + CFErrorRef outError = NULL; + debug_printf("DSAccessChecker::CheckPassword userName=%s password=%s\n", inUsername,inPassword); + + ODNodeRef cfNodeRef= ODNodeCreateWithNodeType( kCFAllocatorDefault, kODSessionDefault, kODTypeAuthenticationSearchNode, NULL ); + + //static ODRecordRef _ODNodeCopyRecord( ODNodeRef inNodeRef, CFStringRef inRecordType, CFStringRef inRecordName, CFArrayRef inAttributes, CFErrorRef *outError ); + + + CFStringRef cfPassword = CFStringCreateWithCString(kCFAllocatorDefault, inPassword, kCFStringEncodingUTF8); + CFStringRef cfUsername = CFStringCreateWithCString(kCFAllocatorDefault, inUsername, kCFStringEncodingUTF8); + + CFTypeRef vals[] = { CFSTR(kDSAttributesStandardAll) }; + CFArrayRef reqAttrs = CFArrayCreate(NULL, vals,1, &kCFTypeArrayCallBacks); + + ODRecordRef cfUserRecord = ODNodeCopyRecord(cfNodeRef, CFSTR(kDSStdRecordTypeUsers), cfUsername, reqAttrs, &outError ); + if (cfNodeRef && cfUserRecord && cfPassword && cfUsername) + { + // this for password + if ( ODRecordVerifyPassword( cfUserRecord, cfPassword , NULL ) ) + { checkedResult = true; + debug_printf("DSAccessChecker::CheckPassword ODRecordVerifyPassword user is authenticated\n"); + } + else + { debug_printf("DSAccessChecker::CheckPassword ODRecordVerifyPassword user failed to authenticate\n"); + } + } + + if (reqAttrs) CFRelease( reqAttrs ); + if (cfUserRecord) CFRelease( cfUserRecord ); + if (cfPassword) CFRelease( cfPassword ); + if (cfUsername) CFRelease( cfUsername ); + if (cfNodeRef) CFRelease( cfNodeRef ); + + return checkedResult; + +} + + +Bool16 DSAccessChecker::CheckDigest(const char* inUsername, const char* inServerChallenge, const char* inClientResponse, const char* inMethod) +{ + Bool16 checkedResult = false; + CFErrorRef outError = NULL; + CFArrayRef outItems = NULL; + + if (NULL == inUsername || NULL == inServerChallenge || NULL == inClientResponse ) + return false; + + ResizeableStringFormatter challengeString; + challengeString.Put((char*) inServerChallenge); + challengeString.PutTerminator(); + char* challengeCString= challengeString.GetBufPtr(); + + ResizeableStringFormatter responseString; + responseString.Put( (char*)inClientResponse); + responseString.PutTerminator(); + char* responseCString= responseString.GetBufPtr(); + + ODNodeRef cfNode = ODNodeCreateWithNodeType( kCFAllocatorDefault, kODSessionDefault, kODTypeAuthenticationSearchNode, NULL ); + debug_printf("DSAccessChecker::CheckDigest \nuserName=[%s] \nchallenge=[%s] \nresponse=[%s] \nmethod=[%s]\n", inUsername,challengeCString, responseCString,inMethod); + + + CFTypeRef vals[] = { CFSTR(kDSAttributesStandardAll) }; + CFArrayRef reqAttrs = CFArrayCreate(NULL, vals,1, &kCFTypeArrayCallBacks); + + CFStringRef cfUsername = CFStringCreateWithCString(kCFAllocatorDefault, inUsername, kCFStringEncodingUTF8); + ODRecordRef cfUserRecord = ODNodeCopyRecord(cfNode, CFSTR(kDSStdRecordTypeUsers), cfUsername, reqAttrs, &outError ); + CFRelease( cfUsername ); + cfUsername = NULL; + + enum { kNumAuthValues=4 }; + CFStringRef cfStringArray[kNumAuthValues]; + cfStringArray[0] = CFStringCreateWithCString(kCFAllocatorDefault, inUsername, kCFStringEncodingUTF8); + cfStringArray[1] = CFStringCreateWithCString(kCFAllocatorDefault, challengeCString, kCFStringEncodingUTF8); + cfStringArray[2] = CFStringCreateWithCString(kCFAllocatorDefault, responseCString, kCFStringEncodingUTF8); + cfStringArray[3] = CFStringCreateWithCString(kCFAllocatorDefault, inMethod, kCFStringEncodingUTF8); + + CFArrayRef cfAuthItems = CFArrayCreate(kCFAllocatorDefault, (const void **) &cfStringArray,kNumAuthValues, &kCFTypeArrayCallBacks); + + if (cfNode && cfUserRecord && cfAuthItems) + { + debug_printf("DSAccessChecker::CheckDigest call ODRecordVerifyPasswordExtended\n"); + // for DIGEST_MD5 + if (ODRecordVerifyPasswordExtended( cfUserRecord, CFSTR(kDSStdAuthDIGEST_MD5), cfAuthItems, &outItems, NULL, &outError )) + { + checkedResult = true; + debug_printf("DSAccessChecker::CheckDigest SUCCESS ODRecordVerifyPasswordExtended response=%d\n",outError); + } + else + { debug_printf("DSAccessChecker::CheckDigest ODRecordVerifyPasswordExtended response=%d\n", outError ? CFErrorGetCode(outError) : -1); + } + } + + for (int i = 0; i < kNumAuthValues; i++) + CFRelease(cfStringArray[i]); + + if (reqAttrs) CFRelease( reqAttrs ); + if (cfAuthItems) CFRelease( cfAuthItems ); + if (cfUserRecord) CFRelease( cfUserRecord ); + if (cfNode) CFRelease( cfNode ); + if (outItems) CFRelease( outItems ); + if (outError) CFRelease( outError ); + + return checkedResult; + +} + + + +#endif + +#if DS_API + +Bool16 DSAccessChecker::CheckPassword(const char* inUsername, const char* inPassword) +{ + tDirReference dsRef = 0; + tDataList userNode = { 0, NULL }; + tDirNodeReference nodeRef = 0; + SInt32 status = eDSNoErr; + + // A Username and Password is needed, if either one is not present then bail! + if ( inUsername == NULL ) + { + debug_printf("QTSSODAuthModule: Username required.\n"); + return false; + } + if ( inPassword == NULL ) + { + debug_printf("QTSSODAuthModule: Password required.\n"); + return false; + } + status = ::dsOpenDirService( &dsRef ); + if ( status != eDSNoErr ) + { + // Some DS error, tell the admin what the error is and bail. + // Error can be found in DirectoryService man page. + debug_printf("QTSSODAuthModule: Could not open Directory Services - error: %"_S32BITARG_"", status); + return false; + } + + status = _FindRecordNode( dsRef, NULL, inUsername, kDSStdRecordTypeUsers, &userNode ); + if ( status != eDSNoErr ) + { + debug_printf("QTSSODAuthModule: Could not find user node.\n"); + return false; + } + + // Now that we know the node location of the user object, lets open that node. + status = ::dsOpenDirNode( dsRef, &userNode, &nodeRef ); + ::dsDataListDeallocate( dsRef, &userNode ); + + if ( status == eDSNoErr ) + { + UInt32 uiLen = std::strlen( inUsername ); + tDataNode *pAuthType = ::dsDataNodeAllocateString( dsRef, kDSStdAuthNodeNativeClearTextOK ); + tDataBuffer *pStepBuff = ::dsDataBufferAllocate( dsRef, 128 ); + tDataBuffer *pAuthBuff = ::dsDataBufferAllocate( dsRef, ( sizeof( UInt32 ) + sizeof( UInt32 ) + uiLen + std::strlen( inPassword ) ) ); + + if ( ( pStepBuff != NULL ) && ( pAuthType != NULL ) && ( pAuthBuff != NULL ) ) + { + UInt32 uiCurr = 0; + + // Copy username (that is passed into this function) into buffer for dsDoDirNodeAuth() + std::memcpy( &(pAuthBuff->fBufferData[ uiCurr ]), &uiLen, sizeof( UInt32 ) ); + uiCurr += sizeof( UInt32 ); + std::memcpy( &(pAuthBuff->fBufferData[ uiCurr ]), inUsername, uiLen ); + uiCurr += uiLen; + + // Copy password into a buffer for dsDoDirNodeAuth() + uiLen = std::strlen( inPassword ); + std::memcpy( &(pAuthBuff->fBufferData[ uiCurr ]), &uiLen, sizeof( UInt32 ) ); + uiCurr += sizeof( UInt32 ); + std::memcpy( &(pAuthBuff->fBufferData[ uiCurr ]), inPassword, uiLen ); + uiCurr += uiLen; + + pAuthBuff->fBufferLength = uiCurr; + + // Perform the authentication + status = ::dsDoDirNodeAuth( nodeRef, pAuthType, 1, pAuthBuff, pStepBuff, NULL ); + // Since the buffer held a name & password, clear it immediately. + std::memset( pAuthBuff, 0, pAuthBuff->fBufferSize ); + } + // Free the auth buffer. + if ( pAuthBuff != NULL ) + { + ::dsDataBufferDeAllocate( dsRef, pAuthBuff ); + } + // Free the ignored step buffer. + if ( pStepBuff != NULL ) + { + ::dsDataBufferDeAllocate( dsRef, pStepBuff ); + } + // Free the auth string. + if ( pAuthType != NULL ) + { + ::dsDataNodeDeAllocate( dsRef, pAuthType ); + } + ::dsCloseDirNode( nodeRef ); + } + ::dsCloseDirService( dsRef ); + + if( status == eDSNoErr ) + { + debug_printf("QTSSODAuthModule: Authentication is good.\n"); + return true; + } + + // For admins running QTSS in debug + debug_printf("QTSSODAuthModule: OD returned %"_S32BITARG_" status.\n", status); + debug_printf("QTSSODAuthModule: Authentication failed.\n"); + // If the Authentication failed then return false, which boots the user... + return false; +} + +Bool16 DSAccessChecker::CheckDigest(const char* inUsername, const char* inServerChallenge, const char* inClientResponse, const char* inMethod) +{ + return false; +} + +#endif + +#pragma mark - +#pragma mark "Protected Methods" + +Bool16 DSAccessChecker::CheckGroupMembership(const char* inUsername, const char* inGroupName) +{ + // In Tiger, group membership is painfully simple: we ask memberd for it! + struct passwd *user = NULL; + struct group *group = NULL; + uuid_t userID; + uuid_t groupID; + int isMember = 0; + + // Look up the user using the POSIX APIs: only care about the UID. + user = getpwnam(inUsername); + endpwent(); + if ( user == NULL ) + return false; + uuid_clear(userID); + if ( mbr_uid_to_uuid(user->pw_uid, userID) ) + return false; + + // Look up the group using the POSIX APIs: only care about the GID. + group = getgrnam(inGroupName); + endgrent(); + if ( group == NULL ) + return false; + uuid_clear(groupID); + if ( mbr_gid_to_uuid(group->gr_gid, groupID) ) + return false; + + // mbr_check_membership() returns 0 on success and error code on failure. + if ( mbr_check_membership(userID, groupID, &isMember) ) + return false; + return (bool)isMember; +} + + diff --git a/APIModules/QTSSDSAuthModule/DSAccessChecker.h b/APIModules/QTSSDSAuthModule/DSAccessChecker.h new file mode 100644 index 0000000..58baf4d --- /dev/null +++ b/APIModules/QTSSDSAuthModule/DSAccessChecker.h @@ -0,0 +1,70 @@ +/* + * + * @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: DSAccessChecker.h + + Contains: Class definition for access checking via Open Directory + + Created By: Dan Sinema + + Created: Jan 14, 2005 +*/ + +#ifndef _QTSSACCESSCHECKER_H_ +#define _QTSSACCESSCHECKER_H_ + +// STL Headers +#include // for struct FILE +#include +#include "QTAccessFile.h" + + +class DSAccessChecker +{ +/* + Access check logic: + + If "modAccess_enabled" == "enabled, + +*/ + +public: + static const char* kDefaultAccessFileName; + + DSAccessChecker(); + virtual ~DSAccessChecker(); + + Bool16 CheckPassword(const char* inUsername, const char* inPassword); + Bool16 CheckDigest(const char* inUsername, const char* inServerChallenge, const char* inClientResponse, const char* inMethod); + + +protected: + Bool16 CheckGroupMembership(const char* inUsername, const char* inGroupName); +}; + + + + +#endif //_QTSSACCESSCHECKER_H_ diff --git a/APIModules/QTSSDSAuthModule/DSWrappers/CDirService.cpp b/APIModules/QTSSDSAuthModule/DSWrappers/CDirService.cpp new file mode 100644 index 0000000..aef207c --- /dev/null +++ b/APIModules/QTSSDSAuthModule/DSWrappers/CDirService.cpp @@ -0,0 +1,91 @@ +/* + * + * @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: CDirService.h + + Contains: Implements DSException. + + Created By: chris jalbert + + Created: 26 July 2000 + */ + + +// This file is only meaningful if exceptions are enabled. +#if USE_EXCEPTIONS + +// STL and Std C++ Library Headers +#include // for abs() +#include // for standard exceptions + +// Project Headers +#include "CDirService.h" + +using namespace DirectoryServices ; +using namespace std ; + + +// ---------------------------------------------------------------------------- +// ¥ CDirService Class Globals +// These private typedefs, globals, and functions are not declared statically +// in the class definition because I want to hide the implementation details +// and reduce unrelated dependencies in the class header. +// ---------------------------------------------------------------------------- + +static const char * const _DSErr = "DirectoryService error: " ; + +/* + * Convert an UInt32 to ASCII for printf purposes, returning + * a pointer to the first character of the string representation. + * Borrowed from vfprintf.c. + */ +static char *_ltoa ( + SInt32 val, + char *endp) +{ + register char *cp = endp ; + register SInt32 sval = std::abs (val) ; + + // Terminate the string. + *--cp = '\0' ; + + do { + *--cp = '0' + (sval % 10) ; + sval /= 10 ; + } while (sval != 0) ; + + // Handle signed values. + if (val < 0) + *--cp = '-' ; + return cp ; +} + +DSException::DSException (tDirStatus err) + : inherited (string (_DSErr) + _ltoa (err, &mErrStr[sizeof (mErrStr)])), + mErr (err) +{ +} + +#endif /* USE_EXCEPTIONS */ diff --git a/APIModules/QTSSDSAuthModule/DSWrappers/CDirService.h b/APIModules/QTSSDSAuthModule/DSWrappers/CDirService.h new file mode 100644 index 0000000..6aaf0be --- /dev/null +++ b/APIModules/QTSSDSAuthModule/DSWrappers/CDirService.h @@ -0,0 +1,87 @@ +/* + * + * @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: CDirService.h + + Contains: Defines DSException. + + Created By: chris jalbert + + Created: 26 July 2000 + */ + + +#ifndef _CDirService_h +#define _CDirService_h + +// Framework Headers +#include + +namespace DirectoryServices { + +const tDirReference kDSDirRefNull = 0 ; + +// This file is only meaningful if exceptions are enabled. +#if __EXCEPTIONS + +// STL and Std C++ Library Headers +#include // for standard exceptions + + +//----------------------------------------------------------------------------- +// ¥ DSException - exception class that wraps a tDirStatus result code. +//----------------------------------------------------------------------------- + +class DSException : public std::runtime_error { + public: + typedef std::runtime_error inherited ; + DSException ( tDirStatus err ) ; + tDirStatus status ( void ) const { return mErr ; } + private: + tDirStatus mErr ; + char mErrStr [12] ; +} ; +#define throw_ds_error(err) throw DSException(err) + +#else /* __EXCEPTIONS */ + +//----------------------------------------------------------------------------- +// ¥ DSException - with exceptions disabled, this is dummy code. +//----------------------------------------------------------------------------- + +class DSException { + public: + DSException ( tDirStatus ) { } +} ; + +// Define away the throw spec. +#define throw(spec) +#define throw_ds_error(err) + +#endif /* __EXCEPTIONS */ + +} // namespace DirectoryServices + +#endif /* _CDirService_h */ diff --git a/APIModules/QTSSDSAuthModule/DSWrappers/DSBuffer.cpp b/APIModules/QTSSDSAuthModule/DSWrappers/DSBuffer.cpp new file mode 100644 index 0000000..93e3ce0 --- /dev/null +++ b/APIModules/QTSSDSAuthModule/DSWrappers/DSBuffer.cpp @@ -0,0 +1,94 @@ +/* + * + * @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: DSBuffer.cpp + + Contains: Class implementation for Open Directory buffers + (wraps tDataBufferPtr) + + Created By: chris jalbert + + Created: 28 November 2000 + */ + + +// ANSI / POSIX headers + +// STL and Std C++ Library Headers +#include // for standard exceptions + +// Framework Headers +#include + +// Project Headers +#include "DSBuffer.h" + + +using namespace DirectoryServices ; + + +// ---------------------------------------------------------------------------- +// ¥ DSBuffer Class Globals +// These private typedefs, globals, and functions are not declared statically +// in the class definition because I want to hide the implementation details +// and reduce unrelated dependencies in the class header. +// ---------------------------------------------------------------------------- + +// ---------------------------------------------------------------------------- +// ¥ DSBuffer Protected Instance Methods +// ---------------------------------------------------------------------------- +#pragma mark **** DSBuffer Protected Instance Methods **** + +// ---------------------------------------------------------------------------- +// ¥ÊGrow +// All memory management (even in the c'tors) are handled in this method. +// An inNewSize value of 0 implies the use of the default buffer size! +// To leave the buffer alone, call with an argument value of 1. +// ---------------------------------------------------------------------------- + +tDataBufferPtr DSBuffer::grow ( size_t inNewSize ) +{ + if (!inNewSize) + inNewSize = kDefaultSize ; + if (mBuffer && (inNewSize <= mBuffer->fBufferSize)) + return mBuffer ; + + register size_t ulTemp = 16 ; + if (inNewSize == kDefaultSize) + ulTemp = inNewSize ; + else + for ( ; ulTemp < inNewSize ; ulTemp <<= 1) ; + + register tDataBufferPtr bufNew = ::dsDataBufferAllocate (mDirRef, ulTemp) ; + if (!bufNew) + throw_ds_error (eDSAllocationFailed) ; + if (mBuffer && (ulTemp = mBuffer->fBufferLength)) + std::memcpy (bufNew->fBufferData, mBuffer->fBufferData, ulTemp) ; + else + ulTemp = 0 ; + bufNew->fBufferLength = ulTemp ; + ::dsDataBufferDeAllocate (mDirRef, mBuffer) ; + return (mBuffer = bufNew) ; +} diff --git a/APIModules/QTSSDSAuthModule/DSWrappers/DSBuffer.h b/APIModules/QTSSDSAuthModule/DSWrappers/DSBuffer.h new file mode 100644 index 0000000..ed61758 --- /dev/null +++ b/APIModules/QTSSDSAuthModule/DSWrappers/DSBuffer.h @@ -0,0 +1,125 @@ +/* + * + * @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: DSBuffer.h + + Contains: Class definition for Open Directory buffers + (wraps tDataBufferPtr) + + Created By: chris jalbert + + Created: 26 July 2000 + */ + + +#ifndef _DSBuffer_h +#define _DSBuffer_h + + +// Framework Headers +#include +#include + +// Project Headers +#include "CDirService.h" + + +namespace DirectoryServices { + +//----------------------------------------------------------------------------- +// ¥ DSBuffer - a wrapper for tDataBufferPtr. +// This should be considered a private class, primarily used by DSNode. +// All methods except Grow() are inlined for performance reasons (sorry). +//----------------------------------------------------------------------------- + +class DSBuffer +{ +public: + /**** Typedefs, enums, and constants. ****/ + enum { kDefaultSize = 128 } ; + +public: + /**** Instance methods. ****/ + // ctor and dtor. + DSBuffer ( tDirReference inDirRef = 0, + size_t inBufferSize = kDefaultSize ) throw (DSException) + : mDirRef (inDirRef), mBuffer (0) + { if (!grow (inBufferSize)) throw_ds_error (eDSAllocationFailed) ; } + ~DSBuffer ( void ) throw () + { if (mBuffer) ::dsDataBufferDeAllocate (mDirRef, mBuffer) ; } + + // Inline accessors. + size_t capacity ( void ) const throw () + { return (size_t) mBuffer->fBufferSize ; } + size_t size ( void ) const throw () + { return (size_t) mBuffer->fBufferSize ; } + size_t length ( void ) const throw () + { return (size_t) mBuffer->fBufferLength ; } + const char *c_str ( void ) const throw () + { return (const char *) mBuffer->fBufferData ; } + const void *data ( void ) const throw () + { return (const void *) mBuffer->fBufferData ; } + + // Inline setters. + void clear ( void ) throw () + { mBuffer->fBufferLength = 0 ; } + void resize ( size_t inLength ) throw (DSException) + { if (inLength > mBuffer->fBufferSize) + throw_ds_error (eDSBufferTooSmall) ; + mBuffer->fBufferLength = inLength ; } + void set ( const char *inString ) throw (DSException) + { clear () ; append (inString) ; } + void set ( const void *inData, size_t inLength ) throw (DSException) + { clear () ; append (inData, inLength) ; } + void append ( const char *inString ) throw (DSException) + { append ((const void *) inString, 1 + strlen (inString)) ; } + void append ( const void *inData, size_t inLength ) throw (DSException) + { grow (mBuffer->fBufferLength + inLength) ; + char *cpBuf = mBuffer->fBufferData + mBuffer->fBufferLength ; + std::memcpy (cpBuf, inData, inLength) ; + mBuffer->fBufferLength += inLength ; } + + // Casting operators. + tDataBufferPtr operator->() throw () + { return mBuffer ; } + operator tDataBufferPtr() const throw () + { return mBuffer ; } + operator const char*() const throw () + { return this->c_str () ; } + operator const void*() const throw () + { return this->data () ; } + +protected: + /**** Instance methods accessible only to class and subclasses. ****/ + tDataBufferPtr grow ( size_t inNewSize ) throw (DSException) ; + + /**** Instance data. ****/ + tDirReference mDirRef ; + tDataBufferPtr mBuffer ; +} ; + +} // namespace DirectoryServices + +#endif /* _DSBuffer_h */ diff --git a/APIModules/QTSSDSAuthModule/DSWrappers/DSDataList.h b/APIModules/QTSSDSAuthModule/DSWrappers/DSDataList.h new file mode 100644 index 0000000..d2bb36e --- /dev/null +++ b/APIModules/QTSSDSAuthModule/DSWrappers/DSDataList.h @@ -0,0 +1,140 @@ +/* + * + * @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: DSDataList.h + + Contains: Class definition for Open Directory data list + (wraps tDataListPtr) + + Created By: chris jalbert + + Created: 26 July 2000 + */ + + +#ifndef _DSDataList_h +#define _DSDataList_h + +// ANSI / POSIX Headers +#include // for varargs stuff +#include // for memset() + +// STL and Std C++ Library Headers +#include // for auto_ptr<> + +// Framework Headers +#include +#include + +// Project Headers +#include "DSDataNode.h" + + +namespace DirectoryServices { + +//----------------------------------------------------------------------------- +// ¥ DSDataList - simple wrapper for tDataBufferPtr. +// This should be considered a private class, primarily used by DSNode. +// All methods are inlined for performance reasons (sorry). +// Logically, a tDataList is a collection of tDataNode's, so this +// implementation offers GetCount() and operator[](u_long) methods. +//----------------------------------------------------------------------------- + +class DSDataList +{ +public: + /**** Instance methods. ****/ + // ctor and dtor. +/* Not used anywhere and conflicts with next ctor, which is more useful. + DSDataList ( tDirReference inDirRef, + const char *inString, ... ) throw (DSException) + : mDirRef (inDirRef) + { va_list args ; va_start (args, inString) ; + std::memset (&mList, 0, sizeof (mList)) ; + tDirStatus nError = ::dsBuildListFromStringsAllocV ( + inDirRef, &mList, inString, args) ; + va_end (args) ; + if (nError) throw_ds_error (nError) ; } +*/ + DSDataList ( tDirReference inDirRef, + const char *inPath ) throw (DSException) + : mDirRef (inDirRef) + { std::memset (&mList, 0, sizeof (mList)) ; + tDirStatus nError = ::dsBuildListFromPathAlloc (inDirRef, &mList, inPath, "/") ; + if (nError) throw_ds_error (nError) ; } + DSDataList ( const DSDataList& inOrg ) throw (DSException) + : mDirRef (inOrg.mDirRef) + { tDataList *dlp = ::dsDataListCopyList (inOrg.mDirRef, &inOrg.mList) ; + if (!dlp) throw_ds_error (eDSAllocationFailed) ; + mList = *dlp ; std::free (dlp) ; } + // The following constructor changes the ownership of the list buffer! + DSDataList ( tDirReference inDirRef, + tDataListPtr inList = 0 ) throw (DSException) + : mDirRef (inDirRef) + { if (inList) { + mList = *inList ; std::memset (inList, 0, sizeof (mList)) ; + } else std::memset (&mList, 0, sizeof (mList)) ; } + ~DSDataList ( void ) throw () + { ::dsDataListDeallocate (mDirRef, &mList) ; } + + // Inline accessors. + UInt32 count ( void ) const throw () + { return ::dsDataListGetNodeCount (&mList) ; } + size_t length ( void ) const throw () + { return (size_t) ::dsGetDataLength (&mList) ; } + // GetPath()'s return value will be freed when it goes out of scope. + // If it is important, COPY IT to another auto_ptr (which will + // properly invalidate the original). + std::auto_ptr path ( const char *inSep = "/" ) const + { return std::auto_ptr (::dsGetPathFromList (mDirRef, + &mList, inSep)) ; } + + // Casting operators. + operator tDataListPtr() throw () + { return &mList ; } + operator const tDataList*() const throw () + { return &mList ; } + DSDataNode* operator[] ( UInt32 inIndex ) const throw (DSException) + { tDataNodePtr dnTemp ; + tDirStatus nError = ::dsDataListGetNodeAlloc ( + mDirRef, &mList, inIndex, &dnTemp) ; + if (nError) throw_ds_error (nError) ; + return new DSDataNode (mDirRef, dnTemp) ; } + + // Setters. + void append ( const char *inString ) throw (DSException) + { tDirStatus nError = ::dsAppendStringToListAlloc ( + mDirRef, &mList, inString) ; + if (nError) throw_ds_error (nError) ; } + +protected: + /**** Instance data. ****/ + tDirReference mDirRef ; + tDataList mList ; +} ; + +} // namespace DirectoryServices + +#endif /* _DSDataList_h */ diff --git a/APIModules/QTSSDSAuthModule/DSWrappers/DSDataNode.h b/APIModules/QTSSDSAuthModule/DSWrappers/DSDataNode.h new file mode 100644 index 0000000..53bd459 --- /dev/null +++ b/APIModules/QTSSDSAuthModule/DSWrappers/DSDataNode.h @@ -0,0 +1,118 @@ +/* + * + * @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: DSDataNode.h + + Contains: Class definition for Open Directory data node + (wraps tDataNodePtr) + + Created By: chris jalbert + + Created: 26 July 2000 + */ + + +#ifndef _DSDataNode_h +#define _DSDataNode_h + + +// Framework Headers +#include +#include + +// Project Headers +#include "CDirService.h" + + +namespace DirectoryServices { + +//----------------------------------------------------------------------------- +// ¥ DSDataNode - simple wrapper for tDataBufferPtr. +// This should be considered a private class, primarily used by DSNode. +// All methods are inlined for performance reasons (sorry). +// tDataNode's are identical to tDataBuffer's in implementation, however, +// nodes are treated as opaque objects with DS accessor functions. As a +// result, DSDataNode is not a subclass of DSBuffer. +//----------------------------------------------------------------------------- + +class DSDataNode +{ +public: + /**** Instance methods. ****/ + // ctor and dtor. + DSDataNode ( tDirReference inDirRef, + size_t inBufSize, + size_t inBufUsed, + const void *inData ) throw (DSException) + : mDirRef (inDirRef), + mNode (::dsDataNodeAllocateBlock (inDirRef, + (UInt32) inBufSize, (UInt32) inBufUsed, + (void *) inData)) + { if (!mNode) throw_ds_error (eDSAllocationFailed) ; } + DSDataNode ( tDirReference inDirRef, + const char *inString ) throw (DSException) + : mDirRef (inDirRef), + mNode (::dsDataNodeAllocateString (inDirRef, inString)) + { if (!mNode) throw_ds_error (eDSAllocationFailed) ; } + // Used by DSDataList + DSDataNode ( tDirReference inDirRef, + tDataNode *inNode ) throw (DSException) + : mDirRef (inDirRef), mNode (inNode) + { if (!mNode) throw_ds_error (eDSAllocationFailed) ; } + // Something of a "copy" constructor + DSDataNode ( tDirReference inDirRef, + const tDataNode *inNode ) throw (DSException) + : mDirRef (inDirRef), + mNode (::dsDataNodeAllocateBlock (inDirRef, + inNode->fBufferSize, inNode->fBufferLength, + (void *) inNode->fBufferData)) + { if (!mNode) throw_ds_error (eDSAllocationFailed) ; } + ~DSDataNode ( void ) throw () + { if (mNode) ::dsDataNodeDeAllocate (mDirRef, mNode) ; } + + // Inline accessors. + size_t capacity ( void ) const throw () + { return (size_t) ::dsDataNodeGetSize (mNode) ; } + size_t size ( void ) const throw () + { return (size_t) ::dsDataNodeGetSize (mNode) ; } + size_t length ( void ) const throw () + { return (size_t) ::dsDataNodeGetLength (mNode) ; } + void resize ( size_t inLength ) throw (DSException) + { tDirStatus nError = ::dsDataNodeSetLength (mNode, inLength) ; + if (nError) throw_ds_error (nError) ; } + + // Casting operators. + operator tDataNodePtr() const throw () + { return mNode ; } + +protected: + /**** Instance data. ****/ + tDirReference mDirRef ; + tDataNodePtr mNode ; +} ; + +} // namespace DirectoryServices + +#endif /* _DSDataNode_h */ diff --git a/APIModules/QTSSDSAuthModule/QTSSDSAuthModule.cpp b/APIModules/QTSSDSAuthModule/QTSSDSAuthModule.cpp new file mode 100644 index 0000000..82717a1 --- /dev/null +++ b/APIModules/QTSSDSAuthModule/QTSSDSAuthModule.cpp @@ -0,0 +1,528 @@ +/* + * + * @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: QTSSODSAuthModule.cpp + + Contains: Implementation of QTSSDSAuthModule, a modified version of the AuthenticateRequestModule + is sample code. + + + +*/ + +#include "QTSSDSAuthModule.h" + + +#include "../../defaultPaths.h" +#include "DSAccessChecker.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" +#include "OSArrayObjectDeleter.h" +#include "SafeStdLib.h" +#include "QTSSMemoryDeleter.h" +#include "QTSS_Private.h" +#include "OS.h" + +//#define SACL 1 +#if OSX_SACL +extern "C" +{ + #include +} +#include +#include +#endif + +// ATTRIBUTES + +// STATIC DATA + +const UInt32 kBuffLen = 512; +#define MODPREFIX_ "modDSAuth_" +#define AUTHDEBUG 0 +#define debug_printf if (AUTHDEBUG) qtss_printf + + +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; +static OSMutex* sAuthMutex = NULL; +static Bool16 sDefaultAuthenticationEnabled = true; +static Bool16 sAuthenticationEnabled = true; +static char* sDefaultAccessFileName = "qtaccess"; +static char* sAccessFileName = NULL; +static Bool16 sAllowGuestDefaultEnabled = true; +static Bool16 sDefaultGuestEnabled = true; + + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSDSAuthModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error Shutdown(); +static QTSS_Error RereadPrefs(); +static QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams); +static QTSS_Error Authorize(QTSS_StandardRTSP_Params* inParams); +static Bool16 AuthenticateRequest(QTSS_StandardRTSP_Params* inParams, const char* pathBuff, const char* movieRootDir, StrPtrLen* ioRealmName, Bool16* foundUserPtr); + + +static int check_sacl(const char *inUser); +#define kSACLNotAuthorized 0 +#define kSACLAuthorized 1 +#define kSACLUnknownUser 2 +#define kSACLAnyUser 3 + +// FUNCTION IMPLEMENTATIONS + + +QTSS_Error QTSSDSAuthModule_Main(void* inPrivateArgs) +{ +printf("QTSSDSAuthModule_Main\n"); +#if OSX_SACL + printf("QTSSDSAuthModule_Main OSX_SACL\n"); +#endif +#if OSX_OD_API + printf("QTSSDSAuthModule_Main OSX_OD_API\n"); +#endif + + return _stublibrary_main(inPrivateArgs, QTSSDSAuthModuleDispatch); +} + + +QTSS_Error QTSSDSAuthModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPAuthenticate_Role: + if (sAuthenticationEnabled) + return AuthenticateRTSPRequest(&inParams->rtspAthnParams); + case QTSS_RTSPAuthorize_Role: + if (sAuthenticationEnabled) + return Authorize(&inParams->rtspRequestParams); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register() +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthenticate_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + sServerPrefs = inParams->inPrefs; + sAuthMutex = new OSMutex(); + + RereadPrefs(); + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + return QTSS_NoErr; +} + +char* GetCheckedFileName() +{ + char *result = NULL; + static char *badChars = "/'\""; + char theBadCharMessage[] = "' '"; + char *theBadChar = NULL; + result = QTSSModuleUtils::GetStringAttribute(sPrefs, MODPREFIX_"dsaccessfilename", sDefaultAccessFileName); + StrPtrLen searchStr(result); + + theBadChar = strpbrk(searchStr.Ptr, badChars); + if ( theBadChar!= NULL) + { + theBadCharMessage[1] = theBadChar[0]; + QTSSModuleUtils::LogErrorStr(qtssWarningVerbosity,MODPREFIX_"found invalid DS access file name in prefs"); + + delete[] result; + result = new char[::strlen(sDefaultAccessFileName) + 2]; + ::strcpy(result, sDefaultAccessFileName); + } + return result; +} + +QTSS_Error RereadPrefs() +{ + OSMutexLocker locker(sAuthMutex); + QTSSModuleUtils::GetAttribute(sPrefs, MODPREFIX_"enabled", qtssAttrDataTypeBool16, + &sAuthenticationEnabled, &sDefaultAuthenticationEnabled, sizeof(sAuthenticationEnabled)); + + QTSSModuleUtils::GetAttribute(sServerPrefs,"enable_allow_guest_default", qtssAttrDataTypeBool16, + &sAllowGuestDefaultEnabled,(void *)&sDefaultGuestEnabled, sizeof(sAllowGuestDefaultEnabled)); + + delete [] sAccessFileName; + sAccessFileName = GetCheckedFileName(); + return QTSS_NoErr; +} + + +Bool16 AuthenticateRequest(QTSS_StandardRTSP_Params* inParams, + const char* pathBuff, + const char* movieRootDir, + StrPtrLen* ioRealmName, + Bool16* foundUserPtr) +{ + if (foundUserPtr) + *foundUserPtr = false; + + if (ioRealmName) //Set Value to Empty for now use whatever is set by access file or the default + { + ioRealmName->Ptr[0] = '\0'; + ioRealmName->Len = 0; + } + QTSS_Error theErr = QTSS_NoErr; + + char passwordBuff[kBuffLen]; + StrPtrLen passwordStr(passwordBuff, kBuffLen -1); + + char nameBuff[kBuffLen]; + StrPtrLen nameStr(nameBuff, kBuffLen -1); + + theErr = QTSS_GetValue (inParams->inRTSPRequest,qtssRTSPReqUserName,0, (void *) nameStr.Ptr, &nameStr.Len); + if ( (QTSS_NoErr != theErr) || (nameStr.Len >= kBuffLen) ) + { + debug_printf("QTSSDSAuthModule:AuthenticateRequest() Username Error - %"_S32BITARG_"\n", theErr); + return false; + } + theErr = QTSS_GetValue (inParams->inRTSPRequest,qtssRTSPReqUserPassword,0, (void *) passwordStr.Ptr, &passwordStr.Len); + if ( (QTSS_NoErr != theErr) || (passwordStr.Len >= kBuffLen) ) + { + debug_printf("QTSSDSAuthModule:AuthenticateRequest() Password Error - %"_S32BITARG_"\n", theErr); + return false; + } + nameBuff[nameStr.Len] = '\0'; + passwordBuff[passwordStr.Len] = '\0'; + + // + // Use the name and password to check access + DSAccessChecker accessChecker; + if ( !accessChecker.CheckPassword( nameBuff, passwordBuff) ) + { + return false; + } + + if (foundUserPtr) + *foundUserPtr = true; + + + return true; +} + + + +QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams) +{ + OSMutexLocker locker(sAuthMutex); + + QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; + QTSS_AuthScheme authScheme = qtssAuthNone; + + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest start\n"); + + if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) ) + { + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest inParams NULL\n"); + return QTSS_RequestFailed; + } + + + // Get the user profile object from the request object + QTSS_UserProfileObject theUserProfile = NULL; + UInt32 len = sizeof(QTSS_UserProfileObject); + QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqUserProfile, 0, (void*)&theUserProfile, &len); + Assert(len == sizeof(QTSS_UserProfileObject)); + if (theErr != QTSS_NoErr) + { + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - username error is %"_S32BITARG_"\n", theErr); + return theErr; + } + char* nameBuff = NULL; + theErr = QTSS_GetValueAsString(theUserProfile, qtssUserName, 0, &nameBuff); + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - username is %s\n", nameBuff); + OSCharArrayDeleter usernameBufDeleter(nameBuff); + if (theErr != QTSS_NoErr) + { + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - theUserProfile nameBuff error is %"_S32BITARG_"\n", theErr); + } + + + len = sizeof(authScheme); + theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&authScheme, &len); + + if (theErr != QTSS_NoErr) + return theErr; + + DSAccessChecker accessChecker; + Bool16 allowed = true; + Bool16 foundUser = true; + Bool16 authHandled = true; + + if ( authScheme == qtssAuthDigest) + { + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - authScheme = qtssAuthDigest\n"); + + char* challengeBuff = NULL; + (void) QTSS_GetValueAsString(theRTSPRequest, qtssRTSPReqDigestChallenge, 0, &challengeBuff); + OSCharArrayDeleter challengeDeleter(challengeBuff); + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - Server Challenge =%s\n",challengeBuff); + + char* responseBuff = NULL; + (void) QTSS_GetValueAsString(theRTSPRequest, qtssRTSPReqDigestResponse, 0, &responseBuff); + OSCharArrayDeleter responseDeleter(responseBuff); + + char* methodBuff = NULL; + (void) QTSS_GetValueAsString(theRTSPRequest, qtssRTSPReqMethodStr, 0, &methodBuff); + OSCharArrayDeleter methodDeleter(methodBuff); + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - Server Method =%s\n",methodBuff); + + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - username is %s challenge=%s response=%s method=%s\n", nameBuff, challengeBuff, responseBuff, methodBuff); + if ( false == accessChecker.CheckDigest(nameBuff, challengeBuff, responseBuff, methodBuff) ) + { debug_printf("QTSSDSAuthModule CheckDigest returned false\n"); + } + else + { debug_printf("QTSSDSAuthModule CheckDigest returned true\n"); + (void) QTSSModuleUtils::AuthorizeRequest(theRTSPRequest,&allowed,&foundUser,&authHandled); + } + + } + if ( authScheme == qtssAuthBasic) + { + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - authScheme = qtssAuthBasic\n"); + + + char passwordBuff[kBuffLen]; + StrPtrLen passwordStr(passwordBuff, kBuffLen -1); + + char nameBuff[kBuffLen]; + StrPtrLen nameStr(nameBuff, kBuffLen -1); + + theErr = QTSS_GetValue (inParams->inRTSPRequest,qtssRTSPReqUserName,0, (void *) nameStr.Ptr, &nameStr.Len); + if ( (QTSS_NoErr != theErr) || (nameStr.Len >= kBuffLen) ) + { + debug_printf("QTSSDSAuthModule:AuthenticateRequest() Username Error - %"_S32BITARG_"\n", theErr); + return false; + } + theErr = QTSS_GetValue (inParams->inRTSPRequest,qtssRTSPReqUserPassword,0, (void *) passwordStr.Ptr, &passwordStr.Len); + if ( (QTSS_NoErr != theErr) || (passwordStr.Len >= kBuffLen) ) + { + debug_printf("QTSSDSAuthModule:AuthenticateRequest() Password Error - %"_S32BITARG_"\n", theErr); + } + nameBuff[nameStr.Len] = '\0'; + passwordBuff[passwordStr.Len] = '\0'; + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - username is %s\n", nameBuff); + debug_printf("QTSSDSAuthModule:AuthenticateRTSPRequest - password is %s\n", passwordBuff); + if ( !accessChecker.CheckPassword(nameBuff, passwordBuff) ) + { debug_printf("QTSSDSAuthModule CheckPassword returned false\n"); + } + else + { + debug_printf("QTSSDSAuthModule CheckPassword returned true\n"); + (void) QTSSModuleUtils::AuthorizeRequest(theRTSPRequest,&allowed,&foundUser,&authHandled); + } + + } + + return QTSS_NoErr; +} + + + +int check_sacl(const char *inUser) +{ + +#if OSX_SACL + int mbrErr = ENOENT; + int isMember = 0; + uuid_t user_uuid; + + uuid_t uu; + mbrErr = mbr_uid_to_uuid(geteuid(), uu); + if (0 == mbrErr) + { + mbrErr = mbr_check_service_membership(uu, "qtss", &isMember); + if (ENOENT == mbrErr) //no acl exists so allow any user. + return kSACLAnyUser; + } + + if( (mbrErr = mbr_user_name_to_uuid(inUser, user_uuid)) != 0) + { + return kSACLUnknownUser; + } + + if((mbrErr = mbr_check_service_membership(user_uuid, "qtss", &isMember)) != 0) + { + if(mbrErr == ENOENT){ // no ACL exists + return kSACLAuthorized; + } else { + return kSACLNotAuthorized; + } + } + + if(isMember == kSACLAuthorized) + { + return kSACLAuthorized; + } + + + return kSACLNotAuthorized; +#else + return kSACLAuthorized; +#endif +} + + + +QTSS_Error Authorize(QTSS_StandardRTSP_Params* inParams) +{ + + OSMutexLocker locker(sAuthMutex); + + + QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; + + if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) ) + { + debug_printf("QTSSDSAuthModule - Authorize inParams: Error"); + return QTSS_RequestFailed; + } + + //get the local file path + char* pathBuffStr = NULL; + QTSS_Error theErr = QTSS_GetValueAsString(theRTSPRequest, qtssRTSPReqLocalPath, 0, &pathBuffStr); + QTSSCharArrayDeleter pathBuffDeleter(pathBuffStr); + if (theErr != QTSS_NoErr) + { + debug_printf("QTSSDSAuthModule - Authorize [QTSS_GetValueAsString]: Error %"_S32BITARG_"", theErr); + return QTSS_RequestFailed; + } + //get the root movie directory + char* movieRootDirStr = NULL; + theErr = QTSS_GetValueAsString(theRTSPRequest,qtssRTSPReqRootDir, 0, &movieRootDirStr); + OSCharArrayDeleter movieRootDeleter(movieRootDirStr); + if (theErr != QTSS_NoErr) + { + debug_printf("QTSSDSAuthModule - Authorize[QTSS_GetValueAsString]: Error %"_S32BITARG_"", theErr); + return false; + } + //check if this user is allowed to see this movie + + DSAccessFile accessFile; + Bool16 allowNoAccessFiles = sAllowGuestDefaultEnabled; //no access files allowed means allowing guest access (unknown users) + Bool16 allowAnyUser = false; + QTSS_ActionFlags noAction = ~qtssActionFlagsRead; //only handle read + QTSS_ActionFlags authorizeAction = QTSSModuleUtils::GetRequestActions(theRTSPRequest); + Bool16 authorized =false; + Bool16 saclUser = false; + + char *name = NULL; + (void) QTSS_GetValueAsString (theRTSPRequest,qtssRTSPReqUserName,0, &name); + OSCharArrayDeleter nameDeleter(name); + if (sAllowGuestDefaultEnabled) // if guest access is on, sacls are ignored. + { + authorized = true; + } + else + { int result = check_sacl(name); + + switch (result) + { + case kSACLAuthorized: authorized = true; + break; + + case kSACLUnknownUser: authorized = false; //set this to true to allow file based and other non-directory service users access, when SACLs are enabled in the system for QTSS. + break; + + case kSACLNotAuthorized: authorized = false; + break; + + case kSACLAnyUser: authorized = true; + break; + + default: authorized = false; + } + + + debug_printf("QTSSDSAuthModule:Authorize sacl_check result=%d for %s authorized = %d\n",result, name, authorized); + if (false == authorized) + saclUser = true; + } + + Bool16 foundUser = false; + Bool16 passwordOK = false; //::AuthenticateRequest(inParams, pathBuffStr, movieRootDirStr, &sRealmNameStr, &foundUser); + if (authorized) //have to be authorized by sacls or guest first before qtaccess file checks can allow or disallow. + { + theErr = accessFile.AuthorizeRequest(inParams,allowNoAccessFiles, noAction, authorizeAction,&authorized, &allowAnyUser); + debug_printf("QTSSDSAuthModule:Authorize AuthorizeRequest() returned authorized=%d allowAnyUser=%d\n", authorized, allowAnyUser); + + } + + debug_printf("QTSSDSAuthModule:Authorize AuthenticateRequest() returned passwordOK=%d foundUser=%d authorized=%d allowAnyUser=%d\n", passwordOK ,foundUser, authorized,allowAnyUser); + + Bool16 allowRequest = authorized; + Bool16 authHandled = true; + + if(!(authorizeAction & qtssActionFlagsRead)) //not for us + { + debug_printf("QTSSDSAuthModule:Authorize(qtssActionFlagsRead) not handled do nothing.\n"); + } + else if (allowRequest) + { + debug_printf("QTSSDSAuthModule:Authorize() succeeded.\n"); + theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &foundUser, &authHandled); + debug_printf("QTSSDSAuthModule:Authorize allowRequest=%d founduser=%d authHandled=%d\n", allowRequest, foundUser, authHandled); + } + else //request denied + { + debug_printf("QTSSDSAuthModule:Authorize() failed.\n"); + foundUser = saclUser; + authHandled = true; + theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &foundUser, &authHandled); + debug_printf("QTSSDSAuthModule:Authorize allowRequest=%d founduser=%d authHandled=%d saclUser=%d\n", allowRequest, foundUser, authHandled,saclUser); + } + + + return theErr; +} diff --git a/APIModules/QTSSDSAuthModule/QTSSDSAuthModule.h b/APIModules/QTSSDSAuthModule/QTSSDSAuthModule.h new file mode 100644 index 0000000..1e24c7a --- /dev/null +++ b/APIModules/QTSSDSAuthModule/QTSSDSAuthModule.h @@ -0,0 +1,46 @@ +/* + * + * @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: QTSSODAuthModule.h + + Contains: This is a modified version of the QTSSAccessModule also released with + QTSS 2.0. It has been modified to shrink the linespacing so that + the code can fit on slides. Also, this module issues redirects to + an error movie. + +*/ + +#ifndef _QTSSDSAUTHMODULE_H__ +#define _QTSSDSAUTHMODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + QTSS_Error QTSSDSAuthModule_Main(void* inPrivateArgs); +} + +#endif + diff --git a/APIModules/QTSSDemoAuthorizationModule.bproj/QTSSDemoAuthorizationModule.cpp b/APIModules/QTSSDemoAuthorizationModule.bproj/QTSSDemoAuthorizationModule.cpp new file mode 100644 index 0000000..07c933f --- /dev/null +++ b/APIModules/QTSSDemoAuthorizationModule.bproj/QTSSDemoAuthorizationModule.cpp @@ -0,0 +1,207 @@ +/* + * + * @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: QTSSDemoAuthorizationModule.cpp + + Contains: Implementation of QTSSDemoAuthorizationModule, which demonstrates an authorization method using + client IP addresses for access control. Connections from IP addresses not included in the "IPAccessList" + preference will be immediately sent an RTSP "401 Unauthorized" error. + + Example of preferences allowing only connections from loopback (or any address in the 127.0.0.X network): + + true + 127.0.0.* + +*/ + +#include "QTSSDemoAuthorizationModule.h" +#include "../../defaultPaths.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" +#include "OSArrayObjectDeleter.h" +#include "SafeStdLib.h" +#include "QTSSMemoryDeleter.h" +#include "StringParser.h" +#include "OSMemory.h" + +// STATIC DATA +static QTSS_ServerObject sServer = NULL; +static QTSS_ModuleObject sModule = NULL; +static QTSS_ModulePrefsObject sModulePrefs = NULL; + +const UInt32 kBuffLen = 512; + +// Module description and version +static char* sDescription = "Demonstrates an authorization method using client IP addresses for access control"; +static UInt32 sVersion = 0x00010000; + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSDemoAuthorizationModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error Shutdown(); +static QTSS_Error RereadPrefs(); +static QTSS_Error Authenticate(); +static QTSS_Error Authorize(QTSS_StandardRTSP_Params* inParams); +static Bool16 AcceptSession(QTSS_RTSPSessionObject inRTSPSession); + +// Module preferences and their defaults +static Bool16 sEnabled = false; +static Bool16 kDefaultEnabled = false; +static char* sIPAccessList = NULL; +static QTSS_AttributeID sIPAccessListID = qtssIllegalAttrID; + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSDemoAuthorizationModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSDemoAuthorizationModuleDispatch); +} + + +QTSS_Error QTSSDemoAuthorizationModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPAuthenticate_Role: + return Authenticate(); + case QTSS_RTSPAuthorize_Role: + return Authorize(&inParams->rtspRequestParams); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthenticate_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); + + // Tell the server our name + static char* sModuleName = "QTSSDemoAuthorizationModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + // Get the server object + sServer = inParams->inServer; + + // Get our prefs object + sModule = inParams->inModule; + sModulePrefs = QTSSModuleUtils::GetModulePrefsObject(sModule); + + // Set our version and description + (void)QTSS_SetValue(sModule, qtssModDesc, 0, sDescription, ::strlen(sDescription)); + (void)QTSS_SetValue(sModule, qtssModVersion, 0, &sVersion, sizeof(sVersion)); + + RereadPrefs(); + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + return QTSS_NoErr; +} + +QTSS_Error RereadPrefs() +{ + QTSSModuleUtils::GetAttribute(sModulePrefs, "enabled", qtssAttrDataTypeBool16, + &sEnabled, &kDefaultEnabled, sizeof(sEnabled)); + + delete [] sIPAccessList; + sIPAccessList = QTSSModuleUtils::GetStringAttribute(sModulePrefs, "IPAccessList", "127.0.0.*"); + sIPAccessListID = QTSSModuleUtils::GetAttrID(sModulePrefs, "IPAccessList"); + + return QTSS_NoErr; +} + +// This is not necessary, but could be used to perform Authentication Role actions +QTSS_Error Authenticate() +{ + return QTSS_NoErr; +} + +QTSS_Error Authorize(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_Error theErr = QTSS_NoErr; + QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; + QTSS_RTSPSessionObject theRTSPSession = inParams->inRTSPSession; + QTSS_ActionFlags noAction = ~qtssActionFlagsRead; //only handle read + + + QTSS_ActionFlags authorizeAction = QTSSModuleUtils::GetRequestActions(theRTSPRequest); + Bool16 allowRequest = false; + + if ((authorizeAction & noAction) != 0) + return QTSS_NoErr; + + allowRequest = AcceptSession(theRTSPSession); + + if( (theErr != QTSS_NoErr) || (allowRequest == false) ) //handle it + { + //Access denied + Bool16 allowed = false; + (void) QTSSModuleUtils::SendErrorResponse(theRTSPRequest, qtssClientUnAuthorized, 0); + (void) QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowed, &allowed, &allowed); + } else { + //Access granted + Bool16 allowed = true; + (void) QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowed, &allowed, &allowed); + } + + return QTSS_NoErr; +} + +Bool16 AcceptSession(QTSS_RTSPSessionObject inRTSPSession) +{ + char remoteAddress[20] = {0}; + StrPtrLen theClientIPAddressStr(remoteAddress,sizeof(remoteAddress)); + QTSS_Error err = QTSS_GetValue(inRTSPSession, qtssRTSPSesRemoteAddrStr, 0, (void*)theClientIPAddressStr.Ptr, &theClientIPAddressStr.Len); + if (err != QTSS_NoErr) return false; + + if (QTSSModuleUtils::AddressInList(sModulePrefs, sIPAccessListID, &theClientIPAddressStr)) + return true; + return false; +} \ No newline at end of file diff --git a/APIModules/QTSSDemoAuthorizationModule.bproj/QTSSDemoAuthorizationModule.h b/APIModules/QTSSDemoAuthorizationModule.bproj/QTSSDemoAuthorizationModule.h new file mode 100644 index 0000000..4238a7f --- /dev/null +++ b/APIModules/QTSSDemoAuthorizationModule.bproj/QTSSDemoAuthorizationModule.h @@ -0,0 +1,47 @@ +/* + * + * @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: QTSSDemoAuthorizationModule.h + + Contains: Header file for QTSSDemoAuthorizationModule. + + + +*/ + + + +#ifndef __QTSS_DEMOAUTHORIZATION_MODULE_H__ +#define __QTSS_DEMOAUTHORIZATION_MODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + QTSS_Error QTSSDemoAuthorizationModule_Main(void* inPrivateArgs); +} + +#endif // __QTSS_DEMOAUTHORIZATION_MODULE_H__ diff --git a/APIModules/QTSSDemoRedirectModule.bproj/QTSSDemoRedirectModule.cpp b/APIModules/QTSSDemoRedirectModule.bproj/QTSSDemoRedirectModule.cpp new file mode 100644 index 0000000..590f987 --- /dev/null +++ b/APIModules/QTSSDemoRedirectModule.bproj/QTSSDemoRedirectModule.cpp @@ -0,0 +1,227 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 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: QTSSDemoRedirectModule.cpp + + Contains: Implementation of QTSSDemoRedirectModule +*/ + +#include "QTSSDemoRedirectModule.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" +#include "SafeStdLib.h" +#include "QTSSMemoryDeleter.h" + +// STATIC DATA +static QTSS_ServerObject sServer = NULL; +static QTSS_ModuleObject sModule = NULL; +static QTSS_ModulePrefsObject sModulePrefs = NULL; +static StrPtrLen sRedirect("RTSP/1.0 302 Found\r\nServer: QTSS/6.0\r\nCSeq: 1\r\nConnection: Close"); +static StrPtrLen sLocation("\r\nLocation: "); +static StrPtrLen sRedirectEnd("\r\n\r\n"); +static const UInt32 kBuffSize = 512; + +// Module description and version +static char* sDescription = "Demonstrates a way to redirect file requests based on client bandwidth headers"; +static UInt32 sVersion = 0x00010000; + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSDemoRedirectModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error Shutdown(); +static QTSS_Error RereadPrefs(); +static QTSS_Error Authenticate(QTSS_StandardRTSP_Params* inParams); + +// Module preferences and their defaults +static Bool16 sEnabled = false; +static Bool16 kDefaultEnabled = true; +static char* sRedirectUrl = NULL; +static char* sRedirectUrlDefault = "sample"; + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSDemoRedirectModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSDemoRedirectModuleDispatch); +} + + +QTSS_Error QTSSDemoRedirectModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPAuthenticate_Role: + return Authenticate(&inParams->rtspRequestParams); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthenticate_Role); + + // Tell the server our name + static char* sModuleName = "QTSSDemoRedirectModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + // Get the server object + sServer = inParams->inServer; + + // Get our prefs object + sModule = inParams->inModule; + sModulePrefs = QTSSModuleUtils::GetModulePrefsObject(sModule); + + // Set our version and description + (void)QTSS_SetValue(sModule, qtssModDesc, 0, sDescription, ::strlen(sDescription)); + (void)QTSS_SetValue(sModule, qtssModVersion, 0, &sVersion, sizeof(sVersion)); + + RereadPrefs(); + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + return QTSS_NoErr; +} + +QTSS_Error RereadPrefs() +{ + QTSSModuleUtils::GetAttribute(sModulePrefs, "enabled", qtssAttrDataTypeBool16, + &sEnabled, &kDefaultEnabled, sizeof(sEnabled)); + + sRedirectUrl = QTSSModuleUtils::GetStringAttribute(sModulePrefs, "url_to_redirect", sRedirectUrlDefault); + + return QTSS_NoErr; +} + + +QTSS_Error Authenticate(QTSS_StandardRTSP_Params* inParams) +{ + + if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) ) { + return QTSS_RequestFailed; + } + + QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; + QTSS_Error theErr = QTSS_NoErr; + + char *movieUrlTrunc = NULL; + theErr = QTSS_GetValueAsString(theRTSPRequest,qtssRTSPReqTruncAbsoluteURL, 0, &movieUrlTrunc); + QTSSCharArrayDeleter movieUrlTruncDeleter(movieUrlTrunc); + if (theErr != QTSS_NoErr) + return QTSS_RequestFailed; + + char *movieUrlFilename = NULL; + theErr = QTSS_GetValueAsString(theRTSPRequest,qtssRTSPReqFileName, 0, &movieUrlFilename); + QTSSCharArrayDeleter movieUrlFilenameDeleter(movieUrlFilename); + if (theErr != QTSS_NoErr) + return QTSS_RequestFailed; + + // Return unless the requested filename matches the redirect URL + if (::strncmp(movieUrlFilename, sRedirectUrl, (::strlen(movieUrlFilename) <= ::strlen(sRedirectUrl)) + ? ::strlen(sRedirectUrl) : ::strlen(movieUrlFilename))) + return QTSS_NoErr; + + // Get the client's bandwidth header + UInt32 bandwidthBits = 0; + UInt32 len = sizeof(bandwidthBits); + (void)QTSS_GetValue(theRTSPRequest, qtssRTSPReqBandwidthBits, 0, (void *)&bandwidthBits, &len); + + // Prepare a filename extension to use for redirection. + // These numbers match the "Streaming Speed" options in the QuickTime Player 7 settings. + // The extension will be appended to the requested URL. + char theNewFilename[kBuffSize]; + ::memset(theNewFilename, 0, kBuffSize); + + if (bandwidthBits <= 0) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_50kbit.3gp", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 28000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_50kbit.3gp", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 56000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_50kbit.3gp", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 112000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_100kbit.mov", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 256000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_100kbit.mov", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 384000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_300kbit.mov", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 512000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_300kbit.mov", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 768000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_300kbit.mov", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 1000000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_h264_1mbit.mp4", movieUrlTrunc, movieUrlFilename); + } else if (bandwidthBits <= 1500000) { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_h264_1mbit.mp4", movieUrlTrunc, movieUrlFilename); + } else { + qtss_snprintf(theNewFilename, sizeof(theNewFilename), "%s/%s_h264_1mbit.mp4", movieUrlTrunc, movieUrlFilename); + } + + // In order to send the redirect, we need to get a QTSS_StreamRef + // so we can send data to the client. Get the QTSS_StreamRef out of the request. + QTSS_StreamRef* theStreamRef = NULL; + UInt32 strRefLen = 0; + + theErr = QTSS_GetValuePtr(theRTSPRequest, qtssRTSPReqStreamRef, 0, + (void**)&theStreamRef, &strRefLen); + if (( QTSS_NoErr != theErr ) || ( sizeof(QTSS_StreamRef) != strRefLen) ) { + return QTSS_RequestFailed; + } + + // Send the redirect + UInt32 theLenWritten = 0; + + (void)QTSS_Write(*theStreamRef, sRedirect.Ptr, sRedirect.Len, &theLenWritten, 0); + (void)QTSS_Write(*theStreamRef, sLocation.Ptr, sLocation.Len, &theLenWritten, 0); + (void)QTSS_Write(*theStreamRef, theNewFilename, ::strlen(theNewFilename), &theLenWritten, 0); + (void)QTSS_Write(*theStreamRef, sRedirectEnd.Ptr, sRedirectEnd.Len, &theLenWritten, 0); + (void)QTSS_Flush(*theStreamRef); + + return QTSS_NoErr; +} \ No newline at end of file diff --git a/APIModules/QTSSDemoRedirectModule.bproj/QTSSDemoRedirectModule.h b/APIModules/QTSSDemoRedirectModule.bproj/QTSSDemoRedirectModule.h new file mode 100644 index 0000000..ff840f1 --- /dev/null +++ b/APIModules/QTSSDemoRedirectModule.bproj/QTSSDemoRedirectModule.h @@ -0,0 +1,47 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 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: QTSSDemoRedirectModule.h + + Contains: Header file for QTSSDemoRedirectModule. + + + +*/ + + + +#ifndef __QTSS_DEMOREDIRECT_MODULE_H__ +#define __QTSS_DEMOREDIRECT_MODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + QTSS_Error QTSSDemoRedirectModule_Main(void* inPrivateArgs); +} + +#endif // __QTSS_DEMOREDIRECT_MODULE_H__ diff --git a/APIModules/QTSSDemoSMILModule.bproj/QTSSDemoSMILModule.cpp b/APIModules/QTSSDemoSMILModule.bproj/QTSSDemoSMILModule.cpp new file mode 100644 index 0000000..f6f492d --- /dev/null +++ b/APIModules/QTSSDemoSMILModule.bproj/QTSSDemoSMILModule.cpp @@ -0,0 +1,417 @@ +/* + * + * @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: QTSSDemoSMILModule.cpp + + Contains: + + + + */ + +/* + + + + + + + + + + + + + + +*/ + +#include +#include +#include +#include "SafeStdLib.h" +#include +#include +#include + +#include "QTSSDemoSMILModule.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "OSFileSource.h" +#include "OSMemory.h" +#include "OSHeaders.h" +#include "ev.h" +#include "QTFile.h" +#include "QTTrack.h" +#include "QTHintTrack.h" +#include "QTSSModuleUtils.h" +#include "OSMutex.h" + +#define HTTP_FILE_ASYNC 1 +#define HTTP_FILE_DEBUGGING 1 + +// STATIC DATA +// For processing the requests +static char* sResponseHeader = "HTTP/1.0 200 OK\r\nServer: QTSS/2.0\r\nContent-Type: video/quicktime\r\n\r\n"; +static QTSS_PrefsObject sPrefs = NULL; + +static StrPtrLen sPathSeparator("/"); +static StrPtrLen sRTSPUrlPrefix("rtsp://"); +static StrPtrLen sRefMovieBufPrefix("rtsptext\r"); +static StrPtrLen sRespHeaderPrefix("HTTP/1.0 200 OK\r\nServer: QTSS/2.0\r\nConnection: Close"); +static StrPtrLen sContentLengthHeaderTag("\r\nContent-Length: "); +static StrPtrLen sContentTypeHeaderTag("\r\nContent-Type: "); +static StrPtrLen sConnectionKeepAliveTag("Keep-Alive"); +static StrPtrLen sQuickTimeMimeType("video/quicktime"); +static StrPtrLen sUnknownMimeType("application/unknown"); +static StrPtrLen sGifMimeType("image/gif"); +static StrPtrLen sSdpMimeType("application/sdp"); +static StrPtrLen sSmilMimeType("application/smil"); +static StrPtrLen sQTSuffix("qt"); +static StrPtrLen sMovSuffix("mov"); +static StrPtrLen sGifSuffix("gif"); +static StrPtrLen sSdpSuffix("sdp"); +static StrPtrLen sSmiSuffix("smi"); +static StrPtrLen sSmilSuffix("smil"); + + +// FUNCTIONS +static QTSS_Error QTSSDemoSMILDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error FilterRequest(QTSS_Filter_Params* inParams); +// For processing the requests + + +//Demo stuff +struct HitCount { + char url[512]; + int hitcount; +}; + +static HitCount gHitcountArray[100] = {}; + +//protos +void CountHit(char* url); +void GenerateHotHitSMIL(char* buffer); +int HitCountCompare(const void * hitCount1, const void *hitCount2); +QTSS_Error CountRequest( QTSS_RTSPRequestObject inRTSPRequest, QTSS_ClientSessionObject inClientSession, + QTSS_RTSPSessionObject inRTSPSession, QTSS_CliSesClosingReason *inCloseReasonPtr ); +void InitHitCountFromFile(); + + +//funcs + +void InitHitCountFromFile() +{ + FILE* hitfile = fopen("hitcount.txt", "r"); + + if (hitfile == NULL) return; + + int c = 0; + int i = 0; + while ( (c = fscanf(hitfile, "%s %d", gHitcountArray[i].url, &gHitcountArray[i].hitcount)) == 2 ) + { + qtss_printf("%s %d\n", gHitcountArray[i].url, gHitcountArray[i].hitcount); + i++; + } + + fclose(hitfile); +} + +void WriteHitCountToFile() +{ + FILE* hitfile = fopen("hitcount.txt", "w"); + + int i=0; + for (i=0;gHitcountArray[i].url[0] && i<512; i++) + { + qtss_fprintf(hitfile, "%s %d\n", gHitcountArray[i].url, gHitcountArray[i].hitcount); + } + + fclose(hitfile); +} + +void CountHit(char* url) +{ +#if HTTP_FILE_DEBUGGING + qtss_printf("Counting Hit for \"%s\"\n", url); +#endif + + if ( url == NULL ) + return; + + int i=0; + for (i=0;gHitcountArray[i].url[0] && i<512; i++) + { + if ( strcmp(url, gHitcountArray[i].url ) == 0) + { + gHitcountArray[i].hitcount++; + qsort(&gHitcountArray, i+1, sizeof(HitCount), &HitCountCompare); + return; + } + } + + ::strcpy(gHitcountArray[i].url, url); + gHitcountArray[i].hitcount++; + + qsort(&gHitcountArray, i+1, sizeof(HitCount), &HitCountCompare); +} + +int HitCountCompare(const void * hitCount1, const void *hitCount2) +{ + return ((const HitCount*)hitCount2)->hitcount - ((const HitCount*)hitCount1)->hitcount; +} + + +QTSS_Error ClientSessionClosing(QTSS_ClientSessionClosing_Params* inParams) +{ +#if HTTP_FILE_DEBUGGING + qtss_printf("ClientSessionClosing\n"); +#endif + return CountRequest(NULL, inParams->inClientSession, NULL, &inParams->inReason); +} + +QTSS_Error CountRequest( QTSS_RTSPRequestObject inRTSPRequest, QTSS_ClientSessionObject inClientSession, + QTSS_RTSPSessionObject inRTSPSession, QTSS_CliSesClosingReason *inCloseReasonPtr ) +{ + + char urlBuf[256] = { 0 }; + StrPtrLen url(urlBuf, 256 -1); + (void)QTSS_GetValue(inClientSession, qtssCliSesFullURL, 0, url.Ptr, &url.Len); + + CountHit( url.Ptr ); + WriteHitCountToFile(); + + return QTSS_NoErr; +} + +void GenerateHotHitSMIL(char* buffer) +{ + char smilTemplate[8192] = {}; + char* templateCursor = smilTemplate; + char* bufferCursor = buffer; + + FILE* smilTemplateFile = fopen("template.smil", "r"); + if (smilTemplateFile != NULL) + { + int len = fread(smilTemplate, sizeof(char), sizeof(smilTemplate), smilTemplateFile); + + smilTemplate[len] = '\0'; + + fclose(smilTemplateFile); + } + else + { + strcpy(smilTemplate, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + } + + int hitNum = 0; + char* p = NULL; + while ( (p = strstr(templateCursor, "%s")) != 0 ) + { + char saveCh = p[2]; + p[2] = '\0'; + int len = qtss_sprintf(bufferCursor, templateCursor, gHitcountArray[hitNum++].url); + p[2] = saveCh; + bufferCursor += len; + templateCursor = &p[2]; + } + + strcat(bufferCursor, templateCursor); + +#if HTTP_FILE_DEBUGGING + qtss_printf("smil generated:\n%s\n", buffer); +#endif + +} + + + + + + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSDemoSMILModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSDemoSMILDispatch); +} + + +QTSS_Error QTSSDemoSMILDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ +#if HTTP_FILE_DEBUGGING + qtss_printf("QTSSDemoSMILDispatch\n"); +#endif + + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RTSPFilter_Role: + return FilterRequest(&inParams->rtspFilterParams); + case QTSS_ClientSessionClosing_Role: + return ClientSessionClosing(&inParams->clientSessionClosingParams); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ +#if HTTP_FILE_DEBUGGING + qtss_printf("Register\n"); +#endif + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RTSPFilter_Role); + (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); + + // Tell the server our name! + static char* sModuleName = "QTSSDemoSMILModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ +#if HTTP_FILE_DEBUGGING + qtss_printf("Initialize\n"); +#endif + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + // Get prefs object + sPrefs = inParams->inPrefs; + + InitHitCountFromFile(); + + return QTSS_NoErr; +} + + +QTSS_Error FilterRequest(QTSS_Filter_Params* inParams) +{ +#if HTTP_FILE_DEBUGGING + qtss_printf("FilterRequest\n"); +#endif + + static Bool16 sFalse = false; + + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + + // Initial state. + StrPtrLen theFullRequest; + StrPtrLen reqMethod; + StrPtrLen reqStr; + StrPtrLen httpVersion; + + (void)QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len); + StringParser fullRequest(&theFullRequest); + + // Parsing the HTTP request + fullRequest.ConsumeWord(&reqMethod); + if ( !(reqMethod.Equal(StrPtrLen("GET")) || reqMethod.Equal(StrPtrLen("HEAD"))) ) + // It's not a "Get" or a "Head" request + return QTSS_NoErr; + + fullRequest.ConsumeWhitespace(); + if ( !fullRequest.Expect('/') ) + // Improperly formed request + return QTSS_NoErr; + + fullRequest.ConsumeUntil(&reqStr, StringParser::sEOLWhitespaceMask); + if( reqStr.Len == 0 ) + //if a file or directory name is not given, return + return QTSS_NoErr; + + if ( !reqStr.Equal(StrPtrLen("Popular.smil")) ) + return QTSS_NoErr; + + // If it's a "Head" request send the Head response header back and just return + if ( reqMethod.Equal(StrPtrLen("HEAD")) ) + { + QTSS_Write(theRequest, sResponseHeader, ::strlen(sResponseHeader), NULL, 0); + return QTSS_NoErr; + } + + // Create a buffer to store data. + char theFileBuffer[8192]; + char contentLength[256]; + + // Before sending any response, set keep alive to off for this connection + // Regardless of what the client sends, the server always closes the connection after sending the file + (void)QTSS_SetValue(theRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse)); + +#if HTTP_FILE_DEBUGGING + qtss_printf("Creating a smil file\n"); +#endif + // Create a ref movie buffer for the single file. It is of the form: + // rtsptext\r + // rtsp://servername/filepath + char smilFileBuf[8192] = {0}; + + GenerateHotHitSMIL(smilFileBuf); + + qtss_sprintf(contentLength, "%"_U32BITARG_"", (UInt32) ::strlen(smilFileBuf)); + // Allocate memory for theFileBuffer + // Write the HTTP header prefix into the buffer + ::strcpy(theFileBuffer, sRespHeaderPrefix.Ptr); + ::strcat(theFileBuffer, sContentLengthHeaderTag.Ptr); + // Write the remaining part of the HTTP header into the file buffer + ::strcat(theFileBuffer, contentLength); + ::strcat(theFileBuffer, sContentTypeHeaderTag.Ptr); + ::strcat(theFileBuffer, sSmilMimeType.Ptr); + ::strcat(theFileBuffer, "\r\n\r\n"); + + // Write the smil file created above to the file buffer + ::strcat(theFileBuffer, smilFileBuf); + + // Write the contents of the file buffer to the request stream and return + QTSS_Write(theRequest, theFileBuffer, strlen(theFileBuffer), NULL, 0); + +#if HTTP_FILE_DEBUGGING + qtss_printf("Wrote the smil file to the request stream. Successful!\n"); +#endif + + return QTSS_NoErr; +} \ No newline at end of file diff --git a/APIModules/QTSSDemoSMILModule.bproj/QTSSDemoSMILModule.h b/APIModules/QTSSDemoSMILModule.bproj/QTSSDemoSMILModule.h new file mode 100644 index 0000000..76c5bbe --- /dev/null +++ b/APIModules/QTSSDemoSMILModule.bproj/QTSSDemoSMILModule.h @@ -0,0 +1,41 @@ +/* + * + * @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: QTSSDemoSMILModule.h + + Contains: +*/ + +#ifndef __QTSSDEMOSMILMODULE_H__ +#define __QTSSDEMOSMILMODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + QTSS_Error QTSSDemoSMILModule_Main(void* inPrivateArgs); +} + +#endif // __QTSSDEMOSMILMODULE_H__ diff --git a/APIModules/QTSSFileModule/QTSSFileModule.cpp b/APIModules/QTSSFileModule/QTSSFileModule.cpp new file mode 100644 index 0000000..e327134 --- /dev/null +++ b/APIModules/QTSSFileModule/QTSSFileModule.cpp @@ -0,0 +1,1692 @@ +/* + * + * @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: QTSSFileModule.cpp + + Contains: Implementation of module described in QTSSFileModule.h. + + + +*/ + +#include + +#include "QTSSFileModule.h" + +#include "QTRTPFile.h" +#include "QTFile.h" +#include "OSMemory.h" +#include "OSArrayObjectDeleter.h" +#include "QTSSMemoryDeleter.h" +#include "SDPSourceInfo.h" +#include "StringFormatter.h" +#include "QTSSModuleUtils.h" +#include "QTSS3GPPModuleUtils.h" +#include "ResizeableStringFormatter.h" +#include "StringParser.h" +#include "SDPUtils.h" + +#include + +#include "QTSS.h" + +class FileSession +{ + public: + + FileSession() : fAdjustedPlayTime(0), fNextPacketLen(0), fLastQualityCheck(0), + fAllowNegativeTTs(false), fSpeed(1), + fStartTime(-1), fStopTime(-1), fStopTrackID(0), fStopPN(0), + fLastRTPTime(0), fLastPauseTime(0),fTotalPauseTime(0), fPaused(true), fAdjustPauseTime(true) + { + fPacketStruct.packetData = NULL; fPacketStruct.packetTransmitTime = -1; fPacketStruct.suggestedWakeupTime=-1; + } + + ~FileSession() {} + + QTRTPFile fFile; + SInt64 fAdjustedPlayTime; + QTSS_PacketStruct fPacketStruct; + int fNextPacketLen; + SInt64 fLastQualityCheck; + SDPSourceInfo fSDPSource; + Bool16 fAllowNegativeTTs; + Float32 fSpeed; + Float64 fStartTime; + Float64 fStopTime; + + UInt32 fStopTrackID; + UInt64 fStopPN; + + UInt32 fLastRTPTime; + UInt64 fLastPauseTime; + SInt64 fTotalPauseTime; + Bool16 fPaused; + Bool16 fAdjustPauseTime; +}; + +// ref to the prefs dictionary object +static QTSS_ModulePrefsObject sPrefs; +static QTSS_PrefsObject sServerPrefs; +static QTSS_Object sServer; + +static StrPtrLen sSDPSuffix(".sdp"); +static StrPtrLen sVersionHeader("v=0"); +static StrPtrLen sSessionNameHeader("s="); +static StrPtrLen sPermanentTimeHeader("t=0 0"); +static StrPtrLen sConnectionHeader("c=IN IP4 0.0.0.0"); +static StrPtrLen sStaticControlHeader("a=control:*"); +static StrPtrLen sEmailHeader; +static StrPtrLen sURLHeader; +static StrPtrLen sEOL("\r\n"); +static StrPtrLen sSDPNotValidMessage("Movie SDP is not valid."); + +const SInt16 sNumSDPVectors = 22; + +// ATTRIBUTES IDs + +static QTSS_AttributeID sFileSessionAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sSeekToNonexistentTimeErr = qtssIllegalAttrID; +static QTSS_AttributeID sNoSDPFileFoundErr = qtssIllegalAttrID; +static QTSS_AttributeID sBadQTFileErr = qtssIllegalAttrID; +static QTSS_AttributeID sFileIsNotHintedErr = qtssIllegalAttrID; +static QTSS_AttributeID sExpectedDigitFilenameErr = qtssIllegalAttrID; +static QTSS_AttributeID sTrackDoesntExistErr = qtssIllegalAttrID; + +static QTSS_AttributeID sFileSessionPlayCountAttrID = qtssIllegalAttrID; +static QTSS_AttributeID sFileSessionBufferDelayAttrID = qtssIllegalAttrID; + +static QTSS_AttributeID sRTPStreamLastSentPacketSeqNumAttrID = qtssIllegalAttrID; + +static QTSS_AttributeID sRTPStreamLastPacketSeqNumAttrID = qtssIllegalAttrID; + +// OTHER DATA + +static UInt32 sFlowControlProbeInterval = 10; +static UInt32 sDefaultFlowControlProbeInterval= 10; +static Float32 sMaxAllowedSpeed = 4; +static Float32 sDefaultMaxAllowedSpeed = 4; + +// File Caching Prefs +static Bool16 sEnableSharedBuffers = false; +static Bool16 sEnablePrivateBuffers = false; + +static UInt32 sSharedBufferUnitKSize = 0; +static UInt32 sSharedBufferInc = 0; +static UInt32 sSharedBufferUnitSize = 0; +static UInt32 sSharedBufferMaxUnits = 0; + +static UInt32 sPrivateBufferUnitKSize = 0; +static UInt32 sPrivateBufferUnitSize = 0; +static UInt32 sPrivateBufferMaxUnits = 0; + +static Float32 sAddClientBufferDelaySecs = 0; + +static Bool16 sRecordMovieFileSDP = false; +static Bool16 sEnableMovieFileSDP = false; + +static Bool16 sPlayerCompatibility = true; +static UInt32 sAdjustMediaBandwidthPercent = 50; +static SInt64 sAdjustRTPStartTimeMilli = 500; + +static Bool16 sAllowInvalidHintRefs = false; + +// Server preference we respect +static Bool16 sDisableThinning = false; +static UInt16 sDefaultStreamingQuality = 0; + +static const StrPtrLen kCacheControlHeader("must-revalidate"); +static const QTSS_RTSPStatusCode kNotModifiedStatus = qtssRedirectNotModified; + + +const Bool16 kAddPauseTimeToRTPTime = true; +const Bool16 kDontAddPauseTimeToRTPTime = false; + + +// FUNCTIONS + +static QTSS_Error QTSSFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParamBlock); +static QTSS_Error RereadPrefs(); +static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParamBlock); +static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock); +static QTSS_Error CreateQTRTPFile(QTSS_StandardRTSP_Params* inParamBlock, char* inPath, FileSession** outFile); +static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock); +static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock); +static QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams); +static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams); +static void DeleteFileSession(FileSession* inFileSession); +static UInt32 WriteSDPHeader(FILE* sdpFile, iovec *theSDPVec, SInt16 *ioVectorIndex, StrPtrLen *sdpHeader); +static void BuildPrefBasedHeaders(); + +QTSS_Error QTSSFileModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSFileModuleDispatch); +} + +inline UInt16 GetPacketSequenceNumber(void * packetDataPtr) +{ + return ntohs( ((UInt16*)packetDataPtr)[1]); +} + +inline UInt16 GetLastPacketSeqNum(QTSS_Object stream) +{ + + UInt16 lastSeqNum = 0; + UInt32 theLen = sizeof(lastSeqNum); + (void) QTSS_GetValue(stream, sRTPStreamLastPacketSeqNumAttrID, 0, (void*)&lastSeqNum, &theLen); + + return lastSeqNum; +} + + +inline SInt32 GetLastSentSeqNumber(QTSS_Object stream) +{ + UInt16 lastSeqNum = 0; + UInt32 theLen = sizeof(lastSeqNum); + QTSS_Error error = QTSS_GetValue(stream, sRTPStreamLastSentPacketSeqNumAttrID, 0, (void*)&lastSeqNum, &theLen); + if (error == QTSS_ValueNotFound) // first packet + { return -1; + } + + return (SInt32)lastSeqNum; // return UInt16 seq num value or -1. +} + +inline void SetPacketSequenceNumber(UInt16 newSequenceNumber, void * packetDataPtr) +{ + ((UInt16*)packetDataPtr)[1] = htons(newSequenceNumber); +} + + +inline UInt32 GetPacketTimeStamp(void * packetDataPtr) +{ + return ntohl( ((UInt32*)packetDataPtr)[1]); +} + +inline void SetPacketTimeStamp(UInt32 newTimeStamp, void * packetDataPtr) +{ + ((UInt32*)packetDataPtr)[1] = htonl(newTimeStamp); +} + +inline UInt32 CalculatePauseTimeStamp(UInt32 timescale, SInt64 totalPauseTime, UInt32 currentTimeStamp) +{ + SInt64 pauseTime = (SInt64) ( (Float64) timescale * ( ( (Float64) totalPauseTime) / 1000.0)); + UInt32 pauseTimeStamp = (UInt32) (pauseTime + currentTimeStamp); + + return pauseTimeStamp; +} + +UInt32 SetPausetimeTimeStamp(FileSession *fileSessionPtr, QTSS_Object theRTPStream, UInt32 currentTimeStamp) +{ + if (false == fileSessionPtr->fAdjustPauseTime || fileSessionPtr->fTotalPauseTime == 0) + return currentTimeStamp; + + UInt32 timeScale = 0; + UInt32 theLen = sizeof(timeScale); + (void) QTSS_GetValue(theRTPStream, qtssRTPStrTimescale, 0, (void*)&timeScale, &theLen); + if (theLen != sizeof(timeScale) || timeScale == 0) + return currentTimeStamp; + + UInt32 pauseTimeStamp = CalculatePauseTimeStamp( timeScale, fileSessionPtr->fTotalPauseTime, currentTimeStamp); + if (pauseTimeStamp != currentTimeStamp) + SetPacketTimeStamp(pauseTimeStamp, fileSessionPtr->fPacketStruct.packetData); + + return pauseTimeStamp; +} + + +UInt32 WriteSDPHeader(FILE* sdpFile, iovec *theSDPVec, SInt16 *ioVectorIndex, StrPtrLen *sdpHeader) +{ + + Assert (ioVectorIndex != NULL); + Assert (theSDPVec != NULL); + Assert (sdpHeader != NULL); + Assert (*ioVectorIndex < sNumSDPVectors); // if adding an sdp param you need to increase sNumSDPVectors + + SInt16 theIndex = *ioVectorIndex; + *ioVectorIndex += 1; + + theSDPVec[theIndex].iov_base = sdpHeader->Ptr; + theSDPVec[theIndex].iov_len = sdpHeader->Len; + + if (sdpFile !=NULL) + ::fwrite(theSDPVec[theIndex].iov_base,theSDPVec[theIndex].iov_len,sizeof(char),sdpFile); + + return theSDPVec[theIndex].iov_len; +} + + + +QTSS_Error QTSSFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParamBlock->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParamBlock->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPRequest_Role: + return ProcessRTSPRequest(&inParamBlock->rtspRequestParams); + case QTSS_RTPSendPackets_Role: + return SendPackets(&inParamBlock->rtpSendPacketsParams); + case QTSS_ClientSessionClosing_Role: + return DestroySession(&inParamBlock->clientSessionClosingParams); + } + return QTSS_NoErr; +} + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Register for roles + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RTSPRequest_Role); + (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + + // Add text messages attributes + static char* sSeekToNonexistentTimeName = "QTSSFileModuleSeekToNonExistentTime"; + static char* sNoSDPFileFoundName = "QTSSFileModuleNoSDPFileFound"; + static char* sBadQTFileName = "QTSSFileModuleBadQTFile"; + static char* sFileIsNotHintedName = "QTSSFileModuleFileIsNotHinted"; + static char* sExpectedDigitFilenameName = "QTSSFileModuleExpectedDigitFilename"; + static char* sTrackDoesntExistName = "QTSSFileModuleTrackDoesntExist"; + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, &sSeekToNonexistentTimeErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sNoSDPFileFoundName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sNoSDPFileFoundName, &sNoSDPFileFoundErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadQTFileName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadQTFileName, &sBadQTFileErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sFileIsNotHintedName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sFileIsNotHintedName, &sFileIsNotHintedErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sTrackDoesntExistName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sTrackDoesntExistName, &sTrackDoesntExistErr); + + // Add an RTP session attribute for tracking FileSession objects + static char* sFileSessionName = "QTSSFileModuleSession"; + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionName, &sFileSessionAttr); + + static char* sFileSessionPlayCountName = "QTSSFileModulePlayCount"; + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionPlayCountName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionPlayCountName, &sFileSessionPlayCountAttrID); + + static char* sFileSessionBufferDelayName = "QTSSFileModuleSDPBufferDelay"; + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionBufferDelayName, NULL, qtssAttrDataTypeFloat32); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionBufferDelayName, &sFileSessionBufferDelayAttrID); + + static char* sRTPStreamLastSentPacketSeqNumName = "QTSSFileModuleLastSentPacketSeqNum"; + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sRTPStreamLastSentPacketSeqNumName, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sRTPStreamLastSentPacketSeqNumName, &sRTPStreamLastSentPacketSeqNumAttrID); + + + static char* sRTPStreamLastPacketSeqNumName = "QTSSFileModuleLastPacketSeqNum"; + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sRTPStreamLastPacketSeqNumName, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sRTPStreamLastPacketSeqNumName, &sRTPStreamLastPacketSeqNumAttrID); + + // Tell the server our name! + static char* sModuleName = "QTSSFileModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + QTRTPFile::Initialize(); + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + QTSS3GPPModuleUtils::Initialize(inParams); + + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + sServerPrefs = inParams->inPrefs; + sServer = inParams->inServer; + + // Read our preferences + RereadPrefs(); + + // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN + static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod }; + QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5); + + return QTSS_NoErr; +} + +void BuildPrefBasedHeaders() +{ + //build the sdp that looks like: \r\ne=http://streaming.apple.com\r\ne=qts@apple.com. + static StrPtrLen sUHeader("u="); + static StrPtrLen sEHeader("e="); + static StrPtrLen sHTTP("http://"); + static StrPtrLen sAdmin("admin@"); + + // Get the default DNS name of the server + StrPtrLen theDefaultDNS; + (void)QTSS_GetValuePtr(sServer, qtssSvrDefaultDNSName, 0, (void**)&theDefaultDNS.Ptr, &theDefaultDNS.Len); + + //-------- URL Header + StrPtrLen sdpURL; + sdpURL.Ptr = QTSSModuleUtils::GetStringAttribute(sPrefs, "sdp_url", ""); + sdpURL.Len = ::strlen(sdpURL.Ptr); + + UInt32 sdpURLLen = sdpURL.Len; + if (sdpURLLen == 0) + sdpURLLen = theDefaultDNS.Len + sHTTP.Len + 1; + + sURLHeader.Delete(); + sURLHeader.Len = sdpURLLen + 10; + sURLHeader.Ptr = NEW char[sURLHeader.Len]; + StringFormatter urlFormatter(sURLHeader); + urlFormatter.Put(sUHeader); + if (sdpURL.Len == 0) + { + urlFormatter.Put(sHTTP); + urlFormatter.Put(theDefaultDNS); + urlFormatter.PutChar('/'); + } + else + urlFormatter.Put(sdpURL); + + sURLHeader.Len = (UInt32)urlFormatter.GetCurrentOffset(); + + + //-------- Email Header + StrPtrLen adminEmail; + adminEmail.Ptr = QTSSModuleUtils::GetStringAttribute(sPrefs, "admin_email", ""); + adminEmail.Len = ::strlen(adminEmail.Ptr); + + UInt32 adminEmailLen = adminEmail.Len; + if (adminEmailLen == 0) + adminEmailLen = theDefaultDNS.Len + sAdmin.Len; + + sEmailHeader.Delete(); + sEmailHeader.Len = (sEHeader.Len * 2) + adminEmailLen + 10; + sEmailHeader.Ptr = NEW char[sEmailHeader.Len]; + StringFormatter sdpFormatter(sEmailHeader); + sdpFormatter.Put(sEHeader); + + if (adminEmail.Len == 0) + { + sdpFormatter.Put(sAdmin); + sdpFormatter.Put(theDefaultDNS); + } + else + sdpFormatter.Put(adminEmail); + + sEmailHeader.Len = (UInt32)sdpFormatter.GetCurrentOffset(); + + + sdpURL.Delete(); + adminEmail.Delete(); +} + +QTSS_Error RereadPrefs() +{ + + QTSSModuleUtils::GetAttribute(sPrefs, "flow_control_probe_interval", qtssAttrDataTypeUInt32, + &sFlowControlProbeInterval, &sDefaultFlowControlProbeInterval, sizeof(sFlowControlProbeInterval)); + + QTSSModuleUtils::GetAttribute(sPrefs, "max_allowed_speed", qtssAttrDataTypeFloat32, + &sMaxAllowedSpeed, &sDefaultMaxAllowedSpeed, sizeof(sMaxAllowedSpeed)); + +// File Cache prefs + + sEnableSharedBuffers = true; + QTSSModuleUtils::GetIOAttribute(sPrefs, "enable_shared_file_buffers", qtssAttrDataTypeBool16, &sEnableSharedBuffers, sizeof(sEnableSharedBuffers)); + + sEnablePrivateBuffers = false; + QTSSModuleUtils::GetIOAttribute(sPrefs, "enable_private_file_buffers", qtssAttrDataTypeBool16, &sEnablePrivateBuffers, sizeof(sEnablePrivateBuffers)); + + sSharedBufferInc = 8; + QTSSModuleUtils::GetIOAttribute(sPrefs, "num_shared_buffer_increase_per_session", qtssAttrDataTypeUInt32,&sSharedBufferInc, sizeof(sSharedBufferInc)); + + sSharedBufferUnitKSize = 256; + QTSSModuleUtils::GetIOAttribute(sPrefs, "shared_buffer_unit_k_size", qtssAttrDataTypeUInt32, &sSharedBufferUnitKSize, sizeof(sSharedBufferUnitKSize)); + + sPrivateBufferUnitKSize = 256; + QTSSModuleUtils::GetIOAttribute(sPrefs, "private_buffer_unit_k_size", qtssAttrDataTypeUInt32, &sPrivateBufferUnitKSize, sizeof(sPrivateBufferUnitKSize)); + + sSharedBufferUnitSize = 1; + QTSSModuleUtils::GetIOAttribute(sPrefs, "num_shared_buffer_units_per_buffer", qtssAttrDataTypeUInt32,&sSharedBufferUnitSize, sizeof(sSharedBufferUnitSize)); + + sPrivateBufferUnitSize = 1; + QTSSModuleUtils::GetIOAttribute(sPrefs, "num_private_buffer_units_per_buffer", qtssAttrDataTypeUInt32,&sPrivateBufferUnitSize, sizeof(sPrivateBufferUnitSize)); + + sSharedBufferMaxUnits = 8; + QTSSModuleUtils::GetIOAttribute(sPrefs, "max_shared_buffer_units_per_buffer", qtssAttrDataTypeUInt32, &sSharedBufferMaxUnits, sizeof(sSharedBufferMaxUnits)); + + sPrivateBufferMaxUnits = 8; + QTSSModuleUtils::GetIOAttribute(sPrefs, "max_private_buffer_units_per_buffer", qtssAttrDataTypeUInt32, &sPrivateBufferMaxUnits, sizeof(sPrivateBufferMaxUnits)); + + sAddClientBufferDelaySecs = 0; + QTSSModuleUtils::GetIOAttribute(sPrefs, "add_seconds_to_client_buffer_delay", qtssAttrDataTypeFloat32, &sAddClientBufferDelaySecs, sizeof(sAddClientBufferDelaySecs)); + + sRecordMovieFileSDP = false; + QTSSModuleUtils::GetIOAttribute(sPrefs, "record_movie_file_sdp", qtssAttrDataTypeBool16, &sRecordMovieFileSDP, sizeof(sRecordMovieFileSDP)); + + sEnableMovieFileSDP = false; + QTSSModuleUtils::GetIOAttribute(sPrefs, "enable_movie_file_sdp", qtssAttrDataTypeBool16, &sEnableMovieFileSDP, sizeof(sEnableMovieFileSDP)); + + sPlayerCompatibility = true; + QTSSModuleUtils::GetIOAttribute(sPrefs, "enable_player_compatibility", qtssAttrDataTypeBool16, &sPlayerCompatibility, sizeof(sPlayerCompatibility)); + + sAdjustMediaBandwidthPercent = 50; + QTSSModuleUtils::GetIOAttribute(sPrefs, "compatibility_adjust_sdp_media_bandwidth_percent", qtssAttrDataTypeUInt32, &sAdjustMediaBandwidthPercent, sizeof(sAdjustMediaBandwidthPercent)); + + sAdjustRTPStartTimeMilli = 500; + QTSSModuleUtils::GetIOAttribute(sPrefs, "compatibility_adjust_rtp_start_time_milli", qtssAttrDataTypeSInt64, &sAdjustRTPStartTimeMilli, sizeof(sAdjustRTPStartTimeMilli)); + + sAllowInvalidHintRefs = false; + QTSSModuleUtils::GetIOAttribute(sPrefs, "allow_invalid_hint_track_refs", qtssAttrDataTypeBool16, &sAllowInvalidHintRefs, sizeof(sAllowInvalidHintRefs)); + + if (sAdjustMediaBandwidthPercent > 100) + sAdjustMediaBandwidthPercent = 100; + + if (sAdjustMediaBandwidthPercent < 1) + sAdjustMediaBandwidthPercent = 1; + + UInt32 len = sizeof(sDisableThinning); + (void) QTSS_GetValue(sServerPrefs, qtssPrefsDisableThinning, 0, (void*)&sDisableThinning, &len); + + len = sizeof(sDefaultStreamingQuality); + (void) QTSS_GetValue(sServerPrefs, qtssPrefsDefaultStreamQuality, 0, (void*)&sDefaultStreamingQuality, &len); + + QTSS3GPPModuleUtils::ReadPrefs(); + + BuildPrefBasedHeaders(); + + return QTSS_NoErr; +} + +QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParamBlock) +{ + QTSS_RTSPMethod* theMethod = NULL; + UInt32 theMethodLen = 0; + if ((QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqMethod, 0, + (void**)&theMethod, &theMethodLen) != QTSS_NoErr) || (theMethodLen != sizeof(QTSS_RTSPMethod))) + { + Assert(0); + return QTSS_RequestFailed; + } + + QTSS_Error err = QTSS_NoErr; + switch (*theMethod) + { + case qtssDescribeMethod: + err = DoDescribe(inParamBlock); + break; + case qtssSetupMethod: + err = DoSetup(inParamBlock); + break; + case qtssPlayMethod: + err = DoPlay(inParamBlock); + break; + case qtssTeardownMethod: + (void)QTSS_Teardown(inParamBlock->inClientSession); + (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, 0); + break; + case qtssPauseMethod: + { (void)QTSS_Pause(inParamBlock->inClientSession); + (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, 0); + + FileSession** theFile = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*))) + return QTSS_RequestFailed; + + (**theFile).fPaused = true; + (**theFile).fLastPauseTime = OS::Milliseconds(); + + break; + } + default: + break; + } + if (err != QTSS_NoErr) + (void)QTSS_Teardown(inParamBlock->inClientSession); + + return QTSS_NoErr; +} + +Bool16 isSDP(QTSS_StandardRTSP_Params* inParamBlock) +{ + Bool16 sdpSuffix = false; + + char* path = NULL; + UInt32 len = 0; + QTSS_LockObject(inParamBlock->inRTSPRequest); + QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, (void**)&path, &len); + Assert(theErr == QTSS_NoErr); + + if (sSDPSuffix.Len <= len) + { + StrPtrLen thePath(&path[len - sSDPSuffix.Len],sSDPSuffix.Len); + sdpSuffix = thePath.Equal(sSDPSuffix); + } + + QTSS_UnlockObject(inParamBlock->inRTSPRequest); + + return sdpSuffix; +} + +QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock) +{ + if (isSDP(inParamBlock)) + { + StrPtrLen pathStr; + (void)QTSS_LockObject(inParamBlock->inRTSPRequest); + (void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&pathStr.Ptr, &pathStr.Len); + QTSS_Error err = QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, qtssClientNotFound, sNoSDPFileFoundErr, &pathStr); + (void)QTSS_UnlockObject(inParamBlock->inRTSPRequest); + return err; + } + + // + // Get the FileSession for this DESCRIBE, if any. + UInt32 theLen = sizeof(FileSession*); + FileSession* theFile = NULL; + QTSS_Error theErr = QTSS_NoErr; + Bool16 pathEndsWithSDP = false; + static StrPtrLen sSDPSuffix(".sdp"); + SInt16 vectorIndex = 1; + ResizeableStringFormatter theFullSDPBuffer(NULL,0); + StrPtrLen bufferDelayStr; + char tempBufferDelay[64]; + StrPtrLen theSDPData; + + (void)QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen); + // Generate the complete file path + UInt32 thePathLen = 0; + OSCharArrayDeleter thePath(QTSSModuleUtils::GetFullPath(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath,&thePathLen, &sSDPSuffix)); + + //first locate the target movie + thePath.GetObject()[thePathLen - sSDPSuffix.Len] = '\0';//truncate the .sdp added in the GetFullPath call + StrPtrLen requestPath(thePath.GetObject(), ::strlen(thePath.GetObject())); + if (requestPath.Len > sSDPSuffix.Len ) + { StrPtrLen endOfPath(&requestPath.Ptr[requestPath.Len - sSDPSuffix.Len], sSDPSuffix.Len); + if (endOfPath.EqualIgnoreCase(sSDPSuffix)) // it is a .sdp + { pathEndsWithSDP = true; + } + } + + if ( theFile != NULL ) + { + // + // There is already a file for this session. This can happen if there are multiple DESCRIBES, + // or a DESCRIBE has been issued with a Session ID, or some such thing. + StrPtrLen moviePath( theFile->fFile.GetMoviePath() ); + + // Stop playing because the new file isn't ready yet to send packets. + // Needs a Play request to get things going. SendPackets on the file is active if not paused. + (void)QTSS_Pause(inParamBlock->inClientSession); + (*theFile).fPaused = true; + + // + // This describe is for a different file. Delete the old FileSession. + if ( !requestPath.Equal( moviePath ) ) + { + DeleteFileSession(theFile); + theFile = NULL; + + // NULL out the attribute value, just in case. + (void)QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile)); + } + } + + if ( theFile == NULL ) + { + theErr = CreateQTRTPFile(inParamBlock, thePath.GetObject(), &theFile); + if (theErr != QTSS_NoErr) + return theErr; + + // Store this newly created file object in the RTP session. + theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile)); + } + + //replace the sacred character we have trodden on in order to truncate the path. + thePath.GetObject()[thePathLen - sSDPSuffix.Len] = sSDPSuffix.Ptr[0]; + + iovec theSDPVec[sNumSDPVectors];//1 for the RTSP header, 6 for the sdp header, 1 for the sdp body + ::memset(&theSDPVec[0], 0, sizeof(theSDPVec)); + + if (sEnableMovieFileSDP) + { + // Check to see if there is an sdp file, if so, return that file instead + // of the built-in sdp. ReadEntireFile allocates memory but if all goes well theSDPData will be managed by the File Session + (void)QTSSModuleUtils::ReadEntireFile(thePath.GetObject(), &theSDPData); + } + OSCharArrayDeleter sdpDataDeleter(theSDPData.Ptr); // Just in case we fail we know to clean up. But we clear the deleter if we succeed. + + if (theSDPData.Len > 0) + { + SDPContainer fileSDPContainer; + fileSDPContainer.SetSDPBuffer(&theSDPData); + if (!fileSDPContainer.IsSDPBufferValid()) + { return QTSSModuleUtils::SendErrorResponseWithMessage(inParamBlock->inRTSPRequest, qtssUnsupportedMediaType, &sSDPNotValidMessage); + } + + + // Append the Last Modified header to be a good caching proxy citizen before sending the Describe + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader, + theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + + //Now that we have the file data, send an appropriate describe + //response to the client + theSDPVec[1].iov_base = theSDPData.Ptr; + theSDPVec[1].iov_len = theSDPData.Len; + + QTSSModuleUtils::SendDescribeResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, + &theSDPVec[0], 3, theSDPData.Len); + } + else + { + // Before generating the SDP and sending it, check to see if there is an If-Modified-Since + // date. If there is, and the content hasn't been modified, then just return a 304 Not Modified + QTSS_TimeVal* theTime = NULL; + (void) QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqIfModSinceDate, 0, (void**)&theTime, &theLen); + if ((theLen == sizeof(QTSS_TimeVal)) && (*theTime > 0)) + { + // There is an If-Modified-Since header. Check it vs. the content. + if (*theTime == theFile->fFile.GetQTFile()->GetModDate()) + { + theErr = QTSS_SetValue( inParamBlock->inRTSPRequest, qtssRTSPReqStatusCode, 0, + &kNotModifiedStatus, sizeof(kNotModifiedStatus) ); + Assert(theErr == QTSS_NoErr); + // Because we are using this call to generate a 304 Not Modified response, we do not need + // to pass in a RTP Stream + theErr = QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, 0); + Assert(theErr == QTSS_NoErr); + return QTSS_NoErr; + } + } + + FILE* sdpFile = NULL; + if (sRecordMovieFileSDP && !pathEndsWithSDP) // don't auto create sdp for an sdp file because it would look like a broadcast + { + sdpFile = ::fopen(thePath.GetObject(),"r"); // see if there already is a .sdp for the movie + if (sdpFile != NULL) // one already exists don't mess with it + { ::fclose(sdpFile); + sdpFile = NULL; + } + else + sdpFile = ::fopen(thePath.GetObject(),"w"); // create the .sdp + } + + UInt32 totalSDPLength = 0; + + //Get filename + //StrPtrLen fileNameStr; + //(void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&fileNameStr.Ptr, (UInt32*)&fileNameStr.Len); + char* fileNameStr = NULL; + (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, &fileNameStr); + QTSSCharArrayDeleter fileNameStrDeleter(fileNameStr); + + //Get IP addr + StrPtrLen ipStr; + (void)QTSS_GetValuePtr(inParamBlock->inRTSPSession, qtssRTSPSesLocalAddrStr, 0, (void**)&ipStr.Ptr, &ipStr.Len); + + +// +// *** The order of sdp headers is specified and required by rfc 2327 +// +// -------- version header + + theFullSDPBuffer.Put(sVersionHeader); + theFullSDPBuffer.Put(sEOL); + +// -------- owner header + + const SInt16 sLineSize = 256; + char ownerLine[sLineSize]=""; + ownerLine[sLineSize - 1] = 0; + + char *ipCstr = ipStr.GetAsCString(); + OSCharArrayDeleter ipDeleter(ipCstr); + + // the first number is the NTP time used for the session identifier (this changes for each request) + // the second number is the NTP date time of when the file was modified (this changes when the file changes) + qtss_sprintf(ownerLine, "o=StreamingServer %"_64BITARG_"d %"_64BITARG_"d IN IP4 %s", (SInt64) OS::UnixTime_Secs() + 2208988800LU, (SInt64) theFile->fFile.GetQTFile()->GetModDate(),ipCstr); + Assert(ownerLine[sLineSize - 1] == 0); + + StrPtrLen ownerStr(ownerLine); + theFullSDPBuffer.Put(ownerStr); + theFullSDPBuffer.Put(sEOL); + +// -------- session header + + theFullSDPBuffer.Put(sSessionNameHeader); + theFullSDPBuffer.Put(fileNameStr); + theFullSDPBuffer.Put(sEOL); + +// -------- uri header + + theFullSDPBuffer.Put(sURLHeader); + theFullSDPBuffer.Put(sEOL); + + +// -------- email header + + theFullSDPBuffer.Put(sEmailHeader); + theFullSDPBuffer.Put(sEOL); + +// -------- connection information header + + theFullSDPBuffer.Put(sConnectionHeader); + theFullSDPBuffer.Put(sEOL); + +// -------- time header + + // t=0 0 is a permanent always available movie (doesn't ever change unless we change the code) + theFullSDPBuffer.Put(sPermanentTimeHeader); + theFullSDPBuffer.Put(sEOL); + +// -------- control header + + theFullSDPBuffer.Put(sStaticControlHeader); + theFullSDPBuffer.Put(sEOL); + + +// -------- add buffer delay + + if (sAddClientBufferDelaySecs > 0) // increase the client buffer delay by the preference amount. + { + Float32 bufferDelay = 3.0; // the client doesn't advertise it's default value so we guess. + + static StrPtrLen sBuffDelayStr("a=x-bufferdelay:"); + + StrPtrLen delayStr; + theSDPData.FindString(sBuffDelayStr, &delayStr); + if (delayStr.Len > 0) + { + UInt32 offset = (delayStr.Ptr - theSDPData.Ptr) + delayStr.Len; // step past the string + delayStr.Ptr = theSDPData.Ptr + offset; + delayStr.Len = theSDPData.Len - offset; + StringParser theBufferSecsParser(&delayStr); + theBufferSecsParser.ConsumeWhitespace(); + bufferDelay = theBufferSecsParser.ConsumeFloat(); + } + + bufferDelay += sAddClientBufferDelaySecs; + + + qtss_sprintf(tempBufferDelay, "a=x-bufferdelay:%.2f",bufferDelay); + bufferDelayStr.Set(tempBufferDelay); + + theFullSDPBuffer.Put(bufferDelayStr); + theFullSDPBuffer.Put(sEOL); + } + + // -------- movie file sdp data + + //now append content-determined sdp ( cached in QTRTPFile ) + int sdpLen = 0; + theSDPData.Ptr = theFile->fFile.GetSDPFile(&sdpLen); + theSDPData.Len = sdpLen; + +// ----------- Add the movie's sdp headers to our sdp headers + + theFullSDPBuffer.Put(theSDPData); + StrPtrLen fullSDPBuffSPL(theFullSDPBuffer.GetBufPtr(),theFullSDPBuffer.GetBytesWritten()); + +// ------------ Check the headers + SDPContainer rawSDPContainer; + rawSDPContainer.SetSDPBuffer( &fullSDPBuffSPL ); + if (!rawSDPContainer.IsSDPBufferValid()) + { return QTSSModuleUtils::SendErrorResponseWithMessage(inParamBlock->inRTSPRequest, qtssUnsupportedMediaType, &sSDPNotValidMessage); + } + +// ------------ reorder the sdp headers to make them proper. + Float32 adjustMediaBandwidthPercent = 1.0; + Bool16 adjustMediaBandwidth = false; + if (sPlayerCompatibility ) + adjustMediaBandwidth = QTSSModuleUtils::HavePlayerProfile(sServerPrefs, inParamBlock,QTSSModuleUtils::kAdjustBandwidth); + + if (adjustMediaBandwidth) + adjustMediaBandwidthPercent = (Float32) sAdjustMediaBandwidthPercent / 100.0; + + ResizeableStringFormatter buffer; + SDPContainer* insertMediaLines = QTSS3GPPModuleUtils::Get3GPPSDPFeatureListCopy(buffer); + SDPLineSorter sortedSDP(&rawSDPContainer,adjustMediaBandwidthPercent,insertMediaLines); + delete insertMediaLines; + StrPtrLen *theSessionHeadersPtr = sortedSDP.GetSessionHeaders(); + StrPtrLen *theMediaHeadersPtr = sortedSDP.GetMediaHeaders(); + +//3GPP-BAD // add the bitrate adaptation string to the SDPLineSorter + //sortedSDP should have a getmedialine[n] + // findstring in line + // getline and insert line to media + /* + 5.3.3.5 The bit-rate adaptation support attribute, Ò3GPP-Adaptation-SupportÓ +To signal the support of bit-rate adaptation, a media level only SDP attribute is defined in ABNF [53]: +sdp-Adaptation-line = "a" "=" "3GPP-Adaptation-Support" ":" report-frequency CRLF +report-frequency = NonZeroDIGIT [ DIGIT ] +NonZeroDIGIT = %x31-39 ;1-9 +A server implementing rate adaptation shall signal the "3GPP-Adaptation-Support" attribute in its SDP. + +*/ + + +// ----------- write out the sdp + + totalSDPLength += ::WriteSDPHeader(sdpFile, theSDPVec, &vectorIndex, theSessionHeadersPtr); + totalSDPLength += ::WriteSDPHeader(sdpFile, theSDPVec, &vectorIndex, theMediaHeadersPtr); + + +// -------- done with SDP processing + + if (sdpFile !=NULL) + ::fclose(sdpFile); + + + Assert(theSDPData.Len > 0); + Assert(theSDPVec[2].iov_base != NULL); + //ok, we have a filled out iovec. Let's send the response! + + // Append the Last Modified header to be a good caching proxy citizen before sending the Describe + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader, + theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + QTSSModuleUtils::SendDescribeResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, + &theSDPVec[0], vectorIndex, totalSDPLength); + } + + Assert(theSDPData.Ptr != NULL); + Assert(theSDPData.Len > 0); + + //now parse the movie media sdp data. We need to do this in order to extract payload information. + //The SDP parser object will not take responsibility of the memory (one exception... see above) + theFile->fSDPSource.Parse(theSDPData.Ptr, theSDPData.Len); + sdpDataDeleter.ClearObject(); // don't delete theSDPData, theFile has it now. + + return QTSS_NoErr; +} + +QTSS_Error CreateQTRTPFile(QTSS_StandardRTSP_Params* inParamBlock, char* inPath, FileSession** outFile) +{ + *outFile = NEW FileSession(); + (*outFile)->fFile.SetAllowInvalidHintRefs(sAllowInvalidHintRefs); + QTRTPFile::ErrorCode theErr = (*outFile)->fFile.Initialize(inPath); + if (theErr != QTRTPFile::errNoError) + { + delete *outFile; + *outFile = NULL; + + char* thePathStr = NULL; + (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, &thePathStr); + QTSSCharArrayDeleter thePathStrDeleter(thePathStr); + StrPtrLen thePath(thePathStr); + + if (theErr == QTRTPFile::errFileNotFound) + return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest, + qtssClientNotFound, + sNoSDPFileFoundErr,&thePath); + if (theErr == QTRTPFile::errInvalidQuickTimeFile) + return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest, + qtssUnsupportedMediaType, + sBadQTFileErr,&thePath); + if (theErr == QTRTPFile::errNoHintTracks) + return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest, + qtssUnsupportedMediaType, + sFileIsNotHintedErr,&thePath); + if (theErr == QTRTPFile::errInternalError) + return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest, + qtssServerInternal, + sBadQTFileErr,&thePath); + + AssertV(0, theErr); + } + + return QTSS_NoErr; +} + + +QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock) +{ + + if (isSDP(inParamBlock)) + { + StrPtrLen pathStr; + (void)QTSS_LockObject(inParamBlock->inRTSPRequest); + (void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&pathStr.Ptr, &pathStr.Len); + QTSS_Error err = QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, qtssClientNotFound, sNoSDPFileFoundErr, &pathStr); + (void)QTSS_UnlockObject(inParamBlock->inRTSPRequest); + return err; + } + + //setup this track in the file object + FileSession* theFile = NULL; + UInt32 theLen = sizeof(FileSession*); + QTSS_Error theErr = QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*))) + { + char* theFullPath = NULL; + //theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, (void**)&theFullPath, &theLen); + theErr = QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPath); + Assert(theErr == QTSS_NoErr); + // This is possible, as clients are not required to send a DESCRIBE. If we haven't set + // anything up yet, set everything up + theErr = CreateQTRTPFile(inParamBlock, theFullPath, &theFile); + QTSS_Delete(theFullPath); + if (theErr != QTSS_NoErr) + return theErr; + + int theSDPBodyLen = 0; + char* theSDPData = theFile->fFile.GetSDPFile(&theSDPBodyLen); + + //now parse the sdp. We need to do this in order to extract payload information. + //The SDP parser object will not take responsibility of the memory (one exception... see above) + theFile->fSDPSource.Parse(theSDPData, theSDPBodyLen); + + // Store this newly created file object in the RTP session. + theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile)); + } + + //unless there is a digit at the end of this path (representing trackID), don't + //even bother with the request + char* theDigitStr = NULL; + (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr); + QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr); + if (theDigitStr == NULL) + return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, + qtssClientBadRequest, sExpectedDigitFilenameErr); + + UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10); + +// QTRTPFile::ErrorCode qtfileErr = theFile->fFile.AddTrack(theTrackID, false); //test for 3gpp monotonic wall clocktime and sequence + QTRTPFile::ErrorCode qtfileErr = theFile->fFile.AddTrack(theTrackID, true); + + //if we get an error back, forward that error to the client + if (qtfileErr == QTRTPFile::errTrackIDNotFound) + return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, + qtssClientNotFound, sTrackDoesntExistErr); + else if (qtfileErr != QTRTPFile::errNoError) + return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, + qtssUnsupportedMediaType, sBadQTFileErr); + + // Before setting up this track, check to see if there is an If-Modified-Since + // date. If there is, and the content hasn't been modified, then just return a 304 Not Modified + QTSS_TimeVal* theTime = NULL; + (void) QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqIfModSinceDate, 0, (void**)&theTime, &theLen); + if ((theLen == sizeof(QTSS_TimeVal)) && (*theTime > 0)) + { + // There is an If-Modified-Since header. Check it vs. the content. + if (*theTime == theFile->fFile.GetQTFile()->GetModDate()) + { + theErr = QTSS_SetValue( inParamBlock->inRTSPRequest, qtssRTSPReqStatusCode, 0, + &kNotModifiedStatus, sizeof(kNotModifiedStatus) ); + Assert(theErr == QTSS_NoErr); + // Because we are using this call to generate a 304 Not Modified response, we do not need + // to pass in a RTP Stream + theErr = QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, 0); + Assert(theErr == QTSS_NoErr); + return QTSS_NoErr; + } + } + + //find the payload for this track ID (if applicable) + StrPtrLen* thePayload = NULL; + UInt32 thePayloadType = qtssUnknownPayloadType; + Float32 bufferDelay = (Float32) 3.0; // FIXME need a constant defined for 3.0 value. It is used multiple places + + for (UInt32 x = 0; x < theFile->fSDPSource.GetNumStreams(); x++) + { + SourceInfo::StreamInfo* theStreamInfo = theFile->fSDPSource.GetStreamInfo(x); + if (theStreamInfo->fTrackID == theTrackID) + { + thePayload = &theStreamInfo->fPayloadName; + thePayloadType = theStreamInfo->fPayloadType; + bufferDelay = theStreamInfo->fBufferDelay; + break; + } + } + //Create a new RTP stream + QTSS_RTPStreamObject newStream = NULL; + theErr = QTSS_AddRTPStream(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, &newStream, 0); + if (theErr != QTSS_NoErr) + return theErr; + + // Set the payload type, payload name & timescale of this track + SInt32 theTimescale = theFile->fFile.GetTrackTimeScale(theTrackID); + + + theErr = QTSS_SetValue(newStream, qtssRTPStrBufferDelayInSecs, 0, &bufferDelay, sizeof(bufferDelay)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayload->Ptr, thePayload->Len); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrTimescale, 0, &theTimescale, sizeof(theTimescale)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID)); + Assert(theErr == QTSS_NoErr); + + // Set the number of quality levels. Allow up to 6 + static UInt32 sNumQualityLevels = 6; + theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels)); + Assert(theErr == QTSS_NoErr); + + // Get the SSRC of this track + UInt32* theTrackSSRC = NULL; + UInt32 theTrackSSRCSize = 0; + (void)QTSS_GetValuePtr(newStream, qtssRTPStrSSRC, 0, (void**)&theTrackSSRC, &theTrackSSRCSize); + + // The RTP stream should ALWAYS have an SSRC assuming QTSS_AddStream succeeded. + Assert((theTrackSSRC != NULL) && (theTrackSSRCSize == sizeof(UInt32))); + + //give the file some info it needs. + theFile->fFile.SetTrackSSRC(theTrackID, *theTrackSSRC); + theFile->fFile.SetTrackCookies(theTrackID, newStream, thePayloadType); + + StrPtrLen theHeader; + theErr = QTSS_GetValuePtr(inParamBlock->inRTSPHeaders, qtssXRTPMetaInfoHeader, 0, (void**)&theHeader.Ptr, &theHeader.Len); + if (theErr == QTSS_NoErr) + { + // + // If there is an x-RTP-Meta-Info header in the request, mirror that header in the + // response. We will support any fields supported by the QTFileLib. + RTPMetaInfoPacket::FieldID* theFields = NEW RTPMetaInfoPacket::FieldID[RTPMetaInfoPacket::kNumFields]; + ::memcpy(theFields, QTRTPFile::GetSupportedRTPMetaInfoFields(), sizeof(RTPMetaInfoPacket::FieldID) * RTPMetaInfoPacket::kNumFields); + + // + // This function does the work of appending the response header based on the + // fields we support and the requested fields. + theErr = QTSSModuleUtils::AppendRTPMetaInfoHeader(inParamBlock->inRTSPRequest, &theHeader, theFields); + + // + // This returns QTSS_NoErr only if there are some valid, useful fields + Bool16 isVideo = false; + if (thePayloadType == qtssVideoPayloadType) + isVideo = true; + if (theErr == QTSS_NoErr) + theFile->fFile.SetTrackRTPMetaInfo(theTrackID, theFields, isVideo); + } + + // + // Our array has now been updated to reflect the fields requested by the client. + //send the setup response + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader, + theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + theErr = QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, newStream, 0); + Assert(theErr == QTSS_NoErr); + return QTSS_NoErr; +} + + + +QTSS_Error SetupCacheBuffers(QTSS_StandardRTSP_Params* inParamBlock, FileSession** theFile) +{ + + UInt32 playCount = 0; + UInt32 theLen = sizeof(playCount); + QTSS_Error theErr = QTSS_GetValue(inParamBlock->inClientSession, sFileSessionPlayCountAttrID, 0, (void*)&playCount, &theLen); + if ( (theErr != QTSS_NoErr) || (theLen != sizeof(playCount)) ) + { + playCount = 1; + theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionPlayCountAttrID, 0, &playCount, sizeof(playCount)); + if (theErr != QTSS_NoErr) + return QTSS_RequestFailed; + } + + if (sEnableSharedBuffers && playCount == 1) // increments num buffers after initialization so do only once per session + (*theFile)->fFile.AllocateSharedBuffers(sSharedBufferUnitKSize, sSharedBufferInc, sSharedBufferUnitSize,sSharedBufferMaxUnits); + + if (sEnablePrivateBuffers) // reinitializes buffers to current location so do every time + (*theFile)->fFile.AllocatePrivateBuffers(sSharedBufferUnitKSize, sPrivateBufferUnitSize, sPrivateBufferMaxUnits); + + playCount ++; + theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionPlayCountAttrID, 0, &playCount, sizeof(playCount)); + if (theErr != QTSS_NoErr) + return QTSS_RequestFailed; + + return theErr; + +} + +QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock) +{ + QTRTPFile::ErrorCode qtFileErr = QTRTPFile::errNoError; + + if (isSDP(inParamBlock)) + { + StrPtrLen pathStr; + (void)QTSS_LockObject(inParamBlock->inRTSPRequest); + (void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&pathStr.Ptr, &pathStr.Len); + QTSS_Error err = QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, qtssClientNotFound, sNoSDPFileFoundErr, &pathStr); + (void)QTSS_UnlockObject(inParamBlock->inRTSPRequest); + return err; + } + + FileSession** theFile = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*))) + return QTSS_RequestFailed; + + theErr = SetupCacheBuffers(inParamBlock, theFile); + if (theErr != QTSS_NoErr) + return theErr; + + //make sure to clear the next packet the server would have sent! + (*theFile)->fPacketStruct.packetData = NULL; + + // Set the default quality before playing. + QTRTPFile::RTPTrackListEntry* thePacketTrack; + for (UInt32 x = 0; x < (*theFile)->fSDPSource.GetNumStreams(); x++) + { + SourceInfo::StreamInfo* theStreamInfo = (*theFile)->fSDPSource.GetStreamInfo(x); + if (!(*theFile)->fFile.FindTrackEntry(theStreamInfo->fTrackID,&thePacketTrack)) + break; + //(*theFile)->fFile.SetTrackQualityLevel(thePacketTrack, QTRTPFile::kAllPackets); + (*theFile)->fFile.SetTrackQualityLevel(thePacketTrack, sDefaultStreamingQuality); + } + + + // How much are we going to tell the client to back up? + Float32 theBackupTime = 0; + + char* thePacketRangeHeader = NULL; + theErr = QTSS_GetValuePtr(inParamBlock->inRTSPHeaders, qtssXPacketRangeHeader, 0, (void**)&thePacketRangeHeader, &theLen); + if (theErr == QTSS_NoErr) + { + StrPtrLen theRangeHdrPtr(thePacketRangeHeader, theLen); + StringParser theRangeParser(&theRangeHdrPtr); + + theRangeParser.ConsumeUntil(NULL, StringParser::sDigitMask); + UInt64 theStartPN = theRangeParser.ConsumeInteger(); + + theRangeParser.ConsumeUntil(NULL, StringParser::sDigitMask); + (*theFile)->fStopPN = theRangeParser.ConsumeInteger(); + + theRangeParser.ConsumeUntil(NULL, StringParser::sDigitMask); + (*theFile)->fStopTrackID = theRangeParser.ConsumeInteger(); + + qtFileErr = (*theFile)->fFile.SeekToPacketNumber((*theFile)->fStopTrackID, theStartPN); + (*theFile)->fStartTime = (*theFile)->fFile.GetRequestedSeekTime(); + } + else + { + Float64* theStartTimeP = NULL; + Float64 currentTime = 0; + theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqStartTime, 0, (void**)&theStartTimeP, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(Float64))) + { // No start time so just start at the last packet ready to send + // This packet could be somewhere out in the middle of the file. + currentTime = (*theFile)->fFile.GetFirstPacketTransmitTime(); + theStartTimeP = ¤tTime; + (*theFile)->fStartTime = currentTime; + } + + Float32* theMaxBackupTime = NULL; + theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqPrebufferMaxTime, 0, (void**)&theMaxBackupTime, &theLen); + Assert(theMaxBackupTime != NULL); + + if (*theMaxBackupTime == -1) + { + // + // If this is an old client (doesn't send the x-prebuffer header) or an mp4 client, + // - don't back up to a key frame, and do not adjust the buffer time + qtFileErr = (*theFile)->fFile.Seek(*theStartTimeP, 0); + (*theFile)->fStartTime = *theStartTimeP; + + // + // burst out -transmit time packets + (*theFile)->fAllowNegativeTTs = false; + } + else + { + qtFileErr = (*theFile)->fFile.Seek(*theStartTimeP, *theMaxBackupTime); + Float64 theFirstPacketTransmitTime = (*theFile)->fFile.GetFirstPacketTransmitTime(); + theBackupTime = (Float32) ( *theStartTimeP - theFirstPacketTransmitTime); + + // + // For oddly authored movies, there are situations in which the packet + // transmit time can be before the sample time. In that case, the backup + // time may exceed the max backup time. In that case, just make the backup + // time the max backup time. + if (theBackupTime > *theMaxBackupTime) + theBackupTime = *theMaxBackupTime; + // + // If client specifies that it can do extra buffering (new client), use the first + // packet transmit time as the start time for this play burst. We don't need to + // burst any packets because the client can do the extra buffering + Bool16* overBufferEnabledPtr = NULL; + theLen = 0; + theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesOverBufferEnabled, 0, (void**)&overBufferEnabledPtr, &theLen); + if ((theErr == QTSS_NoErr) && (theLen == sizeof(Bool16)) && *overBufferEnabledPtr) + (*theFile)->fStartTime = *theStartTimeP; + else + (*theFile)->fStartTime = *theStartTimeP - theBackupTime; + + + (*theFile)->fAllowNegativeTTs = true; + } + } + + if (qtFileErr == QTRTPFile::errCallAgain) + { + // + // If we are doing RTP-Meta-Info stuff, we might be asked to get called again here. + // This is simply because seeking might be a long operation and we don't want to + // monopolize the CPU, but there is no other reason to wait, so just set a timeout of 0 + theErr = QTSS_SetIdleTimer(1); + Assert(theErr == QTSS_NoErr); + return theErr; + } + else if (qtFileErr != QTRTPFile::errNoError) + return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest, + qtssClientBadRequest, sSeekToNonexistentTimeErr); + + //make sure to clear the next packet the server would have sent! + (*theFile)->fPacketStruct.packetData = NULL; + + // Set the movie duration and size parameters + Float64 movieDuration = (*theFile)->fFile.GetMovieDuration(); + (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieDurationInSecs, 0, &movieDuration, sizeof(movieDuration)); + + UInt64 movieSize = (*theFile)->fFile.GetAddedTracksRTPBytes(); + (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieSizeInBytes, 0, &movieSize, sizeof(movieSize)); + + UInt32 bitsPerSecond = (*theFile)->fFile.GetBytesPerSecond() * 8; + (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond)); + + Bool16 adjustPauseTime = kAddPauseTimeToRTPTime; //keep rtp time stamps monotonically increasing + if ( true == QTSSModuleUtils::HavePlayerProfile( sServerPrefs, inParamBlock,QTSSModuleUtils::kDisablePauseAdjustedRTPTime) ) + adjustPauseTime = kDontAddPauseTimeToRTPTime; + + if (sPlayerCompatibility ) // don't change adjust setting if compatibility is off. + (**theFile).fAdjustPauseTime = adjustPauseTime; + + if ( (**theFile).fLastPauseTime > 0 ) + (**theFile).fTotalPauseTime += OS::Milliseconds() - (**theFile).fLastPauseTime; + + // + // For the purposes of the speed header, check to make sure all tracks are + // over a reliable transport + Bool16 allTracksReliable = true; + + // Set the timestamp & sequence number parameters for each track. + QTSS_RTPStreamObject* theRef = NULL; + for ( UInt32 theStreamIndex = 0; + QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesStreamObjects, theStreamIndex, (void**)&theRef, &theLen) == QTSS_NoErr; + theStreamIndex++) + { + UInt32* theTrackID = NULL; + theErr = QTSS_GetValuePtr(*theRef, qtssRTPStrTrackID, 0, (void**)&theTrackID, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theTrackID != NULL); + Assert(theLen == sizeof(UInt32)); + + UInt16 theSeqNum = 0; + UInt32 theTimestamp = (*theFile)->fFile.GetSeekTimestamp(*theTrackID); // this is the base timestamp need to add in paused time. + + Assert(theRef != NULL); + + if ((**theFile).fAdjustPauseTime) + { + UInt32* theTimescale = NULL; + QTSS_GetValuePtr(*theRef, qtssRTPStrTimescale, 0, (void**)&theTimescale, &theLen); + if (theLen != 0) // adjust the timestamps to reflect paused time else leave it alone we can't calculate the timestamp without a timescale. + { + UInt32 pauseTimeStamp = CalculatePauseTimeStamp( *theTimescale, (*theFile)->fTotalPauseTime, (UInt32) theTimestamp); + if (pauseTimeStamp != theTimestamp) + theTimestamp = pauseTimeStamp; + } + } + + theSeqNum = (*theFile)->fFile.GetNextTrackSequenceNumber(*theTrackID); + theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstSeqNumber, 0, &theSeqNum, sizeof(theSeqNum)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstTimestamp, 0, &theTimestamp, sizeof(theTimestamp)); + Assert(theErr == QTSS_NoErr); + + if (allTracksReliable) + { + QTSS_RTPTransportType theTransportType = qtssRTPTransportTypeUDP; + theLen = sizeof(theTransportType); + theErr = QTSS_GetValue(*theRef, qtssRTPStrTransportType, 0, &theTransportType, &theLen); + Assert(theErr == QTSS_NoErr); + + if (theTransportType == qtssRTPTransportTypeUDP) + allTracksReliable = false; + } + } + + //Tell the QTRTPFile whether repeat packets are wanted based on the transport + // we don't care if it doesn't set (i.e. this is a meta info session) + (void) (*theFile)->fFile.SetDropRepeatPackets(allTracksReliable);// if alltracks are reliable then drop repeat packets. + + // + // This module supports the Speed header if the client wants the stream faster than normal. + Float32 theSpeed = 1; + theLen = sizeof(theSpeed); + theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqSpeed, 0, &theSpeed, &theLen); + Assert(theErr != QTSS_BadArgument); + Assert(theErr != QTSS_NotEnoughSpace); + + if (theErr == QTSS_NoErr) + { + if (theSpeed > sMaxAllowedSpeed) + theSpeed = sMaxAllowedSpeed; + if ((theSpeed <= 0) || (!allTracksReliable)) + theSpeed = 1; + } + + (*theFile)->fSpeed = theSpeed; + + if (theSpeed != 1) + { + // + // If our speed is not 1, append the RTSP speed header in the response + char speedBuf[32]; + qtss_sprintf(speedBuf, "%10.5f", theSpeed); + StrPtrLen speedBufPtr(speedBuf); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssSpeedHeader, + speedBufPtr.Ptr, speedBufPtr.Len); + } + + // + // Record the requested stop time, if there is one + (*theFile)->fStopTime = -1; + theLen = sizeof((*theFile)->fStopTime); + theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqStopTime, 0, &(*theFile)->fStopTime, &theLen); + + // + // Append x-Prebuffer header if provided & nonzero prebuffer needed + if (theBackupTime > 0) + { + char prebufferBuf[32]; + qtss_sprintf(prebufferBuf, "time=%.5f", theBackupTime); + StrPtrLen backupTimePtr(prebufferBuf); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssXPreBufferHeader, + backupTimePtr.Ptr, backupTimePtr.Len); + + } + + // add the range header. + { + char rangeHeader[64]; + if (-1 == (*theFile)->fStopTime) + (*theFile)->fStopTime = (*theFile)->fFile.GetMovieDuration(); + + qtss_snprintf(rangeHeader,sizeof(rangeHeader) -1, "npt=%.5f-%.5f", (*theFile)->fStartTime, (*theFile)->fStopTime); + rangeHeader[sizeof(rangeHeader) -1] = 0; + + StrPtrLen rangeHeaderPtr(rangeHeader); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssRangeHeader, + rangeHeaderPtr.Ptr, rangeHeaderPtr.Len); + + } + (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, qtssPlayRespWriteTrackInfo); + + SInt64 adjustRTPStreamStartTimeMilli = 0; + if (sPlayerCompatibility && QTSSModuleUtils::HavePlayerProfile(sServerPrefs, inParamBlock,QTSSModuleUtils::kDelayRTPStreamsUntilAfterRTSPResponse)) + adjustRTPStreamStartTimeMilli = sAdjustRTPStartTimeMilli; + + //Tell the server to start playing this movie. We do want it to send RTCP SRs, but + //we DON'T want it to write the RTP header + (*theFile)->fPaused = false; + theErr = QTSS_Play(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, qtssPlayFlagsSendRTCP); + if (theErr != QTSS_NoErr) + return theErr; + + // Set the adjusted play time. SendPackets can get called between QTSS_Play and + // setting fAdjustedPlayTime below. + SInt64* thePlayTime = NULL; + theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesPlayTimeInMsec, 0, (void**)&thePlayTime, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(thePlayTime != NULL); + Assert(theLen == sizeof(SInt64)); + if (thePlayTime != NULL) + (*theFile)->fAdjustedPlayTime = adjustRTPStreamStartTimeMilli + *thePlayTime - ((SInt64)((*theFile)->fStartTime * 1000) ); + + return QTSS_NoErr; +} + +QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams) +{ + static const UInt32 kQualityCheckIntervalInMsec = 250; // v331=v107 + FileSession** theFile = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theLen == sizeof(FileSession*)); + bool isBeginningOfWriteBurst = true; + QTSS_Object theStream = NULL; + + + if ( theFile == NULL || (*theFile)->fStartTime == -1 || (*theFile)->fPaused == true ) //something is wrong + { + Assert( theFile != NULL ); + Assert( (*theFile)->fStartTime != -1 ); + Assert( (*theFile)->fPaused != true ); + + inParams->outNextPacketTime = qtssDontCallSendPacketsAgain; + return QTSS_NoErr; + } + + if ( (*theFile)->fAdjustedPlayTime == 0 ) // this is system milliseconds + { + Assert( (*theFile)->fAdjustedPlayTime != 0 ); + inParams->outNextPacketTime = kQualityCheckIntervalInMsec; + return QTSS_NoErr; + } + + + QTRTPFile::RTPTrackListEntry* theLastPacketTrack = (*theFile)->fFile.GetLastPacketTrack(); + + while (true) + { + if ((*theFile)->fPacketStruct.packetData == NULL) + { + Float64 theTransmitTime = (*theFile)->fFile.GetNextPacket((char**)&(*theFile)->fPacketStruct.packetData, &(*theFile)->fNextPacketLen); + if ( QTRTPFile::errNoError != (*theFile)->fFile.Error() ) + { + QTSS_CliSesTeardownReason reason = qtssCliSesTearDownUnsupportedMedia; + (void) QTSS_SetValue(inParams->inClientSession, qtssCliTeardownReason, 0, &reason, sizeof(reason)); + (void)QTSS_Teardown(inParams->inClientSession); + return QTSS_RequestFailed; + } + theLastPacketTrack = (*theFile)->fFile.GetLastPacketTrack(); + + if (theLastPacketTrack == NULL) + break; + + theStream = (QTSS_Object)theLastPacketTrack->Cookie1; + Assert(theStream != NULL); + if (theStream == NULL) + return 0; + + + // + // Check to see if we should stop playing now + + if (((*theFile)->fStopTime != -1) && (theTransmitTime > (*theFile)->fStopTime)) + { + // We should indeed stop playing + (void)QTSS_Pause(inParams->inClientSession); + inParams->outNextPacketTime = qtssDontCallSendPacketsAgain; + + (**theFile).fPaused = true; + (**theFile).fLastPauseTime = OS::Milliseconds(); + + return QTSS_NoErr; + } + if (((*theFile)->fStopTrackID != 0) && ((*theFile)->fStopTrackID == theLastPacketTrack->TrackID) && (theLastPacketTrack->HTCB->fCurrentPacketNumber > (*theFile)->fStopPN)) + { + // We should indeed stop playing + (void)QTSS_Pause(inParams->inClientSession); + inParams->outNextPacketTime = qtssDontCallSendPacketsAgain; + + (**theFile).fPaused = true; + (**theFile).fLastPauseTime = OS::Milliseconds(); + + return QTSS_NoErr; + } + + // + // Find out what our play speed is. Send packets out at the specified rate, + // and do so by altering the transmit time of the packet based on the Speed rate. + Float64 theOffsetFromStartTime = theTransmitTime - (*theFile)->fStartTime; + theTransmitTime = (*theFile)->fStartTime + (theOffsetFromStartTime / (*theFile)->fSpeed); + + // + // correct for first packet xmit times that are < 0 + if (( theTransmitTime < 0.0 ) && ( !(*theFile)->fAllowNegativeTTs )) + theTransmitTime = 0.0; + + (*theFile)->fPacketStruct.packetTransmitTime = (*theFile)->fAdjustedPlayTime + ((SInt64)(theTransmitTime * 1000)); + + } + + //We are done playing all streams! + if ((*theFile)->fPacketStruct.packetData == NULL) + { + //TODO not quite good to the last drop -- we -really- should guarantee this, also reflector + // a write of 0 len to QTSS_Write will flush any buffered data if we're sending over tcp + //(void)QTSS_Write((QTSS_Object)(*theFile)->fFile.GetLastPacketTrack()->Cookie1, NULL, 0, NULL, qtssWriteFlagsIsRTP); + inParams->outNextPacketTime = qtssDontCallSendPacketsAgain; + return QTSS_NoErr; + } + + //we have a packet that needs to be sent now + Assert(theLastPacketTrack != NULL); + + //If the stream is video, we need to make sure that QTRTPFile knows what quality level we're at + + if ( (!sDisableThinning) && (inParams->inCurrentTime > ((*theFile)->fLastQualityCheck + kQualityCheckIntervalInMsec) ) ) + { + QTSS_RTPPayloadType thePayloadType = (QTSS_RTPPayloadType)theLastPacketTrack->Cookie2; + if (thePayloadType == qtssVideoPayloadType) + { + (*theFile)->fLastQualityCheck = inParams->inCurrentTime; + + theStream = (QTSS_Object)theLastPacketTrack->Cookie1; + Assert(theStream != NULL); + if (theStream == NULL) + return 0; + + // Get the current quality level in the stream, and this stream's TrackID. + UInt32* theQualityLevel = 0; + theErr = QTSS_GetValuePtr(theStream, qtssRTPStrQualityLevel, 0, (void**)&theQualityLevel, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theQualityLevel != NULL); + Assert(theLen == sizeof(UInt32)); + + (*theFile)->fFile.SetTrackQualityLevel(theLastPacketTrack, *theQualityLevel); + } + } + + // Send the packet! + QTSS_WriteFlags theFlags = qtssWriteFlagsIsRTP; + if (isBeginningOfWriteBurst) + theFlags |= qtssWriteFlagsWriteBurstBegin; + + theStream = (QTSS_Object)theLastPacketTrack->Cookie1; + Assert(theStream != NULL); + if (theStream == NULL) + return 0; + + //adjust the timestamp so it reflects paused time. + void* packetDataPtr = (*theFile)->fPacketStruct.packetData; + UInt32 currentTimeStamp = GetPacketTimeStamp(packetDataPtr); + UInt32 pauseTimeStamp = SetPausetimeTimeStamp(*theFile, theStream, currentTimeStamp); + + UInt16 curSeqNum = GetPacketSequenceNumber(theStream); + (void) QTSS_SetValue(theStream, sRTPStreamLastPacketSeqNumAttrID, 0, &curSeqNum, sizeof(curSeqNum)); + + theErr = QTSS_Write(theStream, &(*theFile)->fPacketStruct, (*theFile)->fNextPacketLen, NULL, theFlags); + + isBeginningOfWriteBurst = false; + + if ( theErr == QTSS_WouldBlock ) + { + + if (currentTimeStamp != pauseTimeStamp) // reset the packet time stamp so we adjust it again when we really do send it + SetPacketTimeStamp(currentTimeStamp, packetDataPtr); + + // + // In the case of a QTSS_WouldBlock error, the packetTransmitTime field of the packet struct will be set to + // the time to wakeup, or -1 if not known. + // If the time to wakeup is not given by the server, just give a fixed guess interval + if ((*theFile)->fPacketStruct.suggestedWakeupTime == -1) + inParams->outNextPacketTime = sFlowControlProbeInterval; // for buffering, try me again in # MSec + else + { + Assert((*theFile)->fPacketStruct.suggestedWakeupTime > inParams->inCurrentTime); + inParams->outNextPacketTime = (*theFile)->fPacketStruct.suggestedWakeupTime - inParams->inCurrentTime; + } + + //qtss_printf("Call again: %qd\n", inParams->outNextPacketTime); + + return QTSS_NoErr; + } + else + { + + (void) QTSS_SetValue(theStream, sRTPStreamLastSentPacketSeqNumAttrID, 0, &curSeqNum, sizeof(curSeqNum)); + (*theFile)->fPacketStruct.packetData = NULL; + } + } + + return QTSS_NoErr; +} + +QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams) +{ + FileSession** theFile = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)) || (theFile == NULL)) + return QTSS_RequestFailed; + + // + // Tell the ClientSession how many samples we skipped because of stream thinning + UInt32 theNumSkippedSamples = (*theFile)->fFile.GetNumSkippedSamples(); + (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesFramesSkipped, 0, &theNumSkippedSamples, sizeof(theNumSkippedSamples)); + + DeleteFileSession(*theFile); + return QTSS_NoErr; +} + +void DeleteFileSession(FileSession* inFileSession) +{ + delete inFileSession; +} diff --git a/APIModules/QTSSFileModule/QTSSFileModule.h b/APIModules/QTSSFileModule/QTSSFileModule.h new file mode 100644 index 0000000..e9c69b2 --- /dev/null +++ b/APIModules/QTSSFileModule/QTSSFileModule.h @@ -0,0 +1,44 @@ +/* + * + * @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: QTSSFileModule.h + + Contains: Content source module that uses the QTFileLib to serve Hinted QuickTime + files to clients. + + +*/ + +#ifndef _RTPFILEMODULE_H_ +#define _RTPFILEMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSFileModule_Main(void* inPrivateArgs); +} + +#endif //_RTPFILEMODULE_H_ diff --git a/APIModules/QTSSFilePrivsModule.bproj/QTSSFilePrivsModule.cpp b/APIModules/QTSSFilePrivsModule.bproj/QTSSFilePrivsModule.cpp new file mode 100644 index 0000000..8124ce8 --- /dev/null +++ b/APIModules/QTSSFilePrivsModule.bproj/QTSSFilePrivsModule.cpp @@ -0,0 +1,738 @@ +/* + * + * @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: QTSSFilePrivsModule.cpp + + Contains: Implementation of QTSSFilePrivsModule. + + + +*/ + +#include "QTSSFilePrivsModule.h" + + +#include "OSArrayObjectDeleter.h" +#include "QTSS_Private.h" +#include "StrPtrLen.h" +#include "OSMemory.h" +#include "MyAssert.h" +#include "StringFormatter.h" +#include "StrPtrLen.h" +#include "StringParser.h" +#include "QTSSModuleUtils.h" +#include "base64.h" +#include "OS.h" + +#ifndef __MW_ + #include + #include + #include + #include + #include + #include +#endif + + + +// STATIC DATA + +static StrPtrLen sDefaultRealm("WWW Streaming Server"); // testing only +static StrPtrLen sSDPSuffix(".sdp"); +static char* sRootUserPtr = "root"; + +const UInt32 kMaxPathLen = 512; + +static OSMutex* sUserMutex = NULL; + +// FUNCTION PROTOTYPES + +QTSS_Error QTSSFilePrivsModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +QTSS_Error Register(QTSS_Register_Params* inParams); +QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +QTSS_Error Shutdown(); +QTSS_Error RereadPrefs(); +QTSS_Error AuthenticateRTSPRequest(QTSS_StandardRTSP_Params* inParams); +Bool16 QTSSAuthorize(QTSS_StandardRTSP_Params* inParams, const char* pathBuff); +Bool16 CheckWorldAccess(const char* pathBuff); +Bool16 CheckPassword(QTSS_StandardRTSP_Params* inParams); +Bool16 FileExists(char* pathBuff); +UInt32 GetPathParentDestructive(const StrPtrLen *thePathPtr, StrPtrLen *resultPathPtr, UInt32 maxLen); + + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSFilePrivsModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSFilePrivsModuleDispatch); +} + + +QTSS_Error QTSSFilePrivsModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + break; + + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + break; + + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + break; + + case QTSS_RTSPAuthorize_Role: + return AuthenticateRTSPRequest(&inParams->rtspRequestParams); + break; + + case QTSS_Shutdown_Role: + return Shutdown(); + break; + } + + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); + + + // Tell the server our name! + static char* sModuleName = "QTSSFilePrivsModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sUserMutex = NEW OSMutex(); + + RereadPrefs(); + + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + return QTSS_NoErr; +} + +QTSS_Error RereadPrefs() +{ + return QTSS_NoErr; +} + + +UInt32 GetPathParentDestructive(const StrPtrLen *thePathPtr, StrPtrLen *resultPathPtr, UInt32 maxLen) +{ + + if (resultPathPtr && thePathPtr) + { + StrPtrLen thePath = *thePathPtr; + + while ( (thePath.Len > 0) && (thePath.Ptr[thePath.Len -1] == '/') ) + { + thePath.Len--; + } + + while ( (thePath.Len > 0) && (thePath.Ptr[thePath.Len -1] != '/') ) + { + thePath.Len--; + } + + if (thePath.Len < maxLen) + { + memcpy (resultPathPtr->Ptr,thePath.Ptr, thePath.Len); + resultPathPtr->Len = thePath.Len; + resultPathPtr->Ptr[thePath.Len] = 0; + //qtss_printf("new dir =%s \n",resultPathPtr->Ptr); + } + + } + + return resultPathPtr->Len; +} + +Bool16 IsUserMember(uid_t userID, gid_t groupID) +{ + struct passwd* user = getpwuid(userID); + struct group* group = getgrgid(groupID); + + if ((user == NULL) || (group == NULL)) + return false; + + if (user->pw_gid == groupID) return true; + + int i = 0; + while (group->gr_mem[i] != NULL) + { + if (!strcmp(user->pw_name, group->gr_mem[i])) + return true; + i++; + } + + return false; +} + +Bool16 CheckFileAccess(struct passwd *passwdStructPtr, StrPtrLen */*nameStrPtr*/, char* pathBuff) +{ + Bool16 result = false; + + /* + + if the user is the owner then + if owner access then succeed + else fail + if user is in group then + if group access then succeed + else fail + + if guest is on then + succed + else fail + + */ + + do // once only check for the current entity + { + struct stat statData; + + int statResult = stat(pathBuff,&statData); + + if (0 != statResult) + { + //qtss_printf("no access to file =%s\n",pathBuff); + result = true; // let access error be handled by server + break; + } + + + if ( statData.st_uid == (UInt16) passwdStructPtr->pw_uid) // check if owner + { + if ( (statData.st_mode & 0400 ) != 0 ) // has owner read access + { //qtss_printf("owner access to file =%s\n",pathBuff); + result = true; + break; + } + else + { //qtss_printf("no owner access to file =%s\n",pathBuff); + result = false; + break; + + } + } + + + if (statData.st_gid == (UInt16) passwdStructPtr->pw_gid) // check if user's default group owns + { + if ( (statData.st_mode & 0040) != 0 ) // has group read access + { //qtss_printf("user default group has access to file =%s\n",pathBuff); + result = true; + break; + } + else + { //qtss_printf("user default group has no access to file =%s\n",pathBuff); + result = false; + break; + } + + } + + if (IsUserMember(passwdStructPtr->pw_uid, statData.st_gid)) // check if user in group + { + if ( (statData.st_mode & 0040) != 0 ) // has group read access + { //qtss_printf("user member group has access to file =%s\n",pathBuff); + result = true; + break; + } + else + { //qtss_printf("user member group has no access to file =%s\n",pathBuff); + result = false; + break; + } + + } + + if ( (statData.st_mode & 0004) != 0 ) // has world read access + { + //qtss_printf("world has access to file =%s\n",pathBuff); + result = true; + break; + + } + + //qtss_printf("world has no read access to file =%s\n",pathBuff); + result = false; + + } while (false); + + + if (!result) + { + //qtss_printf("CheckFileAccess failed for %s on file %s\n",nameStrPtr->Ptr, pathBuff); + } + else + { + //qtss_printf("success on file %s for %s\n",pathBuff, nameStrPtr->Ptr); + } + + return result; +} + +Bool16 CheckDirAccess(struct passwd *passwdStructPtr, StrPtrLen */*nameStrPtr*/, char* pathBuff) +{ + Bool16 result = true; + + char searchBuffer[kMaxPathLen] = {}; + StrPtrLen searchPath(searchBuffer, kMaxPathLen -1); + StrPtrLen thePath(pathBuff); + +/* + for each directory in path until fail + if the user is the owner then + if owner access then succeed-continue + else fail - stop + + if user is in group then + if group access then succeed-continue + else fail - stop + + if guest access then + succeed - continue + else fail - stop +*/ + + if (thePath.Len <= searchPath.Len) + { + memcpy(searchBuffer,thePath.Ptr, thePath.Len); + + while ( (true == result) && (0 != GetPathParentDestructive(&searchPath, &searchPath, kMaxPathLen)) ) // loop until fail or have checked full directory path and file + { + result = false; + + do // once only check for the current entity + { + struct stat statData; + + int statResult = stat(searchBuffer,&statData); + + if (0 != statResult) + { + //qtss_printf("no access to path =%s\n",searchBuffer); + result = true; // let the error be handled in the server + break; // allow + } + + + + if ( statData.st_uid == (UInt16) passwdStructPtr->pw_uid) // check if owner + { + if ( (statData.st_mode & 0100 ) != 0 ) // has owner search access + { //qtss_printf("owner search access to directory =%s\n",pathBuff); + result = true; + break; + } + else + { //qtss_printf("no owner search access to directory =%s\n",pathBuff); + result = false; + break; + + } + } + + + if (statData.st_gid == (UInt16) passwdStructPtr->pw_gid) // check if user's default group owns + { + if ( (statData.st_mode & 0010) != 0 ) // has group search access + { //qtss_printf("user default group has search access to directory =%s\n",pathBuff); + result = true; + break; + } + else + { //qtss_printf("user default group has no search access to directory =%s\n",pathBuff); + result = false; + break; + } + + } + + if (IsUserMember(passwdStructPtr->pw_uid, statData.st_gid)) // check if user in group + { + if ( (statData.st_mode & 0010) != 0 ) // has group search access + { //qtss_printf("user member group has search access to directory =%s\n",pathBuff); + result = true; + break; + } + else + { //qtss_printf("user member group has no search access to directory =%s\n",pathBuff); + result = false; + break; + } + + } + + if ( (statData.st_mode & 0001) != 0 ) // has world search access + { + //qtss_printf("world has search access to directory =%s\n",pathBuff); + result = true; + break; + + } + + //qtss_printf("world has no search access to directory =%s\n",pathBuff); + result = false; + + + } while (false); + + } + + } + + + if (!result) + { + //qtss_printf("CheckDirAccess failed for %s on file %s\n",nameStrPtr->Ptr, searchBuffer); + } + else + { + //qtss_printf("success on file %s for %s\n",searchBuffer, nameStrPtr->Ptr); + } + + return result; +} + + +Bool16 FileExists(char* pathBuff) +{ + struct stat statData; + Bool16 result = true; + + if (0 != stat(pathBuff,&statData) ) // something wrong + { + if ( OSThread::GetErrno() == ENOENT ) // doesn't exist + result = false; + } + + return result; + +} + + + +Bool16 CheckWorldFileAccess(char* pathBuff) +{ + + Bool16 result = false; + struct stat statData; + + do // once only check for the current entity + { + //qtss_printf("stat on %s \n",pathBuff); + if (0 != stat(pathBuff,&statData)) + { + //qtss_printf("no access to path =%s\n",pathBuff); + result = true; // let the server deal with this one + break; + } + + //qtss_printf("statData.st_mode = %x \n",statData.st_mode); + if (0 == (statData.st_mode & 0004) ) // world read access + { + //qtss_printf("no world access to path =%s\n",pathBuff); + break; + } + + result = true; + + + } while (false); + + + return result; +} + +Bool16 CheckWorldDirAccess(char* pathBuff) +{ + + Bool16 result = true; + struct stat statData; + char searchBuffer[kMaxPathLen] = {}; + StrPtrLen searchPath(searchBuffer, kMaxPathLen -1); + StrPtrLen thePath(pathBuff); + + if (thePath.Len <= searchPath.Len) + { + memcpy(searchBuffer,thePath.Ptr, thePath.Len); + + while ( (true == result) && (0 != GetPathParentDestructive(&searchPath, &searchPath, kMaxPathLen)) ) // loop until fail or have checked full directory path and file + { + result = false; + + do // once only check for the current entity + { + //qtss_printf("stat on %s \n",searchBuffer); + if (0 != stat(searchBuffer,&statData)) + { + //qtss_printf("no world access to path =%s\n",searchBuffer); + result = true; // let the server deal with this one + break; + } + + //qtss_printf("statData.st_mode = %x \n",statData.st_mode); + if (0 == (statData.st_mode & 0001) ) + { + //qtss_printf("no world access to path =%s\n",searchBuffer); + result = false; + break; + } + + result = true; + + + } while (false); + } ; + } + + return result; +} + + +Bool16 QTSSAuthorize(QTSS_StandardRTSP_Params* inParams, char* pathBuff) +{ + QTSS_Error theErr = QTSS_NoErr; + Bool16 result = false; + + const int kBuffLen = 256; + char passwordBuff[kBuffLen] = {}; + char nameBuff[kBuffLen] = {}; + StrPtrLen nameStr(nameBuff, kBuffLen -1); + StrPtrLen passwordStr(passwordBuff, kBuffLen -1); + Bool16 noUserName = false; + Bool16 noPassword = false; + Bool16 isSpecialGuest = false; + + do + { + + theErr = QTSS_GetValue (inParams->inRTSPRequest,qtssRTSPReqUserName,0, (void *) nameStr.Ptr, &nameStr.Len); + //qtss_printf("GetValue qtssRTSPReqUserName err =%"_S32BITARG_" \n",theErr); + + if ( (theErr != QTSS_NoErr) || (nameStr.Len == 0) || (nameStr.Ptr == NULL) || (*nameStr.Ptr == '\0')) + { + //qtss_printf ("no user name\n"); + noUserName = true; + } + + //qtss_printf("RTSPRequest dictionary name =%s len = %"_S32BITARG_"\n",nameStr.Ptr, nameStr.Len); + + theErr = QTSS_GetValue (inParams->inRTSPRequest,qtssRTSPReqUserPassword,0, (void *) passwordStr.Ptr, &passwordStr.Len); + //qtss_printf("GetValue qtssRTSPReqUserName err =%"_S32BITARG_" \n",theErr); + if ( (theErr != QTSS_NoErr) || (passwordStr.Len == 0) || (passwordStr.Ptr == NULL) || (*passwordStr.Ptr == '\0')) + { + //qtss_printf ("no Password\n"); + noPassword = true; + } + //qtss_printf("RTSPRequest dictionary password =%s len = %"_S32BITARG_" \n",passwordStr.Ptr, passwordStr.Len); + + if (noUserName && noPassword) isSpecialGuest = true; + + if (isSpecialGuest) // no name and no password means guest + { + //qtss_printf ("User is guest check for access\n"); + + result = CheckWorldDirAccess(pathBuff); + if (true == result) + result = CheckWorldFileAccess(pathBuff); + + break; // no further processing on guest + } + + if (0 == strcasecmp(nameStr.Ptr, sRootUserPtr) ) + { //qtss_printf("user is root no root access to file =%s\n",pathBuff); // must log + result = false; // don't allow + break; + } + + struct passwd *passwdStructPtr = getpwnam(nameStr.Ptr); + if (NULL == passwdStructPtr) + { + //qtss_printf("failed to find name =%s\n",passwordStr.Ptr); + break; + } + + char *theCryptedPassword = crypt(passwordStr.Ptr, passwdStructPtr->pw_passwd); + if ( 0 != strcmp(passwdStructPtr->pw_passwd, theCryptedPassword ) ) + { + //qtss_printf("failed to match name to password =%s\n",passwordStr.Ptr); + break; + } + + result = CheckDirAccess(passwdStructPtr, &nameStr, pathBuff); + if (!result) break; + + result = CheckFileAccess(passwdStructPtr, &nameStr, pathBuff); + if (!result) break; + + //qtss_printf("QTSSAuthorize: user %s is authorized for %s\n",nameStr.Ptr,pathBuff); + + + } while (false); + + if (!result) + { //qtss_printf("QTSSAuthorize: user %s is un authorized for %s\n",nameStr.Ptr,pathBuff); + } + + return result; +} + +Bool16 FileOrSDPFileExists(char *pathBuff, UInt32 *pathLen, const UInt32 maxLen, QTSS_Error *theErrPtr) +{ + Bool16 result = false; + + do + { + if (FileExists(pathBuff)) + { + //qtss_printf("file exists =%s\n",pathBuff); + result = true; + break; // file is there + } + + if ( (*pathLen + sSDPSuffix.Len + 1) >= maxLen) + { + //qtss_printf("buffer too small for path\n"); + if (theErrPtr) *theErrPtr = ENAMETOOLONG; // don't allow request to succed we can't authorize + break; + } + + strcat(pathBuff, sSDPSuffix.Ptr); + //qtss_printf("sdp path =%s\n",pathBuff); + + if (FileExists(pathBuff)) + { + //qtss_printf("sdp file exists =%s\n",pathBuff); + result = true; + break; // sdp file is there + } + + } while (false); + + return result; + +} + + + +QTSS_Error AuthenticateRTSPRequest(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_Error theErr = QTSS_NoErr; + OSMutexLocker locker(sUserMutex); + +/* + + Notes: + pathBuff is cleared so Getting the path should leave a zero terminated string. + + Order is important. + 1) Get the path + 2) See if the path exists or an sdp path based on the path exists + 3) if world access on the path is available, allow the request + 4) if non-world access on the path or file check the name and password on the path and file + +*/ + + do + { + if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) ) + { + theErr = QTSS_RequestFailed; + break; + } + + UInt32 pathLen = kMaxPathLen -1; + char pathBuff[kMaxPathLen] = {}; + QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; + + theErr = QTSS_GetValue (theRTSPRequest,qtssRTSPReqLocalPath,0, (void *) pathBuff, &pathLen); + if ( (theErr != QTSS_NoErr) || (0 == pathLen) ) + { + //qtss_printf("path not found in request\n"); + break; // Bail on the request. The Server will handle the error + } + + Bool16 fileOk = FileOrSDPFileExists(pathBuff, &pathLen, kMaxPathLen, &theErr); + if (!fileOk) + { + //qtss_printf("file not found\n"); + break; // Do nothing. The Server will handle the error + } + + + Bool16 allowRequest = QTSSAuthorize(inParams, pathBuff); + if (allowRequest) + { + //qtss_printf("user is authorized\n"); + break; // Do nothing. Allow the request (the default behavior) + } + + // We are not allowing the request so pass false back to the server. + theErr = QTSS_SetValue(theRTSPRequest,qtssRTSPReqUserAllowed, 0, &allowRequest, sizeof(allowRequest)); + if (theErr != QTSS_NoErr) break; + + #if 0 // test setting a specific realm this is not used in this module + theErr = QTSS_SetValue(theRTSPRequest,qtssRTSPReqURLRealm, 0, sDefaultRealm.Ptr, sDefaultRealm.Len); + if (theErr != QTSS_NoErr) break; + #endif + + } while (false); + + if (theErr) + { + Assert(0); + theErr = QTSS_RequestFailed; + } + + + return theErr; +} + + + + + diff --git a/APIModules/QTSSFilePrivsModule.bproj/QTSSFilePrivsModule.h b/APIModules/QTSSFilePrivsModule.bproj/QTSSFilePrivsModule.h new file mode 100644 index 0000000..4c2457d --- /dev/null +++ b/APIModules/QTSSFilePrivsModule.bproj/QTSSFilePrivsModule.h @@ -0,0 +1,46 @@ +/* + * + * @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: QTSSFILEPRIVSMODULE.h + + Contains: Module that handles and file system authorization + + + +*/ + +#ifndef _QTSSFILEPRIVSMODULE_H_ +#define _QTSSFILEPRIVSMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSFilePrivsModule_Main(void* inPrivateArgs); +} + +#endif //_QTSSFILEPRIVSMODULE_H_ + + diff --git a/APIModules/QTSSFlowControlModule/QTSSFlowControlModule.cpp b/APIModules/QTSSFlowControlModule/QTSSFlowControlModule.cpp new file mode 100644 index 0000000..31ef5ed --- /dev/null +++ b/APIModules/QTSSFlowControlModule/QTSSFlowControlModule.cpp @@ -0,0 +1,458 @@ +/* + * + * @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: QTSSFlowControlModule.cpp + + Contains: Implements object defined in .h file + + + + +*/ + +#include + +#include "QTSSFlowControlModule.h" +#include "OSHeaders.h" +#include "QTSSModuleUtils.h" +#include "MyAssert.h" + +//Turns on printfs that are useful for debugging +#define FLOW_CONTROL_DEBUGGING 0 + +// ATTRIBUTES IDs + +static QTSS_AttributeID sNumLossesAboveTolAttr = qtssIllegalAttrID; +static QTSS_AttributeID sNumLossesBelowTolAttr = qtssIllegalAttrID; +static QTSS_AttributeID sNumWorsesAttr = qtssIllegalAttrID; + +// STATIC VARIABLES + +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; +static QTSS_ServerObject sServer = NULL; + +// Default values for preferences +static UInt32 sDefaultLossThinTolerance = 30; +static UInt32 sDefaultNumLossesToThin = 3; +static UInt32 sDefaultLossThickTolerance = 5; +static UInt32 sDefaultLossesToThick = 6; +static UInt32 sDefaultWorsesToThin = 2; +static Bool16 sDefaultModuleEnabled = true; + +// Current values for preferences +static UInt32 sLossThinTolerance = 30; +static UInt32 sNumLossesToThin = 3; +static UInt32 sLossThickTolerance = 5; +static UInt32 sLossesToThick = 6; +static UInt32 sWorsesToThin = 2; +static Bool16 sModuleEnabled = true; + +// Server preference we respect +static Bool16 sDisableThinning = false; + + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSFlowControlModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error RereadPrefs(); +static QTSS_Error ProcessRTCPPacket(QTSS_RTCPProcess_Params* inParams); +static void InitializeDictionaryItems(QTSS_RTPStreamObject inStream); + + + + +QTSS_Error QTSSFlowControlModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSFlowControlModuleDispatch); +} + +QTSS_Error QTSSFlowControlModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParamBlock->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParamBlock->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTCPProcess_Role: + return ProcessRTCPPacket(&inParamBlock->rtcpProcessParams); + } + return QTSS_NoErr; +} + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RTCPProcess_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + + + // Add other attributes + static char* sNumLossesAboveToleranceName = "QTSSFlowControlModuleLossAboveTol"; + static char* sNumLossesBelowToleranceName = "QTSSFlowControlModuleLossBelowTol"; + static char* sNumGettingWorsesName = "QTSSFlowControlModuleGettingWorses"; + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sNumLossesAboveToleranceName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sNumLossesAboveToleranceName, &sNumLossesAboveTolAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sNumLossesBelowToleranceName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sNumLossesBelowToleranceName, &sNumLossesBelowTolAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sNumGettingWorsesName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sNumGettingWorsesName, &sNumWorsesAttr); + + // Tell the server our name! + static char* sModuleName = "QTSSFlowControlModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sServer = inParams->inServer; + sServerPrefs = inParams->inPrefs; + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + return RereadPrefs(); +} + + +QTSS_Error RereadPrefs() +{ + // + // Use the standard GetPref routine to retrieve the correct values for our preferences + QTSSModuleUtils::GetAttribute(sPrefs, "loss_thin_tolerance", qtssAttrDataTypeUInt32, + &sLossThinTolerance, &sDefaultLossThinTolerance, sizeof(sLossThinTolerance)); + QTSSModuleUtils::GetAttribute(sPrefs, "num_losses_to_thin", qtssAttrDataTypeUInt32, + &sNumLossesToThin, &sDefaultNumLossesToThin, sizeof(sNumLossesToThin)); + QTSSModuleUtils::GetAttribute(sPrefs, "loss_thick_tolerance", qtssAttrDataTypeUInt32, + &sLossThickTolerance, &sDefaultLossThickTolerance, sizeof(sLossThickTolerance)); + QTSSModuleUtils::GetAttribute(sPrefs, "num_losses_to_thick", qtssAttrDataTypeUInt32, + &sLossesToThick, &sDefaultLossesToThick, sizeof(sLossesToThick)); + QTSSModuleUtils::GetAttribute(sPrefs, "num_worses_to_thin", qtssAttrDataTypeUInt32, + &sWorsesToThin, &sDefaultWorsesToThin, sizeof(sWorsesToThin)); + + QTSSModuleUtils::GetAttribute(sPrefs, "flow_control_udp_thinning_module_enabled", qtssAttrDataTypeBool16, + &sModuleEnabled, &sDefaultModuleEnabled, sizeof(sDefaultModuleEnabled)); + + UInt32 len = sizeof(sDisableThinning); + (void) QTSS_GetValue(sServerPrefs, qtssPrefsDisableThinning, 0, (void*)&sDisableThinning, &len); + + return QTSS_NoErr; +} + + + +Bool16 Is3GPPSession(QTSS_RTCPProcess_Params *inParams) +{ + + Bool16 is3GPP = false; + UInt32 theLen = sizeof(is3GPP); + (void)QTSS_GetValue(inParams->inClientSession, qtssCliSessIs3GPPSession, 0, (void*)&is3GPP, &theLen); + + return is3GPP; +} + + +QTSS_Error ProcessRTCPPacket(QTSS_RTCPProcess_Params* inParams) +{ + if (!sModuleEnabled || sDisableThinning || Is3GPPSession(inParams) ) + { + //qtss_printf("QTSSFlowControlModule.cpp:ProcessRTCPPacket processing disabled sModuleEnabled=%d sDisableThinning=%d Is3GPPSession(inParams)=%d\n", sModuleEnabled, sDisableThinning,Is3GPPSession(inParams)); + return QTSS_NoErr; + } + +#if FLOW_CONTROL_DEBUGGING + QTSS_RTPPayloadType* thePayloadType = 0; + UInt32 thePayloadLen = 0; + (void)QTSS_GetValuePtr(inParams->inRTPStream, qtssRTPStrPayloadType, 0, (void**)&thePayloadType, &thePayloadLen); + + if ((*thePayloadType != 0) && (*thePayloadType == qtssVideoPayloadType)) + qtss_printf("Video track reporting:\n"); + else if ((*thePayloadType != 0) && (*thePayloadType == qtssAudioPayloadType)) + qtss_printf("Audio track reporting:\n"); + else + qtss_printf("Unknown track reporting\n"); +#endif + + // + // Find out if this is a qtssRTPTransportTypeUDP. This is the only type of + // transport we should monitor + QTSS_RTPTransportType theTransportType = qtssRTPTransportTypeUDP; + UInt32 theLen = sizeof(theTransportType); + QTSS_Error theErr = QTSS_GetValue(inParams->inRTPStream, qtssRTPStrTransportType, 0, (void*)&theTransportType, &theLen); + Assert(theErr == QTSS_NoErr); + + if (theTransportType != qtssRTPTransportTypeUDP) + return QTSS_NoErr; + + //ALGORITHM FOR DETERMINING WHEN TO MAKE QUALITY ADJUSTMENTS IN THE STREAM: + + //This routine makes quality adjustment determinations for the server. It is designed + //to be flexible: you may swap this algorithm out for another implemented in another module, + //and this algorithm uses settings that are adjustable at runtime. + + //It uses the % loss statistic in the RTCP packets, as well as the "getting better" & + //"getting worse" fields. + + //Less bandwidth will be served if the loss % of N number of RTCP packets is above M, where + //N and M are runtime settings. + + //Less bandwidth will be served if "getting worse" is reported N number of times. + + //More bandwidth will be served if the loss % of N number of RTCPs is below M. + //N will scale up over time. + + //More bandwidth will be served if the client reports "getting better" + + //If the initial values of our dictionary items aren't yet in, put them in. + InitializeDictionaryItems(inParams->inRTPStream); + + QTSS_RTPStreamObject theStream = inParams->inRTPStream; + + Bool16 ratchetMore = false; + Bool16 ratchetLess = false; + + Bool16 clearPercentLossThinCount = true; + Bool16 clearPercentLossThickCount = true; + + UInt32* uint32Ptr = NULL; + UInt16* uint16Ptr = NULL; + theLen = 0; + + UInt32 theNumLossesAboveTol = 0; + UInt32 theNumLossesBelowTol = 0; + UInt32 theNumWorses = 0; + + // Get our current counts for this stream. If any of these aren't present, something is seriously wrong + // with this dictionary, so we should probably just abort + (void)QTSS_GetValuePtr(theStream, sNumLossesAboveTolAttr, 0, (void**)&uint32Ptr, &theLen); + if ((uint32Ptr != NULL) && (theLen == sizeof(UInt32))) + theNumLossesAboveTol = *uint32Ptr; + + (void)QTSS_GetValuePtr(theStream, sNumLossesBelowTolAttr, 0, (void**)&uint32Ptr, &theLen); + if ((uint32Ptr != NULL) && (theLen == sizeof(UInt32))) + theNumLossesBelowTol = *uint32Ptr; + + (void)QTSS_GetValuePtr(theStream, sNumWorsesAttr, 0, (void**)&uint32Ptr, &theLen); + if ((uint32Ptr != NULL) && (theLen == sizeof(UInt32))) + theNumWorses = *uint32Ptr; + + + //First take any action necessitated by the loss percent + (void)QTSS_GetValuePtr(inParams->inRTPStream, qtssRTPStrPercentPacketsLost, 0, (void**)&uint16Ptr, &theLen); + if ((uint16Ptr != NULL) && (theLen == sizeof(UInt16))) + { + UInt16 thePercentLoss = *uint16Ptr; + thePercentLoss /= 256; //Hmmm... looks like the client reports loss percent in multiples of 256 +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Percent loss: %d\n", thePercentLoss); +#endif + + //check for a thinning condition + if (thePercentLoss > sLossThinTolerance) + { + theNumLossesAboveTol++;//we must count this loss + + //We only adjust after a certain number of these in a row. Check to see if we've + //satisfied the thinning condition, and adjust the count + if (theNumLossesAboveTol >= sNumLossesToThin) + { +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Percent loss too high: ratcheting less\n"); +#endif + ratchetLess = true; + } + else + { +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Percent loss too high: Incrementing percent loss count to %"_U32BITARG_"\n", theNumLossesAboveTol); +#endif + (void)QTSS_SetValue(theStream, sNumLossesAboveTolAttr, 0, &theNumLossesAboveTol, sizeof(theNumLossesAboveTol)); + clearPercentLossThinCount = false; + } + } + //check for a thickening condition + else if (thePercentLoss < sLossThickTolerance) + { + theNumLossesBelowTol++;//we must count this loss + if (theNumLossesBelowTol >= sLossesToThick) + { +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Percent is low: ratcheting more\n"); +#endif + ratchetMore = true; + } + else + { +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Percent is low: Incrementing percent loss count to %"_U32BITARG_"\n", theNumLossesBelowTol); +#endif + (void)QTSS_SetValue(theStream, sNumLossesBelowTolAttr, 0, &theNumLossesBelowTol, sizeof(theNumLossesBelowTol)); + clearPercentLossThickCount = false; + } + } + } + + //Now take a look at the getting worse heuristic + (void)QTSS_GetValuePtr(inParams->inRTPStream, qtssRTPStrGettingWorse, 0, (void**)&uint16Ptr, &theLen); + if ((uint16Ptr != NULL) && (theLen == sizeof(UInt16))) + { + UInt16 isGettingWorse = *uint16Ptr; + if (isGettingWorse != 0) + { + theNumWorses++;//we must count this getting worse + + //If we've gotten N number of getting worses, then thin. Otherwise, just + //increment our count of getting worses + if (theNumWorses >= sWorsesToThin) + { +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Client reporting getting worse. Ratcheting less\n"); +#endif + ratchetLess = true; + } + else + { +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Client reporting getting worse. Incrementing num worses count to %"_U32BITARG_"\n", theNumWorses); +#endif + (void)QTSS_SetValue(theStream, sNumWorsesAttr, 0, &theNumWorses, sizeof(theNumWorses)); + } + } + } + + //Finally, if we get a getting better, automatically ratchet up + (void)QTSS_GetValuePtr(inParams->inRTPStream, qtssRTPStrGettingBetter, 0, (void**)&uint16Ptr, &theLen); + if ((uint16Ptr != NULL) && (theLen == sizeof(UInt16)) && (*uint16Ptr > 0)) + ratchetMore = true; + + //For clearing out counts below + UInt32 zero = 0; + + //Based on the ratchetMore / ratchetLess variables, adjust the stream + if (ratchetMore || ratchetLess) + { + + UInt32 curQuality = 0; + (void)QTSS_GetValuePtr(theStream, qtssRTPStrQualityLevel, 0, (void**)&uint32Ptr, &theLen); + if ((uint32Ptr != NULL) && (theLen == sizeof(UInt32))) + curQuality = *uint32Ptr; + + UInt32 numQualityLevels = 0; + (void)QTSS_GetValuePtr(theStream, qtssRTPStrNumQualityLevels, 0, (void**)&uint32Ptr, &theLen); + if ((uint32Ptr != NULL) && (theLen == sizeof(UInt32))) + numQualityLevels = *uint32Ptr; + + if ((ratchetLess) && (curQuality < numQualityLevels)) + { + curQuality++; + if (curQuality > 1) // v3.0.1=v2.0.1 make level 2 means key frames in the file or max if reflected. + curQuality = numQualityLevels; + (void)QTSS_SetValue(theStream, qtssRTPStrQualityLevel, 0, &curQuality, sizeof(curQuality)); + } + else if ((ratchetMore) && (curQuality > 0)) + { + curQuality--; + if (curQuality > 1) // v3.0.1=v2.0.1 make level 2 means key frames in the file or max if reflected. + curQuality = 1; + (void)QTSS_SetValue(theStream, qtssRTPStrQualityLevel, 0, &curQuality, sizeof(curQuality)); + } + + + Bool16 *startedThinningPtr = NULL; + SInt32 numThinned = 0; + (void)QTSS_GetValuePtr(inParams->inClientSession, qtssCliSesStartedThinning, 0, (void**)&startedThinningPtr, &theLen); + if (false == *startedThinningPtr) + { + (void) QTSS_LockObject(sServer); + *startedThinningPtr = true; + + (void)QTSS_GetValue(sServer, qtssSvrNumThinned, 0, (void*)&numThinned, &theLen); + numThinned++; + (void)QTSS_SetValue(sServer, qtssSvrNumThinned, 0, &numThinned, sizeof(numThinned)); + (void) QTSS_UnlockObject(sServer); + } + else if (curQuality == 0) + { + (void) QTSS_LockObject(sServer); + *startedThinningPtr = false; + + (void)QTSS_GetValue(theStream, qtssSvrNumThinned, 0, (void*)&numThinned, &theLen); + numThinned--; + (void)QTSS_SetValue(sServer, qtssSvrNumThinned, 0, &numThinned, sizeof(numThinned)); + (void) QTSS_UnlockObject(sServer); + } + //When adjusting the quality, ALWAYS clear out ALL our counts of EVERYTHING. Note + //that this is the ONLY way that the fNumGettingWorses count gets cleared + (void)QTSS_SetValue(theStream, sNumWorsesAttr, 0, &zero, sizeof(zero)); +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Clearing num worses count\n"); +#endif + clearPercentLossThinCount = true; + clearPercentLossThickCount = true; + } + + //clear thick / thin counts if we are supposed to. + if (clearPercentLossThinCount) + { +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Clearing num losses above tolerance count\n"); +#endif + (void)QTSS_SetValue(theStream, sNumLossesAboveTolAttr, 0, &zero, sizeof(zero)); + } + if (clearPercentLossThickCount) + { +#if FLOW_CONTROL_DEBUGGING + qtss_printf("Clearing num losses below tolerance count\n"); +#endif + + (void)QTSS_SetValue(theStream, sNumLossesBelowTolAttr, 0, &zero, sizeof(zero)); + } + return QTSS_NoErr; +} + +void InitializeDictionaryItems(QTSS_RTPStreamObject inStream) +{ + UInt32* theValue = NULL; + UInt32 theValueLen = 0; + + QTSS_Error theErr = QTSS_GetValuePtr(inStream, sNumLossesAboveTolAttr, 0, (void**)&theValue, &theValueLen); + + if (theErr != QTSS_NoErr) + { + // The dictionary parameters haven't been initialized yet. Just set them all to 0. + (void)QTSS_SetValue(inStream, sNumLossesAboveTolAttr, 0, &theValueLen, sizeof(theValueLen)); + (void)QTSS_SetValue(inStream, sNumLossesBelowTolAttr, 0, &theValueLen, sizeof(theValueLen)); + (void)QTSS_SetValue(inStream, sNumWorsesAttr, 0, &theValueLen, sizeof(theValueLen)); + } +} diff --git a/APIModules/QTSSFlowControlModule/QTSSFlowControlModule.h b/APIModules/QTSSFlowControlModule/QTSSFlowControlModule.h new file mode 100644 index 0000000..4db756a --- /dev/null +++ b/APIModules/QTSSFlowControlModule/QTSSFlowControlModule.h @@ -0,0 +1,46 @@ +/* + * + * @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: QTSSFlowControlModule.h + + Contains: Uses information in RTCP packers to throttle back the server + when it's pumping out too much data to a given client + + + + +*/ + +#ifndef _QTSSFLOWCONTROLMODULE_H_ +#define _QTSSFLOWCONTROLMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSFlowControlModule_Main(void* inPrivateArgs); +} + +#endif //_QTSSFLOWCONTROLMODULE_H_ diff --git a/APIModules/QTSSHomeDirectoryModule/DirectoryInfo.cpp b/APIModules/QTSSHomeDirectoryModule/DirectoryInfo.cpp new file mode 100644 index 0000000..0f11369 --- /dev/null +++ b/APIModules/QTSSHomeDirectoryModule/DirectoryInfo.cpp @@ -0,0 +1,123 @@ +/* + * + * @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: DirectoryInfo.cpp + + Contains: Implementation of class defined in .h file + + +*/ + +#include "DirectoryInfo.h" + +Bool16 SessionListElement::Equal(QTSS_ClientSessionObject* inSessionPtr) +{ + UInt32 *theSessionID = 0; + UInt32 *inSessionID = 0; + UInt32 theLen = 0; + + (void)QTSS_GetValuePtr(fSession, qtssCliSesCounterID, 0, (void **)&theSessionID, &theLen); + Assert(theLen != 0); + (void)QTSS_GetValuePtr(*inSessionPtr, qtssCliSesCounterID, 0, (void **)&inSessionID, &theLen); + Assert(theLen != 0); + + if (*theSessionID == *inSessionID) + return true; + + return false; +} + +UInt32 SessionListElement::CurrentBitRate() +{ + UInt32 *theBitRate = 0; + UInt32 theLen = 0; + + (void)QTSS_GetValuePtr(fSession, qtssCliSesCurrentBitRate, 0, (void **)&theBitRate, &theLen); + Assert(theLen != 0); + + return *theBitRate; +} + +static bool IsSession(PLDoubleLinkedListNode* node, void* userData) +{ + /* + used by ForEachUntil to find a SessionListElement with a given Session + userData is a pointer to the QTSS_ClientSessionObject we want to find + + */ + return node->fElement->Equal((QTSS_ClientSessionObject *)userData); +} + +static void AddCurrentBitRate(PLDoubleLinkedListNode* node, void* userData) +{ + *(UInt64 *)userData += node->fElement->CurrentBitRate(); +} + +DirectoryInfo::~DirectoryInfo() +{ + fMutex.Lock(); + fClientSessionList->ClearList(); + fNumSessions = 0; + fHomeDir.Delete(); + fMutex.Unlock(); +} + +void DirectoryInfo::AddSession(QTSS_ClientSessionObject *sessionPtr) +{ + fMutex.Lock(); + + SessionListElement *theElement = NEW SessionListElement(sessionPtr); + PLDoubleLinkedListNode *sessionNode = new PLDoubleLinkedListNode (theElement); + fClientSessionList->AddNode(sessionNode); + fNumSessions++; + + fMutex.Unlock(); +} + +void DirectoryInfo::RemoveSession(QTSS_ClientSessionObject *sessionPtr) +{ + fMutex.Lock(); + + PLDoubleLinkedListNode *node = NULL; + + node = fClientSessionList->ForEachUntil(IsSession, (void *)sessionPtr); + if (node != NULL) + { + fClientSessionList->RemoveNode(node); + fNumSessions--; + } + + fMutex.Unlock(); +} + +UInt64 DirectoryInfo::CurrentTotalBandwidthInKbps() +{ + fMutex.Lock(); + UInt64 totalBandwidth = 0; + fClientSessionList->ForEach(AddCurrentBitRate, &totalBandwidth); + fMutex.Unlock(); + + return (totalBandwidth/1024); +} diff --git a/APIModules/QTSSHomeDirectoryModule/DirectoryInfo.h b/APIModules/QTSSHomeDirectoryModule/DirectoryInfo.h new file mode 100644 index 0000000..e53f98c --- /dev/null +++ b/APIModules/QTSSHomeDirectoryModule/DirectoryInfo.h @@ -0,0 +1,79 @@ +/* + * + * @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: DirectoryInfo.h + + Contains: Stores an array of client sessions, # of client sessions, + and the home directory + + +*/ +#ifndef _DIRECTORYINFO_H_ +#define _DIRECTORYINFO_H_ +#include "QTSS.h" +#include "StrPtrLen.h" +#include "OSRef.h" +#include "OSMemory.h" +#include "PLDoubleLinkedList.h" + +class SessionListElement { + public: + SessionListElement(QTSS_ClientSessionObject *inSessionPtr) { fSession = *inSessionPtr; } + + virtual ~SessionListElement() { fSession = NULL; } + + Bool16 Equal(QTSS_ClientSessionObject* inSessionPtr); + UInt32 CurrentBitRate(); + + private: + QTSS_ClientSessionObject fSession; +}; + +class DirectoryInfo +{ + public: + DirectoryInfo(StrPtrLen *inHomeDir):fNumSessions(0), fHomeDir(inHomeDir->GetAsCString()) + { + fClientSessionList = NEW PLDoubleLinkedList; + fRef.Set(fHomeDir, (void *)this); + } + + ~DirectoryInfo(); + OSRef* GetRef() { return &fRef; } + void AddSession(QTSS_ClientSessionObject *sessionPtr); + void RemoveSession(QTSS_ClientSessionObject *sessionPtr); + UInt64 CurrentTotalBandwidthInKbps(); + UInt32 NumSessions() { return fNumSessions; } + + private: + OSRef fRef; + OSMutex fMutex; + PLDoubleLinkedList *fClientSessionList; + UInt32 fNumSessions; + StrPtrLen fHomeDir; +}; + + +#endif // _DIRECTORYINFO_H_ diff --git a/APIModules/QTSSHomeDirectoryModule/QTSSHomeDirectoryModule.cpp b/APIModules/QTSSHomeDirectoryModule/QTSSHomeDirectoryModule.cpp new file mode 100644 index 0000000..e3984c4 --- /dev/null +++ b/APIModules/QTSSHomeDirectoryModule/QTSSHomeDirectoryModule.cpp @@ -0,0 +1,416 @@ +/* + * + * @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: QTSSHomeDirectoryModule.cpp + + Contains: Module that expands ~ in request URLs to home directories + + */ + +#include +#include +#include "SafeStdLib.h" +#include +#include +#include +#include +#include "OSMemory.h" +#include "StringParser.h" +#include "ResizeableStringFormatter.h" +#include "QTSSModuleUtils.h" +#include "OSArrayObjectDeleter.h" +#include "DirectoryInfo.h" +#include "QTSSMemoryDeleter.h" + +#include "QTSSHomeDirectoryModule.h" + +#define HOME_DIRECTORY_MODULE_DEBUGGING 0 + +// STATIC DATA +static QTSS_ServerObject sServer = NULL; +static QTSS_ModuleObject sModule = NULL; +static QTSS_ModulePrefsObject sPrefs = NULL; + +// Attributes +static char* sIsFirstRequestName = "QTSSHomeDirectoryModuleIsFirstRequest"; +static QTSS_AttributeID sIsFirstRequestAttr = qtssIllegalAttrID; +static char* sRequestHomeDirAttrName = "QTSSHomeDirectoryModuleHomeDir"; +static QTSS_AttributeID sRequestHomeDirAttr = qtssIllegalAttrID; +static char* sSessionHomeDirAttrName = "QTSSHomeDirectoryModuleHomeDir"; +static QTSS_AttributeID sSessionHomeDirAttr = qtssIllegalAttrID; + +// Module description and version +static char* sDescription = "Provides support for streaming from users' home directories"; +static UInt32 sVersion = 0x00010000; + +// Module preferences and their defaults +static Bool16 sEnabled = false; +static Bool16 kDefaultEnabled = false; +static char* sMoviesDirectory = NULL; // Streaming dir in user's home dir +static char* kDefaultMoviesDirectory = "/Sites/Streaming/"; +static UInt32 sMaxNumConnsPerHomeDir = 0; //Max conns. allowed per home directory +static UInt32 kDefaultMaxNumConnsPerHomeDir = 0; // 0 => no limit enforced +static UInt32 sMaxBWInKbpsPerHomeDir = 0; // Max BW allowed per home directory +static UInt32 kDefaultMaxBWInKbpsPerHomeDir = 0; // 0 => no limit enforced + +enum { kDefaultHomeDirSize = 64 }; +static Bool16 sTrue = true; + +static OSRefTable* sDirectoryInfoMap = NULL; + +// FUNCTIONS +static QTSS_Error QTSSHomeDirectoryDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error RereadPrefs(); +static QTSS_Error RewriteRequestFilePathAndRootDir(QTSS_StandardRTSP_Params* inParams); +static void RewriteRootDir(QTSS_StandardRTSP_Params* inParams, StrPtrLen* inHomeDir); +static QTSS_Error AuthorizeRequest(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error RemoveClientSessionFromMap(QTSS_ClientSessionClosing_Params* inParams); + +// Helper functions +static Bool16 ExpandPath(const StrPtrLen *inPathPtr, StrPtrLen *outPathPtr); + +// FUNCTION IMPLEMENTATIONS +QTSS_Error QTSSHomeDirectoryModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSHomeDirectoryDispatch); +} + + +QTSS_Error QTSSHomeDirectoryDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPRoute_Role: + { + if (!sEnabled) + break; + return RewriteRequestFilePathAndRootDir(&inParams->rtspRouteParams); + } + case QTSS_RTSPAuthorize_Role: + { + if (!sEnabled) + break; + return AuthorizeRequest(&inParams->rtspAuthParams); + } + case QTSS_ClientSessionClosing_Role: + { + if (!sEnabled) + break; + return RemoveClientSessionFromMap(&inParams->clientSessionClosingParams); + } + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPRoute_Role); + (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); + (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); + + // Add an RTSP session attribute to track if the request is the first request of the session + // so that the bandwidth/# of connections quota check can be done only if it is the first request + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sIsFirstRequestName, NULL, qtssAttrDataTypeBool16); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sIsFirstRequestName, &sIsFirstRequestAttr); + + // Add an RTSP request object attribute for tracking the home directory it is being served from + (void)QTSS_AddStaticAttribute(qtssRTSPRequestObjectType, sRequestHomeDirAttrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRTSPRequestObjectType, sRequestHomeDirAttrName, &sRequestHomeDirAttr); + + // Add an RTP session object attribute for tracking the home directory it is being served from + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sSessionHomeDirAttrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sSessionHomeDirAttrName, &sSessionHomeDirAttr); + + + // Tell the server our name! + static char* sModuleName = "QTSSHomeDirectoryModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + // Get the server object + sServer = inParams->inServer; + + // Get our prefs object + sModule = inParams->inModule; + sPrefs = QTSSModuleUtils::GetModulePrefsObject(sModule); + + // Set our version and description + (void)QTSS_SetValue(sModule, qtssModDesc, 0, sDescription, ::strlen(sDescription)); + (void)QTSS_SetValue(sModule, qtssModVersion, 0, &sVersion, sizeof(sVersion)); + + RereadPrefs(); + + sDirectoryInfoMap = NEW OSRefTable(); + + return QTSS_NoErr; +} + + +QTSS_Error RereadPrefs() +{ + QTSSModuleUtils::GetAttribute(sPrefs, "enabled", qtssAttrDataTypeBool16, + &sEnabled, &kDefaultEnabled, sizeof(sEnabled)); + delete [] sMoviesDirectory; + sMoviesDirectory = QTSSModuleUtils::GetStringAttribute(sPrefs, "movies_directory", kDefaultMoviesDirectory); + QTSSModuleUtils::GetAttribute(sPrefs, "max_num_conns_per_home_directory", qtssAttrDataTypeUInt32, + &sMaxNumConnsPerHomeDir, &kDefaultMaxNumConnsPerHomeDir, sizeof(sMaxNumConnsPerHomeDir)); + QTSSModuleUtils::GetAttribute(sPrefs, "max_bandwidth_kbps_per_home_directory", qtssAttrDataTypeUInt32, + &sMaxBWInKbpsPerHomeDir, &kDefaultMaxBWInKbpsPerHomeDir, sizeof(sMaxBWInKbpsPerHomeDir)); + return QTSS_NoErr; +} + + +QTSS_Error RewriteRequestFilePathAndRootDir(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + + char* requestPathStr; + (void)QTSS_GetValueAsString(theRequest, qtssRTSPReqFilePath, 0, &requestPathStr); + QTSSCharArrayDeleter requestPathStrDeleter(requestPathStr); + StrPtrLen theRequestPath(requestPathStr); + StringParser theRequestPathParser(&theRequestPath); + + // request path begins with a '/' for ex. /mysample.mov or /~joe/mysample.mov + theRequestPathParser.Expect('/'); + + if (theRequestPathParser.PeekFast() == '~') + { + theRequestPathParser.Expect('~'); + + StrPtrLen theFirstPath; + theRequestPathParser.ConsumeUntil(&theFirstPath, '/'); + if (theFirstPath.Len != 0) // if the URI is /~/mysample.mov --> do nothing + { + StrPtrLen theHomeDir; + // ExpandPath allocates memory, so delete it before exiting + if (ExpandPath(&theFirstPath, &theHomeDir)) + { + // Rewrite the qtssRTSPReqRootDir attribute + RewriteRootDir(inParams, &theHomeDir); + + StrPtrLen theStrippedRequestPath; + theRequestPathParser.ConsumeLength(&theStrippedRequestPath, theRequestPathParser.GetDataRemaining()); + (void) QTSS_SetValue(theRequest, qtssRTSPReqFilePath, 0, theStrippedRequestPath.Ptr, theStrippedRequestPath.Len); + } + theHomeDir.Delete(); + } + } + + return QTSS_NoErr; +} + +void RewriteRootDir(QTSS_StandardRTSP_Params* inParams, StrPtrLen* inHomeDir) +{ + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + QTSS_ClientSessionObject theSession = inParams->inClientSession; + QTSS_Error theErr = QTSS_NoErr; + + Assert(inHomeDir->Len != 0); + if (inHomeDir->Len == 0) + return; + + char homeDirFormatterBuf[kDefaultHomeDirSize] = ""; + ResizeableStringFormatter theHomeDirFormatter(homeDirFormatterBuf, kDefaultHomeDirSize); + theHomeDirFormatter.Put(inHomeDir->Ptr, inHomeDir->Len); + // Append a path separator if the movies directory doesn't begin with it + if ((::strlen(sMoviesDirectory) != 0) && (sMoviesDirectory[0] != '/')) + theHomeDirFormatter.PutChar('/'); + // Append the movies_directory + theHomeDirFormatter.Put(sMoviesDirectory); + + StrPtrLen theRootDir(theHomeDirFormatter.GetBufPtr(), theHomeDirFormatter.GetBytesWritten()); + + // Set qtssRTSPReqRootDir + (void)QTSS_SetValue(theRequest, qtssRTSPReqRootDir, 0, theRootDir.Ptr, theRootDir.Len); + + // Set the client session's home dir attribute + (void)QTSS_SetValue(theSession, sSessionHomeDirAttr, 0, theRootDir.Ptr, theRootDir.Len); + + // If the request is a PLAY, then add this to the session list for the home directory + QTSS_RTSPMethod *theMethod = NULL; + UInt32 theLen = 0; + (void)QTSS_GetValuePtr(theRequest, qtssRTSPReqMethod, 0, (void **)&theMethod, &theLen); + if (*theMethod == qtssPlayMethod) + { + OSRef* theDirectoryInfoRef = sDirectoryInfoMap->Resolve(&theRootDir); + if (theDirectoryInfoRef == NULL) + { + DirectoryInfo* newDirInfo = NEW DirectoryInfo(&theRootDir); + theErr = sDirectoryInfoMap->Register(newDirInfo->GetRef()); + Assert(theErr == QTSS_NoErr); + theDirectoryInfoRef = sDirectoryInfoMap->Resolve(&theRootDir); + Assert (theDirectoryInfoRef == newDirInfo->GetRef()); + } + + DirectoryInfo* theDirInfo = (DirectoryInfo *)theDirectoryInfoRef->GetObject(); + theDirInfo->AddSession(&theSession); + sDirectoryInfoMap->Release(theDirectoryInfoRef); + } + +#if HOME_DIRECTORY_MODULE_DEBUGGING + char* newRequestPath = NULL; + theErr = QTSS_GetValueAsString (theRequest, qtssRTSPReqFilePath, 0, &newRequestPath); + qtss_printf("QTSSHomeDirectoryModule::RewriteRootDir qtssRTSPReqFilePath = %s\n", newRequestPath); + QTSS_Delete(newRequestPath); + + char* newRequestRootDir = NULL; + theErr = QTSS_GetValueAsString (theRequest, qtssRTSPReqRootDir, 0, &newRequestRootDir); + qtss_printf("QTSSHomeDirectoryModule::RewriteRootDir qtssRTSPReqRootDir = %s\n", newRequestRootDir); + QTSS_Delete(newRequestRootDir); +#endif + +} + + +QTSS_Error AuthorizeRequest(QTSS_StandardRTSP_Params* inParams) +{ + // Only do anything if this is the first request + Bool16 *isFirstRequest = NULL; + UInt32 theLen = sizeof(isFirstRequest); + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sIsFirstRequestAttr, 0, (void**)&isFirstRequest, &theLen); + if (isFirstRequest != NULL) + return QTSS_NoErr; + + OSRef *theDirectoryInfoRef = NULL; + DirectoryInfo *theDirInfo = NULL; + StrPtrLen theHomeDir; + + // if no limits are imposed, do nothing + if ((sMaxNumConnsPerHomeDir == 0) && (sMaxBWInKbpsPerHomeDir == 0)) + goto end_authorize; + + // Get this client session's home dir + (void)QTSS_GetValuePtr(inParams->inClientSession, sSessionHomeDirAttr, 0, (void**)&theHomeDir.Ptr, &theHomeDir.Len); + if ((theHomeDir.Ptr == NULL) || (theHomeDir.Len == 0)) // If it's NULL it's not served out of the home dir + goto end_authorize; + + // Get the sessions for this home dir + theDirectoryInfoRef = sDirectoryInfoMap->Resolve(&theHomeDir); + if (theDirectoryInfoRef == NULL) // No sessions exist yet for this homeDir + goto end_authorize; + + theDirInfo = (DirectoryInfo *)(theDirectoryInfoRef->GetObject()); + + if ((sMaxNumConnsPerHomeDir != 0) && ((theDirInfo->NumSessions()) >= sMaxNumConnsPerHomeDir)) + QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientNotEnoughBandwidth, qtssMsgTooManyClients); + else if((sMaxBWInKbpsPerHomeDir != 0) && ((theDirInfo->CurrentTotalBandwidthInKbps()) >= sMaxBWInKbpsPerHomeDir)) + QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientNotEnoughBandwidth, qtssMsgTooMuchThruput); + + sDirectoryInfoMap->Release(theDirectoryInfoRef); + +end_authorize: + // Mark the request so we'll know subsequent ones aren't the first. + (void)QTSS_SetValue(inParams->inRTSPSession, sIsFirstRequestAttr, 0, &sTrue, sizeof(sTrue)); + + return QTSS_NoErr; +} + +QTSS_Error RemoveClientSessionFromMap(QTSS_ClientSessionClosing_Params* inParams) +{ + QTSS_ClientSessionObject theSession = inParams->inClientSession; + + StrPtrLen theHomeDir; + (void)QTSS_GetValuePtr (theSession, sSessionHomeDirAttr, 0, (void**)&theHomeDir.Ptr, &theHomeDir.Len); + if ((theHomeDir.Ptr != NULL) && (theHomeDir.Len != 0)) + { + OSRef* theDirectoryInfoRef = sDirectoryInfoMap->Resolve(&theHomeDir); + if (theDirectoryInfoRef != NULL) + { + DirectoryInfo *theDirInfo = (DirectoryInfo *)theDirectoryInfoRef->GetObject(); + theDirInfo->RemoveSession(&theSession); + sDirectoryInfoMap->Release(theDirectoryInfoRef); + (void)sDirectoryInfoMap->TryUnRegister(theDirectoryInfoRef); + } + } + + return QTSS_NoErr; +} + +// Expand path expands the ~ or ~username to the home directory of the user +// It allocates memory for the outPathPtr->Ptr, so the caller should delete it +// inPathPtr has the tilde stripped off, so inPathPtr->Len == 0 is valid +Bool16 ExpandPath(const StrPtrLen *inPathPtr, StrPtrLen *outPathPtr) +{ + Assert(inPathPtr != NULL); + Assert(outPathPtr != NULL); + + char *newPath = NULL; + + if (inPathPtr->Len != 0) + { + char* inPathStr = inPathPtr->GetAsCString(); + struct passwd *passwdStruct = getpwnam(inPathStr); + delete [] inPathStr; + if (passwdStruct != NULL) + newPath = passwdStruct->pw_dir; + } + + +/* + if (inPathPtr->Len == 0) + { + newPath = getenv("HOME"); + + if (newPath == NULL) + { + struct passwd *passwdStruct = getpwuid(getuid()); + if (passwdStruct != NULL) + newPath = passwdStruct->pw_dir; + } + } +*/ + + if (newPath == NULL) + return false; + + UInt32 newPathLen = ::strlen(newPath); + char* newPathStr = NEW char[newPathLen + 1]; + ::strcpy(newPathStr, newPath); + + outPathPtr->Ptr = newPathStr; + outPathPtr->Len = newPathLen; + + return true; +} diff --git a/APIModules/QTSSHomeDirectoryModule/QTSSHomeDirectoryModule.h b/APIModules/QTSSHomeDirectoryModule/QTSSHomeDirectoryModule.h new file mode 100644 index 0000000..cde362b --- /dev/null +++ b/APIModules/QTSSHomeDirectoryModule/QTSSHomeDirectoryModule.h @@ -0,0 +1,41 @@ +/* + * + * @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: QTSSHomeDirectoryModule.h + + Contains: Module that expands ~ in request URLs to home directories +*/ + +#ifndef __QTSS_HOMEDIRECTORY_MODULE_H__ +#define __QTSS_HOMEDIRECTORY_MODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + QTSS_Error QTSSHomeDirectoryModule_Main(void* inPrivateArgs); +} + +#endif // __QTSS_HOMEDIRECTORY_MODULE_H__ diff --git a/APIModules/QTSSHomeDirectoryModule/createuserstreamingdir b/APIModules/QTSSHomeDirectoryModule/createuserstreamingdir new file mode 100755 index 0000000..0a3a355 --- /dev/null +++ b/APIModules/QTSSHomeDirectoryModule/createuserstreamingdir @@ -0,0 +1,178 @@ +#!/bin/sh +# +# +OPTION="" +PLAT=`uname` + +if [ ${1} ]; then + OPTION=${1} +fi + +if [ "$OPTION" = "-h" ]; then + OPTION="" +fi + +if [ ${OPTION} ]; then + OK=YES +else + echo "" + echo "usage: createuserstreamingdir user" + echo "" + echo "This tool will create the directory ~user/Sites/Streaming/." + echo "The created directory gives the QuickTimeStreamingServer access to user managed content." + echo "" + exit 0 +fi +echo "" + +CALLER=`whoami` + +if [ "$1" = "$CALLER" ] ; then + OK=YES +else + if [ `id -u` != 0 ] + then + echo "You must be root, ${1}, or use the sudo command to proceed. " + echo "Cannot continue." + echo "" + echo "usage: createuserstreamingdir user" + echo "" + exit 1 + fi +fi + + +# +# Home dir +# +NEWPATH=~"${1}" +HOMEDIR=`eval "echo $NEWPATH"` + +echo "examining the home directory for ${NEWPATH}" +echo "home directory path = ${HOMEDIR}" +if [ -e "${HOMEDIR}" ] ; then + OK=YES +else + echo "The path \"${HOMEDIR}\" is not found." + echo "Cannot continue." + echo "" + echo "usage: createuserstreamingdir user" + echo "" + exit 1 +fi + +if [ -d "${HOMEDIR}" ] ; then + OK=YES +else + echo "${HOMEDIR} is not a directory." + echo "Cannot continue." + echo "" + exit 1 +fi + +if [ "Darwin" != "$PLAT" ]; then + chmod 755 "${HOMEDIR}" + echo "Set privileges for ${HOMEDIR} to 755 " +fi + +# +# /Sites +# + +if [ -e "${HOMEDIR}/Sites" ]; then + OK=YES +else + if [ -w "${HOMEDIR}/" ]; then + mkdir "${HOMEDIR}/Sites" + chmod 755 "${HOMEDIR}/Sites" + chown ${1} "${HOMEDIR}/Sites" + fi +fi + +if [ -e "${HOMEDIR}/Sites" ]; then + OK=YES +else + echo "You do not have privileges to create ${HOMEDIR}/Sites." + echo "Cannot continue." + echo "" + exit 1 +fi + +if [ -d "${HOMEDIR}/Sites" ]; then + OK=YES +else + echo "${HOMEDIR}/Sites is not a directory." + echo "Cannot continue." + echo "" + exit 1 +fi + +# +# /Sites/Streaming +# + +if [ -e "${HOMEDIR}/Sites/Streaming" ]; then + OK=YES +else + if [ -w "${HOMEDIR}/Sites" ]; then + mkdir -m 755 "${HOMEDIR}/Sites/Streaming" + chown ${1}:qtss "${HOMEDIR}/Sites/Streaming" + fi +fi + +if [ -e "${HOMEDIR}/Sites/Streaming" ]; then + OK=YES +else + echo "You do not have privileges to create ${HOMEDIR}/Sites/Streaming." + echo "Cannot continue." + echo "" + exit 1 +fi + +if [ -d "${HOMEDIR}/Sites/Streaming" ]; then + OK=YES +else + echo "${HOMEDIR}/Sites/Streaming is not a directory." + echo "Cannot continue." + echo "" + exit 1 +fi + +# +# Test access +# + +if [ -w "${HOMEDIR}/Sites/Streaming/" ]; then + OK=YES +else + echo "You do not have privileges to modify ${HOMEDIR}/Sites/Streaming." + echo "Cannot continue." + echo "" + exit 1 +fi + +chown ${1}:qtss "${HOMEDIR}/Sites/Streaming" > /dev/null 2>&1 +if [ $? = 0 ]; then + OK=YES +else + echo "You are not the owner." + echo "You may need to run this tool again as root or use the sudo command." + echo "" + exit 1 +fi + +chmod 755 "${HOMEDIR}/Sites/Streaming" > /dev/null 2>&1 +if [ $? = 0 ]; then + OK=YES +else + echo "The permissions are not correct." + echo "You may need to run this tool again as root or use the sudo command." + echo "" + exit 1 +fi + + +echo "${HOMEDIR}/Sites/Streaming is ready for streaming." +echo "" + +exit 0 \ No newline at end of file diff --git a/APIModules/QTSSHttpFileModule/QTSSHttpFileModule.cpp b/APIModules/QTSSHttpFileModule/QTSSHttpFileModule.cpp new file mode 100644 index 0000000..d12b54c --- /dev/null +++ b/APIModules/QTSSHttpFileModule/QTSSHttpFileModule.cpp @@ -0,0 +1,1283 @@ +/* + * + * @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: QTSSHttpFileModule.cpp + + Contains: Implements the HTTP file module + */ + + +#ifndef __Win32__ +#include +#include +#endif + + +#include +#include +#include "SafeStdLib.h" +#include +#include + +#include "QTSSHttpFileModule.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "OSMemory.h" +#include "OSHeaders.h" +#include "ev.h" +#include "QTFile.h" +#include "QTTrack.h" +#include "QTHintTrack.h" +#include "QTSSModuleUtils.h" +#include "QTSSRollingLog.h" +#include "OSMutex.h" +#include "HTTPProtocol.h" +#include "HTTPRequest.h" + +#define HTTP_FILE_ASYNC 1 +#define HTTP_FILE_DEBUGGING 0 + +class QTSSHttpAccessLog; + +// STATIC DATA +// For processing the requests +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; +static QTSS_ServerObject sServer = NULL; + + +static StrPtrLen sPathSeparator("/"); +static StrPtrLen sRTSPUrlPrefix("rtsp://"); +static StrPtrLen sRefMovieBufPrefix("rtsptext\r"); +static StrPtrLen sRespHeaderPrefix("HTTP/1.0 200 OK\r\nServer: QTSS/2.0\r\nConnection: Close"); +static StrPtrLen sContentLengthHeaderTag("\r\nContent-Length: "); +static StrPtrLen sContentTypeHeaderTag("\r\nContent-Type: "); +static StrPtrLen sConnectionKeepAliveTag("Keep-Alive"); +static StrPtrLen sQuickTimeMimeType("video/quicktime"); +static StrPtrLen sUnknownMimeType("application/unknown"); +static StrPtrLen sGifMimeType("image/gif"); +static StrPtrLen sSdpMimeType("application/sdp"); +static StrPtrLen sSmilMimeType("application/smil"); +static StrPtrLen sQTSuffix("qt"); +static StrPtrLen sMovSuffix("mov"); +static StrPtrLen sGifSuffix("gif"); +static StrPtrLen sSdpSuffix("sdp"); +static StrPtrLen sSmiSuffix("smi"); +static StrPtrLen sSmilSuffix("smil"); + +static const UInt32 kReadingBufferState = 0; +static const UInt32 kWritingBufferState = 1; + +// For logging the requests +// Default values for preferences +static Bool16 sDefaultHTTPFileXferEnabled = false; // This module is not enabled by default. +static Bool16 sDefaultLogEnabled = false; +static char* sDefaultLogName = "StreamingServerHttp"; +static char* sDefaultLogDir = NULL; + +static UInt32 sDefaultMaxLogBytes = 50000000; +static UInt32 sDefaultRollInterval = 7; +static char* sVoidField = "-"; +// Current values for preferences +static Bool16 sHTTPFileXferEnabled = false; +static Bool16 sLogEnabled = false; +static UInt32 sMaxLogBytes = 50000000; +static UInt32 sRollInterval = 7; + +static OSMutex* sLogMutex = NULL;// Log module isn't reentrant +static QTSSHttpAccessLog* sAccessLog = NULL; + +// ATTRIBUTES +// For processing the http requests +static QTSS_AttributeID sTransferTypeAttr = qtssIllegalAttrID; +static QTSS_AttributeID sEventContextAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFileAttr = qtssIllegalAttrID; +static QTSS_AttributeID sStateAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFileBufferAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFileBufferLenAttr = qtssIllegalAttrID; +static QTSS_AttributeID sReadOffsetAttr = qtssIllegalAttrID; +static QTSS_AttributeID sWriteOffsetAttr = qtssIllegalAttrID; + +// For logging the requests +static QTSS_AttributeID sRequestAttr = qtssIllegalAttrID; +static QTSS_AttributeID sContentLengthAttr = qtssIllegalAttrID; + +class QTSSHttpAccessLog : public QTSSRollingLog +{ + public: + + QTSSHttpAccessLog() : QTSSRollingLog() { this->SetTaskName("QTSSHttpAccessLog"); } + virtual ~QTSSHttpAccessLog() {} + + virtual char* GetLogName() { return QTSSModuleUtils::GetStringAttribute(sPrefs, "http_logfile_name", sDefaultLogName);} + virtual char* GetLogDir() { return QTSSModuleUtils::GetStringAttribute(sPrefs, "http_logfile_dir", sDefaultLogDir); } + virtual UInt32 GetRollIntervalInDays() { return sRollInterval; } + virtual UInt32 GetMaxLogBytes() { return sMaxLogBytes; } +}; + +// FUNCTIONS +static QTSS_Error QTSSHttpFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error FilterRequest(QTSS_Filter_Params* inParams); +static QTSS_Error CloseRTSPSession(QTSS_RTSPSession_Params* inParams); +// For processing the requests +static UInt32 GetBitRate(char* filePath); +static void* MakeMoov(void* rmda, UInt32 rmdaLen, UInt32* moovLen); +static void* MakeRmda(char* url, UInt32 rate, UInt32* rmdaLen); +static StrPtrLen* GetMimeType(StrPtrLen* fileName); +// For logging the requests +static QTSS_Error RereadPrefs(); +static void LogRequest(QTSS_RTSPSessionObject inRTSPSession); +static void CheckHttpAccessLogState(Bool16 forceEnabled); + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSHttpFileModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSHttpFileModuleDispatch); +} + + +QTSS_Error QTSSHttpFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RTSPFilter_Role: + return FilterRequest(&inParams->rtspFilterParams); + case QTSS_RTSPSessionClosing_Role: + return CloseRTSPSession(&inParams->rtspSessionClosingParams); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + sLogMutex = new OSMutex(); + + // Add attributes for processing the requests + static char* sTransferTypeName = "QTSSHttpFileModuleTransferType"; + static char* sEventContextName = "QTSSHttpFileModuleContext"; + static char* sFileName = "QTSSHttpFileModuleFile"; + static char* sStateName = "QTSSHttpFileModuleState"; + static char* sFileBufferName = "QTSSHttpFileModuleFileBuffer"; + static char* sFileBufferLenName = "QTSSHttpFileModuleFileBufferLen"; + static char* sReadOffsetName = "QTSSHttpFileModuleReadOffset"; + static char* sWriteOffsetName = "QTSSHttpFileModuleWriteOffset"; + + // Add attributes for logging the requests + static char* sRequestName = "QTSSHttpFileModuleRequestName"; + static char* sContentLengthName = "QTSSHttpFileModuleContentLengthName"; + + + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RTSPFilter_Role); + (void)QTSS_AddRole(QTSS_RTSPSessionClosing_Role); + + // Attributes for processing requests + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sTransferTypeName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sTransferTypeName, &sTransferTypeAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sEventContextName, NULL, qtssAttrDataTypeUnknown); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sEventContextName, &sEventContextAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileName, &sFileAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sStateName, NULL, qtssAttrDataTypeUnknown); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sStateName, &sStateAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileBufferName, NULL, qtssAttrDataTypeUnknown); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileBufferName, &sFileBufferAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileBufferLenName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileBufferLenName, &sFileBufferLenAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sReadOffsetName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sReadOffsetName, &sReadOffsetAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sWriteOffsetName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sWriteOffsetName, &sWriteOffsetAttr); + + // Attributes for logging requests + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sRequestName, NULL, qtssAttrDataTypeUnknown); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sRequestName, &sRequestAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sContentLengthName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sContentLengthName, &sContentLengthAttr); + + // Tell the server our name! + static char* sModuleName = "QTSSHttpFileModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + // Get prefs object + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + sServerPrefs = inParams->inPrefs; + sServer = inParams->inServer; + + RereadPrefs(); + + // This creates the log + CheckHttpAccessLogState(false); + + + return QTSS_NoErr; +} + +QTSS_Error RereadPrefs() +{ + delete [] sDefaultLogDir; + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorLogDir, 0, &sDefaultLogDir); + + // Code from Access Log module + QTSSModuleUtils::GetAttribute(sPrefs, "http_xfer_enabled", qtssAttrDataTypeBool16, + &sHTTPFileXferEnabled, &sDefaultHTTPFileXferEnabled, sizeof(sHTTPFileXferEnabled)); + QTSSModuleUtils::GetAttribute(sPrefs, "http_logging", qtssAttrDataTypeBool16, + &sLogEnabled, &sDefaultLogEnabled, sizeof(sLogEnabled)); + QTSSModuleUtils::GetAttribute(sPrefs, "http_logfile_size", qtssAttrDataTypeUInt32, + &sMaxLogBytes, &sDefaultMaxLogBytes, sizeof(sMaxLogBytes)); + QTSSModuleUtils::GetAttribute(sPrefs, "http_logfile_interval", qtssAttrDataTypeUInt32, + &sRollInterval, &sDefaultRollInterval, sizeof(sRollInterval)); + return QTSS_NoErr; +} + +QTSS_Error FilterRequest(QTSS_Filter_Params* inParams) +{ + static UInt32 sFileBufSize = 32768; + static UInt32 sZero = 0; + static Bool16 sFalse = false; + + UInt32 theLen = 0; + UInt32 initialState; + UInt32* theStateP = NULL; + DIR* theDirectory = NULL; + QTSS_Object theFile = NULL; + QTSS_Error theErr = QTSS_NoErr; + UInt64 theFileLength = 0; + + HTTPRequest* httpRequest = NULL; + + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + QTSS_RTSPSessionObject theSession = inParams->inRTSPSession; + + // If this feature is disabled just return + if (!sHTTPFileXferEnabled) + return QTSS_NoErr; + + StrPtrLen serverHdr; + (void)QTSS_GetValuePtr(sServer, qtssSvrRTSPServerHeader, 0, (void**)&serverHdr.Ptr, &serverHdr.Len); + + (void)QTSS_GetValuePtr(theSession, sStateAttr, 0, (void**)&theStateP, &theLen); + if ( (theStateP == NULL) || (theLen != sizeof(UInt32)) ) + { + // Initial state. + TransferType type = 0; // Enum datatype defined in QTSSHttpFileModule.h + StrPtrLen theFullRequest; + HTTPMethod reqMethod; + StrPtrLen reqStr; + UInt32 index; + StrPtrLen* responseHeader; + + (void)QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len); + httpRequest = NEW HTTPRequest(&serverHdr, &theFullRequest); + + theErr = httpRequest->Parse(); + + // If the header couldn't be parsed, return from the role + if(theErr != QTSS_NoErr) + return QTSS_NoErr; + + reqMethod = httpRequest->GetMethod(); + // If is not a HEAD or a GET request, just return + if ((reqMethod != httpGetMethod) && (reqMethod != httpHeadMethod)) + return QTSS_NoErr; + + char *reqPath = httpRequest->GetRequestPath(); + if(::strcmp(reqPath, "") != 0) + { + reqStr.Ptr = reqPath; + reqStr.Len = ::strlen(reqPath); + } + + // If a filename or directory is not given in the request, just return + if(reqStr.Ptr == NULL) + return QTSS_NoErr; + + StrPtrLen* requestLine = httpRequest->GetRequestLine(); + // Store the request line in the request attribute for the purposes of logging + (void)QTSS_SetValue(theSession, sRequestAttr, 0, requestLine, sizeof(*requestLine)); + + // Get the movie folder name + char* movieFolderString = NULL; + QTSS_Error err = QTSS_GetValueAsString (sServerPrefs, qtssPrefsMovieFolder, 0, &movieFolderString); + if (err != QTSS_NoErr) + return QTSS_NoErr; + + OSCharArrayDeleter movieFolder(movieFolderString); + StrPtrLen theMovieFolder(movieFolderString); + + // Get the http folder name + OSCharArrayDeleter httpFolderDtr(QTSSModuleUtils::GetStringAttribute(sPrefs, "http_folder", "")); + StrPtrLen theHttpFolder(httpFolderDtr.GetObject()); + + char* thePath = NULL; + if ( theMovieFolder.Len != 0 ) + { + // Create the entire path with the movie folder as root directory + thePath = NEW char[theMovieFolder.Len + sPathSeparator.Len + reqStr.Len + 1]; + ::memcpy(thePath, theMovieFolder.Ptr, theMovieFolder.Len); + index = theMovieFolder.Len; + ::strcpy(thePath + index, sPathSeparator.Ptr); + index += sPathSeparator.Len; + ::memcpy(thePath + index, reqStr.Ptr, reqStr.Len); + index += reqStr.Len; + thePath[index] = '\0'; + + theDirectory = ::opendir(thePath); + + if ( theDirectory == NULL ) + { // If it's not a directory in the movie folder +#if HTTP_FILE_ASYNC + theErr = QTSS_OpenFileObject(thePath, qtssOpenFileAsync, &theFile); +#else + theErr = QTSS_OpenFileObject(thePath, qtssOpenFileNoFlags, &theFile); +#endif + if ((theErr == QTSS_NoErr)) // If it's a file in the movie folder, + // then it's on-the-fly ref movie generation + { // for a single movie file +#if HTTP_FILE_DEBUGGING + qtss_printf("Request for on-the-fly generated ref movie of a hinted QuickTime file\n"); +#endif + type = transferRefMovieFile; + delete [] thePath; + } + else + { // If it's not a file in the movie folder, + // then it's not on-the-fly ref movie generation + if ( theFile != NULL) + (void)QTSS_CloseFileObject(theFile); + delete [] thePath; + type = 0; + } + } + else // If it's a directory in the movie folder, then it's on-the-fly ref movie generation + { // for a directory of movie files +#if HTTP_FILE_DEBUGGING + qtss_printf("Request for on-the-fly generated ref movie for a directory of hinted QuickTime files\n"); +#endif + type = transferRefMovieFolder; + } + } + + // If the movie folder doesn't exist or it exists and the request is not for a file or a directory + // in the movie folder + if ( type == 0 && theHttpFolder.Len != 0 ) + { + // Create the entire path with the http folder as root directory + thePath = NEW char[theHttpFolder.Len + sPathSeparator.Len + reqStr.Len + 1]; + ::memcpy(thePath, theHttpFolder.Ptr, theHttpFolder.Len); + index = theHttpFolder.Len; + ::strcpy(thePath + index, sPathSeparator.Ptr); + index += sPathSeparator.Len; + ::memcpy(thePath + index, reqStr.Ptr, reqStr.Len); + index += reqStr.Len; + thePath[index] = '\0'; + +#if HTTP_FILE_ASYNC + theErr = QTSS_OpenFileObject(thePath, qtssOpenFileAsync, &theFile); +#else + theErr = QTSS_OpenFileObject(thePath, qtssOpenFileNoFlags, &theFile); +#endif + if( (theErr == QTSS_NoErr) ) // If the file is in the http folder + { // it is a request for progressive download +#if HTTP_FILE_DEBUGGING + qtss_printf("Request for the HTTP transfer of a file\n"); +#endif + type = transferHttpFile; + delete [] thePath; + } + else + { // If the file is not found in the http folder, just return + if ( theFile != NULL ) + (void)QTSS_CloseFileObject(theFile); + delete [] thePath; + type = 0; +#if HTTP_FILE_DEBUGGING + qtss_printf("Request file not found by this module\n"); +#endif + return QTSS_NoErr; + } + } + + + // If type is still 0, it is neither an HTTP file transfer request nor an on-the-fly ref movie request + // Just return + if ( type == 0 ) + { +#if HTTP_FILE_DEBUGGING + qtss_printf("Request file not found by this module\n"); +#endif + return QTSS_NoErr; + } + + // If it's a "Head" request send the Head response header back and just return + if ( reqMethod == httpHeadMethod) + { + httpRequest->CreateResponseHeader(http10Version, httpOK); + httpRequest->AppendResponseHeader(httpContentTypeHeader, &sQuickTimeMimeType); + responseHeader = httpRequest->GetCompleteResponseHeader(); + QTSS_Write(theRequest, responseHeader->Ptr, responseHeader->Len, NULL, 0); + if ( theDirectory != NULL ) + ::closedir(theDirectory); + else if( theFile != NULL ) + (void)QTSS_CloseFileObject(theFile); + return QTSS_NoErr; + } + + // Create a buffer to store data. + char* theFileBuffer; + char* contentLength = NEW char[256]; + UInt32 headerLength = sZero; + UInt32 fileBufferLen = sZero; + + char serverIPAddrBuf[20] = { 0 }; + StrPtrLen serverIPAddr(serverIPAddrBuf, 19); // Server name + + // Get the server nameif it's a ref movie transfer + if ( type == transferRefMovieFile || type == transferRefMovieFolder ) + { + // Get the server ip address + (void)QTSS_GetValue(theSession, qtssRTSPSesLocalAddrStr, 0, serverIPAddr.Ptr, &serverIPAddr.Len); + + if ( serverIPAddr.Len == 0 ) + // The local IP address for this session is not found; the ref movie cannot be created, so return + return QTSS_NoErr; + } + + // Before sending any response, set keep alive to off for this connection + // Regardless of what the client sends, the server always closes the connection after sending the file + (void)QTSS_SetValue(theRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse)); + + switch (type) + { + case transferHttpFile: + { // Allocate memory for theFileBuffer + theFileBuffer = NEW char[sFileBufSize]; + httpRequest->CreateResponseHeader(http10Version, httpOK); + httpRequest->AppendConnectionCloseHeader(); + theLen = sizeof(UInt64); + theErr = QTSS_GetValue(theFile, qtssFlObjLength, 0, (void*)&theFileLength, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theLen == sizeof(UInt64)); + httpRequest->AppendContentLengthHeader(theFileLength); + httpRequest->AppendResponseHeader(httpContentTypeHeader, GetMimeType(&reqStr)); + responseHeader = httpRequest->GetCompleteResponseHeader(); + headerLength = responseHeader->Len; + ::memcpy(theFileBuffer, responseHeader->Ptr, responseHeader->Len); + + // We need content length string for logging purposes + qtss_sprintf(contentLength, "%"_64BITARG_"d", theFileLength); + + //Delete the http request object ...no use for it anymore + delete httpRequest; + + // Set the intial file buffer length to be zero as there is nothing in the file buffer (other than the header). + // The read sets this attribute to the length that needs to be written to the stream + fileBufferLen = sZero; + // Set the state to reading so that file contents can be read into the buffer + initialState = kReadingBufferState; + break; + } + case transferRefMovieFolder: + { +#if HTTP_FILE_DEBUGGING + qtss_printf("Creating a ref movie for the folder\n"); + qtss_printf("filenames:\n"); +#endif + UInt32 fileCount = 0; + struct dirent* entry; + // Find the number of .mov files in the requested directory of the movie folder + while( (entry = ::readdir(theDirectory)) != NULL) + { + if( ::strcmp(entry->d_name + ::strlen(entry->d_name) - 4, ".mov") == 0 ) + fileCount ++; + + } +#if HTTP_FILE_DEBUGGING + qtss_printf("Number of .mov files in the directory: %"_U32BITARG_"\n", fileCount); +#endif + // Go back to the beginning of the directory + ::rewinddir(theDirectory); + char** fileNames; + fileNames = NEW char*[fileCount]; + fileCount = 0; + // Find all the .mov files in the requested directory of the movie folder + while( (entry = ::readdir(theDirectory)) != NULL) + { + char* fileName = NEW char[::strlen(entry->d_name) + 1]; + ::strcpy(fileName, entry->d_name); + if( ::strcmp(fileName + strlen(fileName) - 4, ".mov") == 0 ) + { + fileNames[fileCount] = fileName; + fileCount ++; +#if HTTP_FILE_DEBUGGING + qtss_printf("%"_U32BITARG_" : %s\n", fileCount, fileName); +#endif + } + } + // Close the directory stream - We no longer have any use for it + ::closedir(theDirectory); + + // Create the base url for each of the files ( ex. rtsp://servername/dirname/ ) + OSCharArrayDeleter baseUrl(NEW char[sRTSPUrlPrefix.Len + serverIPAddr.Len + sPathSeparator.Len + reqStr.Len + sPathSeparator.Len + 1]); + ::memcpy(baseUrl.GetObject(), sRTSPUrlPrefix.Ptr, sRTSPUrlPrefix.Len); + index = sRTSPUrlPrefix.Len; + ::memcpy(baseUrl.GetObject() + index, serverIPAddr.Ptr, serverIPAddr.Len); + index += serverIPAddr.Len; + ::strcpy(baseUrl.GetObject() + index, sPathSeparator.Ptr); + index += sPathSeparator.Len; + ::memcpy(baseUrl.GetObject() + index, reqStr.Ptr, reqStr.Len); + index += reqStr.Len; + ::strcpy(baseUrl.GetObject() + index, sPathSeparator.Ptr); + index += sPathSeparator.Len; + baseUrl.GetObject()[index] = '\0'; + + UInt32 *rmdas = NULL, rmdasLen = 0, *moov = NULL, moovLen = 0, *tempRmda = NULL, tempRmdaLen = 0; + UInt32 *rmdasTemp; + UInt32 arrayIndex; + char* filePath; + char* url; + UInt32 bitRate; + + for (arrayIndex = 0; arrayIndex < fileCount; arrayIndex++) + { + // Get the filePath for each .mov file in the directory + filePath = NEW char[::strlen(thePath) + sPathSeparator.Len + ::strlen(fileNames[arrayIndex]) + 1]; + ::strcpy(filePath, thePath); + ::strcat(filePath, sPathSeparator.Ptr); + ::strcat(filePath, fileNames[arrayIndex]); + // Create the complete url from the base url for each .mov file in the directory + url = NEW char[index + ::strlen(fileNames[arrayIndex]) + 1]; + ::memcpy(url, baseUrl.GetObject(), index); + ::strcpy(url + index, fileNames[arrayIndex]); + //Find the approximate bit rate for each .mov file in the directory + bitRate = GetBitRate(filePath); +#if HTTP_FILE_DEBUGGING + qtss_printf("%"_U32BITARG_"\t: Path = %s\n", arrayIndex + 1, filePath); + qtss_printf("Url = %s\n", url); + qtss_printf("Rate = %"_U32BITARG_"\n", bitRate); +#endif + if ( bitRate != 0 ) + { + // Create "rmda" for each .mov file in the directory + tempRmda = (UInt32 *) MakeRmda(url, bitRate, &tempRmdaLen); + } + + // Deallocate the memory for the fileName, filePath and url + delete [] fileNames[arrayIndex]; + delete [] filePath; + delete [] url; + + // If tempRmdaLen is NULL, tempRmdaLen is zero: + // Just move to next file in the folder + // if( tempRmda == NULL ) // Couldn't create tempRmda. Plan of action: Barf! + // return QTSS_NoErr; + + // Reallocate the rmdas buffer so as to append the tempRmda + if ( tempRmdaLen != 0 ) + { + rmdasTemp = rmdas; // Store the rmdas in a temporary pointer and reallocate + rmdas = NEW UInt32[rmdasLen + tempRmdaLen]; + + if( rmdas == NULL ) // Couldn't create rmdas. Barf now! + return QTSS_NoErr; + + ::memcpy((char *)rmdas, rmdasTemp, rmdasLen); // Copy old contents and append tempRmda + ::memcpy((char *)rmdas + rmdasLen, tempRmda, tempRmdaLen); + rmdasLen += tempRmdaLen; + // Delete the old pointer and the tempRmda pointer + if (rmdasTemp != NULL) + delete rmdasTemp; + delete tempRmda; + } + } + // Delete the array of filenames + delete [] fileNames; + // Free the path pointer. We are done with it. + delete [] thePath; + + if ( rmdasLen != 0 ) + // Create a "moov" for the ref movie + moov = (UInt32 *) MakeMoov(rmdas, rmdasLen, &moovLen); + + if( moov == NULL ) // Couldn't create moov after doing everything else! + return QTSS_NoErr; + + // Create the HTTP response header + httpRequest->CreateResponseHeader(http10Version, httpOK); + httpRequest->AppendConnectionCloseHeader(); + httpRequest->AppendContentLengthHeader(moovLen); + httpRequest->AppendResponseHeader(httpContentTypeHeader, &sQuickTimeMimeType); + responseHeader = httpRequest->GetCompleteResponseHeader(); + headerLength = responseHeader->Len; + + // Allocate memory for theFileBuffer + theFileBuffer = NEW char[headerLength + moovLen]; + + // Write the HTTP response header into the file buffer + ::memcpy(theFileBuffer, responseHeader->Ptr, responseHeader->Len); + + // We need content length string for logging purposes + qtss_sprintf(contentLength, "%"_U32BITARG_"", moovLen); + + //Delete the http request object ...no use for it anymore + delete httpRequest; + + // Write the ref movie into the buffer + ::memcpy(theFileBuffer + headerLength, moov, moovLen); + delete moov; // Delete the moov pointer + // Set the size of the file buffer to that of header length + ref movie length + fileBufferLen = headerLength + moovLen; + initialState = kWritingBufferState; + break; + } + case transferRefMovieFile: + { +#if HTTP_FILE_DEBUGGING + qtss_printf("Creating a ref movie for the hinted file\n"); +#endif + // Create a ref movie buffer for the single file. It is of the form: + // rtsptext\r + // rtsp://servername/filepath + OSCharArrayDeleter refMovieBuf(NEW char[sRefMovieBufPrefix.Len + sRTSPUrlPrefix.Len + serverIPAddr.Len + sPathSeparator.Len + reqStr.Len + 1]); + ::memcpy(refMovieBuf.GetObject(), sRefMovieBufPrefix.Ptr, sRefMovieBufPrefix.Len); + index = sRefMovieBufPrefix.Len; + ::memcpy(refMovieBuf.GetObject() + index, sRTSPUrlPrefix.Ptr, sRTSPUrlPrefix.Len); + index += sRTSPUrlPrefix.Len; + ::memcpy(refMovieBuf.GetObject() + index, serverIPAddr.Ptr, serverIPAddr.Len); + index += serverIPAddr.Len; + ::strcpy(refMovieBuf.GetObject() + index, sPathSeparator.Ptr); + index += sPathSeparator.Len; + ::memcpy(refMovieBuf.GetObject() + index, reqStr.Ptr, reqStr.Len); + index += reqStr.Len; + refMovieBuf.GetObject()[index] = '\0'; + + // Create the HTTP response header + httpRequest->CreateResponseHeader(http10Version, httpOK); + httpRequest->AppendConnectionCloseHeader(); + httpRequest->AppendContentLengthHeader(index); + httpRequest->AppendResponseHeader(httpContentTypeHeader, &sQuickTimeMimeType); + responseHeader = httpRequest->GetCompleteResponseHeader(); + headerLength = responseHeader->Len; + + // Allocate memory for theFileBuffer + theFileBuffer = NEW char[headerLength + index]; + // Write the HTTP response header into the file buffer + ::memcpy(theFileBuffer, responseHeader->Ptr, responseHeader->Len); + + // Write the ref movie created above to the file buffer + ::memcpy(theFileBuffer + headerLength, refMovieBuf.GetObject(), index); + + // We need content length string for logging purposes + qtss_sprintf(contentLength, "%"_U32BITARG_"", index); + + // Write the contents of the file buffer to the request stream and return + QTSS_Write(theRequest, theFileBuffer, (headerLength + index), NULL, 0); + + // Before returning, deallocate theFileBuffer memory + delete [] theFileBuffer; + // Also, delete the file descriptor + + (void)QTSS_CloseFileObject(theFile); +#if HTTP_FILE_DEBUGGING + qtss_printf("Wrote the ref movie to the request stream. Successful!\n"); +#endif + // Store the content length string for the purposes of logging + // Must be done here as we return a response to the client after this. + (void)QTSS_SetValue(theSession, sContentLengthAttr, 0, &contentLength, sizeof(contentLength)); + // Log the request before returning + LogRequest(theSession); + + //Delete the http request object ...no use for it anymore + delete httpRequest; + + return QTSS_NoErr; + + } + default: + { // Shouldn't ever happen +#if HTTP_FILE_DEBUGGING + qtss_printf("Illegal transfer type: Not a ref movie file/directory or http file transfer\n"); +#endif + return QTSS_NoErr; + } + } + + + // Setup all the dictionary values we need + // Store the content length string for the purposes of logging + (void)QTSS_SetValue(theSession, sContentLengthAttr, 0, &contentLength, sizeof(contentLength)); + // Store our initial state + (void)QTSS_SetValue(theSession, sStateAttr, 0, &initialState, sizeof(initialState)); + theStateP = &initialState; + // Set the file buffer pointer and the file buffer length attributes + (void)QTSS_SetValue(theSession, sFileBufferAttr, 0, &theFileBuffer, sizeof(theFileBuffer)); + (void)QTSS_SetValue(theSession, sFileBufferLenAttr, 0, &fileBufferLen, sizeof(fileBufferLen)); + // Set the initial write offset to zero so that the buffer can be flushed to the stream from the beginning + (void)QTSS_SetValue(theSession, sWriteOffsetAttr, 0, &sZero, sizeof(sZero)); + // Set the type of transfer so that only the required attributes can be read for reading/writing states + (void)QTSS_SetValue(theSession, sTransferTypeAttr, 0, &type, sizeof(type)); + + // For http file transfer type, store the file pointer, response header length, + // and the read offset so that the file can be read into the buffer + // For Async I/O, Create an event context for the file read and store it in the event context attribute + if ( type == transferHttpFile ) + { + // Store the file pointer so that we can read from it + (void)QTSS_SetValue(theSession, sFileAttr, 0, &theFile, sizeof(theFile)); + + // Keep track of the initial write into the buffer, so that we can read from file into the buffer at the correct offset + (void)QTSS_SetValue(theSession, sReadOffsetAttr, 0, &headerLength, sizeof(headerLength)); + } + + } + + // Get our attributes + char** theFileBufferP = NULL; // File buffer pointer + (void)QTSS_GetValuePtr(theSession, sFileBufferAttr, 0, (void**)&theFileBufferP, &theLen); + Assert(theFileBufferP != NULL); + Assert(theLen == sizeof(char**)); + + UInt32* theFileBufferLenP = NULL; // File buffer length + (void)QTSS_GetValuePtr(theSession, sFileBufferLenAttr, 0, (void**)&theFileBufferLenP, &theLen); + Assert(theFileBufferLenP != NULL); + Assert(theLen == sizeof(UInt32*)); + + UInt32* theWriteOffsetP = NULL; // Write offset for the file buffer + (void)QTSS_GetValuePtr(theSession, sWriteOffsetAttr, 0, (void**)&theWriteOffsetP, &theLen); + Assert(theWriteOffsetP != NULL); + Assert(theLen == sizeof(UInt32*)); + + TransferType* theTransferTypeP = NULL; + (void)QTSS_GetValuePtr(theSession, sTransferTypeAttr, 0, (void**)&theTransferTypeP, &theLen); + Assert(theTransferTypeP != NULL); + Assert(theLen == sizeof(TransferType*)); + + UInt32 theState = *theStateP; + UInt32 theFileBufferLen = *theFileBufferLenP; + UInt32 theWriteOffset = *theWriteOffsetP; + TransferType theTransferType = *theTransferTypeP; + + QTSS_Object* theFileP = NULL; + UInt32* theReadOffsetP = NULL; + UInt32 theReadOffset = 0; + UInt64 theOffset = 0; + + // Only if the type is HTTP file transfer, we need to get the File pointer, the Event Context for the read, and the Read Offset into the buffer. + if ( theTransferType == transferHttpFile ) + { + // File pointer -- Only for Http File transfer type + (void)QTSS_GetValuePtr(theSession, sFileAttr, 0, (void**)&theFileP, &theLen); + Assert(theFileP != NULL); + Assert(theLen == sizeof(QTSS_Object)); + + (void)QTSS_GetValuePtr(theSession, sReadOffsetAttr, 0, (void**)&theReadOffsetP, &theLen); + Assert(theReadOffsetP != NULL); + Assert(theLen == sizeof(UInt32*)); + theReadOffset = *theReadOffsetP; + + + // Get the length of the file onto the stack + theLen = sizeof(UInt64); + theErr = QTSS_GetValue(*theFileP, qtssFlObjLength, 0, (void*)&theFileLength, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theLen == sizeof(UInt64)); + + // Get the offset in the file onto the stack + theLen = sizeof(UInt64); + theErr = QTSS_GetValue(*theFileP, qtssFlObjPosition, 0, (void*)&theOffset, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theLen == sizeof(UInt64)); + } + + Bool16 isBlocked = false; + while (!isBlocked) + { + UInt32 theBufferSize = 0; + // The buffer is down adjusted to size of the remaining data in the file for Http file transfer + if( theTransferType == transferHttpFile ) + { + // If we have less than the full buffer size left to go in the file, + // down adjust our buffer size to be the amount of data remaining in the file + theBufferSize = sFileBufSize - theReadOffset; + if ((theFileLength - theOffset) < theBufferSize) + theBufferSize = theFileLength - theOffset; + } + + switch (theState) + { + case kReadingBufferState: // Is valid for the Http File transfer case only. + { + Assert(theTransferType == transferHttpFile); // Just a check + // Read as much data as possible out of the file + UInt32 theRecvLen = 0; + (void)QTSS_Read(*theFileP, (*theFileBufferP) + theReadOffset, theBufferSize, &theRecvLen); + theReadOffset += theRecvLen; + theOffset += theRecvLen; +#if HTTP_FILE_DEBUGGING + qtss_printf("Got %"_U32BITARG_" bytes back from file read. Now at: %"_64BITARG_"u\n", theRecvLen, theOffset); +#endif + if (theRecvLen < theBufferSize) + { +#if HTTP_FILE_DEBUGGING + qtss_printf("Flow controlled on file. Waiting for read event\n"); +#endif + isBlocked = true; + break; + } + + theFileBufferLen = theReadOffset; + (void)QTSS_SetValue(theSession, sFileBufferLenAttr, 0, &theFileBufferLen, sizeof(theFileBufferLen)); + theReadOffset = 0; + Assert(theWriteOffset == 0); + theState = kWritingBufferState; + } + + case kWritingBufferState: // For both the ref movie directory transfer and the http file transfer + { + UInt32 theWrittenLen = 0; + + (void)QTSS_Write(theSession, (*theFileBufferP) + theWriteOffset, theFileBufferLen - theWriteOffset, &theWrittenLen, 0); + theWriteOffset += theWrittenLen; + +#if HTTP_FILE_DEBUGGING + qtss_printf("Got %"_U32BITARG_" bytes back from socket write.\n", theWrittenLen); +#endif + if (theWriteOffset < theFileBufferLen) + { +#if HTTP_FILE_DEBUGGING + qtss_printf("Flow controlled on socket.Waiting for write event. \n"); +#endif + isBlocked = true; + break; + } + + // We have flushed the buffer. Check to see if we are done. + // If it is Http transfer of the file, we need to check if + // we read the entire file. If we are, delete stuff and return + if ((theTransferType == transferHttpFile) && (theOffset == theFileLength)) + { +#if HTTP_FILE_DEBUGGING + qtss_printf("File transfer complete\n"); +#endif + // If we've gotten here, we're done sending the file! + // Deletion of resources is handled in another role : RTSPSessionClosing role + // Log the request before returning + LogRequest(theSession); + return QTSS_NoErr; + } + + // In the ref movie case, we are done once we flush the buffer. + if(theTransferType != transferHttpFile) + { +#if HTTP_FILE_DEBUGGING + qtss_printf("Refmovie buffer transfer complete\n"); +#endif + // Deletion of resources is handled in another role : RTSPSessionClosing role + theWriteOffset = 0; + theState = 0; + // Log the request before returning + LogRequest(theSession); + return QTSS_NoErr; + } + + // For Http file transfer, we need to set attributes to read the remaining part of the file + // since we haven't come to the end of the file yet. + if ( theTransferType == transferHttpFile ) + { + theWriteOffset = 0; + Assert(theReadOffset == 0); + theState = kReadingBufferState; + } + } + } + + } + + Assert(isBlocked); + + // We've reached a blocking condition for some reason. + // Save our state, request an event, and return. + (void)QTSS_SetValue(theSession, sWriteOffsetAttr, 0, &theWriteOffset, sizeof(theWriteOffset)); + (void)QTSS_SetValue(theSession, sStateAttr, 0, &theState, sizeof(theState)); + + // For http file transfer, we need to save the read offset too. + if( theTransferType == transferHttpFile ) + (void)QTSS_SetValue(theSession, sReadOffsetAttr, 0, &theReadOffset, sizeof(theReadOffset)); + + // If we're reading, wait for the file to become readable (valid only for the http file transfer) + if (theState == kReadingBufferState) + { + Assert(theTransferType == transferHttpFile); // Just a check + (void)QTSS_RequestEvent(*theFileP, QTSS_ReadableEvent); + } + // If we're writing, wait for the socket to become writable (valid for both ref movie and http file transfer) + else + (void)QTSS_RequestEvent(theSession, QTSS_WriteableEvent); + + return QTSS_NoErr; +} + +QTSS_Error CloseRTSPSession(QTSS_RTSPSession_Params* inParams) +{ + // In this role, the allocated resources are deleted before closing the RTSP session + QTSS_RTSPSessionObject theSession = inParams->inRTSPSession; + UInt32 theLen = 0; + + // If this feature is disabled just return + if (!sHTTPFileXferEnabled) + return QTSS_NoErr; + + // Delete the content length string + char** theContentLengthP = NULL; + (void)QTSS_GetValuePtr(theSession, sContentLengthAttr, 0, (void**)&theContentLengthP, &theLen); + if ( theContentLengthP != NULL ) + delete [] *theContentLengthP; + + TransferType* theTransferTypeP = NULL; // Transfer type + (void)QTSS_GetValuePtr(theSession, sTransferTypeAttr, 0, (void**)&theTransferTypeP, &theLen); + if (theTransferTypeP != NULL) + { + TransferType theTransferType = *theTransferTypeP; + + // Get our file buffer pointer and delete it + char** theFileBufferP = NULL; // File buffer pointer + (void)QTSS_GetValuePtr(theSession, sFileBufferAttr, 0, (void**)&theFileBufferP, &theLen); + if (theFileBufferP != NULL) + delete [] *theFileBufferP; + + QTSS_Object* theFileP = NULL; + + // Only if the type is HTTP file transfer, we need to delete the File pointer and destroy the Event Context + if ( theTransferType == transferHttpFile ) + { + // Get our file pointer and delete it + (void)QTSS_GetValuePtr(theSession, sFileAttr, 0, (void**)&theFileP, &theLen); + Assert(theFileP != NULL); + Assert(theLen == sizeof(QTSS_Object)); + (void)QTSS_CloseFileObject(*theFileP); + } + } + return QTSS_NoErr; +} + +UInt32 GetBitRate(char* filePath) +{ + UInt32 actualRate = 0, rate = 0; + QTFile file(false, false); + QTTrack* track = NULL; + UInt64 totalRTPBytes = 0; + + QTFile::ErrorCode err = file.Open(filePath); + if(err != QTFile::errNoError) + return 0; + + while( file.NextTrack(&track, track) ) + { + QTHintTrack* hintTrack; + if( track->Initialize() != QTTrack::errNoError ) + { + qtss_printf("!!! Failed to initialize track !!!\n"); + continue; + } + + if( file.IsHintTrack(track) ) + { + hintTrack = (QTHintTrack*) track; + totalRTPBytes += hintTrack->GetTotalRTPBytes(); + } + } + + Float64 duration = file.GetDurationInSeconds(); + + if(duration > 0) + actualRate = (UInt32) ((Float64) (totalRTPBytes * 8) / duration); + + if(actualRate <= 14000) + rate = 14000; + else if(actualRate <= 28000) + rate = 28000; + else if(actualRate <= 56000) + rate = 56000; + else if(actualRate <= 112000) + rate = 112000; + else + rate = 150000; + +#if HTTP_FILE_DEBUGGING + qtss_printf("Actual rate: %"_U32BITARG_"\n", actualRate); +#endif + + return rate; +} + +void* MakeMoov(void* rmda, UInt32 rmdaLen, UInt32* moovLen) +{ + UInt32 *rmra, rmraLen, *moov; + + // Make the RMRA + rmraLen = rmdaLen + 8; + rmra = NEW UInt32[rmraLen]; + if( rmra == NULL ) + return NULL; + + rmraLen = htonl(rmraLen); + + ::memcpy(&rmra[0], &rmraLen, 4); + ::memcpy(&rmra[1], "rmra", 4); + ::memcpy((char *)rmra + 8, rmda, rmdaLen); + + // Make the MOOV + + *moovLen = ntohl(rmraLen) + 8; + moov = NEW UInt32[*moovLen]; + if( moov == NULL ) + return NULL; + + *moovLen = htonl(*moovLen); + + ::memcpy(&moov[0], moovLen, 4); + ::memcpy(&moov[1], "moov", 4); + ::memcpy((char *)moov + 8, rmra, ntohl(rmraLen)); + + delete rmra; + + *moovLen = ntohl(*moovLen); + + // moov needs to be deleted by the calling function + return moov; +} + +void* MakeRmda(char* url, UInt32 rate, UInt32* rmdaLen) +{ + UInt32 *rdrf, rdrfLen, *rmdr, rmdrLen, *rmda, zero, size; + + zero = htonl(0); // Okay, this is silly ??? + rate = htonl(rate); + + // Make the RDRF + size = ::strlen(url) + 1; + rdrfLen = 20 + size; + rdrf = NEW UInt32[rdrfLen]; + if( rdrf == NULL ) + return NULL; + + rdrfLen = htonl(rdrfLen); + size = htonl(size); + + ::memcpy(&rdrf[0], &rdrfLen, 4); + ::memcpy(&rdrf[1], "rdrf", 4); + ::memcpy(&rdrf[2], &zero, 4); + ::memcpy(&rdrf[3], "url ", 4); + ::memcpy(&rdrf[4], &size, 4); + ::strcpy((char *)&rdrf[5], url); + + // Make the RMDR + rmdrLen = 16; + rmdr = NEW UInt32[rmdrLen]; + if( rmdr == NULL ) + return NULL; + + rmdrLen = htonl(rmdrLen); + + ::memcpy(&rmdr[0], &rmdrLen, 4); + ::memcpy(&rmdr[1], "rmdr", 4); + ::memcpy(&rmdr[2], &zero, 4); + ::memcpy(&rmdr[3], &rate, 4); + + // Make the RMDA + + *rmdaLen = ntohl(rdrfLen) + ntohl(rmdrLen) + 8; + rmda = NEW UInt32[*rmdaLen]; + if( rmda == NULL ) + return NULL; + + *rmdaLen = htonl(*rmdaLen); + + ::memcpy(&rmda[0], rmdaLen, 4); + ::memcpy(&rmda[1], "rmda", 4); + ::memcpy((char *)rmda + 8, rmdr, ntohl(rmdrLen)); + ::memcpy((char *)rmda + 8 + ntohl(rmdrLen), rdrf, ntohl(rdrfLen)); + + delete rdrf; + delete rmdr; + + *rmdaLen = ntohl(*rmdaLen); + + // rmda needs to be deleted by the calling function + return rmda; +} + +StrPtrLen* GetMimeType(StrPtrLen* fileName) +{ + + StringParser fileNameParser(fileName); + StrPtrLen str; + UInt32 suffixSize; + + // If the filename doesn't have a suffix, return unknown mime type + if ( !fileNameParser.GetThru(&str, '.') ) + return &sUnknownMimeType; + + suffixSize = fileName->Len - fileNameParser.GetDataParsedLen(); + StrPtrLen suffix(fileNameParser.GetCurrentPosition(), suffixSize); + + if ( suffix.Equal(sMovSuffix) || suffix.Equal(sQTSuffix) ) + return &sQuickTimeMimeType; + + if( suffix.Equal(sGifSuffix) ) + return &sGifMimeType; + + if( suffix.Equal(sSdpSuffix) ) + return &sSdpMimeType; + + if( suffix.Equal(sSmiSuffix) || suffix.Equal(sSmilSuffix) ) + return &sSmilMimeType; + + return &sUnknownMimeType; +} + +void LogRequest(QTSS_RTSPSessionObject inRTSPSession) +{ + static StrPtrLen sUnknownStr(sVoidField); + static char* sStatus = "200"; + UInt32 theLen = 0; + + OSMutexLocker locker(sLogMutex); + CheckHttpAccessLogState(false); + if (sAccessLog == NULL) // Http logging is disabled; just return + return; + + // If http logging is on, then log the request... first construct a timestamp + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, true); // date field + + //for now, just ignore the error. + if (!result) + theDateBuffer[0] = '\0'; + + // Get info to log. Each attribute must be copied out to ensure that it is NULL terminated. + // To ensure NULL termination, just memset the buffers to 0, and make sure that + // the last byte of each array is untouched. + + // Get the remost host field + char remoteAddrBuf[20]; + StrPtrLen remoteAddr(remoteAddrBuf, 19); + if (inRTSPSession != NULL) + (void)QTSS_GetValue(inRTSPSession, qtssRTSPSesRemoteAddrStr, 0, remoteAddr.Ptr, &remoteAddr.Len); + + // Get the request field + StrPtrLen* theRequestP = NULL; + (void)QTSS_GetValuePtr(inRTSPSession, sRequestAttr, 0, (void**)&theRequestP, &theLen); + Assert(theRequestP != NULL); + Assert(theLen == sizeof(StrPtrLen)); + OSCharArrayDeleter request(NEW char[(*theRequestP).Len + 1]); + ::memcpy(request.GetObject(), (*theRequestP).Ptr, (*theRequestP).Len); + request.GetObject()[(*theRequestP).Len] = '\0'; + + // Get the bytes (content length of the document transferred) field + char** theContentLengthP = NULL; + char* contentLengthStr = NULL; + (void)QTSS_GetValuePtr(inRTSPSession, sContentLengthAttr, 0, (void**)&theContentLengthP, &theLen); + Assert(theContentLengthP != NULL); + Assert(theLen == sizeof(char*)); + contentLengthStr = *theContentLengthP; + + char tempLogBuffer[2048]; + qtss_sprintf(tempLogBuffer, "%s %s %s %s \"%s\" %s %s\n", + (remoteAddr.Ptr[0] == '\0') ? sVoidField : remoteAddr.Ptr, + sVoidField, + sVoidField, + (theDateBuffer[0] == '\0') ? sVoidField : theDateBuffer, + (request.GetObject()[0] == '\0') ? sVoidField : request.GetObject(), + sStatus, + (contentLengthStr[0] == '\0' ) ? sVoidField : contentLengthStr + ); + + + Assert(::strlen(tempLogBuffer) < 2048); + + //Write the log message + sAccessLog->WriteToLog(tempLogBuffer, kAllowLogToRoll); +} + +void CheckHttpAccessLogState(Bool16 forceEnabled) +{ + // Code from Access Log module + // This function makes sure the logging state is in sync with the preferences. + // extern variable declared in QTSSPreferences.h + if ((NULL == sAccessLog) && (forceEnabled || sLogEnabled)) + { + sAccessLog = NEW QTSSHttpAccessLog(); + sAccessLog->EnableLog(); + } + + if ((NULL != sAccessLog) && ((!forceEnabled) && (!sLogEnabled))) + { + delete sAccessLog; + sAccessLog = NULL; + } +} + +/* +The Common Logfile Format is used for logging all the http requests + +format: remotehost rfc931 authuser [date] "request" status bytes + +Field Name Value +remotehost Remote hostname (or IP number if DNS hostname is not available, or if DNSLookup is Off. +rfc931 The remote logname of the user. +authuser The username as which the user has authenticated himself. +[date] Date and time of the request. +"request" The request line exactly as it came from the client. +status The HTTP status code returned to the client. +bytes The content-length of the document transferred. + +*/ diff --git a/APIModules/QTSSHttpFileModule/QTSSHttpFileModule.h b/APIModules/QTSSHttpFileModule/QTSSHttpFileModule.h new file mode 100644 index 0000000..5c12ebe --- /dev/null +++ b/APIModules/QTSSHttpFileModule/QTSSHttpFileModule.h @@ -0,0 +1,51 @@ +/* + * + * @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: QTSSHttpFileModule.h + + Contains: A module for HTTP file transfer of files and for + on-the-fly ref movie creation. + Uses the Filter module feature of QTSS API. + +*/ + +#ifndef __QTSSHTTPFILEMODULE_H__ +#define __QTSSHTTPFILEMODULE_H__ + +#include "QTSS.h" + +enum { + transferHttpFile = 1, + transferRefMovieFolder = 2, + transferRefMovieFile = 3 +}; +typedef UInt32 TransferType; + +extern "C" +{ + QTSS_Error QTSSHttpFileModule_Main(void* inPrivateArgs); +} + +#endif // __QTSSHTTPFILEMODULE_H__ diff --git a/APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.cpp b/APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.cpp new file mode 100755 index 0000000..8f6a88b --- /dev/null +++ b/APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.cpp @@ -0,0 +1,2890 @@ +/* + * + * @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: QTSSMP3StreamingModule.cpp + + Contains: This module will reflect a ShoutCast/IceCast-style MP3 broadcast + out to multiple clients. + + Written by: Steve Ussery + +*/ + +#ifndef kVersionString +#include "../../revision.h" +#endif + +#include "QTSSMP3StreamingModule.h" +#include "QTSSModuleUtils.h" +#include "QTSSRollingLog.h" +#include "StringParser.h" +#include "OSMemory.h" +#include "OS.h" + +#define DEBUG_MP3STREAMING_MODULE 0 + +#if DEBUG_MP3STREAMING_MODULE +#define DTRACE(s) qtss_printf(s) +#define DTRACE1(s,n1) qtss_printf(s,n1) +#define DTRACE2(s,n1,n2) qtss_printf(s,n1,n2) +#else +#define DTRACE(s) +#define DTRACE1(s,n1) +#define DTRACE2(s,n1,n2) +#endif + +class QTSSMP3AccessLog; + +//************************************************** +#define kOKHeader "OK\r\n" +#ifdef __MacOSX__ +#define kClientAcceptHeader "HTTP/1.1 200 OK\r\nServer: QuickTime Streaming Server %s/%s\r\nContent-Type: audio/mpeg\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\n" +#define kM3UReplyHeader "HTTP/1.1 200 OK\r\nServer: QuickTime Streaming Server %s/%s\r\nContent-Type: audio/mpegurl\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\nContent-Length:" +#else +#define kClientAcceptHeader "HTTP/1.1 200 OK\r\nServer: Darwin Streaming Server %s/%s\r\nContent-Type: audio/mpeg\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\n" +#define kM3UReplyHeader "HTTP/1.1 200 OK\r\nServer: Darwin Streaming Server %s/%s\r\nContent-Type: audio/mpegurl\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\nContent-Length:" +#endif +#define kSourceReject "Error - Mount Point Taken or Invalid\r\n" +#define kSourceBadPassword "Error - Bad Password\r\n" +#define kClientCLHeader "Content-Length: 54000000\r\n\r\n" +#define kRemoteAddressSize 20 +#define kBandwidthToAddEstimate 16000 + +// STATIC DATA +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; +static OSBufferPool* sOSBufferPoolPtr = NULL; +static QTSSMP3AccessLog* sMP3AccessLog = NULL; +static QTSS_ServerObject sServer = NULL; +static OSMutex* sLogMutex = NULL; // MP3 access log isn't reentrant +static OSMutex* sAtomicMutex = NULL; +static Bool16 sServerIdle = true; + +// Server global MP3 Session management classes. +static MP3BroadcasterQueue sMP3BroadcasterQueue; +static MP3SessionTable sMP3SessionTable; + +// PREFERENCE VALUES +static UInt32 sBroadcastBufferSize = 8192; +static UInt32 sDefaultBroadcastBufferSize = 8192; +static char* sBroadcastPassword = NULL; +static char* sDefaultBroadcastPassword = " "; +static Bool16 sMP3StreamingEnabled = true; +static Bool16 sDefaultMP3StreamingEnabled = true; +static SInt32 sMaximumConnections = 0; +static SInt32 sMaximumBandwidth = 0; +static UInt32 sDefaultRollInterval = 7; +static Bool16 sDefaultLogTimeInGMT = true; +static UInt32 sDefaultMaxLogBytes = 10240000; +static SInt32 sDefaultFlowControlTimeInMSec = 10000; +static SInt32 sMaxFlowControlTimeInMSec = 10000; + +static Bool16 sDefaultLogEnabled = true; +static char* sDefaultLogName = "mp3_access"; +static char* sDefaultLogDir = NULL; + +static char* sLogName = NULL; +static char* sLogDir = NULL; +static Bool16 sLogEnabled = true; +static UInt32 sMaxLogBytes = 10240000; +static UInt32 sRollInterval = 7; +static Bool16 sLogTimeInGMT = true; + +static char* sLogHeader = "#Software: %s\n" + "#Version: %s\n" //%s == version + "#Date: %s\n" //%s == date/time + "#Remark: All date values are in %s.\n" //%s == "GMT" or "local time" + "#Remark: c-duration is in seconds.\n" + "#Fields: c-ip c-user-agent [date time] cs-uri c-status c-bytes c-duration\n"; + +static char gClientAcceptHeader[1024]; +static char gM3UReplyHeader[1024]; + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSMP3StreamingModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error FilterRequest(QTSS_Filter_Params* inParams); +static QTSS_Error RereadPrefs(); +static QTSS_Error SessionClosing(QTSS_RTSPSession_Params* inParams); +static QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams); +static QTSS_Error Shutdown(); + +static void IncrementMP3SessionCount(); +static void DecrementMP3SessionCount(); +static void IncrementTotalMP3Bytes(UInt32 bytes); +static Bool16 CheckBandwidth(SInt32 bandwidth); + +#if DEBUG_MP3STREAMING_MODULE + static void PrintStringBuffer(StrPtrLen& stringBuffer); +#else + #define PrintStringBuffer(stringBuffer) ; +#endif + +static void KeepSession(QTSS_RTSPRequestObject theRequest,Bool16 keep); +static UInt32 GetRTSPSessionID(QTSS_RTSPSessionObject session); +static Bool16 IsActiveBroadcastSession(QTSS_RTSPSessionObject session); +static MP3BroadcasterSession* FindBroadcastSession(QTSS_RTSPSessionObject session); +static Bool16 IsBroadcastPassword(StrPtrLen& theRequest); +static Bool16 IsShoutcastPassword(StrPtrLen& theRequest); +static Bool16 IsHTTPGet(StrPtrLen& theRequest); +static Bool16 IsA_m3u_URL(char* theURL); +static Bool16 IsMetaDataURL(char* theURL); +static void ParseMetaDataURL(char* theURL); +static Bool16 CheckPassword(QTSS_Filter_Params* inParams, StrPtrLen& theRequest, StrPtrLen& mountpoint); +static Bool16 NeedsMetaData(StrPtrLen& theRequest); +static Bool16 ParseURL(StrPtrLen& theRequest, char* outURL, UInt16 maxlen); +static QTSS_Error LogRequest(QTSS_RTSPSessionObject inRTSPSession, MP3ClientSession* client); +static void WriteStartupMessage(); +static void WriteShutdownMessage(); +static void url_strcpy(char* dest, const char* src); +static QTSS_Error ReEnterFilterRequest(QTSS_Filter_Params* inParams, MP3Session* mp3Session); + +static Bool16 IsHTTP(StrPtrLen& theRequest); +static Bool16 IsRTSP(StrPtrLen& theRequest); + + +//************************************************** +// CLASS DECLARATIONS +//************************************************** + +class QTSSMP3AccessLog : public QTSSRollingLog +{ + public: + + QTSSMP3AccessLog(); + virtual ~QTSSMP3AccessLog() {} + + virtual char* GetLogName() { return QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_request_logfile_name", sDefaultLogName); } + virtual char* GetLogDir() { return QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_request_logfile_dir", sDefaultLogDir); } + virtual UInt32 GetRollIntervalInDays() { return sRollInterval; } + virtual UInt32 GetMaxLogBytes() { return sMaxLogBytes; } + virtual time_t WriteLogHeader(FILE *inFile); +}; + +// --------------------------------------------------------------------------- +// CLASS IMPLEMENTATIONS +// --------------------------------------------------------------------------- + +// **************************************************************************** +// QTSSMP3AccessLog -- subclass of QTSSRollingLog +// **************************************************************************** +QTSSMP3AccessLog::QTSSMP3AccessLog() : QTSSRollingLog() +{ + this->SetTaskName("QTSSMP3AccessLog"); +} + +time_t QTSSMP3AccessLog::WriteLogHeader(FILE *inFile) +{ + //The point of this header is to record the exact time the log file was created, + //in a format that is easy to parse through whenever we open the file again. + //This is necessary to support log rolling based on a time interval, and POSIX doesn't + //support a create date in files. + time_t calendarTime = ::time(NULL); + Assert(-1 != calendarTime); + if (-1 == calendarTime) + return -1; + + struct tm timeResult; + struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult); + Assert(NULL != theLocalTime); + if (NULL == theLocalTime) + return -1; + + char tempBuffer[1024] = { 0 }; + qtss_strftime(tempBuffer, sizeof(tempBuffer), "#Log File Created On: %m/%d/%Y %H:%M:%S\n", theLocalTime); + this->WriteToLog(tempBuffer, !kAllowLogToRoll); + tempBuffer[0] = '\0'; + + //format a date for the startup time + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes] = { 0 }; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + if (result) + { + StrPtrLen serverName; + (void)QTSS_GetValuePtr(sServer, qtssSvrServerName, 0, (void**)&serverName.Ptr, &serverName.Len); + StrPtrLen serverVersion; + (void)QTSS_GetValuePtr(sServer, qtssSvrServerVersion, 0, (void**)&serverVersion.Ptr, &serverVersion.Len); + qtss_sprintf(tempBuffer, sLogHeader, serverName.Ptr , serverVersion.Ptr, + theDateBuffer, sLogTimeInGMT ? "GMT" : "local time"); + this->WriteToLog(tempBuffer, !kAllowLogToRoll); + } + + return calendarTime; +} + +// **************************************************************************** +// MP3Session -- This is a base class to hold all the MP3 related +// session state info. +// **************************************************************************** + +// initialize static value of class. +SInt32 MP3Session::sTotalNumMP3Sessions = 0L; + + +MP3Session::MP3Session(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream) : + fState(0), + fResult(200), + fStream(stream), + fSession(sess), + fSessID(0) +{ + sTotalNumMP3Sessions++; + sMP3SessionTable.RegisterSession(this); +} + +MP3Session::MP3Session() : + fState(0), + fResult(0), + fStream(NULL), + fSession(NULL), + fSessID(0) +{ + // this should never be called + Assert(0); +} + +MP3Session::~MP3Session() +{ + --sTotalNumMP3Sessions; + sMP3SessionTable.UnRegisterSession(this); + fStream = NULL; + fSession = NULL; + fSessID = 0; +} + +UInt8 MP3Session::IsA() const +{ + return kMP3UndefinedSessionType; +} + +// **************************************************************************** +// MP3BroadcasterSession -- This is a class to hold all the MP3 Broadcaster +// session state info. +// **************************************************************************** +MP3BroadcasterSession::MP3BroadcasterSession() +{ + // this should never be called. + Assert(0); +} + +MP3BroadcasterSession::MP3BroadcasterSession(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream) : + + MP3Session(sess, stream), + fMP3ClientQueue(NULL), + fDataBufferLen(0), + fDataBufferSize(sBroadcastBufferSize), + fNewSongName(false) +{ + fBuffer = (char*) sOSBufferPoolPtr->Get(); + InitBroadcastSessionState(); + fHeader[0] = '\0'; + fSongName[0] = '\0'; +} + +MP3BroadcasterSession::~MP3BroadcasterSession() +{ + SetState(MP3BroadcasterSession::kBroadcasterShutDownState); + delete fMP3ClientQueue; + fMP3ClientQueue = NULL; + sOSBufferPoolPtr->Put(fBuffer); + fBuffer = NULL; +} + +UInt8 MP3BroadcasterSession::IsA() const +{ + return kMP3BroadcasterSessionType; +} + +void MP3BroadcasterSession::InitBroadcastSessionState() +{ + ::memset(fMountpoint, 0, kURLBufferSize); + ::memset(fHeader, 0, kHeaderBufferSize); + ::memset(fSongName, 0, kSongNameBufferSize); + ::memset(fBuffer, 0, fDataBufferSize); + SetMountpoint("/"); + fDataBufferLen = 0; + SetSessionID(0); + if (fMP3ClientQueue == NULL) + fMP3ClientQueue = NEW MP3ClientQueue(); + SetState(MP3BroadcasterSession::kBroadcasterInitState); +} + +// Run this broadcaster session's state machine. +// This is called each time through the QTSS_FilterRequest mode which +// is highly re-entrant. +QTSS_Error MP3BroadcasterSession::ExecuteState() +{ + QTSS_Error err = QTSS_NoErr; + + // The only states we handle here are kBroadcasterGetHeaderState and + // the kBroadcasterRecvDataState. All others are ignored. + + if (GetState() == MP3BroadcasterSession::kBroadcasterGetHeaderState) + { + err = GetBroadcastHeaders(); + } + else if (GetState() == MP3BroadcasterSession::kBroadcasterRecvDataState) + { + err = GetBroadcastData(); + } + + return err; +} + +// Put this broadcast session in a state where it will accept incoming +// broadcaster connection request. +void MP3BroadcasterSession::AcceptBroadcastSessionState() +{ + SetState(MP3BroadcasterSession::kBroadcasterValidatePasswordState); +} + +// Put this broadcast session in a state where it has accepted +// the broadcaster connection request. +void MP3BroadcasterSession::AcceptPasswordState() +{ + SetState(MP3BroadcasterSession::kBroadcasterGetHeaderState); +} + +// Put this broadcast session in a state where it will reflect +// MP3 data to connected clients. +void MP3BroadcasterSession::AcceptDataStreamState() +{ + SetState(MP3BroadcasterSession::kBroadcasterRecvDataState); +} + +// Put this broadcast session in a state where it will no +// longer accept connections or reflect MP3 data. +void MP3BroadcasterSession::ShutDownState() +{ + SetState(MP3BroadcasterSession::kBroadcasterShutDownState); +} + +// Set the mount point URL that clients will associate +// with this broadcaster session. +void MP3BroadcasterSession::SetMountpoint(char* mp) +{ + StrPtrLen mpStr(mp); + SetMountpoint(mpStr); +} + +// Set the mount point URL that clients will associate +// with this broadcaster session. +void MP3BroadcasterSession::SetMountpoint(StrPtrLen& mp) +{ + ::memset(fMountpoint, 0, kURLBufferSize); + if (mp.Ptr != NULL && mp.Len > 0 && mp.Len < kURLBufferSize) + ::memcpy(fMountpoint, mp.Ptr, mp.Len); +} + +// Set the song name of the currently broadcasting session. +void MP3BroadcasterSession::SetSongName(char* sn) +{ + if (sn != NULL && *sn != '\0') + { + // This mutex is to guard against updating a + // song name string while we're updating our queued + // clients. + OSMutexLocker locker(&fSongNameMutex); + url_strcpy(fSongName, sn); + fNewSongName = true; + } +} + +// See if a given mount point URL matches. +Bool16 MP3BroadcasterSession::MountpointEqual(char* mp) +{ + StrPtrLen mpStr(mp); + return MountpointEqual(mpStr); +} + +// See if a given mount point URL matches. +Bool16 MP3BroadcasterSession::MountpointEqual(StrPtrLen& mp) +{ + if (0 == *fMountpoint) + return false; + + return mp.Equal(fMountpoint); +} + +// Add a MP3 client session ref to our broadcaster +QTSS_Error MP3BroadcasterSession::AddClient(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream) +{ + if (fMP3ClientQueue == NULL) + { + Assert(0); + return QTSS_RequestFailed; + } + return fMP3ClientQueue->AddClient(sess, stream, this); +} + +// Is this RTSP session one of my clients? +Bool16 MP3BroadcasterSession::IsMyClient(QTSS_RTSPSessionObject sess) +{ + if (fMP3ClientQueue == NULL) + { + return false; + } + return fMP3ClientQueue->InQueue(sess); +} + +// Remove a MP3 client session ref from our broadcaster +QTSS_Error MP3BroadcasterSession::RemoveClient(QTSS_RTSPSessionObject sess) +{ + if (fMP3ClientQueue == 0) + { + Assert(0); + return QTSS_BadArgument; + } + return fMP3ClientQueue->RemoveClient(sess); +} + +//Send acknowledgement response to broacaster +QTSS_Error MP3BroadcasterSession::SendOKResponse() +{ + QTSS_Error theErr = QTSS_NoErr; + if (GetStreamRef() == 0) + { + Assert(0); + return theErr; + } + theErr = QTSS_Write(GetStreamRef(), kOKHeader, ::strlen(kOKHeader), NULL, qtssWriteFlagsBufferData); + if (theErr != QTSS_NoErr) + { + return theErr; + } + theErr = QTSS_Flush(GetStreamRef()); + Assert(theErr == QTSS_NoErr); + return theErr; +} + +// Read the broadcast headers +QTSS_Error MP3BroadcasterSession::GetBroadcastHeaders() +{ + QTSS_Error theErr = QTSS_NoErr; + UInt32 theLen = 0; + + Assert(GetStreamRef() != 0); + + ::memset(fHeader, 0, kHeaderBufferSize); + theErr = QTSS_Read(GetStreamRef(), fHeader, kHeaderBufferSize, &theLen); + if (theErr == QTSS_WouldBlock) + { + // we're blocked from reading the broadcast headers so we will + // schedule a read so we can try again later. + theErr = QTSS_RequestEvent(GetStreamRef(), QTSS_ReadableEvent); + Assert(theErr == QTSS_NoErr); + } + else if (theErr == QTSS_NoErr) + { + // The read of the headers succeeded + TerminateHeaders(); + // parse headers here... + if (strncmp(fHeader, "icy-name", 8) != 0) + AcceptDataStreamState(); + // Send another OK response. + SendOKResponse(); + // schedule the first read of the data stream. + theErr = QTSS_RequestEvent(GetStreamRef(), QTSS_ReadableEvent); + Assert(theErr == QTSS_NoErr); + } + else + { + // The read failed with an error other than QTSS_WouldBlock + DTRACE1("MP3BroadcasterSession::GetBroadcastHeaders() - read failed with err = %"_S32BITARG_"\n", theErr); + Assert(0); + } + return theErr; +} + +// Read the broadcast data stream +QTSS_Error MP3BroadcasterSession::GetBroadcastData() +{ + QTSS_Error theErr = QTSS_NoErr; + + if (GetStreamRef() == 0) + { + Assert(0); + return theErr; + } + theErr = QTSS_Read(GetStreamRef(), fBuffer, fDataBufferSize, &fDataBufferLen); + if (theErr == QTSS_WouldBlock) + { + // we're blocked from reading the broadcast headers so we will + // schedule a read so we can try again later. + theErr = QTSS_RequestEvent(GetStreamRef(), QTSS_ReadableEvent); + Assert(theErr == QTSS_NoErr); + } + else if (theErr == QTSS_NoErr) + { + // The read of the broadcast stream succeeded + // Send all our clients a copy of the data + theErr = SendClientsData(); + + // Set up to read the next chunk of incoming broadcast data + // If we didn't do this we'd exit the kFilterRequest state after returning. + // By doing this we get called back when more data arrives. + theErr = QTSS_RequestEvent(GetStreamRef(), QTSS_ReadableEvent); + Assert(theErr == QTSS_NoErr); + } + else if (theErr == QTSS_NotConnected) + { + DTRACE("MP3BroadcasterSession::GetBroadcastData() - read failed with err = QTSS_NotConnected\n"); + ShutDownState(); + } + else + { + DTRACE1("MP3BroadcasterSession::GetBroadcastData() - read failed with err = %"_S32BITARG_"\n", theErr); + Assert(0); + ShutDownState(); + } + return theErr; +} + +// Send the broadcast data stream to all our clients +QTSS_Error MP3BroadcasterSession::SendClientsData() +{ + QTSS_Error theErr = QTSS_NoErr; + + if (fMP3ClientQueue == 0) + { + return theErr; + } + if (fDataBufferLen > 0) + { + // Make sure all queued client sessions have the + // current song name + PreflightClients(); + + // Send MP3 data to everything in our client queue. + + theErr = fMP3ClientQueue->SendToAllClients(fBuffer, fDataBufferLen); + } + return theErr; +} + +// Update all of our queued clients with the currently +// active song name +void MP3BroadcasterSession::PreflightClients() +{ + if (fMP3ClientQueue == 0) + { + return; + } + if (fNewSongName) + { + OSMutexLocker locker(&fSongNameMutex); + // Copy new song name to all queue MP3ClientSessions. + // and attempt to resend data that was previously blocked. + fMP3ClientQueue->PreflightClients(GetSongName()); + fNewSongName = false; + } +} + +// Search through the audiocast headers and terminate them with a '\0' +// to mark the end. +void MP3BroadcasterSession::TerminateHeaders() +{ + char *bp = fHeader; + char *end = fHeader + kHeaderBufferSize; + while (bp < end) + { + // scan for newlines until end of buffer. + if (*bp++ == '\n') + { + // if the next line after newline starts with + // 'x' or 'i' we assume it's an 'x-audiocast-xxx' line + // or an 'icy-xxxx' line, otherwise we're done. + if (*bp != 'x' && *bp != 'i') + break; + } + } + // make sure we didn't go past the end of buffer. + if (bp > end) + bp = end; + // terminate the buffer. + *bp = '\0'; +} + +// **************************************************************************** +// MP3ClientSession -- This is a class to hold all the MP3Client state info. +// **************************************************************************** +MP3ClientSession::MP3ClientSession() +{ + Assert(0); + // this should never be called. +} + +MP3ClientSession::MP3ClientSession( QTSS_RTSPSessionObject sess, + QTSS_StreamRef stream, + MP3BroadcasterSession* owner) : + MP3Session(sess, stream), + fBytesSent(0), + fCurrentBitRate(0), + fLastBitRateBytes(0), + fLastBitRateUpdateTime(0), + fConnectTime(0), + fSendCount(0), + fRetryCount(0), + fOwner(owner), + fNewSongName(false), + fWasBlocked(false), + fBlockTime(0), + fNeedsContentLength(false), + fWantsMetaData(true) +{ + QTSS_Error err = QTSS_NoErr; + const char* kType = "MP3 Client"; + + ::memset(fSongName, 0, kSongNameBufferSize); + ::memset(fHeader, 0, kHeaderBufferSize); + ::memset(fHostName, 0, kHostNameBufferSize); + ::memset(fUserAgent, 0, kUserAgentBufferSize); + ::memset(fRequestBuffer, 0, kRequestBufferSize); + + ParseRequestParams(stream); + + QTSS_LockObject(sServer); + UInt32 index; + UInt32 tempInt; + char tempBuffer[1024]; + UInt32 len; + QTSS_CreateObjectValue(sServer, qtssSvrConnectedUsers, qtssConnectedUserObjectType, &index, &fQTSSObject); + QTSS_SetValue(fQTSSObject, qtssConnectionType, 0, kType, strlen(kType) + 1); + if (fHostName[0] != '\0') + QTSS_SetValue(fQTSSObject, qtssConnectionHostName, 0, fHostName, ::strlen(fHostName) + 1); + QTSS_SetValuePtr(fQTSSObject, qtssConnectionBytesSent, &fBytesSent, sizeof(fBytesSent)); + QTSS_SetValuePtr(fQTSSObject, qtssConnectionCurrentBitRate, &fCurrentBitRate, sizeof(fBytesSent)); + tempInt = 0; + QTSS_SetValue(fQTSSObject, qtssConnectionPacketLossPercent, 0, &tempInt, sizeof(tempInt)); + fConnectTime = OS::Milliseconds(); + QTSS_SetValue(fQTSSObject, qtssConnectionCreateTimeInMsec, 0, &fConnectTime, sizeof(fConnectTime)); + + len = sizeof(tempBuffer); + err = QTSS_GetValue(sess, qtssRTSPSesLocalAddrStr, 0, tempBuffer, &len); + if (err == QTSS_NoErr) + (void)QTSS_SetValue(fQTSSObject, qtssConnectionSessLocalAddrStr, 0, tempBuffer, len); + + len = sizeof(tempBuffer); + err = QTSS_GetValue(sess, qtssRTSPSesRemoteAddrStr, 0, tempBuffer, &len); + if (err == QTSS_NoErr) + (void)QTSS_SetValue(fQTSSObject, qtssConnectionSessRemoteAddrStr, 0, tempBuffer, len); + + char* mountpoint = owner->GetMountpoint(); + (void)QTSS_SetValue(fQTSSObject, qtssConnectionMountPoint, 0, mountpoint, strlen(mountpoint) + 1); + + QTSS_UnlockObject(sServer); + + // Increment the server's MP3 session count. + IncrementMP3SessionCount(); +} + +MP3ClientSession::~MP3ClientSession() +{ + SetState(MP3ClientSession::kClientShutDownState); + + QTSS_Object sessionObject; + UInt32 len = sizeof(QTSS_Object); + + QTSS_LockObject(sServer); + for (int x = 0; QTSS_GetValue(sServer, qtssSvrConnectedUsers, x, &sessionObject, &len) == QTSS_NoErr; x++) + { + Assert(sessionObject != NULL); + Assert(len == sizeof(QTSS_Object)); + + if (sessionObject == fQTSSObject) + { + (void)QTSS_RemoveValue(sServer, qtssSvrConnectedUsers, x); + break; + } + } + QTSS_UnlockObject(sServer); + // Decrease the server's count of MP3 sessions by one. + DecrementMP3SessionCount(); +} + +UInt8 MP3ClientSession::IsA() const +{ + return kMP3ClientSessionType; +} + +// Send the client it HTTP response +QTSS_Error MP3ClientSession::SendResponse() +{ + QTSS_Error theErr = QTSS_NoErr; + UInt32 len = 0; + char buffer[512]; + + if (GetState() == MP3ClientSession::kClientInitState) + { + // + // Note: We don't Parse any client request HTTP parms here... + // + SetState(MP3ClientSession::kClientSendResponse); + theErr = QTSS_Write(GetStreamRef(), gClientAcceptHeader, ::strlen(gClientAcceptHeader), NULL, qtssWriteFlagsBufferData); + if (theErr != QTSS_NoErr) + { + SetState(MP3ClientSession::kClientShutDownState); + return theErr; + } + // x-audiocast header parameters are added and sent here + if (fWantsMetaData) + { + // add the "icy-metaint:xxxx" parameter to the header. + qtss_sprintf(buffer, "icy-metaint:%d\r\n", kClientMetaInt); + ::strcat(fHeader, buffer); + } + len = ::strlen(fHeader); + if (len > 0) + { + theErr = QTSS_Write(GetStreamRef(), fHeader, len, NULL, qtssWriteFlagsBufferData); + if (theErr != QTSS_NoErr) + { + SetState(MP3ClientSession::kClientShutDownState); + return theErr; + } + } + // ...and send the phony content length or just a newline to mark end of header + if (WantsContentLength()) + theErr = QTSS_Write(GetStreamRef(), kClientCLHeader, ::strlen(kClientCLHeader), NULL, qtssWriteFlagsBufferData); + else + theErr = QTSS_Write(GetStreamRef(), "\r\n", 2, NULL, qtssWriteFlagsBufferData); + if (theErr != QTSS_NoErr) + { + Assert(0); + SetState(MP3ClientSession::kClientShutDownState); + return theErr; + } + else + { + SetState(MP3ClientSession::kClientSendDataState); + } + + // + // Make sure the client session remains in FilterRequest server role. + // We do this by scheduling ourself to be called back in + // kClientPollInterval milliseconds. When called back again we will + // continue to schedule ourself until we get into another + // + + KeepSession((QTSS_RTSPRequestObject) GetStreamRef(), true); + theErr = QTSS_SetIdleTimer(kClientPollInterval); + if (theErr != QTSS_NoErr) + { + Assert(0); + SetState(MP3ClientSession::kClientShutDownState); + } + } + return theErr; +} + +// Send the broadcast data stream to our client including meta-data +// if needed. +QTSS_Error MP3ClientSession::SendMP3Data(char* buffer, UInt32 bufferlen) +{ + QTSS_Error theErr = QTSS_NoErr; + UInt32 sendcount; + UInt32 sendlen = 0L; + UInt32 remaininglen = 0L; + + // get the broadcast sendcount into our local copy. + sendcount = fSendCount; + Assert(buffer != NULL); + // We only send data if we are int the kClientSendDataState. + if (GetState() != MP3ClientSession::kClientSendDataState) + { + return theErr; + } + // If we are using meta data then check and see if we hit our + // meta-data send interval. + // if so calculate the second write size. + if (fWantsMetaData && ((sendcount + bufferlen) > kClientMetaInt)) + { + // sendlen is the amount of buffered data to send before meta data + // is interposed. + sendlen = (UInt32) kClientMetaInt - sendcount; + // remainglen is the reaming amount of buffered data to send + // after sending the meta-data. + remaininglen = bufferlen - sendlen; + } + else + { + // either it's not time to send metadata or we are not + // doing meta-data for this client. + sendlen = bufferlen; + remaininglen = 0L; + } + // Send up to sendlen bytes of data from the buffer. + QTSS_StreamRef stream = GetStreamRef(); + + Assert(stream != NULL); + + Assert(sendlen <= bufferlen); + + theErr = QTSS_Write(stream, buffer, sendlen, NULL, qtssWriteFlagsBufferData); + sendcount += sendlen; + + if (theErr != QTSS_NoErr) + { + SetState(MP3ClientSession::kClientShutDownState); + return theErr; + } + // If there is meta-data this time then send it followed by remaininglen bytes + // from the buffer. + if (remaininglen != 0L) + { + // send the meta-data + theErr = SendMetaData(); + if (theErr != QTSS_NoErr) + { + SetState(MP3ClientSession::kClientShutDownState); + return theErr; + } + // sendcount must be zero after sending meta-data. + sendcount = 0; + // send the remainder of the data buffer. + theErr = QTSS_Write(GetStreamRef(), buffer+sendlen, remaininglen, NULL, qtssWriteFlagsBufferData); + sendcount += remaininglen; + + if (theErr != QTSS_NoErr) + { + SetState(MP3ClientSession::kClientShutDownState); + return theErr; + } + } + fSendCount = sendcount; + // Flush to perform the actuall send to the client. + theErr = QTSS_Flush(GetStreamRef()); + if (theErr == QTSS_WouldBlock) + { + // the client is flow controlled. + SInt64 curTime = OS::Milliseconds(); + fWasBlocked = true; + QTSS_RequestEvent(GetStreamRef(), QTSS_WriteableEvent); + DTRACE2("Got blocked at time %qd, numRetries = %"_S32BITARG_"\n", curTime, fRetryCount); + if (fBlockTime == 0) + fBlockTime = curTime; + fRetryCount++; + if (curTime - fBlockTime > sMaxFlowControlTimeInMSec) + { + SetResult(453); + DTRACE1("MP3ClientSession::SendMP3Data - too many tries. Terminating client session = %"_S32BITARG_"\n", GetSessionID()); + SetState(MP3ClientSession::kClientShutDownState); + } + } + else if (theErr != QTSS_NoErr) + { + DTRACE1("MP3ClientSession::SendMP3Data - Terminating client session = %"_S32BITARG_"\n", GetSessionID()); + SetState(MP3ClientSession::kClientShutDownState); + } + else + { + // we successfully sent data to the client. + // fSendCount only gets incremented if the QTSS_Flush() was successful + SetResult(200); + fWasBlocked = false; + fBlockTime = 0; + fRetryCount = 0; + } + + fBytesSent += bufferlen; + // Increase the server's total MP3 byte count attribute + IncrementTotalMP3Bytes(bufferlen); + UpdateBitRateInternal(OS::Milliseconds()); + + return theErr; +} + +// Retry to send previously blocked data. +QTSS_Error MP3ClientSession::RetrySendData() +{ + QTSS_Error theErr = QTSS_NoErr; + + // Flush to perform the actuall send to the client. + theErr = QTSS_Flush(GetStreamRef()); + if (theErr == QTSS_WouldBlock) + { + // the client is still flow controlled. + fWasBlocked = true; + QTSS_RequestEvent(GetStreamRef(), QTSS_WriteableEvent); + return theErr; + } + else if (theErr != QTSS_NoErr) + { + // some error other than QTSS_WouldBlock occured. + SetResult(454); + SetState(MP3ClientSession::kClientShutDownState); + return theErr; + } + else + { + // we successfully sent data to the client. + // cancel future retries + fWasBlocked = false; + SetResult(200); + fRetryCount = 0; + } + return QTSS_NoErr; +} + +// Send the broadcast meta data stream to our client +QTSS_Error MP3ClientSession::SendMetaData() +{ + QTSS_Error theErr = QTSS_NoErr; + char buffer[1024]; + UInt16 bufferlen; + + // Don't allow an update of the song name while we're sending + // it to the client. + OSMutexLocker locker(&fSongNameMutex); + // format the meta-data + ::memset(buffer, 0, 1024); + // Make sure we have something to send + if (!fNewSongName || fSongName[0] == '\0') + { + // setup to write a single NULL byte. + bufferlen = 1; + } + else + { + // Setup to write meta-data + pad NULL bytes. + // first value in the buffer is the number of 16 byte chunks + // to send. + char tmp[512]; + qtss_sprintf(tmp, "StreamTitle='%s';StreamUrl='';", fSongName); + bufferlen = ::strlen(tmp); + buffer[0] = (unsigned char)((bufferlen/16) + 1); + ::strcat(buffer, tmp); + bufferlen = (buffer[0]*16) + 1; + } + // send the meta-data + theErr = QTSS_Write(GetStreamRef(), buffer, bufferlen, NULL, qtssWriteFlagsBufferData); + + if (theErr == QTSS_NoErr) + { + // meta data was written successfully + fNewSongName = false; + } + fBytesSent += bufferlen; + return theErr; +} + +// Set the x-audiocast headers of the currently broadcasting client. +// Lines that end in '\n' will have a '\r' prepended. +void MP3ClientSession::SetHeader(char* header) +{ + char *bp = header; + char *cp = fHeader; + char *end = fHeader + kHeaderBufferSize; + while (cp < end) + { + if (*bp == '\0') + { + // end of header buffer string + break; + } + // scan for newlines . + if (*bp == '\n') + { + // add a CR before the newline + *cp = '\r'; + cp++; + *cp = '\n'; + } + else + { + // otherwise just copy the byte + *cp = *bp; + } + cp++; + bp++; + } + // make sure we didn't go past the end of buffer. + if (cp > end) + cp = end; + // terminate the buffer. + *cp = '\0'; +} + +// Set the song name of the currently broadcasting client. +void MP3ClientSession::SetSongName(char* sn) +{ + if (sn != NULL && *sn != '\0') + { + // This mutex is to guard against updating a + // song name string while we're trying to send it + // to a remote client. + OSMutexLocker locker(&fSongNameMutex); + ::strcpy(fSongName, sn); + fNewSongName = true; + } +} + +void MP3ClientSession::UpdateBitRateInternal(const SInt64& curTime) +{ + if (curTime < fLastBitRateUpdateTime + 10000) + return; // update the bitrate every 10 seconds + + UInt32 bitsInInterval = (fBytesSent - fLastBitRateBytes) * 8; + SInt64 updateTime = (curTime - fLastBitRateUpdateTime) / 1000; + if (updateTime > 0) // leave Bit Rate the same if updateTime is 0 also don't divide by 0. + fCurrentBitRate = (UInt32) ( bitsInInterval / updateTime ); + fLastBitRateBytes = fBytesSent; + fLastBitRateUpdateTime = curTime; +} + +void MP3ClientSession::ParseRequestParams(QTSS_StreamRef stream) +{ + QTSS_Error err = QTSS_NoErr; + char *bp = NULL; + unsigned int idx; + // get a copy of the first line of the HTTP request + QTSS_RTSPRequestObject theRequest = (QTSS_RTSPRequestObject) stream; + StrPtrLen theFullRequest; + if (theRequest != NULL) + err = QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len); + else + err = QTSS_BadArgument; + if (err == QTSS_NoErr) + { + // make sure we don't overflow our buffer + if (theFullRequest.Len >= kRequestBufferSize) + theFullRequest.Len = kRequestBufferSize-1; + // copy the request into our private class buffer. + ::memcpy(fRequestBuffer, theFullRequest.Ptr, theFullRequest.Len); + // first see if the client wants to recieve meta-data + fWantsMetaData = NeedsMetaData(theFullRequest); + + // Do we need to send a phony HTTP content length field? + if (::strstr(fRequestBuffer, "MSIE") != NULL) + fNeedsContentLength = true; + else if (::strstr(fRequestBuffer, "RMA/1.0") != NULL) + fNeedsContentLength = true; + else if (::strstr(fRequestBuffer, "NSPlayer") != NULL) + fNeedsContentLength = true; + else + fNeedsContentLength = false; + // Get a copy ot the requestor's host name if it exists + bp = ::strstr(fRequestBuffer, "Host:"); + if (bp != NULL) + { + bp += ::strlen("Host:"); + while (*bp == ' ') + bp++; + idx = 0; + while (bp[idx]) + { + if (idx >= kHostNameBufferSize-1) + break; + if (bp[idx] == '\n' || bp[idx] == '\r') + break; + fHostName[idx] = bp[idx]; + idx++; + } + } + // Get a copy ot the requesting User Agent's name if it exists + bp = ::strstr(fRequestBuffer, "User-Agent:"); + if (bp != NULL) + { + bp += ::strlen("User-Agent:"); + while (*bp == ' ') + bp++; + idx = 0; + while (bp[idx]) + { + if (idx >= kUserAgentBufferSize-1) + break; + if (bp[idx] == '\n' || bp[idx] == '\r') + break; + fUserAgent[idx] = bp[idx]; + idx++; + } + } + // Now that we're done parsing the request we will + // terminate first line of the request with '\0'. + for (idx=0; idx < theFullRequest.Len; idx++) + { + if (fRequestBuffer[idx] == '\r' || fRequestBuffer[idx] == '\n') + { + fRequestBuffer[idx] = '\0'; + break; + } + } + } + else + { + // dummy value so that we have something to log + ::strcpy(fRequestBuffer, "GET /* HTTP/1.0"); + } +} + +// **************************************************************************** +// MP3SessionRef -- This class is just a wrapper class for handling the +// mapping of RTSP Session refs to the corresponding MP3 Session class refs. +// It will be the an element of our lookup hash table. +// **************************************************************************** +MP3SessionRef::MP3SessionRef(MP3Session* mp3Session) : + fNextHashEntry(NULL), + fHashValue(0), + fMP3Session(mp3Session) +{ +} + +MP3SessionRef::~MP3SessionRef() +{ +} + +// **************************************************************************** +// MP3SessionRefKey -- This class is used to generate hash keys for looking +// up values in our MP3SessionTable. The Hash key is just the RTSP Session +// reference value. +// **************************************************************************** +MP3SessionRefKey::MP3SessionRefKey(MP3SessionRef* mp3SessRef) : + fKeyValue(mp3SessRef) +{ + fHashValue = 0L; + if (fKeyValue != NULL) + { + fMP3Session = fKeyValue->GetMP3Session(); + if (fMP3Session != NULL) + fHashValue = (PointerSizedInt)fMP3Session->GetSession(); + } + else + { + fMP3Session = NULL; + } +} + +MP3SessionRefKey::MP3SessionRefKey(QTSS_RTSPSessionObject rtspSessRef) : + fKeyValue(NULL), + fHashValue((PointerSizedInt)rtspSessRef), + fMP3Session(NULL) +{ +} + +MP3SessionRefKey::~MP3SessionRefKey() +{ +} + +// **************************************************************************** +// MP3SessionTable -- This class provides a way to map RTSP Session references +// into the corresponding MP3Session class instances if any. +// **************************************************************************** +MP3SessionTable::MP3SessionTable(UInt32 tableSize) : + fTable(tableSize), + fMutex() +{ +} + +MP3SessionTable::~MP3SessionTable() +{ +} + +// Attempt to add a new MP3Session ref to the table's map. +// returns true on success or false if it fails. +Bool16 MP3SessionTable::RegisterSession(MP3Session* session) +{ + // sanity check + if (session == NULL) + { + Assert(0); + return false; + } + // construct a new entry for the table. + MP3SessionRef* newEntry = NEW MP3SessionRef(session); + if (newEntry == NULL) + { + return false; + } + // generate it's hash key. + MP3SessionRefKey key(newEntry); + // Lock the table while we try and add the new entry. + OSMutexLocker locker(&fMutex); + // check for a duplicate entry; + MP3SessionRef* duplicateRef = fTable.Map(&key); + if (duplicateRef != NULL) + { + delete newEntry; + Assert(0); + return false; + } + // add the new entry to the table. + fTable.Add(newEntry); + return true; +} + +// Given an QTSS_RTSPSessionObject resolve it into a MP3Session class +// reference. Returns NULL if there's none in out map. +MP3Session* MP3SessionTable::Resolve(QTSS_RTSPSessionObject rtspSession) +{ + // sanity check + if (rtspSession == NULL) + { + Assert(0); + return NULL; + } + // generate it's hash key. + MP3SessionRefKey key(rtspSession); + // Lock the table while we do our lookup. + OSMutexLocker locker(&fMutex); + // Look for the entry in the table's map; + MP3SessionRef* entry = fTable.Map(&key); + if (entry != NULL) + { + // We found it. return the entry's MP3Session ref. + return entry->GetMP3Session(); + } + // it's not in the table. + return NULL; +} + +// Attempt to remove a MP3Session ref from the table's map. +// returns true on success or false if it fails. +Bool16 MP3SessionTable::UnRegisterSession(MP3Session* session) +{ + // sanity check + if (session == NULL) + { + Assert(0); + return false; + } + // generate it's hash key. + MP3SessionRef entryRef(session); + MP3SessionRefKey key(&entryRef); + // Lock the table while we try and remove the entry. + OSMutexLocker locker(&fMutex); + // look for the entry in the table's map; + MP3SessionRef* entry = fTable.Map(&key); + if (entry != NULL) + { + // We found it. Remove it from the table and delete the + // reference instance created by RegisterSession(). + fTable.Remove(entry); + delete entry; + return true; + } + // it's not in the table. + return false; +} + +// **************************************************************************** +// MP3BroadcasterQueue -- This is a class manages a queue of MP3Broadcaster +// objects. +// **************************************************************************** +MP3BroadcasterQueue::MP3BroadcasterQueue() +{ +} + +MP3BroadcasterQueue::~MP3BroadcasterQueue() +{ +} + +// Create a new MP3BroadcasterSession object and queue it. +QTSS_Error MP3BroadcasterQueue::CreateBroadcaster(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream, StrPtrLen& mountpt) +{ + QTSS_Error theErr = QTSS_NoErr; + UInt32 uSessID = 0L; + + if (sess == 0 || stream == 0) + { + return QTSS_BadArgument; + } + + if ((fQueue.GetLength() > 0) && (FindByMountPoint(mountpt) != NULL)) + { + theErr = QTSS_Write(stream, kSourceReject, ::strlen(kSourceReject), NULL, qtssWriteFlagsBufferData); + if (theErr != QTSS_NoErr) + { + return theErr; + } + theErr = QTSS_Flush(stream); + DTRACE("MP3BroadcasterQueue::CreateBroadcaster - duplicate mountpoint\n"); + return QTSS_BadArgument; + } + + if ( (fQueue.GetLength() > 0) && InQueue(sess) ) + { + theErr = QTSS_Write(stream, kSourceBadPassword, ::strlen(kSourceBadPassword), NULL, qtssWriteFlagsBufferData); + if (theErr != QTSS_NoErr) + { + return theErr; + } + theErr = QTSS_Flush(stream); + DTRACE("MP3BroadcasterQueue::CreateBroadcaster - duplicate session\n"); + return QTSS_BadArgument; + } + + MP3BroadcasterSession* broadcaster = NEW MP3BroadcasterSession(sess, stream); + if (broadcaster == NULL) + { + return QTSS_NotEnoughSpace; + } + uSessID = GetRTSPSessionID(sess); + broadcaster->SetSessionID(uSessID); + broadcaster->SetMountpoint(mountpt); + broadcaster->AcceptPasswordState(); + OSQueueElem* elem = NEW OSQueueElem(broadcaster); + if (elem != 0) + { + OSMutexLocker locker(&fMutex); + fQueue.EnQueue(elem); + DTRACE1("MP3BroadcasterQueue::CreateBroadcaster() Session(%"_S32BITARG_") broadcaster added!\n", uSessID); + // send the "OK" back to the broadcaster + theErr = broadcaster->SendOKResponse(); + // If we failed to send the "OK" probably becuase of flow control + // the only thing to do is to fail the attempt. + if (theErr != QTSS_NoErr) + broadcaster->ShutDownState(); + } + else + { + delete broadcaster; + broadcaster = NULL; + theErr = QTSS_NotEnoughSpace; + } + return theErr; +} + +// Dequeue a MP3BroadcasterSession object and delete it. +QTSS_Error MP3BroadcasterQueue::RemoveBroadcaster(QTSS_RTSPSessionObject sess) +{ + UInt32 uSessID = 0L; + if (sess == 0) + { + return QTSS_BadArgument; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject(); + if ((current != 0) && (current->GetSession() == sess)) + { + elem->Remove(); + uSessID = current->GetSessionID(); + DTRACE1("MP3BroadcasterQueue::RemoveBroadcaster() Session(%"_S32BITARG_") broadcaster removed!\n", uSessID); + delete elem; + delete current; + return QTSS_NoErr; + } + iter.Next(); + } + return QTSS_BadArgument; +} + +// Find and dequeue a MP3ClientSession object and delete it. +QTSS_Error MP3BroadcasterQueue::RemoveClient(QTSS_RTSPSessionObject sess) +{ + if (sess == 0) + { + return QTSS_BadArgument; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject(); + if ((current != 0) && (current->IsMyClient(sess))) + { + return current->RemoveClient(sess); + } + iter.Next(); + } + return QTSS_BadArgument; +} + + + +// See if a particular Broadcaster RTSP session is in our queue. +Bool16 MP3BroadcasterQueue::InQueue(QTSS_RTSPSessionObject sess) +{ + if (sess == 0) + { + return false; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject(); + if ((current != 0) && (current->GetSession() == sess)) + { + return true; + } + iter.Next(); + } + return false; +} + + +// See if a particular Client RTSP session is in any of our broadcasters' queues. +Bool16 MP3BroadcasterQueue::IsActiveClient(QTSS_RTSPSessionObject sess) +{ + if (sess == 0) + { + return false; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject(); + if ((current != 0) && (current->IsMyClient(sess))) + { + return true; + } + iter.Next(); + } + return false; +} + + +// Find a MP3BroadcasterSession in the queue by it's mountpoint. +MP3BroadcasterSession* MP3BroadcasterQueue::FindByMountPoint(char* mountpoint) +{ + if (mountpoint == NULL || mountpoint[0] == '\0') + { + return NULL; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject(); + if ((current != 0) && (current->MountpointEqual(mountpoint))) + { + return current; + } + iter.Next(); + } + return NULL; +} + +// Find a MP3BroadcasterSession in the queue by it's mountpoint. +MP3BroadcasterSession* MP3BroadcasterQueue::FindByMountPoint(StrPtrLen& mountpoint) +{ + if (mountpoint.Ptr == NULL || mountpoint.Len == 0) + { + return NULL; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject(); + if ((current != 0) && (current->MountpointEqual(mountpoint))) + { + return current; + } + iter.Next(); + } + return NULL; +} + +// Find a MP3BroadcasterSession in the queue by it's RTSP Session. +MP3BroadcasterSession* MP3BroadcasterQueue::FindBySession(QTSS_RTSPSessionObject sess) +{ + if (sess == 0) + { + return NULL; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject(); + if ((current != 0) && (current->GetSession() == sess)) + { + return current; + } + iter.Next(); + } + return NULL; +} + + +// Mark all broadcast sessions to be terminated when they are next called. +void MP3BroadcasterQueue::TerminateAllBroadcastSessions() +{ + OSMutexLocker locker(&fMutex); + OSQueueElem* elem = NULL; + OSQueueIter iter(&fQueue); + + while ((elem = iter.GetCurrent()) != NULL) + { + MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject(); + if (current != NULL) + { + current->SetState(MP3BroadcasterSession::kBroadcasterShutDownState); + } + iter.Next(); + } +} + +// **************************************************************************** +// MP3ClientQueue -- This is a class maintains a queue of MP3Client objects. +// **************************************************************************** +MP3ClientQueue::MP3ClientQueue() +{ +} + +MP3ClientQueue::~MP3ClientQueue() +{ + // Make sure all queued client sessions are marked for termination since + // since they are now orphaned and no longer have an owner. + TerminateClients(); +} + +// Create a new MP3ClientSession object and queue it. +QTSS_Error MP3ClientQueue::AddClient(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream, + MP3BroadcasterSession* owner) +{ + QTSS_Error theErr = QTSS_NoErr; + UInt32 uSessID = 0L; + + if (sess == 0 || stream == 0) + { + return QTSS_BadArgument; + } + + if ( InQueue (sess) ) + { + return QTSS_NoErr; + } + + MP3ClientSession* client = NEW MP3ClientSession(sess, stream, owner); + if (client == 0) + { + return QTSS_NotEnoughSpace; + } + uSessID = GetRTSPSessionID(sess); + client->SetSessionID(uSessID); + if (owner != NULL) + { + // Get a copy of the x-audiocast headers from the + // broadcaster's cached copy. + client->SetHeader(owner->GetHeader()); + // Make sure we have a song name also + client->SetSongName(owner->GetSongName()); + } + OSQueueElem* elem = NEW OSQueueElem(client); + if (elem != 0) + { + OSMutexLocker locker(&fMutex); + fQueue.EnQueue(elem); + DTRACE1("MP3ClientQueue::AddClient() Session(%"_S32BITARG_") client added!\n", uSessID); + // send the "OK" back to the broadcaster + theErr = client->SendResponse(); + } + else + { + delete client; + theErr = QTSS_NotEnoughSpace; + } + return theErr; +} + +// Dequeue a MP3ClientSession object and delete it. +QTSS_Error MP3ClientQueue::RemoveClient(QTSS_RTSPSessionObject sess) +{ + UInt32 uSessID = 0L; + if (sess == 0) + { + return QTSS_BadArgument; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject(); + if ((curClient != 0) && (curClient->GetSession() == sess)) + { + elem->Remove(); + uSessID = curClient->GetSessionID(); + DTRACE1("MP3ClientQueue::RemoveClient() Session(%"_S32BITARG_") client removed!\n", uSessID); + delete elem; + delete curClient; + return QTSS_NoErr; + } + iter.Next(); + } + return QTSS_BadArgument; +} + + +// See if a particular Client RTSP session is in our queue. +Bool16 MP3ClientQueue::InQueue(QTSS_RTSPSessionObject sess) +{ + if (sess == 0) + { + return false; + } + OSMutexLocker locker(&fMutex); + OSQueueIter iter(&fQueue); + OSQueueElem* elem = NULL; + while ((elem = iter.GetCurrent()) != 0) + { + MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject(); + if ((curClient != 0) && (curClient->GetSession() == sess)) + { + return true; + } + iter.Next(); + } + return false; +} + + +// Send MP3 data to all queued MP3ClientSession objects. +QTSS_Error MP3ClientQueue::SendToAllClients(char* buffer, UInt32 bufferlen) +{ + QTSS_Error theErr = QTSS_NoErr; + OSMutexLocker locker(&fMutex); + OSQueueElem* elem = NULL; + OSQueueIter iter(&fQueue); + + while ((elem = iter.GetCurrent()) != NULL) + { + MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject(); + if (curClient != NULL) + { + QTSS_Error err; + + err = curClient->SendMP3Data(buffer, bufferlen); + if (err != QTSS_NoErr) + theErr = err; + } + iter.Next(); + } + return theErr; +} + +// Update the current song name of queued MP3ClientSession objects. +// and attempt to resend data thatwas previously blocked. +void MP3ClientQueue::PreflightClients(char* sn) +{ + QTSS_Error theErr = QTSS_NoErr; + OSMutexLocker locker(&fMutex); + OSQueueElem* elem = NULL; + OSQueueIter iter(&fQueue); + + while ((elem = iter.GetCurrent()) != NULL) + { + MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject(); + if (curClient != NULL) + { + if (curClient->WasBlocked()) + { + theErr = curClient->RetrySendData(); + } + curClient->SetSongName(sn); + } + iter.Next(); + } +} + +// Mark all clients to be terminated when they are next polled. +// This should be called by the MP3BroadcasterSession whenever it dies +// with active client else we'll have a nasty memory leak and clients +// will pause until they timeout. +void MP3ClientQueue::TerminateClients() +{ + OSMutexLocker locker(&fMutex); + OSQueueElem* elem = NULL; + OSQueueIter iter(&fQueue); + + while ((elem = iter.GetCurrent()) != NULL) + { + MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject(); + if (curClient != NULL) + { + curClient->SetOwner(NULL); + curClient->SetState(MP3ClientSession::kClientShutDownState); + } + iter.Next(); + } +} + +// **************************************************************************** +// MODULE FUNCTION IMPLEMENTATIONS +// **************************************************************************** +#if DEBUG_MP3STREAMING_MODULE + void PrintStringBuffer(StrPtrLen& stringBuffer) + { + int len = (int)stringBuffer.Len; + char localBuffer[256]; + + if (len > 255) + len = 255; + ::memcpy(localBuffer, stringBuffer.Ptr, len); + localBuffer[len] = '\0'; + qtss_printf("%s", localBuffer); + } +#endif + +inline void KeepSession(QTSS_RTSPRequestObject theRequest,Bool16 keep) +{ + (void)QTSS_SetValue(theRequest, qtssRTSPReqRespKeepAlive, 0, &keep, sizeof(keep)); +} + +QTSS_Error QTSSMP3StreamingModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSMP3StreamingModuleDispatch); +} + +// QTSSMP3StreamingModuleDispatch - Dispatch all the API roles that this module handles +QTSS_Error QTSSMP3StreamingModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RTSPFilter_Role: + return FilterRequest(&inParams->rtspFilterParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPSessionClosing_Role: + return SessionClosing(&inParams->rtspSessionClosingParams); + case QTSS_StateChange_Role: + return StateChange(&inParams->stateChangeParams); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + +// Register - register all the API roles that this module handles +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPFilter_Role); + (void)QTSS_AddRole(QTSS_RTSPSessionClosing_Role); + (void)QTSS_AddRole(QTSS_StateChange_Role); + (void)QTSS_AddRole(QTSS_Shutdown_Role); + + // Tell the server our name! + static char* sModuleName = "QTSSMP3StreamingModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sServer = inParams->inServer; + sServerPrefs = inParams->inPrefs; + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + + RereadPrefs(); + + sOSBufferPoolPtr = NEW OSBufferPool(sBroadcastBufferSize); + sLogMutex = NEW OSMutex(); + sAtomicMutex = NEW OSMutex(); + + // pre-format the standard HTTP reply headers + qtss_sprintf(gClientAcceptHeader, kClientAcceptHeader, kVersionString, kBuildString); + qtss_sprintf(gM3UReplyHeader, kM3UReplyHeader, kVersionString, kBuildString); + + sMP3AccessLog = NEW QTSSMP3AccessLog(); + + if (sMP3AccessLog != NULL && sLogEnabled) + sMP3AccessLog->EnableLog(); + WriteStartupMessage(); + + return QTSS_NoErr; +} + +// RereadPrefs - refread our prefs from the prefs file. +QTSS_Error RereadPrefs() +{ + delete [] sDefaultLogDir; + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorLogDir, 0, &sDefaultLogDir); + + delete [] sLogName; + sLogName = QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_request_logfile_name", sDefaultLogName); + + delete [] sLogDir; + sLogDir = QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_request_logfile_dir", sDefaultLogDir); + + QTSSModuleUtils::GetAttribute(sPrefs, "mp3_streaming_enabled", qtssAttrDataTypeBool16, + &sMP3StreamingEnabled, &sDefaultMP3StreamingEnabled, sizeof(sMP3StreamingEnabled)); + + delete [] sBroadcastPassword; + sBroadcastPassword = QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_broadcast_password", sDefaultBroadcastPassword); + + // if broadcast password is blank or empty don't allow MP3 streaming. + if (sBroadcastPassword == NULL || *sBroadcastPassword == '\0' || *sBroadcastPassword == ' ') + { + sMP3StreamingEnabled = false; + } + + QTSSModuleUtils::GetAttribute(sPrefs, "mp3_broadcast_buffer_size", qtssAttrDataTypeUInt32, + &sBroadcastBufferSize, &sDefaultBroadcastBufferSize, sizeof(sBroadcastBufferSize)); + + QTSSModuleUtils::GetAttribute(sPrefs, "mp3_max_flow_control_time", qtssAttrDataTypeSInt32, + &sMaxFlowControlTimeInMSec, &sDefaultFlowControlTimeInMSec, sizeof(sMaxFlowControlTimeInMSec)); + + QTSSModuleUtils::GetAttribute(sPrefs, "mp3_request_logging", qtssAttrDataTypeBool16, + &sLogEnabled, &sDefaultLogEnabled, sizeof(sLogEnabled)); + + QTSSModuleUtils::GetAttribute(sPrefs, "mp3_request_logfile_size", qtssAttrDataTypeUInt32, + &sMaxLogBytes, &sDefaultMaxLogBytes, sizeof(sMaxLogBytes)); + + QTSSModuleUtils::GetAttribute(sPrefs, "mp3_request_logfile_interval", qtssAttrDataTypeUInt32, + &sRollInterval, &sDefaultRollInterval, sizeof(sRollInterval)); + + QTSSModuleUtils::GetAttribute(sPrefs, "mp3_request_logtime_in_gmt", qtssAttrDataTypeBool16, + &sLogTimeInGMT, &sDefaultLogTimeInGMT, sizeof(sLogTimeInGMT)); + + UInt32 len = sizeof(SInt32); + (void) QTSS_GetValue(sServerPrefs, qtssPrefsMaximumConnections, 0, (void*)&sMaximumConnections, &len); + (void) QTSS_GetValue(sServerPrefs, qtssPrefsMaximumBandwidth, 0, (void*)&sMaximumBandwidth, &len); + + // handle changing the sLogEnabled state of the access log. + if (sMP3AccessLog != NULL) + { + if (sLogEnabled) + { + if (!sMP3AccessLog->IsLogEnabled()) + sMP3AccessLog->EnableLog(); + } + else + { + if (sMP3AccessLog->IsLogEnabled()) + sMP3AccessLog->CloseLog(); + } + } + + return QTSS_NoErr; +} + +// SessionClosing - clean up broadcaster/client connections. +QTSS_Error SessionClosing(QTSS_RTSPSession_Params* inParams) +{ + Assert(inParams != NULL); +#if DEBUG_MP3STREAMING_MODULE + UInt32 uSessID = 0L; + uSessID = GetRTSPSessionID(inParams->inRTSPSession); + DTRACE1("Closing SessionID = %"_S32BITARG_"\n", uSessID); +#endif + MP3Session* mp3sess = sMP3SessionTable.Resolve(inParams->inRTSPSession); + // If Resolve() returns a non-NULL value then we are closing + // the RTSP session related to either a reflected MP3 broadcast + // session or we are closing an RTSP session related to an + // MP3 client session. + // + // If Resolve() returns NULL this RTSP session does not belong to + // this module and we just ignore it by returning QTSS_NoErr. + if (mp3sess != NULL) + { + if (mp3sess->IsA() == kMP3BroadcasterSessionType) + { + // Remove the MP3BroadcasterSession from the global + // server queue and delete its instance. + return sMP3BroadcasterQueue.RemoveBroadcaster(inParams->inRTSPSession); + } + else if (mp3sess->IsA() == kMP3ClientSessionType) + { + MP3ClientSession* client = (MP3ClientSession*)mp3sess; + MP3BroadcasterSession* owner = client->GetOwner(); + if (sLogEnabled) + LogRequest(inParams->inRTSPSession, client); + if (owner != NULL) + { + // Let the the owner, a MP3BroadcasterSession, + // remove the client from its queue and delete it. + return owner->RemoveClient(inParams->inRTSPSession); + } + else + { + // otherwise, we no longer have an owner for this + // client session and we just delete the instance. + delete client; + return QTSS_NoErr; + } + } + } + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + WriteShutdownMessage(); + return QTSS_NoErr; +} + +QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams) +{ + if (stateChangeParams->inNewState == qtssIdleState) + { + WriteShutdownMessage(); + sMP3BroadcasterQueue.TerminateAllBroadcastSessions(); + } + else + { + WriteStartupMessage(); + } + + return QTSS_NoErr; +} + +// IncrementMP3SessionCount - This increments the MP3 session counter in the server's attributes +void IncrementMP3SessionCount() +{ + QTSS_Error err = QTSS_NoErr; + + // Increment the server's MP3 session count. + UInt32* ptrValue = NULL; + UInt32 valueLen = sizeof(ptrValue); + + OSMutexLocker locker(sAtomicMutex); + + err = QTSS_GetValuePtr(sServer, qtssMP3SvrCurConn, 0, (void**)&ptrValue, &valueLen); + if ((err == QTSS_NoErr) && (ptrValue != NULL)) + { + *ptrValue += 1; + } + else + { + DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrCurConn failed with err = %"_S32BITARG_"\n", (SInt32)err); + } + // bump the total sessions count up also + err = QTSS_GetValuePtr(sServer, qtssMP3SvrTotalConn, 0, (void**)&ptrValue, &valueLen); + if ((err == QTSS_NoErr) && (ptrValue != NULL)) + { + *ptrValue += 1; + } + else + { + DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrTotalConn failed with err = %"_S32BITARG_"\n", (SInt32)err); + } +} + +// DecrementMP3SessionCount - This decrements the MP3 session counter in the server's attributes +void DecrementMP3SessionCount() +{ + QTSS_Error err = QTSS_NoErr; + + // Decrement the server's MP3 session count. + UInt32* ptrValue = NULL; + UInt32 valueLen = sizeof(ptrValue); + + err = QTSS_GetValuePtr(sServer, qtssMP3SvrCurConn, 0, (void**)&ptrValue, &valueLen); + if ((err == QTSS_NoErr) && (ptrValue != NULL)) + { + OSMutexLocker locker(sAtomicMutex); + *ptrValue -= 1; + } + else + { + DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrCurConn failed with err = %"_S32BITARG_"\n", (SInt32)err); + } +} + +// IncrementTotalMP3Bytes - This increments the MP3 byte counter in the server's attributes +void IncrementTotalMP3Bytes(UInt32 bytes) +{ + QTSS_Error err = QTSS_NoErr; + + // Increment the server's MP3 total byte count. + UInt64* ptrValue = NULL; + UInt32 valueLen = sizeof(ptrValue); + + err = QTSS_GetValuePtr(sServer, qtssMP3SvrTotalBytes, 0, (void**)&ptrValue, &valueLen); + if ((err == QTSS_NoErr) && (ptrValue != NULL)) + { + OSMutexLocker locker(sAtomicMutex); + *ptrValue += bytes; + } + else + { + DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrTotalBytes failed with err = %"_S32BITARG_"\n", (SInt32)err); + } +} + +// CheckBandwidth - Check and see if we can handle any more bandwidth. +Bool16 CheckBandwidth(SInt32 bandwidth) +{ + QTSS_Error err = QTSS_NoErr; + + // Get our current bandwidth and check against server's maximum. + SInt32* ptrValue = NULL; + UInt32 valueLen = sizeof(ptrValue); + + err = QTSS_GetValuePtr(sServer, qtssMP3SvrCurBandwidth, 0, (void**)&ptrValue, &valueLen); + if ((err == QTSS_NoErr) && (ptrValue != NULL)) + { + OSMutexLocker locker(sAtomicMutex); + if (((*ptrValue + bandwidth)/1000) <= sMaximumBandwidth) + return true; + } + else + { + DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrCurBandwidth failed with err = %"_S32BITARG_"\n", (SInt32)err); + } + return false; +} + +// GetRTSPSessionID - returns the RTSP Session ID for the given RTSPSession object. +// return 0 on failure. +UInt32 GetRTSPSessionID(QTSS_RTSPSessionObject session) +{ + UInt32 uSessID = 0L; + UInt32 paramLen = sizeof(uSessID); + + QTSS_Error err = QTSS_GetValue(session, qtssRTSPSesID, 0, (void*)&uSessID, ¶mLen); + if (err != QTSS_NoErr) + { + uSessID = 0L; + } + return uSessID; +} + +// IsActiveBroadcastSession - This is true when the specified session belongs to +// a current broadcast session. We use this to let us know if we were called back +// in our FilterRequest() role because a read or write was blocked previously. +inline Bool16 IsActiveBroadcastSession(QTSS_RTSPSessionObject session) +{ + return (FindBroadcastSession(session) != NULL); +} + +// FindBroadcastSession - Given a RTSP session find the MP3BroadcasterSession +// object that handles this session. returns NULL pointer if none. +MP3BroadcasterSession* FindBroadcastSession(QTSS_RTSPSessionObject session) +{ + MP3Session* mp3sess = sMP3SessionTable.Resolve(session); + if (mp3sess != NULL && mp3sess->IsA() == kMP3BroadcasterSessionType) + return (MP3BroadcasterSession*) mp3sess; + else + return NULL; +} + +// This determines if an incoming request is an broadcaster sending +// us his password. It will be in the format of: +// "SOURCE \n". +Bool16 IsBroadcastPassword(StrPtrLen& theRequest) +{ + if (IsShoutcastPassword(theRequest)) + return true; + StrPtrLen token = theRequest; + token.Len = 6; + return token.Equal(StrPtrLen("SOURCE")); +} + +// This determines if an incoming request is a Shoutcast broadcaster sending +// us his password. It will be in the format of: +// \n". +Bool16 IsShoutcastPassword(StrPtrLen& theRequest) +{ + char* bp = (char*)theRequest.Ptr; + int i; + int n = (int)theRequest.Len; + + for (i=0; i 0) do + { + lastWord = currentWord; + lineParse.ConsumeUntilWhitespace(¤tWord); + lineParse.ConsumeWhitespace(); + } while (currentWord.Len > 0); + + if (IsProtocolString(lastWord, protocol))// test for line ending with protocol/version as in a request from the client + return true; + + return false; +} + +Bool16 IsHTTP(StrPtrLen& theRequest) +{ + return IsProtocol(theRequest, "HTTP"); +} + +Bool16 IsRTSP(StrPtrLen& theRequest) +{ + return IsProtocol(theRequest, "RTSP"); +} + + +// This determines if an incoming request is an HTTP GET +// request. +Bool16 IsHTTPGet(StrPtrLen& theRequest) +{ + StrPtrLen token = theRequest; + token.Len = 3; + Bool16 found = false; + if (token.EqualIgnoreCase(StrPtrLen("GET")) && IsHTTP(theRequest) ) + found = true; + return found; +} + +// Is this URL a *.m3u file request. +Bool16 IsA_m3u_URL(char* theURL) +{ + if (::strstr(theURL, ".m3u") != 0) + return true; + return false; +} + +// Is this URL a metdata xfer. +Bool16 IsMetaDataURL(char* theURL) +{ + if (::strncmp(theURL, "/admin.cgi?mode=updinfo", 23) == 0) + return true; + return false; +} + +// Parse this URL and extract metdata. +void ParseMetaDataURL(char* theURL) +{ + char URLBuffer[kURLBufferSize]; + + if (theURL == NULL) + { + Assert(0); + return; + } + else if (::strlen(theURL) == 0) + { + Assert(0); + return; + } + DTRACE1("##ParseMetaDataURL() - meta-data URL <%s>\n", theURL); + + StrPtrLen urlPtr(theURL); + StrPtrLen token; + StringParser urlParser(&urlPtr); + + urlParser.ConsumeUntil(&token, '&'); + if ( !token.Equal(StrPtrLen("/admin.cgi?mode=updinfo")) ) + { + // expected '/admin.cgi?mode=updinfo' in URL! + Assert(0); + return; + } + if ( !urlParser.Expect('&') ) + { + // expected '&' in URL after '/admin.cgi?mode=updinfo' + Assert(0); + return; + } + urlParser.ConsumeUntil(&token, '='); + if ( !token.Equal(StrPtrLen("pass")) ) + { + // expected 'pass=' in URL! + Assert(0); + return; + } + if ( !urlParser.Expect('=') ) + { + // expected '=' in URL after 'pass'! + Assert(0); + return; + } + urlParser.ConsumeUntil(&token, '&'); + if ( !token.Equal((const char*)sBroadcastPassword) ) + { + DTRACE1("##ParseMetaDataURL() - expected Password of '%s' in URL!\n", sBroadcastPassword); + return; + } + else + { + if ( !urlParser.Expect('&') ) + { + // expected '&' in URL after 'pass=xxx'! + Assert(0); + return; + } + } + + urlParser.ConsumeUntil(&token, '='); + if ( !token.Equal(StrPtrLen("mount")) ) + { + // expected 'mount=' in URL! + Assert(0); + return; + } + + if ( !urlParser.Expect('=') ) + { + // expected '=' in URL after 'mount'! + Assert(0); + return; + } + + urlParser.ConsumeUntil(&token, '&'); + if ( token.Len < 1 ) + { + // expected mountpoint in URL! + Assert(0); + return; + } + else + { + if ( !urlParser.Expect('&') ) + { + // expected '&' in URL after 'mount=xxx'! + Assert(0); + return; + } + } + ::memset(URLBuffer, 0, kURLBufferSize); + ::memcpy(URLBuffer, token.Ptr, token.Len); + + DTRACE1("##ParseMetaDataURL() - got mountpoint of '%s' in URL.\n", URLBuffer); + MP3BroadcasterSession* xs = sMP3BroadcasterQueue.FindByMountPoint(token); + if (xs == NULL) + { + DTRACE("##ParseMetaDataURL() - no matching mountpoint found!\n"); + return; + } + + urlParser.ConsumeUntil(&token, '='); + if ( !token.Equal(StrPtrLen("song")) ) + { + // expected 'song=' in URL! + Assert(0); + return; + } + + if ( !urlParser.Expect('=') ) + { + // expected '=' in URL after 'song'! + Assert(0); + return; + } + xs->SetSongName(urlParser.GetCurrentPosition()); +} + +void SendBroadcastAuthErr(QTSS_Filter_Params* inParams, char * errormessage) +{ + if (errormessage != NULL) + { + UInt32 len = ::strlen(errormessage); + (void)QTSS_Write(inParams->inRTSPRequest,errormessage ,len, NULL, 0); + (void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespMsg, 0, errormessage, len); + } + + QTSS_SessionStatusCode inStatusCode = qtssClientUnAuthorized; + (void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqStatusCode, 0, &inStatusCode, sizeof(inStatusCode)); + + const Bool16 sFalse = false; + (void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse)); + +} + +// This parses and checks the password in the broadcaster's +// incoming request. +Bool16 CheckPassword(QTSS_Filter_Params* inParams, StrPtrLen& theRequest, StrPtrLen& mountpoint) +{ + Bool16 passwordOK = false; + StrPtrLen strPtr; + + mountpoint.Ptr = NULL; + mountpoint.Len = 0; + StringParser reqParse(&theRequest); + reqParse.ConsumeUntilWhitespace(&strPtr); + char* tmp; + + if ( strPtr.Equal(StrPtrLen("SOURCE")) ) + { + + //it's an x-audio broadcast request + // PARSE X-AUDIO HERE!!!! + reqParse.ConsumeWhitespace(); + reqParse.ConsumeUntilWhitespace(&strPtr); + tmp = strPtr.GetAsCString(); + // Let's see if the password matches... + DTRACE1("CheckPassword() - #Got X-AUDIO password: %s\n", tmp); + if (strcmp(tmp, sBroadcastPassword) != 0) + { + SendBroadcastAuthErr(inParams, "ERROR - Bad Password\r\n"); //ice cast style error + delete[] tmp; + return false; + } + delete[] tmp; + // get the mount point + reqParse.ConsumeWhitespace(); + reqParse.ConsumeUntilWhitespace(&strPtr); + mountpoint.Ptr = strPtr.Ptr; + mountpoint.Len = strPtr.Len; + tmp = strPtr.GetAsCString(); + DTRACE1("CheckPassword() - #Got X-AUDIO mountpoint: %s\n", tmp); + delete[] tmp; + passwordOK = true; + } + else if ( IsShoutcastPassword(strPtr) ) + { + tmp = strPtr.GetAsCString(); + if (strPtr.Len > 1) + tmp[strPtr.Len] = '\0'; + // Let's see if the password matches... + DTRACE1("CheckPassword() - #Got Shoutcast password: %s\n", tmp); + if (strcmp(tmp, sBroadcastPassword) != 0) + { + SendBroadcastAuthErr(inParams, "invalid password\r\n"); // shoutcast style error + delete[] tmp; + return false; + } + delete[] tmp; + mountpoint.Ptr = "/"; + mountpoint.Len = 1; + passwordOK = true; + } + return passwordOK; +} + + +Bool16 GetHeaderAndValueFromeLine(StrPtrLen *line, StrPtrLen* header, char separator, StrPtrLen* value) +{ + StringParser lineParser(line); + lineParser.ConsumeWhitespace(); + + Bool16 foundHeader = lineParser.GetThru(header, separator); + + lineParser.ConsumeWhitespace(); + lineParser.GetThruEOL(value); + + return foundHeader; +} + + +// Find out if the HTTP header wants meta-data. +Bool16 NeedsMetaData(StrPtrLen& theRequest) +{ + + + StrPtrLen line; + StrPtrLen header; + StrPtrLen value; + StringParser requestParser(&theRequest); + + requestParser.GetThruEOL(NULL); // strip off command line + while ( requestParser.GetThruEOL(&line) || line.Len > 0 ) // pull out each line + { + if (!GetHeaderAndValueFromeLine(&line, &header, ':', &value)) + return false; // no separator found + + if (header.EqualIgnoreCase("icy-metadata") && (StringParser(&value).ConsumeInteger() > 0) ) + return true; // icy-metadata: (non zero value) + } + + return false; + +} + +/* NeedsMetaData Test cases +StrPtrLen temp; +temp.Set("\r\nIcy-metadatA: 1\r"); + if ( NeedsMetaData(temp) ) + printf("icy-metadata: 1-1\n"); + +temp.Set("\ricy-metadata: 1\r"); +if ( NeedsMetaData(temp) ) + printf("icy-metadata: 1-2\n"); + +temp.Set("\nicy-metadata: 1\r"); +if ( NeedsMetaData(temp) ) + printf("icy-metadata: 1 -3\n"); + +temp.Set("\r\nicy-metadata:1\r"); +if ( NeedsMetaData(temp) ) + printf("icy-metadata:1-4\n"); + +temp.Set("\r\nicy-metadata:\r"); +if ( NeedsMetaData(temp) ) + printf("icy-metadata:-5\n"); + +temp.Set("\r\nicy-metadata 1\n"); +if ( NeedsMetaData(temp) ) + printf("icy-metadata 1-6\n"); + +temp.Set("\r\nicy-metadata: 0\r"); +if ( NeedsMetaData(temp) ) + printf("icy-metadata: 0-7\n"); + +temp.Set("\r\nicy-metadata: 3\r"); +if ( NeedsMetaData(temp) ) + printf("icy-metadata: 3-8\n"); + +temp.Set("\r\nIcy-metadatA: 1"); + if ( NeedsMetaData(temp) ) + printf("icy-metadata: 1-0\n"); + +temp.Set("\nIcy-metadatA: 1"); + if ( NeedsMetaData(temp) ) + printf("icy-metadata: 1-10\n"); +*/ + + +// Parse out the URL from the HTTP GET line. +Bool16 ParseURL(StrPtrLen& theRequest, char* outURL, UInt16 maxlen) +{ + StringParser reqParse(&theRequest); + StrPtrLen strPtr; + + ::memset(outURL, 0, maxlen); + reqParse.ConsumeWord(&strPtr); + + if ( !strPtr.Equal(StrPtrLen("GET")) ) + { + return false; + } + reqParse.ConsumeWhitespace(); + reqParse.ConsumeUntilWhitespace(&strPtr); + if (strPtr.Len == 0) + return false; + else if ((UInt16)strPtr.Len > maxlen-1) + strPtr.Len = maxlen-1; + ::memcpy(outURL, strPtr.Ptr, strPtr.Len); + + return true; +} + +void WriteStartupMessage() +{ + if (!sServerIdle) + return; + sServerIdle = false; + + //format a date for the startup time + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + char tempBuffer[1024]; + if (result) + qtss_sprintf(tempBuffer, "#Remark: Streaming beginning STARTUP %s\n", theDateBuffer); + + // log startup message to error log as well. + if ((result) && (sMP3AccessLog != NULL)) + sMP3AccessLog->WriteToLog(tempBuffer, kAllowLogToRoll); +} + +void WriteShutdownMessage() +{ + if (sServerIdle) + return; + sServerIdle = true; + + //log shutdown message + //format a date for the shutdown time + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + char tempBuffer[1024]; + if (result) + qtss_sprintf(tempBuffer, "#Remark: Streaming beginning SHUTDOWN %s\n", theDateBuffer); + + if ( result && sMP3AccessLog != NULL ) + sMP3AccessLog->WriteToLog(tempBuffer, kAllowLogToRoll); +} + +QTSS_Error LogRequest(QTSS_RTSPSessionObject inRTSPSession, MP3ClientSession* client) +{ + char logbuffer[1024]; + char reqBuffer[kRequestBufferSize]; + char userAgent[kUserAgentBufferSize]; + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + UInt16 reqResult = 200; + UInt32 byteCount = 54000000; + UInt32 duration = 0; + QTSS_Error theError = QTSS_NoErr; + + // Sanity check... + if (sMP3AccessLog == NULL) + return QTSS_NoErr; + + // LogRequest() is not re-entrant. Hold a mutex until done. + OSMutexLocker locker(sLogMutex); + + // Construct the timestamp for the entry + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, sLogTimeInGMT); + // this should never happen, but just in case... + if (!result) + theDateBuffer[0] = '\0'; + + // Get the IP address of the client who made the request + char remoteAddress[kRemoteAddressSize] = {0}; + StrPtrLen theClientIPAddressStr; + theClientIPAddressStr.Set(remoteAddress,kRemoteAddressSize); + QTSS_Error err = QTSS_GetValue( inRTSPSession, + qtssRTSPSesRemoteAddrStr, + 0, + (void*)theClientIPAddressStr.Ptr, + &theClientIPAddressStr.Len); + if (err != QTSS_NoErr) + ::strcpy(remoteAddress, "127.0.0.1"); + + // Pull the actual HTTP request line from the MP3 client session. + ::strcpy(reqBuffer, client->GetRequest()); + + // Get the user agent's name or "unknown user agent" if none was specified + // in the original HTTP request. + userAgent[0] = '\0'; + ::strcpy(userAgent, client->GetUserAgent()); + if (userAgent[0] == '\0') + ::strcpy(userAgent, "unknown user agent"); + + // Determine how may bytes this client was streamed. + byteCount = client->GetTotalCount(); + + // Calculate the total connect duration in seconds. + duration = (UInt32)((OS::Milliseconds() - client->GetConnectTime())/1000); + + // Finally, get the HTTP result code. + reqResult = client->GetResult(); + + // Format the access log entry parameters here... + qtss_sprintf(logbuffer, "%s \"%s\" [%s] \"%s\" %d %"_S32BITARG_" %"_S32BITARG_"\n", + remoteAddress, + userAgent, + theDateBuffer, + reqBuffer, + reqResult, + byteCount, + duration + ); + + // Commit it to disk... + sMP3AccessLog->WriteToLog(logbuffer, kAllowLogToRoll); + return theError; +} + +// url_strcpy - works like strcpy except that it handles URL escape +// conversions as it copies the string. +void url_strcpy(char* dest, const char* src) +{ + int c1, c2; + while(*src) + { + if (*src == '%') + { + src++; + c1 = *src++; + if (c1 >= '0' && c1 <= '9') + c1 -= '0'; + else if (c1 >= 'A' && c1 <= 'F') + c1 -= 'A' + 10; + else if (c1 >= 'a' && c1 <= 'f') + c1 -= 'a' + 10; + else + c1 = 0; + c2 = *src; + if (c2 >= '0' && c2 <= '9') + c2 -= '0'; + else if (c2 >= 'A' && c2 <= 'F') + c2 -= 'A' + 10; + else if (c2 >= 'a' && c2 <= 'f') + c2 -= 'a' + 10; + else + c2 = 32; + *dest = (c1 * 16) + c2; + } + else + { + *dest = *src; + } + // we need to replace single quotes with + // a tick so that the client can parse meta-data. + if (*dest == '\'') + { + *dest = '`'; + } + dest++; + src++; + } + *dest = '\0'; +} + +// ReEnterFilterRequest - This function is called whenever we +// have called back our module in the QTSS_FilterRequest role +// after calling it the first time. +// This will happen in one of two ways: +// 1. A MP3BroadcasterSession posted a read request event and data +// became available on its stream. +// 2. The Idle timer called us back to poll a MP3ClientSession to +// see if it needs to die. +QTSS_Error ReEnterFilterRequest(QTSS_Filter_Params* inParams, MP3Session* mp3Session) +{ + QTSS_Error err = QTSS_NoErr; + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + + if (mp3Session != NULL) + { + UInt8 sessTyp = mp3Session->IsA(); + switch(sessTyp) + { + case kMP3BroadcasterSessionType: + // this is a MP3 broadcaster session. Invoke it's state machine. + return ((MP3BroadcasterSession*)mp3Session)->ExecuteState(); + case kMP3ClientSessionType: + // this is a MP3 client session. If we are active schedule ourself + // to be called back again in kClientPollInterval milliseconds. + if (mp3Session->GetState() == MP3ClientSession::kClientSendDataState) + { + MP3ClientSession* client = (MP3ClientSession*)mp3Session; + if (client->WasBlocked()) + client->RetrySendData(); + + KeepSession(theRequest, true); + err = QTSS_SetIdleTimer(kClientPollInterval); + return err; + } + else + { + KeepSession(theRequest, false); + } + return QTSS_NoErr; + default: + Assert(0); + return QTSS_NoErr; + } + } + return err; +} + +// FilterRequest - filter the incoming HTTP/Broadcast request. +QTSS_Error FilterRequest(QTSS_Filter_Params* inParams) +{ + QTSS_Error err = QTSS_NoErr; + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + + // Pull the actual request data from this RTSP session's attributes. + + StrPtrLen theFullRequest; + err = QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len); + if (err != QTSS_NoErr) + { + return QTSS_NoErr; + } + + if (IsRTSP(theFullRequest)) // don't process rtsp requests + return QTSS_NoErr; + + if (!sMP3StreamingEnabled || sServerIdle) + { + // This allows MP3 streaming to be disabled through a pref setting + // or ignore requests when the server is in the idle state. + + if (NeedsMetaData(theFullRequest)) // this is an HTTP icy request + return QTSSModuleUtils::SendHTTPErrorResponse(theRequest,qtssServerUnavailable,true, NULL); + + return QTSS_NoErr; + } + + // See if this RTSP session is an existing MP3 Session. + // If it is then we have re-entered FilterRequest() from a previous + // invokation and we will call ReEnterFilterRequest() to do its thing. + MP3Session* mp3Session = sMP3SessionTable.Resolve(inParams->inRTSPSession); + if (mp3Session != NULL) + { + err = ReEnterFilterRequest(inParams, mp3Session); + return err; + } + + // Check and see if this request is a broadcaster sending it's password or + // an HTTP GET. If it's not, it's assumed to be a RTSP request and we ignore + // it. In that case, some other module must handle this request. +#if DEBUG_MP3STREAMING_MODULE + DTRACE("####\n"); + PrintStringBuffer(theFullRequest); +#endif + if (!IsBroadcastPassword(theFullRequest) && !IsHTTPGet(theFullRequest)) + { + return QTSS_NoErr; + } + + MP3BroadcasterSession* xs = NULL; + +#if DEBUG_MP3STREAMING_MODULE + // Debugging info - Retrieve the RTSP session ID associated with this session. + UInt32 uSessID = 0L; + uSessID = GetRTSPSessionID(inParams->inRTSPSession); + DTRACE1("####\n###Filtering SessionID = %"_S32BITARG_"\n", uSessID); +#endif + + // See if we have exceeded our maximum number of client connections + if (MP3Session::GetTotalNumMP3Sessions() >= sMaximumConnections) + { + // we are currently at our connection limit. return a 400 error for compatibility with icecast style error. + return QTSSModuleUtils::SendHTTPErrorResponse(theRequest,qtssClientBadRequest,true, NULL); + } + else if (!CheckBandwidth(kBandwidthToAddEstimate)) + { + // we are currently at our bandwidth limit. return a 400 error for compatibility with icecast style error. + return QTSSModuleUtils::SendHTTPErrorResponse(theRequest,qtssClientBadRequest,true, NULL); + } + + StrPtrLen mountpoint; + if (!IsHTTP(theFullRequest) && IsBroadcastPassword(theFullRequest) && CheckPassword(inParams, theFullRequest, mountpoint)) + { + err = sMP3BroadcasterQueue.CreateBroadcaster(inParams->inRTSPSession, inParams->inRTSPRequest, mountpoint); + if (err != QTSS_NoErr) + { + // CreateBroadcaster returned an error but it has already sent the reject reply + // so there's nothing left to do. + return QTSS_NoErr; + } + // If we're here this is a new connection request from a broadcaster. + // Read the broadcast headers from the broadcaster + xs = FindBroadcastSession(inParams->inRTSPSession); + if (xs != NULL) + err = xs->GetBroadcastHeaders(); + else + err = QTSS_NoErr; + return err; + } + else if ( IsHTTPGet(theFullRequest) ) + { + char theURL[kURLBufferSize]; + + MP3BroadcasterSession* xs = NULL; + if (!ParseURL(theFullRequest, theURL, kURLBufferSize)) + { + return QTSS_NoErr; + } + // if this was a '*.m3u' file URL then handle it here + // sendind back a reply containing the URL. + // This effectively accomplishes a redirect. + if ( IsA_m3u_URL(theURL) ) + { + char tmpbuf[1024]; + tmpbuf[sizeof(tmpbuf) -1] = 0; + char tmp[1024]; + tmp[sizeof(tmp) -1] = 0; + // remove the '.m3u' prefix from the URL + UInt32 ulen = ::strlen(theURL) - 4; + UInt32 serverIPAddr; + UInt8 x1, x2, x3, x4; + theURL[ulen] = '\0'; + // Get the server IP address for building the reply playlist path + ulen = sizeof(serverIPAddr); + err = QTSS_GetValue(sServer, qtssSvrDefaultIPAddr, 0, &serverIPAddr, &ulen); + if (err == QTSS_NoErr) + { + x1 = (UInt8)((serverIPAddr >> 24) & 0xff); + x2 = (UInt8)((serverIPAddr >> 16) & 0xff); + x3 = (UInt8)((serverIPAddr >> 8) & 0xff); + x4 = (UInt8)((serverIPAddr) & 0xff); + } + else + { + x1 = 127; x2 = x3 = 0; x4 = 1; + } + + // construct the reply string for the client. + qtss_snprintf(tmp,sizeof(tmp) -1, "http://%d.%d.%d.%d:8000%s", x1,x2,x3,x4, theURL); + ulen = ::strlen(tmp); + qtss_snprintf(tmpbuf,sizeof(tmpbuf) -1, "%s %"_U32BITARG_"\r\n\r\n%s\r\n", gM3UReplyHeader, ulen, tmp); + ulen = ::strlen(tmpbuf); + // send the reply to the client. + err = QTSS_Write(inParams->inRTSPRequest, tmpbuf, ulen, NULL, qtssWriteFlagsBufferData); + return QTSS_NoErr; + } + // if this was a meta-data transfer URL then handle it here + if ( IsMetaDataURL(theURL) ) + { + // Parse meta-data request here + ParseMetaDataURL(theURL); + // tell the broadcaster we got it. + err = QTSS_Write(inParams->inRTSPRequest, kOKHeader, ::strlen(kOKHeader), NULL, qtssWriteFlagsBufferData); + return QTSS_NoErr; + } + // Since this URL was not a metadata transfer let's assume it is + // a broadcast mount point and search for it. + xs = sMP3BroadcasterQueue.FindByMountPoint(theURL); + if (xs == NULL) + { + if (NeedsMetaData(theFullRequest)) // this is an HTTP icy request + return QTSSModuleUtils::SendHTTPErrorResponse(theRequest,qtssClientNotFound,true, NULL); + + // Let some other module handle this URL. + return QTSS_NoErr; + } + // if we're here this is a new MP3 connection request from a client. + // Start the clients stream up... + err = xs->AddClient(inParams->inRTSPSession, inParams->inRTSPRequest); + return err; + } + return QTSS_NoErr; +} + diff --git a/APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.h b/APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.h new file mode 100755 index 0000000..0803ff4 --- /dev/null +++ b/APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.h @@ -0,0 +1,508 @@ +/* + * + * @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: QTSSMP3StreamingModule.h + + Contains: Handle ShoutCast/IceCast-style MP3 streaming. + + Written by: Steve Ussery + +*/ + +#ifndef __QTSSMP3STREAMINGMODULE_H__ +#define __QTSSMP3STREAMINGMODULE_H__ + +#include "QTSS.h" +#include "OSQueue.h" +#include "OSMutex.h" +#include "OSHashTable.h" +#include "OSBufferPool.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSMP3StreamingModule_Main(void* inPrivateArgs); +} + +#define kURLBufferSize 1024 +#define kSongNameBufferSize 1024 +#define kHeaderBufferSize 1024 +#define kRequestBufferSize 512 +#define kHostNameBufferSize 256 +#define kUserAgentBufferSize 256 +#define kClientMetaInt 24576 + +// The client poll interval in milliseconds +#define kClientPollInterval 253 + +// FORWARD CLASS DEFINES + +class MP3ClientSessionRef; +class MP3ClientSession; +class MP3ClientQueue; +class MP3BroadcasterSession; +class MP3BroadcasterQueue; +class MP3SessionRef; +class MP3SessionRefKey; +class MP3SessionTable; + +enum { + kMP3UndefinedSessionType = 0, + kMP3BroadcasterSessionType, + kMP3ClientSessionType +}; + +// +// MP3Session -- This is a base class to hold all the MP3 Session state info. +// We will subclass it for MP3 Broadcaster sessions and MP3 Client sessions. +// (See the MP3BroadcasterSession & MP3ClientSession classes defined below.) +// +class MP3Session +{ +public: + MP3Session(); + + MP3Session(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream); + + virtual ~MP3Session(); + + // The IsA() method is a mechanism for subclasses of this class to + // identify their type. It is kMP3UndefinedSessionType for this + // base class. + + virtual UInt8 IsA() const; + + // Session data field accessor methods + + QTSS_RTSPSessionObject GetSession() { return fSession; } + + void SetSessionID(UInt32 sessID) { fSessID = sessID; } + + UInt32 GetSessionID() { return fSessID; } + + QTSS_StreamRef GetStreamRef() { return fStream; } + + UInt16 GetState() { return fState; } + + void SetState(UInt16 state) { fState = state; } + + UInt16 GetResult() { return fResult; } + + void SetResult(UInt16 result) { fResult = result; } + + static SInt32 GetTotalNumMP3Sessions() { return sTotalNumMP3Sessions; } + +protected: + + void SetSession(QTSS_RTSPSessionObject session) { fSession = session; } + + void SetStreamRef(QTSS_StreamRef stream) { fStream = stream; } + +private: + + static SInt32 sTotalNumMP3Sessions; + + UInt16 fState; + UInt16 fResult; + QTSS_StreamRef fStream; + QTSS_RTSPSessionObject fSession; + UInt32 fSessID; +}; + +// +// MP3BroadcasterSession -- This is a class to hold all the MP3 Broadcaster +// session-related state info. There is a global queue of these managed by the +// server. Each instance of this class must have a unique mountpoint name +// string which clients will use to identify the broadcast stream they are +// "tuning" into. +// +class MP3BroadcasterSession : public MP3Session +{ +public: + + + // MP3BroadcasterSession states + + enum { + kBroadcasterInitState = 0, + kBroadcasterValidatePasswordState, + kBroadcasterGetHeaderState, + kBroadcasterRecvDataState, + kBroadcasterShutDownState + }; + + MP3BroadcasterSession(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream); + + virtual ~MP3BroadcasterSession(); + + // The IsA() method is a mechanism for subclasses of the MP3Session class + // to identify their type. It is kMP3BroadcasterSessionType for this + // subclass. + + virtual UInt8 IsA() const; + + // Session state control methods + + QTSS_Error ExecuteState(); + + void InitBroadcastSessionState(); + + void AcceptBroadcastSessionState(); + + void AcceptPasswordState(); + + void AcceptDataStreamState(); + + void ShutDownState(); + + // MP3 Client handling methods + + QTSS_Error AddClient(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream); + + QTSS_Error RemoveClient(QTSS_RTSPSessionObject sess); + + Bool16 IsMyClient(QTSS_RTSPSessionObject sess); + + // Session data field accessor methods + + void SetMountpoint(char* mp); + + void SetMountpoint(StrPtrLen& mp); + + Bool16 MountpointEqual(char* mp); + + Bool16 MountpointEqual(StrPtrLen& mp); + + char* GetMountpoint() { return fMountpoint; } + + char* GetHeader() { return fHeader; } + + void SetSongName(char* sn); + + char* GetSongName() { return fSongName; } + + // Session data handlers + + QTSS_Error SendOKResponse(); + + QTSS_Error GetBroadcastHeaders(); + + QTSS_Error GetBroadcastData(); + + QTSS_Error SendClientsData(); + + void PreflightClients(); + +protected: + + void TerminateHeaders(); + +private: + + MP3BroadcasterSession(); + + MP3ClientQueue* fMP3ClientQueue; + UInt32 fDataBufferLen; + UInt32 fDataBufferSize; + char fMountpoint[kURLBufferSize]; + char fHeader[kHeaderBufferSize]; + char fSongName[kSongNameBufferSize]; + char* fBuffer; + OSMutex fSongNameMutex; + Bool16 fNewSongName; +}; + +// +// MP3ClientSession -- This is a class to hold all the MP3 client +// session-related state info. These class instances are always owned by a +// instance of the MP3BroadcasterSession class which has a queue of these. +// +class MP3ClientSession : public MP3Session +{ +public: + + + // MP3ClientSession states + + enum { + kClientInitState = 0, + kClientSendResponse, + kClientSendDataState, + kClientShutDownState + }; + + MP3ClientSession(QTSS_RTSPSessionObject sess, + QTSS_StreamRef stream, + MP3BroadcasterSession* owner); + + virtual ~MP3ClientSession(); + + // The IsA() method is a mechanism for subclasses of the MP3Session class + // to identify their type. It is kMP3ClientSessionType for this subclass. + + virtual UInt8 IsA() const; + + // Session stream handlers + + QTSS_Error SendResponse(); + + QTSS_Error SendMP3Data(char* buffer, UInt32 bufferlen); + + QTSS_Error RetrySendData(); + + QTSS_Error SendMetaData(); + + // Session data field accessor methods + + void SetHeader(char* header); + + char* GetHeader() { return fHeader; } + + char* GetHostName() { return fHostName; } + + char* GetUserAgent() { return fUserAgent; } + + void SetSongName(char* sn); + + char* GetSongName() { return fSongName; } + + MP3BroadcasterSession* GetOwner() { return fOwner; } + + void SetOwner(MP3BroadcasterSession* owner) { fOwner = owner; } + + Bool16 WasBlocked() { return fWasBlocked; } + + Bool16 WantsContentLength() { return fNeedsContentLength; } + + void SetRequest(char* req) { ::strcpy(fRequestBuffer, req); } + + char* GetRequest() { return fRequestBuffer; } + + UInt32 GetTotalCount() { return fBytesSent; } + + SInt64 GetConnectTime() { return fConnectTime; } + +private: + + MP3ClientSession(); + + void UpdateBitRateInternal(const SInt64& curTime); + + void ParseRequestParams(QTSS_StreamRef stream); + + UInt32 fBytesSent; + UInt32 fCurrentBitRate; + UInt32 fLastBitRateBytes; + SInt64 fLastBitRateUpdateTime; + SInt64 fConnectTime; + UInt32 fSendCount; + UInt32 fRetryCount; + MP3BroadcasterSession* fOwner; + char fHeader[kHeaderBufferSize]; + char fHostName[kHostNameBufferSize]; + char fUserAgent[kUserAgentBufferSize]; + char fSongName[kSongNameBufferSize]; + OSMutex fSongNameMutex; + Bool16 fNewSongName; + Bool16 fWasBlocked; + SInt64 fBlockTime; + Bool16 fNeedsContentLength; + Bool16 fWantsMetaData; + char fRequestBuffer[kRequestBufferSize]; + QTSS_Object fQTSSObject; +}; + +// +// MP3SessionRef -- This class is just a wrapper class for handling the +// mapping of RTSP Session refs to the corresponding MP3 Session class refs. +// It will be the an element of our hash table in the MP3SessionTable class. +// +class MP3SessionRef +{ +public: + MP3SessionRef(MP3Session* mp3Session); + + ~MP3SessionRef(); + +private: + + MP3Session* GetMP3Session() { return fMP3Session; } + + QTSS_RTSPSessionObject GetSession() { return fMP3Session->GetSession(); } + + MP3SessionRef* fNextHashEntry; + PointerSizedInt fHashValue; + MP3Session* fMP3Session; + + friend class MP3SessionRefKey; + friend class OSHashTable; + friend class OSHashTableIter; + friend class MP3SessionTable; +}; + +// +// MP3SessionRefKey -- This class is used to generate hash keys for looking +// up values in our MP3SessionTable. +// +class MP3SessionRefKey +{ +public: + MP3SessionRefKey(MP3SessionRef* mp3SessRef); + + MP3SessionRefKey(QTSS_RTSPSessionObject rtspSessRef); + + ~MP3SessionRefKey(); + +private: + + PointerSizedInt GetHashKey() { return fHashValue; } + + friend int operator ==(const MP3SessionRefKey &key1, const MP3SessionRefKey &key2) + { + return (key1.fHashValue == key2.fHashValue); + } + + MP3SessionRef* fKeyValue; + PointerSizedInt fHashValue; + MP3Session* fMP3Session; + + friend class OSHashTable; +}; + +typedef OSHashTable MP3SessRefHashTable; +typedef OSHashTableIter MP3SessRefHashTableIter; + +// +// MP3SessionTable -- This class provides a way to map RTSP Session references +// into the corresponding MP3Session class instances if any. +// +class MP3SessionTable +{ +public: + + enum + { + kDefaultTableSize = 19 + }; + + // tableSize is the number of hash buckets not the number of entries. + + MP3SessionTable(UInt32 tableSize = kDefaultTableSize); + + ~MP3SessionTable(); + + UInt32 GetNumSessionsInTable() { return (UInt32) fTable.GetNumEntries(); } + + // Attempt to add a new MP3Session ref to the table's map. + // returns true on success or false if it fails. + Bool16 RegisterSession(MP3Session* session); + + // Given an QTSS_RTSPSessionObject resolve it into a MP3Session class + // reference. Returns NULL if there's none in out map. + MP3Session* Resolve(QTSS_RTSPSessionObject rtspSession); + + // Attempt to remove a MP3Session ref from the table's map. + // returns true on success or false if it fails. + Bool16 UnRegisterSession(MP3Session* session); + +private: + + MP3SessRefHashTable fTable; + OSMutex fMutex; +}; + +// +// MP3ClientQueue -- This is a class manages a queue of MP3Client objects. +// Every MP3BroadcasterSession class instance contains one of these objects. +// +class MP3ClientQueue +{ +public: + + MP3ClientQueue(); + + ~MP3ClientQueue(); + + UInt32 GetNumClients() { return fQueue.GetLength(); } + + QTSS_Error AddClient(QTSS_RTSPSessionObject clientsess, QTSS_StreamRef clientstream, + MP3BroadcasterSession* owner); + + QTSS_Error RemoveClient(QTSS_RTSPSessionObject clientsess); + + Bool16 InQueue(QTSS_RTSPSessionObject clientsess); + + QTSS_Error SendToAllClients(char* buffer, UInt32 bufferlen); + + void PreflightClients(char* songname); + +private: + + void TerminateClients(); + + OSMutex fMutex; + OSQueue fQueue; +}; + +// +// MP3BroadcasterQueue -- This is a class manages a queue of MP3Broadcaster objects. +// There is only one global instance of this class owned by the server. +// +class MP3BroadcasterQueue +{ +public: + + MP3BroadcasterQueue(); + + ~MP3BroadcasterQueue(); + + UInt32 GetNumBroadcasters() { return fQueue.GetLength(); } + + QTSS_Error CreateBroadcaster(QTSS_RTSPSessionObject bcastsess, QTSS_StreamRef bcaststream, StrPtrLen& mountpt); + + QTSS_Error RemoveBroadcaster(QTSS_RTSPSessionObject bcastsess); + + QTSS_Error RemoveClient(QTSS_RTSPSessionObject clientsess); + + Bool16 InQueue(QTSS_RTSPSessionObject bcastsess); + + Bool16 IsActiveClient(QTSS_RTSPSessionObject clientsess); + + MP3BroadcasterSession* FindByMountPoint(char* mountpoint); + + MP3BroadcasterSession* FindByMountPoint(StrPtrLen& mountpoint); + + MP3BroadcasterSession* FindBySession(QTSS_RTSPSessionObject bcastsess); + + void TerminateAllBroadcastSessions(); + +private: + + OSMutex fMutex; + OSQueue fQueue; +}; + +#endif // __QTSSMP3STREAMINGMODULE_H__ + diff --git a/APIModules/QTSSPOSIXFileSysModule/QTSSPosixFileSysModule.cpp b/APIModules/QTSSPOSIXFileSysModule/QTSSPosixFileSysModule.cpp new file mode 100644 index 0000000..05c22d5 --- /dev/null +++ b/APIModules/QTSSPOSIXFileSysModule/QTSSPosixFileSysModule.cpp @@ -0,0 +1,247 @@ +/* + * + * @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: QTSSPosixFileSysModule.cpp + + Contains: Implements web stats module + + Written By: Denis Serenyi + + + +*/ + +#include "QTSSPosixFileSysModule.h" +#include "QTSSModuleUtils.h" + +#include "OSMemory.h" +#include "OSFileSource.h" +#include "Socket.h" + +// ATTRIBUTES +static QTSS_AttributeID sOSFileSourceAttr = qtssIllegalAttrID; +static QTSS_AttributeID sEventContextAttr = qtssIllegalAttrID; + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSPosixFileSysModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error OpenFile(QTSS_OpenFile_Params* inParams); +static QTSS_Error AdviseFile(QTSS_AdviseFile_Params* inParams); +static QTSS_Error ReadFile(QTSS_ReadFile_Params* inParams); +static QTSS_Error CloseFile(QTSS_CloseFile_Params* inParams); +static QTSS_Error RequestEventFile(QTSS_RequestEventFile_Params* inParams); + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSPosixFileSysModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSPosixFileSysModuleDispatch); +} + + +QTSS_Error QTSSPosixFileSysModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_OpenFile_Role: + return OpenFile(&inParams->openFileParams); + case QTSS_AdviseFile_Role: + return AdviseFile(&inParams->adviseFileParams); + case QTSS_ReadFile_Role: + return ReadFile(&inParams->readFileParams); + case QTSS_CloseFile_Role: + return CloseFile(&inParams->closeFileParams); + case QTSS_RequestEventFile_Role: + return RequestEventFile(&inParams->reqEventFileParams); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + + // Only open needs to be registered for, the rest happen naturally + (void)QTSS_AddRole(QTSS_OpenFile_Role); + + // Add an attribute to the file object type to store a pointer to our OSFileSource + static char* sOSFileSourceName = "QTSSPosixFileSysModuleOSFileSource"; + (void)QTSS_AddStaticAttribute(qtssFileObjectType, sOSFileSourceName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssFileObjectType, sOSFileSourceName, &sOSFileSourceAttr); + + static char* sEventContextName = "QTSSPosixFileSysModuleEventContext"; + (void)QTSS_AddStaticAttribute(qtssFileObjectType, sEventContextName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssFileObjectType, sEventContextName, &sEventContextAttr); + + // Tell the server our name! + static char* sModuleName = "QTSSPosixFileSysModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + return QTSS_NoErr; +} + +QTSS_Error OpenFile(QTSS_OpenFile_Params* inParams) +{ + OSFileSource* theFileSource = NEW OSFileSource(inParams->inPath); + + UInt64 theLength = theFileSource->GetLength(); + + // + // OSFileSource returns mod date as a time_t. + // This is the same as a QTSS_TimeVal, except the latter is in msec + QTSS_TimeVal theModDate = (QTSS_TimeVal)theFileSource->GetModDate(); + theModDate *= 1000; + + // + // Check to see if the file actually exists + if (theLength == 0) + { + delete theFileSource; + return QTSS_FileNotFound; + } + + // + // Add this new file source object to the file object + QTSS_Error theErr = QTSS_SetValue(inParams->inFileObject, sOSFileSourceAttr, 0, &theFileSource, sizeof(theFileSource)); + if (theErr != QTSS_NoErr) + { + delete theFileSource; + return QTSS_RequestFailed; + } + + // + // If caller wants async I/O, at this point we should set up the EventContext + if (inParams->inFlags & qtssOpenFileAsync) + { + EventContext* theEventContext = NEW EventContext(EventContext::kInvalidFileDesc, Socket::GetEventThread()); + theEventContext->InitNonBlocking(theFileSource->GetFD()); + + theErr = QTSS_SetValue(inParams->inFileObject, sEventContextAttr, 0, &theEventContext, sizeof(theEventContext)); + if (theErr != QTSS_NoErr) + { + delete theFileSource; + delete theEventContext; + return QTSS_RequestFailed; + } + } + + // + // Set up the other attribute values in the file object + (void)QTSS_SetValue(inParams->inFileObject, qtssFlObjLength, 0, &theLength, sizeof(theLength)); + (void)QTSS_SetValue(inParams->inFileObject, qtssFlObjModDate, 0, &theModDate, sizeof(theModDate)); + + return QTSS_NoErr; +} + +QTSS_Error AdviseFile(QTSS_AdviseFile_Params* inParams) +{ + OSFileSource** theFile = NULL; + UInt32 theLen = 0; + + (void)QTSS_GetValuePtr(inParams->inFileObject, sOSFileSourceAttr, 0, (void**)&theFile, &theLen); + Assert(theLen == sizeof(OSFileSource*)); + (*theFile)->Advise(inParams->inPosition, inParams->inSize); + + return QTSS_NoErr; +} + +QTSS_Error ReadFile(QTSS_ReadFile_Params* inParams) +{ + OSFileSource** theFile = NULL; + UInt32 theLen = 0; + + (void)QTSS_GetValuePtr(inParams->inFileObject, sOSFileSourceAttr, 0, (void**)&theFile, &theLen); + Assert(theLen == sizeof(OSFileSource*)); + OS_Error osErr = (*theFile)->Read(inParams->inFilePosition, inParams->ioBuffer, inParams->inBufLen, inParams->outLenRead); + + if (osErr == EAGAIN) + return QTSS_WouldBlock; + else if (osErr != OS_NoErr) + return QTSS_RequestFailed; + else + return QTSS_NoErr; +} + +QTSS_Error CloseFile(QTSS_CloseFile_Params* inParams) +{ + OSFileSource** theFile = NULL; + EventContext** theContext = NULL; + UInt32 theLen = 0; + + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inFileObject, sOSFileSourceAttr, 0, (void**)&theFile, &theLen); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_GetValuePtr(inParams->inFileObject, sEventContextAttr, 0, (void**)&theContext, &theLen); + + if (theErr == QTSS_NoErr) + { + // + // If this file was opened up in async mode, there is an EventContext associated with it. + // We should first make sure that the OSFileSource destructor doesn't close the FD + // (the EventContext will do it), then close the EventContext + (*theFile)->ResetFD(); + delete *theContext; + } + delete *theFile; + return QTSS_NoErr; +} + +QTSS_Error RequestEventFile(QTSS_RequestEventFile_Params* inParams) +{ + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + Assert(theState->curTask != NULL); + + EventContext** theContext = NULL; + UInt32 theLen = 0; + + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inFileObject, sEventContextAttr, 0, (void**)&theContext, &theLen); + if (theErr == QTSS_NoErr) + { + // + // This call only works if the file is async! + (*theContext)->SetTask(theState->curTask); + (*theContext)->RequestEvent(); + + return QTSS_NoErr; + } + return QTSS_RequestFailed; +} diff --git a/APIModules/QTSSPOSIXFileSysModule/QTSSPosixFileSysModule.h b/APIModules/QTSSPOSIXFileSysModule/QTSSPosixFileSysModule.h new file mode 100644 index 0000000..f4d9999 --- /dev/null +++ b/APIModules/QTSSPOSIXFileSysModule/QTSSPosixFileSysModule.h @@ -0,0 +1,50 @@ +/* + * + * @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: QTSSPosixFileSysModule.h + + Contains: + + Written By: Denis Serenyi + + + +*/ + +#ifndef __QTSSPOSIXFILESYSMODULE_H__ +#define __QTSSPOSIXFILESYSMODULE_H__ + +#include "QTSS.h" +#include "QTSS_Private.h" // This module MUST be compiled directly into the server! + // This is because it uses the server's private internal event + // mechanism for doing async file I/O + +extern "C" +{ + EXPORT QTSS_Error QTSSPosixFileSysModule_Main(void* inPrivateArgs); +} + +#endif // __QTSSPOSIXFILESYSMODULE_H__ + diff --git a/APIModules/QTSSProxyModule/QTSSProxyModule.cpp b/APIModules/QTSSProxyModule/QTSSProxyModule.cpp new file mode 100644 index 0000000..61196ef --- /dev/null +++ b/APIModules/QTSSProxyModule/QTSSProxyModule.cpp @@ -0,0 +1,939 @@ +/* + * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. + * + * @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: QTSSProxyModule.cpp + + Contains: Implementation of QTSSProxyModule class. + + This module is intended to be used in a stripped down version of QTSS/DSS + in order to handle firewall proxy behaviour for the RTP protocol. + + +*/ + +#ifndef __Win32__ +// +// For gethostbyname +#include +#endif + +#include "QTSSProxyModule.h" +#include "QTSSModuleUtils.h" +#include "UDPSocketPool.h" +#include "OSArrayObjectDeleter.h" +#include "OSMemory.h" +#include "RTSPClient.h" +#include "ClientSocket.h" +#include "SocketUtils.h" + +class ProxyTask : public Task +{ + public: + + // + // Task that just polls on all the sockets in the pool, sending data on all available sockets + ProxyTask() : Task() {this->SetTaskName("ProxyTask"); this->Signal(Task::kStartEvent); } + virtual ~ProxyTask() {} + + private: + + virtual SInt64 Run(); + + enum + { + kProxyTaskPollIntervalMsec = 10 + }; +}; + + +class ProxySocketPool : public UDPSocketPool +{ + public: + + // + // Global pool of UDP sockets used by the Proxy for receiving + // UDP data from the upstream servers. + ProxySocketPool() {} + virtual ~ProxySocketPool() {} + + virtual UDPSocketPair* ConstructUDPSocketPair(); + virtual void DestructUDPSocketPair(UDPSocketPair *inPair); + virtual void SetUDPSocketOptions(UDPSocketPair* inPair); +}; + + +class ProxyDemuxerTask : public UDPDemuxerTask +{ + public: + + ProxyDemuxerTask(QTSS_RTPStreamObject inStream, UDPSocketPair* inSockets) + : UDPDemuxerTask(), + fStream(inStream), + fQueueElem(this), + fSockets(inSockets) {} + virtual ~ProxyDemuxerTask() {} + + // + // ACCESSORS + + QTSS_RTPStreamObject GetStream() { return fStream; } + OSQueueElem* GetQueueElem() { return &fQueueElem; } + UDPSocketPair* GetSockets() { return fSockets; } + UInt16 GetOriginServerPort() { return fOriginServerPort; } + + // + // SetOriginServerPort - + // The origin server tells us what ports to send to on the SETUP + // response. This object gets created on the SETUP request, so we + // don't know the origin server ports when it is constructed. Once + // we know what they are, call this method to let the object know what the ports are + void SetOriginServerPort(UInt16 inPort) { fOriginServerPort = inPort; } + + private: + + QTSS_RTPStreamObject fStream; + OSQueueElem fQueueElem; + UDPSocketPair* fSockets; + UInt16 fOriginServerPort; +}; + + +class ProxyClientInfo +{ + public: + + // + // This class contains all the proxy specific resources used by a single client of the + // proxy. It is a UDP demuxer because it needs to get signalled when there is incoming + // UDP data for this client + ProxyClientInfo() : fStream(NULL), + fClientSocket(Socket::kNonBlockingSocketType), + fClient(&fClientSocket, false), + fLastDemuxerTask(NULL) {} + ~ProxyClientInfo(); + + // + // There are generally several streams for a single client. Each stream must + // have a different source port from the origin server. These functions + // accomplish this mapping. + ProxyDemuxerTask* AddStream(QTSS_RTPStreamObject inStream); + ProxyDemuxerTask* GetDemuxerTaskForStream(QTSS_RTPStreamObject inStream); + + ProxyDemuxerTask* GetLastDemuxerTask() { return fLastDemuxerTask; } + + // + // An RTSPClient object that handles the details of communicating with + // the upstream server over RTSP + RTSPClient* GetRTSPClient() { return &fClient; } + + // + // A QTSS_SocketStream for doing async I/O in QTSS API with the RTSP socket + QTSS_SocketStream GetSocketStream() { return fStream; } + void SetSocketStream(QTSS_SocketStream inStream) { fStream = inStream; } + + private: + + // + // All the resources needed by 1 client of the proxy stored here, so this all can be + // stuffed into a ClientSession + QTSS_SocketStream fStream; + TCPClientSocket fClientSocket; + RTSPClient fClient; + ProxyDemuxerTask* fLastDemuxerTask; + OSQueue fDemuxerTaskQueue; + OSRef fRef; +}; + + +// ATTRIBUTES + +static QTSS_AttributeID sProxyClientInfoAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sPortNumberTooBigErr = qtssIllegalAttrID; +static QTSS_AttributeID sRemoteHostRefusedConnectionErr = qtssIllegalAttrID; +static QTSS_AttributeID sNoTransportHeaderInSetupErr = qtssIllegalAttrID; + +// STATIC DATA + +static QTSS_PrefsObject sServerPrefs = NULL; +static ProxySocketPool* sSocketPool = NULL; +static ProxyTask* sProxyTask = NULL; +static OSRefTable* sSessionMap = NULL; + + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSProxyModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error Shutdown(); +static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams); +static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error ProcessIncomingRTCPPacket(QTSS_RTCPProcess_Params* inParams); + +static QTSS_Error HandleRTSPClientErr(QTSS_RTSPRequestObject inRequest, ProxyClientInfo* inClient, QTSS_Error inErr); +static void RequestSocketEvent(QTSS_StreamRef inStream, UInt32 inEventMask); +static QTSS_Error DoRequestPreProcessing(ProxyClientInfo* inProxyClientInfo, QTSS_RTSPRequestObject inRequest, QTSS_ClientSessionObject inSession); +static QTSS_Error DoRequestPostProcessing(ProxyClientInfo* inProxyClientInfo, QTSS_RTSPRequestObject inRequest, + QTSS_ClientSessionObject inClientSession); +static UInt32 ReplaceSessionAndTransportHeaders(StrPtrLen* inResponse, iovec* outNewResponse, StrPtrLen* inNewSessionID, + StrPtrLen* inNewTransportHeader, UInt32* outNewResponseLen); + + +// FUNCTION IMPLEMENTATIONS + + +QTSS_Error QTSSProxyModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSProxyModuleDispatch); +} + + +QTSS_Error QTSSProxyModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RTSPRequest_Role: + return ProcessRTSPRequest(&inParams->rtspRequestParams); + case QTSS_ClientSessionClosing_Role: + return DestroySession(&inParams->clientSessionClosingParams); + case QTSS_RTCPProcess_Role: + return ProcessIncomingRTCPPacket(&inParams->rtcpProcessParams); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_Shutdown_Role); + (void)QTSS_AddRole(QTSS_RTSPRequest_Role); + (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); + (void)QTSS_AddRole(QTSS_RTCPProcess_Role); + + // Tell the server our name! + static char* sModuleName = "QTSSProxyModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + static char* sProxyClientInfoName = "QTSSProxyModuleProxyClientInfo"; + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sProxyClientInfoName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sProxyClientInfoName, &sProxyClientInfoAttr); + + static char* sPortNumberToBigErrName = "QTSSProxyModulePortNumberTooBig"; + static char* sRemoteHostRefusedConnectionErrName = "QTSSProxyModuleRemoteHostRefusedConnection"; + static char* sNoTransportHeaderInSetupErrName = "QTSSProxyModuleNoTransportHeaderInSetup"; + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sPortNumberToBigErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sPortNumberToBigErrName, &sPortNumberTooBigErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sRemoteHostRefusedConnectionErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sRemoteHostRefusedConnectionErrName, &sRemoteHostRefusedConnectionErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sNoTransportHeaderInSetupErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sNoTransportHeaderInSetupErrName, &sNoTransportHeaderInSetupErr); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + // + // Setup global data structures + sServerPrefs = inParams->inPrefs; + sSocketPool = NEW ProxySocketPool(); + sProxyTask = NEW ProxyTask(); + sSessionMap = NEW OSRefTable(); + + // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN + static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod }; + QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5); + + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + return QTSS_NoErr; +} + +QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams) +{ + ProxyClientInfo* theClient = NULL; + UInt32 theLen = sizeof(theClient); + QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, + &theClient, &theLen); + + if (theErr != QTSS_NoErr) + return theErr; + + delete theClient; + + // + // NULL out the attribute so in case there is a race condition no one will + // get this dangling pointer + (void)QTSS_SetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, NULL, 0); + return QTSS_NoErr; +} + +QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams) +{ + ProxyClientInfo* theClient = NULL; + UInt32 theLen = sizeof(theClient); + QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, + &theClient, &theLen); + + // + // If there is no client yet, this is the first request made on this session. + // Create an RTSPClient + if (theErr != QTSS_NoErr) + { + theClient = NEW ProxyClientInfo(); + + // + // Parse out the DNS name of the origin server and the port + StrPtrLen theDNSNameAndPort; + theErr = QTSS_GetValuePtr(inParams->inRTSPHeaders, qtssHostHeader, 0, (void**)&theDNSNameAndPort.Ptr, &theDNSNameAndPort.Len); + Assert(theErr == QTSS_NoErr); + + StringParser extractPortNumber(&theDNSNameAndPort); + + StrPtrLen theDNSNamePtr; + extractPortNumber.GetThru(&theDNSNamePtr, ':'); + UInt32 thePort = extractPortNumber.ConsumeInteger(NULL); + + // + // For now, if there was no port specified, use 554 + if (thePort == 0) + thePort = 554; + + // + // Make sure the port is in the range of a 2-byte integer + if (thePort > 65536) + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, + sPortNumberTooBigErr); + + // + // gethostbyname takes a NULL terminated C-string + OSCharArrayDeleter theDNSName(theDNSNamePtr.GetAsCString()); + + // + // Do a DNS lookup on the host to find its IP address + struct hostent* theHostent = ::gethostbyname(theDNSName); + + UInt32 theIPAddr = 0; + if (theHostent != NULL) + theIPAddr = ntohl(*(UInt32*)(theHostent->h_addr_list[0])); + else + theIPAddr = SocketUtils::ConvertStringToAddr(theDNSName); + + // + // Give this information to the ClientSocket object + theClient->GetRTSPClient()->GetSocket()->Set(theIPAddr, (UInt16)thePort); + + theErr = QTSS_SetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, &theClient, sizeof(theClient)); + Assert(theErr == QTSS_NoErr); + + // + // Start the process of connecting to the origin server, first + // just by telling the ClientSocket to send an empty message + theErr = theClient->GetRTSPClient()->GetSocket()->Send(NULL, 0); + if (theErr != QTSS_NoErr) + // + // we are connecting. This function will be called when connection is set up + return HandleRTSPClientErr(inParams->inRTSPRequest, theClient, theErr); + } + + // + // If we aren't in the middle of sending a request, we have to set it up + if (!theClient->GetRTSPClient()->IsTransactionInProgress()) + theErr = DoRequestPreProcessing(theClient, inParams->inRTSPRequest, inParams->inClientSession); + else + // + // Continue our attempt to send this request + theErr = theClient->GetRTSPClient()->DoTransaction(); + + if (theErr != QTSS_NoErr) + HandleRTSPClientErr(inParams->inRTSPRequest, theClient, theErr); + else + // + // The response has come back from the origin server. Do whatever + // processing we need on it, and send the response to the proxy client. + theErr = DoRequestPostProcessing(theClient, inParams->inRTSPRequest, inParams->inClientSession); + + return theErr; +} + +QTSS_Error HandleRTSPClientErr(QTSS_RTSPRequestObject inRequest, ProxyClientInfo* inClient, QTSS_Error inErr) +{ + if (inClient->GetSocketStream() == NULL) + { + QTSS_SocketStream theSockStream = NULL; + + // Create a socket stream for the TCP socket in the RTSPClient object. The socket stream will + // allow this module to receive events on the socket + QTSS_Error theErr = QTSS_CreateStreamFromSocket(inClient->GetRTSPClient()->GetSocket()->GetSocket()->GetSocketFD(), &theSockStream); + Assert(theErr == QTSS_NoErr); + Assert(theSockStream != NULL); + + // + // Cache it for future use + inClient->SetSocketStream(theSockStream); + } + + if ((inErr == EAGAIN) || (inErr == EINPROGRESS)) + { + // + // Still not done. Return back to the server and wait for an event + + // We're making an assumption here that inClient only uses one socket to connect to + // the server. We only have one stream, so we have to make that assumption. + + // Note that it is not necessary to have any kind of timeout here, because the server + // naturally times out idle connections. If the server doesn't respond for awhile, + // this session will naturally go away + inClient->GetRTSPClient()->GetSocket()->GetSocket()->DontAutoCleanup(); + RequestSocketEvent(inClient->GetSocketStream(), inClient->GetRTSPClient()->GetSocket()->GetEventMask()); + return QTSS_NoErr; // We'll get called in the same method again when there is more work to do + } + + if (inErr == QTSS_RequestFailed) + { + // + // The remote host responded with a non 200 response. Forward it onto + // the proxy client as normal, let it figure out what to do. + + // + // Or this might just be that we got back an error from QTSS_AddRTPStream, + // in which case we don't need to do anything special. + return QTSS_NoErr; + } + else + return QTSSModuleUtils::SendErrorResponse(inRequest, qtssServerGatewayTimeout, + sRemoteHostRefusedConnectionErr); +} + +void RequestSocketEvent(QTSS_StreamRef inStream, UInt32 inEventMask) +{ + // + // Job of this function is to convert a CommonUtilitiesLib event mask to a QTSS Event mask + QTSS_EventType theEvent = 0; + + if (inEventMask & EV_RE) + theEvent |= QTSS_ReadableEvent; + if (inEventMask & EV_WR) + theEvent |= QTSS_WriteableEvent; + + QTSS_Error theErr = QTSS_RequestEvent(inStream, theEvent); + Assert(theErr == QTSS_NoErr); +} + + +QTSS_Error DoRequestPreProcessing(ProxyClientInfo* inProxyClientInfo, + QTSS_RTSPRequestObject inRequest, + QTSS_ClientSessionObject inSession) +{ + QTSS_RTSPMethod* theMethod = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inRequest, qtssRTSPReqMethod, 0, + (void**)&theMethod, &theLen); + Assert(theErr == QTSS_NoErr); + + char theTransportHeaderBuf[128]; + StrPtrLen theTransportHeader(theTransportHeaderBuf, 0); + + // + // The session ID passed to us is this server's session ID. Make sure to replace it + // with the session ID passed to us by the upstream server, or else the upstream + // server will not be able to properly handle this request. + StrPtrLen* theProxyClientSessionIDPtr = inProxyClientInfo->GetRTSPClient()->GetSessionID(); + + // + // Truncate the \r\n at the end of this + StrPtrLen theProxyClientSessionID(*theProxyClientSessionIDPtr); + if (theProxyClientSessionID.Len > 0) + { + Assert(theProxyClientSessionID.Len >= 2); + // + // Truncate off the EOL + if ((theProxyClientSessionID.Ptr[theProxyClientSessionID.Len - 1] == '\r') || + (theProxyClientSessionID.Ptr[theProxyClientSessionID.Len - 1] == '\n')) + theProxyClientSessionID.Len--; + if ((theProxyClientSessionID.Ptr[theProxyClientSessionID.Len - 1] == '\r') || + (theProxyClientSessionID.Ptr[theProxyClientSessionID.Len - 1] == '\n')) + theProxyClientSessionID.Len--; + + // + // Truncate off "Session: " + Assert(theProxyClientSessionID.Len > 9); + theProxyClientSessionID.Ptr += 9; + theProxyClientSessionID.Len -= 9; + } + + StrPtrLen theRequest; + theErr = QTSS_GetValuePtr(inRequest, qtssRTSPReqFullRequest, 0, + (void**)&theRequest.Ptr, &theRequest.Len); + Assert(theErr == QTSS_NoErr); + + if (*theMethod == qtssSetupMethod) + { + // + // If this is a SETUP, we need to do some special processing, + // and rewrite the Transport header before sending it on. + + // + // First, tell the server to setup a new QTSS_RTPStreamObject + // The proxy only supports UDP transport right now, so make sure that that is what we get + QTSS_RTPStreamObject theStream = NULL; + theErr = QTSS_AddRTPStream(inSession, inRequest, &theStream, qtssASFlagsForceUDPTransport); + if (theErr != QTSS_NoErr) + { + Assert(theErr == QTSS_RequestFailed); // if any other error is returned, + // our error handling logic will get confused. + return theErr; + } + + // + // Tell the ProxyClientInfo object about this new stream. + // This will also allocate client ports on this proxy. + ProxyDemuxerTask* theProxyDemuxerTask = inProxyClientInfo->AddStream(theStream); + + // + // Build a new Transport header + UInt16 theRTPPort = theProxyDemuxerTask->GetSockets()->GetSocketA()->GetLocalPort(); + + qtss_sprintf(theTransportHeaderBuf, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n", theRTPPort, theRTPPort + 1); + theTransportHeader.Len = ::strlen(theTransportHeaderBuf); + } + + // + // Build the new request and send it + + iovec theNewRequest[5]; + UInt32 totalResponseLen = 0; + + UInt32 theNumVecs = ReplaceSessionAndTransportHeaders(&theRequest, theNewRequest, &theProxyClientSessionID, &theTransportHeader, &totalResponseLen); + + return inProxyClientInfo->GetRTSPClient()->SendRTSPRequest(theNewRequest, theNumVecs); +} + +QTSS_Error DoRequestPostProcessing(ProxyClientInfo* inProxyClientInfo, QTSS_RTSPRequestObject inRequest, + QTSS_ClientSessionObject inClientSession) +{ + + QTSS_RTSPMethod* theMethod = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inRequest, qtssRTSPReqMethod, 0, (void**)&theMethod, &theLen); + Assert(theErr == QTSS_NoErr); + + char theTransportHeaderBuf[128]; + StrPtrLen theTransportHeader(theTransportHeaderBuf, 0); + + StrPtrLen theSessionID; + theErr = QTSS_GetValuePtr(inClientSession, qtssCliSesRTSPSessionID, 0, (void**)&theSessionID.Ptr, &theSessionID.Len); + Assert(theErr == QTSS_NoErr); + + StrPtrLen theResponse(inProxyClientInfo->GetRTSPClient()->GetResponse(), inProxyClientInfo->GetRTSPClient()->GetResponseLen()); + + if ((*theMethod == qtssSetupMethod) && + (inProxyClientInfo->GetRTSPClient()->GetStatus() == 200)) + { + ProxyDemuxerTask* thisStreamsDemuxer = inProxyClientInfo->GetLastDemuxerTask(); + thisStreamsDemuxer->SetOriginServerPort(inProxyClientInfo->GetRTSPClient()->GetServerPort()); + + // + // Build a new Transport header. This should basically be exactly what the + // server would be sending back if the proxy module wasn't generating the response + UInt16 theSvrRTPPort = 0; + UInt16 theCliRTPPort = 0; + UInt32 theLen = sizeof(theSvrRTPPort); + theErr = QTSS_GetValue(thisStreamsDemuxer->GetStream(), qtssRTPStrSvrRTPPort, 0, &theSvrRTPPort, &theLen); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_GetValue(thisStreamsDemuxer->GetStream(), qtssRTPStrClientRTPPort, 0, &theCliRTPPort, &theLen); + Assert(theErr == QTSS_NoErr); + + char theIPAddrStr[20]; + theLen = 20; + theErr = QTSS_GetValue(inClientSession, qtssCliRTSPSessLocalAddrStr, 0, &theIPAddrStr[0], &theLen); + Assert(theErr == QTSS_NoErr); + theIPAddrStr[theLen] = '\0'; + + qtss_sprintf(theTransportHeader.Ptr, "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d;source=%s\r\n", + theCliRTPPort, theCliRTPPort + 1, theSvrRTPPort, theSvrRTPPort + 1, theIPAddrStr); + + theTransportHeader.Len = ::strlen(theTransportHeader.Ptr); + } + else if ((*theMethod == qtssPlayMethod) && + (inProxyClientInfo->GetRTSPClient()->GetStatus() == 200)) + { + // + // The client session needs to know that we are entering the playing state. + // Otherwise, when we write RTP packet data to the QTSS_RTPStreamObjects, + // the server won't really be able to figure out what to do with the packets. + theErr = QTSS_Play(inClientSession, inRequest, 0); + if (theErr != QTSS_NoErr) + { + Assert(theErr == QTSS_RequestFailed); + return theErr; + } + } + else if ((*theMethod == qtssPauseMethod) && + (inProxyClientInfo->GetRTSPClient()->GetStatus() == 200)) + { + // + // Same thing as above, just let the server know that we are no longer playing + theErr = QTSS_Pause(inClientSession); + Assert(theErr == QTSS_NoErr); + } + // + // Build our ioVec with the new response + // When building the iovec used for the response, we need to leave the first + // iovec in the array empty, per QTSS API conventions. + iovec theNewResponse[7]; + theNewResponse[0].iov_len = 0; + UInt32 totalResponseLen = 0; + + // + // Internally, the server keeps an RTSP Session ID for this QTSS_ClientSessionObject. + // We need to replace the one the upstream server has given us with this server's + // RTSP Session ID. Otherwise, when the server receives the next RTSP request, + // this server will not be able to map the RTSP session ID properly. + UInt32 theNumVecs = ReplaceSessionAndTransportHeaders(&theResponse, &theNewResponse[1], &theSessionID, &theTransportHeader, &totalResponseLen); + theNumVecs++; // Take into account that the first one is empty + // + // Also add in the content body of the response if there is one + theNewResponse[theNumVecs].iov_base = inProxyClientInfo->GetRTSPClient()->GetContentBody(); + theNewResponse[theNumVecs].iov_len = inProxyClientInfo->GetRTSPClient()->GetContentLength(); + totalResponseLen += theNewResponse[theNumVecs].iov_len; + theNumVecs++; + + UInt32 lenWritten = 0; + theErr = QTSS_WriteV(inRequest, theNewResponse, theNumVecs, totalResponseLen, &lenWritten); + Assert(lenWritten == totalResponseLen); + Assert(theErr == QTSS_NoErr); + + return QTSS_NoErr; +} + +UInt32 ReplaceSessionAndTransportHeaders(StrPtrLen* inResponse, iovec* outNewResponse, StrPtrLen* inNewSessionID, + StrPtrLen* inNewTransportHeader, UInt32* outNewResponseLen) +{ + static StrPtrLen sTransportHeader("Transport"); + static StrPtrLen sSessionHeader("Session"); + + StringParser reqParser(inResponse); + StrPtrLen theHeaderName; + + UInt32 curVecIndex = 0; + outNewResponse[0].iov_base = inResponse->Ptr; + *outNewResponseLen = 0; + + while (reqParser.GetDataRemaining() > 0) + { + reqParser.ConsumeWord(&theHeaderName); + + if (theHeaderName.EqualIgnoreCase(sTransportHeader.Ptr, sTransportHeader.Len)) + { + // + // Mark off the length of the last section of the header + outNewResponse[curVecIndex].iov_len = (UInt32) ((PointerSizedUInt) theHeaderName.Ptr) - ((PointerSizedUInt) outNewResponse[curVecIndex].iov_base ); + *outNewResponseLen += outNewResponse[curVecIndex].iov_len; + + // + // Insert the new Transport header + outNewResponse[curVecIndex+1].iov_base = inNewTransportHeader->Ptr; + outNewResponse[curVecIndex+1].iov_len = inNewTransportHeader->Len; + *outNewResponseLen += inNewTransportHeader->Len; + + // + // Move onto the next iovec + curVecIndex+=2; + + // + // Mark the start of the next section + reqParser.GetThruEOL(NULL); + outNewResponse[curVecIndex].iov_base = reqParser.GetCurrentPosition(); + } + else if (theHeaderName.EqualIgnoreCase(sSessionHeader.Ptr, sSessionHeader.Len)) + { + reqParser.ConsumeLength(NULL, 2); //skip over ": " + + // + // Mark off the length of the last section of the header + outNewResponse[curVecIndex].iov_len = (UInt32) ((PointerSizedUInt) reqParser.GetCurrentPosition()) - ((PointerSizedUInt) outNewResponse[curVecIndex].iov_base); + *outNewResponseLen += outNewResponse[curVecIndex].iov_len; + + // + // Insert new session ID + outNewResponse[curVecIndex+1].iov_base = inNewSessionID->Ptr; + outNewResponse[curVecIndex+1].iov_len = inNewSessionID->Len; + *outNewResponseLen += inNewSessionID->Len; + + // + // Move onto the next empty vec + curVecIndex+=2; + + // + // Move past the session ID. Be careful, there may be a ';' that we need to look for + + StrPtrLen theSessionID; + reqParser.GetThruEOL(&theSessionID); + + outNewResponse[curVecIndex].iov_base = &theSessionID.Ptr[theSessionID.Len]; + + while (theSessionID.Ptr < outNewResponse[curVecIndex].iov_base) + { + if (*theSessionID.Ptr == ';') + { + outNewResponse[curVecIndex].iov_base = theSessionID.Ptr; + break; + } + theSessionID.Ptr++; + } + } + else + reqParser.GetThruEOL(NULL); + } + + // + // Finish off the vec by marking the len of the last section + outNewResponse[curVecIndex].iov_len = (UInt32) ((PointerSizedUInt) reqParser.GetCurrentPosition()) - ((PointerSizedUInt) outNewResponse[curVecIndex].iov_base ); + + // + // And finish off the total length + *outNewResponseLen += outNewResponse[curVecIndex].iov_len; + + // + // Return the number of vecs written + return curVecIndex+1; +} + +QTSS_Error ProcessIncomingRTCPPacket(QTSS_RTCPProcess_Params* inParams) +{ + ProxyClientInfo* theClient = NULL; + UInt32 theLen = sizeof(theClient); + QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, + &theClient, &theLen); + + // + // This role receives ALL RTCP packets. We are only interested in RTCP packets + // sent to proxy sessions. So we figure this out based on whether there + // is a ProxyClientInfo object in the client session + if (theErr != QTSS_NoErr) + return QTSS_NoErr; + + // + // Let's forward this RTCP packet to the right upstream server + ProxyDemuxerTask* theTask = theClient->GetDemuxerTaskForStream(inParams->inRTPStream); + Assert(theTask != NULL); + if (theTask == NULL) + return QTSS_NoErr; + + // + // Using the RTCP socket (SocketB) of the pair, send the packet to the origin server's + // RTCP port (the port number stored is the RTP port) + (void)theTask->GetSockets()->GetSocketB()-> + SendTo(theTask->GetRemoteAddr(), theTask->GetOriginServerPort() + 1, inParams->inRTCPPacketData, inParams->inRTCPPacketDataLen); + + return QTSS_NoErr; +} + + +SInt64 ProxyTask::Run() +{ + const UInt32 kMaxRTCPPacketSize = 2048; + char thePacketBuffer[kMaxRTCPPacketSize]; + QTSS_PacketStruct thePacketStruct; + thePacketStruct.packetTransmitTime = QTSS_Milliseconds(); + thePacketStruct.packetData = thePacketBuffer; + + (void)this->GetEvents(); + + OSMutexLocker locker(sSocketPool->GetMutex()); + for (OSQueueIter iter(sSocketPool->GetSocketQueue()); !iter.IsDone(); iter.Next()) + { + UInt32 theRemoteAddr = 0; + UInt16 theRemotePort = 0; + + UDPSocketPair* thePair = (UDPSocketPair*)iter.GetCurrent()->GetEnclosingObject(); + Assert(thePair != NULL); + + for (UInt32 x = 0; x < 2; x++) + { + QTSS_WriteFlags theFlags = qtssWriteFlagsNoFlags; + + UDPSocket* theSocket = NULL; + if (x == 0) + { + theFlags = qtssWriteFlagsIsRTP; + theSocket = thePair->GetSocketA(); + } + else + { + theFlags = qtssWriteFlagsIsRTCP; + theSocket = thePair->GetSocketB(); + } + + Assert(theSocket->GetDemuxer() != NULL); + OSMutexLocker locker(theSocket->GetDemuxer()->GetMutex()); + + //get all the outstanding packets for this socket + while (true) + { + UInt32 thePacketLen = 0; + theSocket->RecvFrom(&theRemoteAddr, &theRemotePort, thePacketStruct.packetData, + kMaxRTCPPacketSize, &thePacketLen); + if (thePacketLen == 0) + break;//no more packets on this socket! + + ProxyDemuxerTask* theDemuxerTask = (ProxyDemuxerTask*)theSocket->GetDemuxer()->GetTask(theRemoteAddr, 0); + if (theDemuxerTask != NULL) + { + QTSS_RTPStreamObject theStream = theDemuxerTask->GetStream(); + (void)QTSS_Write(theStream, &thePacketStruct, thePacketLen, NULL, theFlags); + } + } + } + } + return kProxyTaskPollIntervalMsec; +} + + +UDPSocketPair* ProxySocketPool::ConstructUDPSocketPair() +{ + Assert(sProxyTask != NULL); + //construct a pair of UDP sockets, the lower one for RTP data (outgoing only, no demuxer + //necessary), and one for RTCP data (incoming, so definitely need a demuxer). + //These are nonblocking sockets that DON'T receive events (we are going to poll for data) + return NEW + UDPSocketPair( NEW UDPSocket(sProxyTask, UDPSocket::kWantsDemuxer | Socket::kNonBlockingSocketType), + NEW UDPSocket(sProxyTask, UDPSocket::kWantsDemuxer | Socket::kNonBlockingSocketType)); +} + +void ProxySocketPool::DestructUDPSocketPair(UDPSocketPair* inPair) +{ + delete inPair->GetSocketA(); + delete inPair->GetSocketB(); + delete inPair; +} + +void ProxySocketPool::SetUDPSocketOptions(UDPSocketPair* inPair) +{ +#ifdef __Win32__ + // + // On Win32, apparently the socket buffer size matters even though this is UDP and being + // used for sending... on UNIX typically the socket buffer size doesn't matter because the + // packet goes right down to the driver. On Win32, unless this is really big, we get packet loss. + inPair->GetSocketA()->SetSocketBufSize(256 * 1024); + inPair->GetSocketB()->SetSocketBufSize(256 * 1024); +#endif + + // + // Always set the Rcv buf size for the RTCP sockets. This is important because the + // server is going to be getting many packets on these sockets. + inPair->GetSocketA()->SetSocketRcvBufSize(512 * 1024); + inPair->GetSocketB()->SetSocketRcvBufSize(512 * 1024); +} + + +ProxyClientInfo::~ProxyClientInfo() +{ + UInt32 theHostAddr = fClient.GetSocket()->GetHostAddr(); + + for (OSQueueIter iter(&fDemuxerTaskQueue); !iter.IsDone(); ) + { + OSQueueElem* theElem = iter.GetCurrent(); + ProxyDemuxerTask* theTask = (ProxyDemuxerTask*)theElem->GetEnclosingObject(); + + // + // Move onto the next element becase we are going to be deleting this one + iter.Next(); + theElem->Remove(); // Get off the queue before deleting + + // + // Clean up + UDPSocketPair* thePair = theTask->GetSockets(); + thePair->GetSocketA()->GetDemuxer()->UnregisterTask(theHostAddr, 0, theTask); + thePair->GetSocketB()->GetDemuxer()->UnregisterTask(theHostAddr, 0, theTask); + + delete theTask; + sSocketPool->ReleaseUDPSocketPair(thePair); + } +} + +ProxyDemuxerTask* ProxyClientInfo::GetDemuxerTaskForStream(QTSS_RTPStreamObject inStream) +{ + // + // Iterate through the queue of demuxer tasks, finding the one with + // the stream that matches this input + for (OSQueueIter iter(&fDemuxerTaskQueue); !iter.IsDone(); iter.Next()) + { + ProxyDemuxerTask* theTask = (ProxyDemuxerTask*)iter.GetCurrent()->GetEnclosingObject(); + if (theTask->GetStream() == inStream) + return theTask; + } + return NULL; +} + + +ProxyDemuxerTask* ProxyClientInfo::AddStream(QTSS_RTPStreamObject inStream) +{ + // + // Allocate some UDP sockets out of our pool to receive the UDP data from + // the server. Demuxing is based on the origin server's (host's) IP addr, so + // pass that to the socket pool so it can properly allocate a pair of UDP sockets. + // We don't know what the remote port is yet (we only find out when we get the + // SETUP response from the origin), so just pass in 0. + UInt32 theHostAddr = fClient.GetSocket()->GetHostAddr(); + UInt32 theLocalAddr = fClient.GetSocket()->GetLocalAddr(); + + UDPSocketPair* thePair = sSocketPool->GetUDPSocketPair(theLocalAddr, 0, theHostAddr, 0); + + fLastDemuxerTask = NEW ProxyDemuxerTask(inStream, thePair); + fDemuxerTaskQueue.EnQueue(fLastDemuxerTask->GetQueueElem()); + + // + // Tell the demuxers for these sockets to send any packets from this IP addr + // to the ProxyDemuxerTask for this stream. This is how incoming UDP packets + // will be routed to the proper QTSS_RTPStreamObject + thePair->GetSocketA()->GetDemuxer()->RegisterTask(theHostAddr, 0, fLastDemuxerTask); + thePair->GetSocketB()->GetDemuxer()->RegisterTask(theHostAddr, 0, fLastDemuxerTask); + + // + // return the newly created ProxyDemuxerTask + return fLastDemuxerTask; +} diff --git a/APIModules/QTSSProxyModule/QTSSProxyModule.h b/APIModules/QTSSProxyModule/QTSSProxyModule.h new file mode 100644 index 0000000..1477464 --- /dev/null +++ b/APIModules/QTSSProxyModule/QTSSProxyModule.h @@ -0,0 +1,43 @@ +/* + * + * @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: QTSSProxyModule.h + + Contains: QTSS API module + + +*/ + +#ifndef _QTSSPROXYMODULE_H_ +#define _QTSSPROXYMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSProxyModule_Main(void* inPrivateArgs); +} + +#endif //_QTSSPROXYMODULE_H_ diff --git a/APIModules/QTSSRTPFileModule/QTSSRTPFileModule.cpp b/APIModules/QTSSRTPFileModule/QTSSRTPFileModule.cpp new file mode 100644 index 0000000..f9f0948 --- /dev/null +++ b/APIModules/QTSSRTPFileModule/QTSSRTPFileModule.cpp @@ -0,0 +1,780 @@ +/* + * + * @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: QTSSFileModule.cpp + + Contains: Implementation of module described in QTSSFileModule.h. + + +*/ + +#include + +#include "QTSSRTPFileModule.h" + +#include "RTPFileSession.h" +#include "OSMemory.h" +#include "OSArrayObjectDeleter.h" +#include "QTSSModuleUtils.h" +#include "StringFormatter.h" +#include "SDPSourceInfo.h" +#include "QTSSMemoryDeleter.h" + +#include "QTSS.h" + + +struct FileSession +{ + public: + + FileSession() : fAdjustedPlayTime(0), fNextPacketLen(0), + fStream(NULL), fSpeed(1), fStartTime(0), fStopTime(-1) + {} + + RTPFileSession fFile; + SInt64 fAdjustedPlayTime; + QTSS_PacketStruct fPacketStruct; + UInt32 fNextPacketLen; + QTSS_RTPStreamObject fStream; + Float32 fSpeed; + Float64 fStartTime; + Float64 fStopTime; +}; + + + +// ref to the prefs dictionary object +static QTSS_ModulePrefsObject sFileModulePrefs; + +static StrPtrLen sRTPSuffix(".rtp"); +static StrPtrLen sSDPHeader1("v=0\r\ns="); +static StrPtrLen sSDPHeader2; +static StrPtrLen sSDPHeader3("c=IN IP4 "); +static StrPtrLen sSDPHeader4("\r\na=control:/\r\n"); +static const StrPtrLen kCacheControlHeader("must-revalidate"); + +// ATTRIBUTES IDs + +static QTSS_AttributeID sFileSessionAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sSeekToNonexistentTimeErr = qtssIllegalAttrID; +static QTSS_AttributeID sBadQTFileErr = qtssIllegalAttrID; +static QTSS_AttributeID sExpectedDigitFilenameErr = qtssIllegalAttrID; +static QTSS_AttributeID sTrackDoesntExistErr = qtssIllegalAttrID; + +// OTHER DATA + +static UInt32 sFlowControlProbeInterval = 50; +static UInt32 sDefaultFlowControlProbeInterval= 50; +static Float32 sMaxAllowedSpeed = 4; +static Float32 sDefaultMaxAllowedSpeed = 4; + +// FUNCTIONS + +static QTSS_Error QTSSRTPFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParamBlock); +static QTSS_Error RereadPrefs(); +static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParamBlock); +static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock); +static QTSS_Error CreateRTPFileSession(QTSS_StandardRTSP_Params* inParamBlock, const StrPtrLen& inPath, FileSession** outFile); +static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock); +static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock); +static QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams); +static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams); + + + +QTSS_Error QTSSRTPFileModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSRTPFileModuleDispatch); +} + +QTSS_Error QTSSRTPFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParamBlock->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParamBlock->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPPreProcessor_Role: + return ProcessRTSPRequest(&inParamBlock->rtspPreProcessorParams); + case QTSS_RTPSendPackets_Role: + return SendPackets(&inParamBlock->rtpSendPacketsParams); + case QTSS_ClientSessionClosing_Role: + return DestroySession(&inParamBlock->clientSessionClosingParams); + } + return QTSS_NoErr; +} + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Register for roles + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role); + (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + + // Add text messages attributes + static char* sSeekToNonexistentTimeName = "QTSSFileModuleSeekToNonExistentTime"; + static char* sBadQTFileName = "QTSSFileModuleBadQTFile"; + static char* sExpectedDigitFilenameName = "QTSSFileModuleExpectedDigitFilename"; + static char* sTrackDoesntExistName = "QTSSFileModuleTrackDoesntExist"; + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, &sSeekToNonexistentTimeErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadQTFileName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadQTFileName, &sBadQTFileErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sTrackDoesntExistName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sTrackDoesntExistName, &sTrackDoesntExistErr); + + // Add an RTP session attribute for tracking FileSession objects + static char* sFileSessionName = "QTSSRTPFileModuleSession"; + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionName, &sFileSessionAttr); + + // Tell the server our name! + static char* sModuleName = "QTSSRTPFileModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + // + // We need some prefs created and maintained by the QTSSFileModule, + // cuz we don't want to duplicate basically the same stuff + StrPtrLen theFileModule("QTSSFileModule"); + sFileModulePrefs = QTSSModuleUtils::GetModulePrefsObject(QTSSModuleUtils::GetModuleObjectByName(theFileModule)); + + static StrPtrLen sEHeader("\r\ne="); + static StrPtrLen sUHeader("\r\nu="); + static StrPtrLen sHTTP("http://"); + static StrPtrLen sAdmin("admin@"); + + // Read our preferences + RereadPrefs(); + + //build the sdp that looks like: \r\ne=http://streaming.apple.com\r\ne=qts@apple.com\r\n. + + // Get the default DNS name of the server + StrPtrLen theDefaultDNS; + (void)QTSS_GetValuePtr(inParams->inServer, qtssSvrDefaultDNSName, 0, + (void**)&theDefaultDNS.Ptr, &theDefaultDNS.Len); + + StrPtrLen sdpURL; + StrPtrLen adminEmail; + sdpURL.Ptr = QTSSModuleUtils::GetStringAttribute(sFileModulePrefs, "sdp_url", ""); + sdpURL.Len = ::strlen(sdpURL.Ptr); + + adminEmail.Ptr = QTSSModuleUtils::GetStringAttribute(sFileModulePrefs, "admin_email", ""); + adminEmail.Len = ::strlen(adminEmail.Ptr); + + UInt32 sdpURLLen = sdpURL.Len; + UInt32 adminEmailLen = adminEmail.Len; + + if (sdpURLLen == 0) + sdpURLLen = theDefaultDNS.Len + sHTTP.Len + 1; + if (adminEmailLen == 0) + adminEmailLen = theDefaultDNS.Len + sAdmin.Len; + + //calculate the length of the string & allocate memory + sSDPHeader2.Len = (sEHeader.Len * 2) + sdpURLLen + adminEmailLen + 10; + sSDPHeader2.Ptr = NEW char[sSDPHeader2.Len]; + + //write it! + StringFormatter sdpFormatter(sSDPHeader2); + sdpFormatter.Put(sUHeader); + + //if there are preferences for default URL & admin email, use those. Otherwise, build the + //proper string based on default dns name. + if (sdpURL.Len == 0) + { + sdpFormatter.Put(sHTTP); + sdpFormatter.Put(theDefaultDNS); + sdpFormatter.PutChar('/'); + } + else + sdpFormatter.Put(sdpURL); + + sdpFormatter.Put(sEHeader); + + //now do the admin email. + if (adminEmail.Len == 0) + { + sdpFormatter.Put(sAdmin); + sdpFormatter.Put(theDefaultDNS); + } + else + sdpFormatter.Put(adminEmail); + + sdpFormatter.PutEOL(); + sSDPHeader2.Len = (UInt32)sdpFormatter.GetCurrentOffset(); + + delete [] sdpURL.Ptr; + delete [] adminEmail.Ptr; + + // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN + static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod }; + QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5); + + return QTSS_NoErr; +} + +QTSS_Error RereadPrefs() +{ + QTSSModuleUtils::GetAttribute(sFileModulePrefs, "flow_control_probe_interval", qtssAttrDataTypeUInt32, + &sFlowControlProbeInterval, &sDefaultFlowControlProbeInterval, sizeof(sFlowControlProbeInterval)); + + QTSSModuleUtils::GetAttribute(sFileModulePrefs, "max_allowed_speed", qtssAttrDataTypeFloat32, + &sMaxAllowedSpeed, &sDefaultMaxAllowedSpeed, sizeof(sMaxAllowedSpeed)); + + return QTSS_NoErr; +} + + +QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_RTSPMethod* theMethod = NULL; + UInt32 theMethodLen = 0; + if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0, + (void**)&theMethod, &theMethodLen) != QTSS_NoErr) || (theMethodLen != sizeof(QTSS_RTSPMethod))) + { + Assert(0); + return QTSS_RequestFailed; + } + + switch (*theMethod) + { + case qtssDescribeMethod: + return DoDescribe(inParams); + case qtssSetupMethod: + return DoSetup(inParams); + case qtssPlayMethod: + return DoPlay(inParams); + case qtssTeardownMethod: + // Tell the server that this session should be killed, and send a TEARDOWN response + (void)QTSS_Teardown(inParams->inClientSession); + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0); + break; + case qtssPauseMethod: + (void)QTSS_Pause(inParams->inClientSession); + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0); + break; + default: + break; + } + + return QTSS_NoErr; +} + +QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock) +{ + // Check and see if this is a request we should handle. We handle all requests with URLs that + // end in a '.rtp' + char* theFullPathStr = NULL; + QTSS_Error theError = QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr); + QTSSCharArrayDeleter theFullPathDeleter(theFullPathStr); + Assert(theError == QTSS_NoErr); + + StrPtrLen theFullPath(theFullPathStr); + + if ((theFullPath.Len <= sRTPSuffix.Len) || + (!sRTPSuffix.NumEqualIgnoreCase(&theFullPath.Ptr[theFullPath.Len - sRTPSuffix.Len], sRTPSuffix.Len))) + return QTSS_RequestFailed; + + // It is, so let's set everything up... + + // + // Get the FileSession for this DESCRIBE, if any. + UInt32 theLen = sizeof(FileSession*); + FileSession* theFile = NULL; + QTSS_Error theErr = QTSS_NoErr; + + (void)QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen); + + if ( theFile != NULL ) + { + // + // There is already a file for this session. This can happen if there are multiple DESCRIBES, + // or a DESCRIBE has been issued with a Session ID, or some such thing. + + if ( !theFullPath.Equal( *theFile->fFile.GetMoviePath() ) ) + { + delete theFile; + theFile = NULL; + + // NULL out the attribute value, just in case. + (void)QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile)); + } + } + + if ( theFile == NULL ) + { + theErr = CreateRTPFileSession(inParamBlock, theFullPath, &theFile); + if (theErr != QTSS_NoErr) + { + (void)QTSS_Teardown(inParamBlock->inClientSession); + return theErr; + } + + // Store this newly created file object in the RTP session. + theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile)); + } + + //generate the SDP. + UInt32 totalSDPLength = sSDPHeader1.Len; + iovec theSDPVec[10];//1 for the RTSP header, 6 for the sdp header, 1 for the sdp body + theSDPVec[1].iov_base = sSDPHeader1.Ptr; + theSDPVec[1].iov_len = sSDPHeader1.Len; + + //filename goes here + //(void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&theSDPVec[2].iov_base, (UInt32*)&theSDPVec[2].iov_len); + char* filenameStr = NULL; + (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, &filenameStr); + QTSSCharArrayDeleter filenameStrDeleter(filenameStr); + theSDPVec[2].iov_base = filenameStr; + theSDPVec[2].iov_len = ::strlen(filenameStr); + + totalSDPLength += theSDPVec[2].iov_len; + + //url & admin email goes here + theSDPVec[3].iov_base = sSDPHeader2.Ptr; + theSDPVec[3].iov_len = sSDPHeader2.Len; + totalSDPLength += sSDPHeader2.Len; + + //connection header + theSDPVec[4].iov_base = sSDPHeader3.Ptr; + theSDPVec[4].iov_len = sSDPHeader3.Len; + totalSDPLength += sSDPHeader3.Len; + + //append IP addr + (void)QTSS_GetValuePtr(inParamBlock->inRTSPSession, qtssRTSPSesLocalAddrStr, 0, + (void**)&theSDPVec[5].iov_base, (UInt32*)&theSDPVec[5].iov_len); + totalSDPLength += theSDPVec[5].iov_len; + + //last static sdp line + theSDPVec[6].iov_base = sSDPHeader4.Ptr; + theSDPVec[6].iov_len = sSDPHeader4.Len; + totalSDPLength += sSDPHeader4.Len; + + //now append content-determined sdp + theSDPVec[7].iov_base = theFile->fFile.GetSDPFile()->Ptr; + theSDPVec[7].iov_len = theFile->fFile.GetSDPFile()->Len; + totalSDPLength += theSDPVec[7].iov_len; + + Assert(theSDPVec[2].iov_base != NULL); + + + // Append the Last Modified header to be a good caching proxy citizen before sending the Describe + //(void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader, + // theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + + //ok, we have a filled out iovec. Let's send it! + QTSSModuleUtils::SendDescribeResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, + &theSDPVec[0], 8, totalSDPLength); + + return QTSS_NoErr; +} + +QTSS_Error CreateRTPFileSession(QTSS_StandardRTSP_Params* inParamBlock, const StrPtrLen& inPath, FileSession** outFile) +{ + *outFile = NEW FileSession(); + StrPtrLen thePath(inPath); + RTPFileSession::ErrorCode theErr = (*outFile)->fFile.Initialize(thePath, 8); + if (theErr != RTPFileSession::errNoError) + { + delete *outFile; + *outFile = NULL; + + if (theErr == RTPFileSession::errFileNotFound) + return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest, + qtssClientNotFound, + sBadQTFileErr); + AssertV(0, theErr); + } + return QTSS_NoErr; +} + + +QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock) +{ + //setup this track in the file object + FileSession* theFile = NULL; + UInt32 theLen = sizeof(FileSession*); + QTSS_Error theErr = QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*))) + { + // This is possible, as clients are not required to send a DESCRIBE. If we haven't set + // anything up yet, set everything up + char* theFullPathStr = NULL; + theErr = QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr); + Assert(theErr == QTSS_NoErr); + QTSSCharArrayDeleter theFullPathDeleter(theFullPathStr); + StrPtrLen theFullPath(theFullPathStr); + + if ((theFullPath.Len <= sRTPSuffix.Len) || + (!sRTPSuffix.NumEqualIgnoreCase(&theFullPath.Ptr[theFullPath.Len - sRTPSuffix.Len], sRTPSuffix.Len))) + return QTSS_RequestFailed; + + theErr = CreateRTPFileSession(inParamBlock, theFullPath, &theFile); + if (theErr != QTSS_NoErr) + return theErr; + + // Store this newly created file object in the RTP session. + theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile)); + } + + //unless there is a digit at the end of this path (representing trackID), don't + //even bother with the request + char* theDigitStr = NULL; + (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr); + QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr); + if (theDigitStr == NULL) + return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, + qtssClientBadRequest, sExpectedDigitFilenameErr); + + UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10); + + RTPFileSession::ErrorCode qtfileErr = theFile->fFile.AddTrack(theTrackID); + + //if we get an error back, forward that error to the client + if (qtfileErr == RTPFileSession::errTrackDoesntExist) + return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, + qtssClientNotFound, sTrackDoesntExistErr); + else if (qtfileErr != RTPFileSession::errNoError) + return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest, + qtssUnsupportedMediaType, sBadQTFileErr); + + //find the payload for this track ID (if applicable) + StrPtrLen* thePayload = NULL; + UInt32 thePayloadType = qtssUnknownPayloadType; + Float32 bufferDelay = (Float32) 3.0; // FIXME need a constant defined for 3.0 value. It is used multiple places + + for (UInt32 x = 0; x < theFile->fFile.GetSourceInfo()->GetNumStreams(); x++) + { + SourceInfo::StreamInfo* theStreamInfo = theFile->fFile.GetSourceInfo()->GetStreamInfo(x); + if (theStreamInfo->fTrackID == theTrackID) + { + thePayload = &theStreamInfo->fPayloadName; + thePayloadType = theStreamInfo->fPayloadType; + bufferDelay = theStreamInfo->fBufferDelay; + break; + } + } + + //Create a new RTP stream + QTSS_RTPStreamObject newStream = NULL; + theErr = QTSS_AddRTPStream(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, &newStream, 0); + if (theErr != QTSS_NoErr) + return theErr; + + // Set the payload type, payload name & timescale of this track + SInt32 theTimescale = theFile->fFile.GetTrackTimeScale(theTrackID); + + theErr = QTSS_SetValue(newStream, qtssRTPStrBufferDelayInSecs, 0, &bufferDelay, sizeof(bufferDelay)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayload->Ptr, thePayload->Len); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrTimescale, 0, &theTimescale, sizeof(theTimescale)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID)); + Assert(theErr == QTSS_NoErr); + + // Set the number of quality levels. Allow up to 6 + static UInt32 sNumQualityLevels = 0; + + theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels)); + Assert(theErr == QTSS_NoErr); + + // Get the SSRC of this track + UInt32* theTrackSSRC = NULL; + UInt32 theTrackSSRCSize = 0; + (void)QTSS_GetValuePtr(newStream, qtssRTPStrSSRC, 0, (void**)&theTrackSSRC, &theTrackSSRCSize); + + // The RTP stream should ALWAYS have an SSRC assuming QTSS_AddStream succeeded. + Assert((theTrackSSRC != NULL) && (theTrackSSRCSize == sizeof(UInt32))); + + //give the file some info it needs. + theFile->fFile.SetTrackSSRC(theTrackID, *theTrackSSRC); + theFile->fFile.SetTrackCookie(theTrackID, newStream); + + // + // Our array has now been updated to reflect the fields requested by the client. + //send the setup response + //(void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader, + // theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + + //send the setup response + (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, newStream, 0); + return QTSS_NoErr; +} + +QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock) +{ + FileSession** theFile = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*))) + return QTSS_RequestFailed; + + Float64* theStartTime = 0; + theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqStartTime, 0, (void**)&theStartTime, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(Float64))) + return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest, + qtssClientBadRequest, sSeekToNonexistentTimeErr); + + RTPFileSession::ErrorCode qtFileErr = (*theFile)->fFile.Seek(*theStartTime); + if (qtFileErr != RTPFileSession::errNoError) + return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest, + qtssClientBadRequest, sSeekToNonexistentTimeErr); + + //make sure to clear the next packet the server would have sent! + (*theFile)->fPacketStruct.packetData = NULL; + + // Set the movie duration and size parameters + Float64 movieDuration = (*theFile)->fFile.GetMovieDuration(); + (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieDurationInSecs, 0, &movieDuration, sizeof(movieDuration)); + + UInt64 movieSize = (*theFile)->fFile.GetAddedTracksRTPBytes(); + (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieSizeInBytes, 0, &movieSize, sizeof(movieSize)); + + //UInt32 bitsPerSecond = (*theFile)->fFile.GetBytesPerSecond() * 8; + //(void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond)); + + // + // For the purposes of the speed header, check to make sure all tracks are + // over a reliable transport + Bool16 allTracksReliable = true; + + // Set the timestamp & sequence number parameters for each track. + QTSS_RTPStreamObject* theRef = NULL; + for ( UInt32 theStreamIndex = 0; + QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesStreamObjects, theStreamIndex, (void**)&theRef, &theLen) == QTSS_NoErr; + theStreamIndex++) + { + UInt32* theTrackID = NULL; + theErr = QTSS_GetValuePtr(*theRef, qtssRTPStrTrackID, 0, (void**)&theTrackID, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theTrackID != NULL); + Assert(theLen == sizeof(UInt32)); + + SInt16 theSeqNum = (*theFile)->fFile.GetNextTrackSequenceNumber(*theTrackID); + SInt32 theTimestamp = (*theFile)->fFile.GetSeekTimestamp(*theTrackID); + + Assert(theRef != NULL); + Assert(theLen == sizeof(QTSS_RTPStreamObject)); + + theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstSeqNumber, 0, &theSeqNum, sizeof(theSeqNum)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstTimestamp, 0, &theTimestamp, sizeof(theTimestamp)); + Assert(theErr == QTSS_NoErr); + + if (allTracksReliable) + { + QTSS_RTPTransportType theTransportType = qtssRTPTransportTypeUDP; + theLen = sizeof(theTransportType); + theErr = QTSS_GetValue(*theRef, qtssRTPStrTransportType, 0, &theTransportType, &theLen); + Assert(theErr == QTSS_NoErr); + + if (theTransportType == qtssRTPTransportTypeUDP) + allTracksReliable = false; + } + } + + //Tell the server to start playing this movie. We do want it to send RTCP SRs, but + //we DON'T want it to write the RTP header + theErr = QTSS_Play(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, qtssPlayFlagsSendRTCP); + if (theErr != QTSS_NoErr) + return theErr; + + // qtssRTPSesAdjustedPlayTimeInMsec is valid only after calling QTSS_Play + // theAdjustedPlayTime is a way to delay the sending of data until a time after the + // the Play response should have been received. + SInt64* theAdjustedPlayTime = 0; + theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesAdjustedPlayTimeInMsec, 0, (void**)&theAdjustedPlayTime, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theAdjustedPlayTime != NULL); + Assert(theLen == sizeof(SInt64)); + (*theFile)->fAdjustedPlayTime = *theAdjustedPlayTime; + + // + // This module supports the Speed header if the client wants the stream faster than normal. + Float32 theSpeed = 1; + theLen = sizeof(theSpeed); + theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqSpeed, 0, &theSpeed, &theLen); + Assert(theErr != QTSS_BadArgument); + Assert(theErr != QTSS_NotEnoughSpace); + + if (theErr == QTSS_NoErr) + { + if (theSpeed > sMaxAllowedSpeed) + theSpeed = sMaxAllowedSpeed; + if ((theSpeed <= 0) || (!allTracksReliable)) + theSpeed = 1; + } + + (*theFile)->fSpeed = theSpeed; + + if (theSpeed != 1) + { + // + // If our speed is not 1, append the RTSP speed header in the response + char speedBuf[32]; + qtss_sprintf(speedBuf, "%10.5f", theSpeed); + StrPtrLen speedBufPtr(speedBuf); + (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssSpeedHeader, + speedBufPtr.Ptr, speedBufPtr.Len); + } + + // + // Record the requested start and stop time. + + (*theFile)->fStopTime = -1; + theLen = sizeof((*theFile)->fStopTime); + theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqStopTime, 0, &(*theFile)->fStopTime, &theLen); + Assert(theErr == QTSS_NoErr); + + (*theFile)->fStartTime = 0; + theLen = sizeof((*theFile)->fStopTime); + theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqStartTime, 0, &(*theFile)->fStartTime, &theLen); + Assert(theErr == QTSS_NoErr); + + //the client doesn't necessarily specify this information in a play, + //if it doesn't, fall back on some defaults. + if ((*theFile)->fStartTime == -1) + (*theFile)->fStartTime = 0; + + (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, qtssPlayRespWriteTrackInfo); + return QTSS_NoErr; +} + +QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams) +{ + FileSession** theFile = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*))) + { + Assert( 0 ); + return QTSS_RequestFailed; + } + + while (true) + { + if ((*theFile)->fPacketStruct.packetData == NULL) + { + void* theCookie = NULL; + Float64 theTransmitTime = (*theFile)->fFile.GetNextPacket((UInt8**)&(*theFile)->fPacketStruct.packetData, &(*theFile)->fNextPacketLen, &theCookie); + + // + // Check to see if we should stop playing based on a client specified stop time + if (((*theFile)->fStopTime != -1) && (theTransmitTime > (*theFile)->fStopTime)) + { + // We should indeed stop playing + (void)QTSS_Pause(inParams->inClientSession); + inParams->outNextPacketTime = qtssDontCallSendPacketsAgain; + return QTSS_NoErr; + } + + // + // Adjust transmit time based on speed + Float64 theOffsetFromStartTime = theTransmitTime - (*theFile)->fStartTime; + theTransmitTime = (*theFile)->fStartTime + (theOffsetFromStartTime / (*theFile)->fSpeed); + + (*theFile)->fStream = (QTSS_RTPStreamObject)theCookie; + (*theFile)->fPacketStruct.packetTransmitTime = (*theFile)->fAdjustedPlayTime + ((SInt64)(theTransmitTime * 1000)); +#if RTP_FILE_MODULE_DEBUGGING >= 8 + UInt16* theSeqNumPtr = (UInt16*)(*theFile)->fNextPacket; + UInt32* theTimestampP = (UInt32*)(*theFile)->fNextPacket; + UInt32* theTrackID = NULL; + (void)QTSS_GetValuePtr((*theFile)->fStream, qtssRTPStrTrackID, 0, (void**)&theTrackID, &theLen); + qtss_printf("Got packet. Seq num: %d. Timestamp: %d. TrackID: %d. Transmittime: %f\n",theSeqNumPtr[1], theTimestampP[1], *theTrackID, theTransmitTime); +#endif + } + + //We are done playing all streams! + if ((*theFile)->fPacketStruct.packetData == NULL) + { +#if RTP_FILE_MODULE_DEBUGGING >= 8 + qtss_printf("done w all packets\n"); +#endif + inParams->outNextPacketTime = qtssDontCallSendPacketsAgain; + return QTSS_NoErr; + } + //we have a packet that needs to be sent now + Assert((*theFile)->fStream != NULL); + + // Send the packet! + theErr = QTSS_Write((*theFile)->fStream, &(*theFile)->fPacketStruct, (*theFile)->fNextPacketLen, NULL, qtssWriteFlagsIsRTP); + + if ( theErr == QTSS_WouldBlock ) + { + if ((*theFile)->fPacketStruct.packetTransmitTime == -1) + inParams->outNextPacketTime = sFlowControlProbeInterval; // for buffering, try me again in # MSec + else + { + Assert((*theFile)->fPacketStruct.packetTransmitTime > inParams->inCurrentTime); + inParams->outNextPacketTime = (*theFile)->fPacketStruct.packetTransmitTime - inParams->inCurrentTime; + } + + return QTSS_NoErr; + } + + (*theFile)->fPacketStruct.packetData = NULL; + } + Assert(0); + return QTSS_NoErr; +} + +QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams) +{ + FileSession** theFile = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)) || (theFile == NULL)) + return QTSS_RequestFailed; + + delete *theFile; + return QTSS_NoErr; +} diff --git a/APIModules/QTSSRTPFileModule/QTSSRTPFileModule.h b/APIModules/QTSSRTPFileModule/QTSSRTPFileModule.h new file mode 100644 index 0000000..b3a08f4 --- /dev/null +++ b/APIModules/QTSSRTPFileModule/QTSSRTPFileModule.h @@ -0,0 +1,45 @@ +/* + * + * @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: QTSSFileModule.h + + Contains: Content source module that uses the QTFileLib to serve Hinted QuickTime + files to clients. + + + +*/ + +#ifndef _RTPRTPFILEMODULE_H_ +#define _RTPRTPFILEMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSRTPFileModule_Main(void* inPrivateArgs); +} + +#endif //_RTPRTPFILEMODULE_H_ diff --git a/APIModules/QTSSRTPFileModule/RTPFileSession.cpp b/APIModules/QTSSRTPFileModule/RTPFileSession.cpp new file mode 100644 index 0000000..121aa44 --- /dev/null +++ b/APIModules/QTSSRTPFileModule/RTPFileSession.cpp @@ -0,0 +1,571 @@ +/* + * + * @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: RTPFileSession.cpp + + Contains: + + + +*/ + +#define RTPFILESESSIONDEBUG 0 + +#include "RTPFileSession.h" +#include "OSMemory.h" + +static OSRefTable sOpenFileMap; + + +RTPFileSession::RTPFileSession() +: fFileSource(NULL), + fFileLength(0), + fCurrentPosition(0), + fFile(NULL), + fTrackInfo(NULL), + fNumTracksEnabled(0), + fReadBuffer(NULL), + fReadBufferOffset(0), + fDataBuffer(NULL), + fDataBufferSize(0), + fDataBufferLen(0), + fCurrentPacket(NULL), + fAddedTracksRTPBytes(0) +{} + +RTPFileSession::~RTPFileSession() +{ + // Check to see if we should destroy this file + OSMutexLocker locker (sOpenFileMap.GetMutex()); + +#if RTPFILESESSIONDEBUG + qtss_printf("Dropping refcount on file\n"); +#endif + if (fFile == NULL) + return; + + sOpenFileMap.Release(fFile->GetRef()); + if (fFile->GetRef()->GetRefCount() == 0) + { +#if RTPFILESESSIONDEBUG + qtss_printf("Refcount dropped to 0. Deleting file\n"); +#endif + sOpenFileMap.UnRegister(fFile->GetRef()); + delete fFile; + } + + // Delete our data buffer + delete [] fDataBuffer; + delete [] fTrackInfo; +} + +RTPFileSession::ErrorCode RTPFileSession::Initialize(StrPtrLen& inFilePath, Float32 inBufferSeconds) +{ + Assert(fFile == NULL); + + // Check to see if this file is already open + OSMutexLocker locker(sOpenFileMap.GetMutex()); + OSRef* theFileRef = sOpenFileMap.Resolve((StrPtrLen*)&inFilePath); + + if (theFileRef == NULL) + { + //qtss_printf("Didn't find file in map. Creating new one\n"); + fFile = NEW RTPFile(); + ErrorCode theErr = fFile->Initialize(inFilePath); + if (theErr != errNoError) + { + delete fFile; + fFile = NULL; + return theErr; + } + + OS_Error osErr = sOpenFileMap.Register(fFile->GetRef()); + Assert(osErr == OS_NoErr); + + //unless we do this, the refcount won't increment (and we'll delete the session prematurely + OSRef* debug = sOpenFileMap.Resolve((StrPtrLen*)&inFilePath); + Assert(debug == fFile->GetRef()); + } + else + { + //qtss_printf("Found file. Refcounting.\n"); + fFile = (RTPFile*)theFileRef->GetObject(); + } + + //Open the file no matter what + //fFileSource.Set(inFilePath.Ptr); + //Assert(fFileSource.GetLength() > 0); + QTSS_Error theErr = QTSS_OpenFileObject(inFilePath.Ptr, 0, &fFileSource); + Assert(theErr == QTSS_NoErr); + + // + // Get the file length + UInt32 theLen = sizeof(fFileLength); + theErr = QTSS_GetValue(fFileSource, qtssFlObjLength, 0, &fFileLength, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theLen == sizeof(fFileLength)); + + // Allocate our data buffer + fDataBufferSize = this->PowerOf2Floor((UInt32)(inBufferSeconds * fFile->GetBytesPerSecond())); + + // Check to see if the size is out of range. If so, adjust it + if (fDataBufferSize > kMaxDataBufferSize) + fDataBufferSize = kMaxDataBufferSize; + if (fDataBufferSize < kBlockSize) + fDataBufferSize = kBlockSize; + + fReadBuffer = fDataBuffer = NEW UInt8[fDataBufferSize]; + + // Allocate a buffer of TrackInfos + fTrackInfo = NEW RTPFileSessionTrackInfo[fFile->GetMaxTrackNumber() + 1]; + ::memset(fTrackInfo, 0, fFile->GetMaxTrackNumber() * sizeof(RTPFileSessionTrackInfo)); + return errNoError; +} + +RTPFileSession::ErrorCode RTPFileSession::AddTrack(UInt32 trackID) +{ + if (fFile->TrackExists(trackID)) + { + if (!fTrackInfo[trackID].fEnabled) + { + fTrackInfo[trackID].fEnabled = true; + fAddedTracksRTPBytes += fFile->GetTrackBytes(trackID); + fNumTracksEnabled++; + } + else + return errTrackAlreadyAdded; + } + else + return errTrackDoesntExist; + return errNoError; +} + + +RTPFileSession::ErrorCode RTPFileSession::Seek(Float64 inTime) +{ + if ((inTime < 0) || (inTime > fFile->GetMovieDuration())) + return errSeekToNonexistentTime; + + UInt64 theBlockLocation = fFile->GetBlockLocation(inTime); + Assert(theBlockLocation >= fFile->fHeader.fDataStartPos); + Assert(theBlockLocation < fFileLength); + + // Seek to the right file location. + //fFileSource.Seek(theBlockLocation); + QTSS_Error theErr = QTSS_Seek(fFileSource, theBlockLocation); + Assert(theErr == QTSS_NoErr); + fCurrentPosition = theBlockLocation; + + // Read the file data + this->ReadAndAdvise(); + + for (UInt32 x = 0; x <= fFile->GetMaxTrackNumber(); x++) + fTrackInfo[x].fMarked = false; + + // + // We need to find out what the first packet is for each enabled track. + // So scan ahead until we find the very first packet we need to send. + // At that point, "freeze" the current block in memory, and that position, + // because that's the position we'll be starting from when GetNextPacket gets + // called. In order to "freeze" we store lots of info about the current position + // on the stack with the variables defined below. + // + // Keep on going until we find the first packets for all the enabled tracks, + // and if that involves traversing multiple blocks, keep those blocks in a temporary + // buffer, allowing us to easily go back to the start point when done. + + UInt8* theStartPos = NULL; + UInt32 origDataBufferLen = 0; + UInt64 currentFileOffset = 0; + UInt32 tracksFound = 0; + + // Needed to call GetNextPacket + UInt8* thePacketP = NULL; + UInt32 thePacketLength = 0; + void* theCookie = NULL; + + while (tracksFound < fNumTracksEnabled) + { + Float64 theTransmitTime = this->GetNextPacket(&thePacketP, &thePacketLength, &theCookie); + if (thePacketP == NULL) + { + Assert(tracksFound > 0); + break; // We're at the end of the file! + } + // Ignore < 0 timed packets + RTPFilePacket* thePacket = (RTPFilePacket*)thePacketP; + Assert((thePacket - 1)->fTransmitTime == theTransmitTime); + if (theTransmitTime < 0) + theTransmitTime = 0; + + UInt32 theTrackID = (thePacket - 1)->fTrackID; + + if ((theTransmitTime >= inTime) && (!fTrackInfo[theTrackID].fMarked) && + (fTrackInfo[theTrackID].fEnabled)) + { + // This is the first packet for this track after our fCurrentPtr mark. + // Record the first seq # and timestamp of the packet + UInt16* theSeqNumPtr = (UInt16*)thePacketP; + UInt32* theTimestampPtr = (UInt32*)thePacketP; + + fTrackInfo[theTrackID].fSeekSeqNumber = theSeqNumPtr[1]; + fTrackInfo[theTrackID].fSeekTimestamp = theTimestampPtr[1]; + fTrackInfo[theTrackID].fMarked = true; + + if (tracksFound == 0) + { + // + // If this is the first packet that we're going to send (for all + // streams), then mark the position, and make sure that if we + // need to dump this buffer to find first packets for other tracks, + // we'll be able to come back to this very place so we can start streaming. + fReadBuffer = NULL; + theStartPos = (UInt8*)(thePacket-1); + origDataBufferLen = fDataBufferLen; + currentFileOffset = fCurrentPosition; + } + + tracksFound++; + } + } + + if (fReadBuffer != NULL) + { + // We had to skip ahead in the file. Restore everything to + // the way it was when we found the first packet, so GetNextPacket + // will work fine. + delete [] fReadBuffer; + fReadBuffer = fDataBuffer; + Assert(origDataBufferLen > 0); + fDataBufferLen = origDataBufferLen; + Assert(currentFileOffset > 0); + //fFileSource.Seek(currentFileOffset); + theErr = QTSS_Seek(fFileSource, theBlockLocation); + Assert(theErr == QTSS_NoErr); + } + + // Start at the first packet we need to send. + Assert(theStartPos != NULL); + fCurrentPacket = theStartPos; + + return errNoError; +} + +UInt16 RTPFileSession::GetNextTrackSequenceNumber(UInt32 inTrackID) +{ + Assert(inTrackID <= fFile->GetMaxTrackNumber()); + Assert(fTrackInfo[inTrackID].fMarked); + return fTrackInfo[inTrackID].fSeekSeqNumber; +} + +UInt32 RTPFileSession::GetSeekTimestamp(UInt32 inTrackID) +{ + Assert(inTrackID <= fFile->GetMaxTrackNumber()); + Assert(fTrackInfo[inTrackID].fMarked); + return fTrackInfo[inTrackID].fSeekTimestamp; +} + + +Float64 RTPFileSession::GetNextPacket(UInt8** outPacket, UInt32* outPacketLength, void** outCookie) +{ + Bool16 isValidPacket = false; + RTPFilePacket* thePacket = NULL; + + // Loop until we find a legal packet + while (!isValidPacket) + { + // If we are between blocks, read the next block + if (fCurrentPacket == NULL) + { + if (fCurrentPosition == fFileLength) + { + +#if RTPFILESESSIONDEBUG + qtss_printf("RTPFileSession::GetNextPacket fCurrentPosition == fFileLength quit\n"); +#endif + *outPacket = NULL; + return -1; + } + + this->ReadAndAdvise(); + } + Assert(fCurrentPacket != NULL); + thePacket = (RTPFilePacket*)fCurrentPacket; + + if (thePacket->fTrackID & kPaddingBit) + { + // We hit a padding packet, move the fCurrentPacket pointer to the next block + fReadBufferOffset += kBlockSize; + fReadBufferOffset &= kBlockMask; //Rounds down to the nearest block size + +#if RTPFILESESSIONDEBUG + qtss_printf("Found a pad packet. Moving on\n"); +#endif + // Check to make sure we aren't at the end of the buffer + if (fReadBufferOffset >= fDataBufferLen) + fCurrentPacket = NULL; + else + fCurrentPacket = fDataBuffer + fReadBufferOffset; + } + else if (!fTrackInfo[thePacket->fTrackID].fEnabled) + { + // This is a valid packet, but track not enabled, so skip it + Assert(thePacket->fTrackID <= fFile->GetMaxTrackNumber()); + this->SkipToNextPacket(thePacket); + } + else + // This is a valid packet, and the track is enabled + isValidPacket = true; + } + + // We must have a valid packet here + Assert(thePacket != NULL); + Assert(thePacket->fTrackID <= fFile->GetMaxTrackNumber()); + + // Set the return values + *outPacket = (UInt8*)(thePacket + 1); + *outPacketLength = thePacket->fPacketLength; + *outCookie = fTrackInfo[thePacket->fTrackID].fCookie; + + // Set the packet's SSRC + UInt32* ssrcPtr = (UInt32*)*outPacket; + ssrcPtr[2] = fTrackInfo[thePacket->fTrackID].fSSRC; + + Float64 transmitTime = thePacket->fTransmitTime; + + this->SkipToNextPacket(thePacket); + return transmitTime; +} + +void RTPFileSession::SkipToNextPacket(RTPFilePacket* inCurPacket) +{ + // Skip over this packet + Assert(inCurPacket->fPacketLength < 1500); + fReadBufferOffset += inCurPacket->fPacketLength; + fCurrentPacket += (inCurPacket->fPacketLength + sizeof(RTPFilePacket)); + + // Check to see if we need to read more data + if (fReadBufferOffset >= fDataBufferLen) + { +#if RTPFILESESSIONDEBUG + qtss_printf("In SkipToNextPacket. Out of data\n"); +#endif + fCurrentPacket = NULL; + } +} + +void RTPFileSession::ReadAndAdvise() +{ + if (fReadBuffer == NULL) + { + // In some situations, callers of this function may not want the fDataBuffer + // to be disturbed (see Seek()). If that's the case, the caller will set + // fReadBuffer to NULL, and we must allocate it here + fReadBuffer = NEW UInt8[fDataBufferSize]; + } + + // Read the next block. There should always be at least one packet + // here, as we have a valid block in the block table. +#if RTPFILESESSIONDEBUG + //qtss_printf("Moving onto next block. File loc: %qd\n",fFileSource.GetCurOffset()); +#endif + fDataBufferLen = 0; + //(void)fFileSource.Read(fDataBuffer, fDataBufferSize, &fDataBufferLen); + QTSS_Error theErr = QTSS_Read(fFileSource, fDataBuffer, fDataBufferSize, &fDataBufferLen); + Assert(theErr == QTSS_NoErr); + Assert(fDataBufferLen > sizeof(RTPFilePacket)); + fCurrentPosition += fDataBufferLen; + + // Now do an advise for the next block, if this block isn't the last. + if (fCurrentPosition < fFileLength) + { + Assert(fDataBufferLen == fDataBufferSize); + //fFileSource.Advise(fFileSource.GetCurOffset(), fDataBufferSize); + theErr = QTSS_Advise(fFileSource, fCurrentPosition, fDataBufferSize); + } + fReadBufferOffset = 0; + fCurrentPacket = fDataBuffer; +} + +UInt32 RTPFileSession::PowerOf2Floor(UInt32 inNumToFloor) +{ + UInt32 retVal = 0x10000000; + while (retVal > 0) + { + if (retVal & inNumToFloor) + return retVal; + else + retVal >>= 1; + } + return retVal; +} + + +RTPFile::RTPFile() +: fTrackInfo(NULL), + fBlockMap(NULL), + fBytesPerSecond(0), + fMaxTrackNumber(0) +{} + +RTPFile::~RTPFile() +{ + delete [] fFilePath.Ptr; + delete [] fSDPData.Ptr; + delete [] fTrackInfo; + delete [] fBlockMap; +} + +RTPFileSession::ErrorCode RTPFile::Initialize(const StrPtrLen& inFilePath) +{ + //OSFileSource theFile(inFilePath.Ptr); + QTSS_Object theFile = NULL; + QTSS_Error theErr = QTSS_OpenFileObject(inFilePath.Ptr, 0, &theFile); + if (theErr != QTSS_NoErr) + return RTPFileSession::errFileNotFound; + + // Copy the path. + fFilePath.Ptr = NEW char[inFilePath.Len + 1]; + ::memcpy(fFilePath.Ptr, inFilePath.Ptr, inFilePath.Len); + fFilePath.Len = inFilePath.Len; + + // Setup our osref + fRef.Set(fFilePath, this); + + // Read the header + //OS_Error theErr = theFile.Read(&fHeader, sizeof(fHeader)); + UInt32 theLengthRead = 0; + theErr = QTSS_Read(theFile, &fHeader, sizeof(fHeader), &theLengthRead); + Assert(theErr == QTSS_NoErr); + Assert(theLengthRead == sizeof(fHeader)); + + // Read the SDP data + fSDPData.Len = fHeader.fSDPLen; + fSDPData.Ptr = NEW char[fSDPData.Len + 1]; + //theErr = theFile.Read(fSDPData.Ptr, fSDPData.Len); + theErr = QTSS_Read(theFile, fSDPData.Ptr, fSDPData.Len, &theLengthRead); + Assert(theErr == QTSS_NoErr); + Assert(theLengthRead == fSDPData.Len); + + // Parse the SDP Information + fSourceInfo.Parse(fSDPData.Ptr, fSDPData.Len); + + // Read the track info + fTrackInfo = NEW RTPFileTrackInfo[fHeader.fNumTracks]; + //theErr = theFile.Read(fTrackInfo, sizeof(RTPFileTrackInfo) * fHeader.fNumTracks); + theErr = QTSS_Read(theFile, fTrackInfo, sizeof(RTPFileTrackInfo) * fHeader.fNumTracks, &theLengthRead); + Assert(theErr == QTSS_NoErr); + Assert(theLengthRead == sizeof(RTPFileTrackInfo) * fHeader.fNumTracks); + + // Create and read the block map + fBlockMap = NEW UInt8[fHeader.fBlockMapSize]; + //theErr = theFile.Read(fBlockMap, fHeader.fBlockMapSize); + theErr = QTSS_Read(theFile, fBlockMap, fHeader.fBlockMapSize, &theLengthRead); + Assert(theErr == QTSS_NoErr); + Assert(theLengthRead == fHeader.fBlockMapSize); + + // Calculate bit rate of all the tracks combined + Float64 totalBytes = 0; + for (UInt32 x = 0; x < fHeader.fNumTracks; x++) + totalBytes += (Float64)fTrackInfo[x].fBytesInTrack; + totalBytes /= fHeader.fMovieDuration; + fBytesPerSecond = (UInt32)totalBytes; + + //Get the max track number + fMaxTrackNumber = 0; + for (UInt32 y = 0; y < fHeader.fNumTracks; y++) + if (fTrackInfo[y].fID > fMaxTrackNumber) + fMaxTrackNumber = fTrackInfo[y].fID; + + (void)QTSS_CloseFileObject(theFile); + return RTPFileSession::errNoError; +} + +SInt64 RTPFile::GetBlockLocation(Float64 inTimeInSecs) +{ + if (inTimeInSecs == 0) + return fHeader.fDataStartPos; + + UInt32 theTime = 0; + UInt32 x = 0; + for ( ; x < fHeader.fBlockMapSize ; x++) + { + theTime += fBlockMap[x]; + if (theTime >= (UInt32)inTimeInSecs) + { + if ((theTime > (UInt32)inTimeInSecs) && (x > 0)) + // If we've moved too far, go back to the previous block + x--; + return fHeader.fDataStartPos + (kBlockSize * x); + } + } + return fHeader.fDataStartPos + (kBlockSize * x);// The requested time must be in the last block (or nonexistent). +} + + +Float64 RTPFile::GetTrackDuration(UInt32 inTrackID) +{ + for (UInt32 x = 0; x < fHeader.fNumTracks; x++) + { + if (fTrackInfo[x].fID == inTrackID) + return fTrackInfo[x].fDuration; + } + Assert(0); + return -1; +} + + +UInt32 RTPFile::GetTrackTimeScale(UInt32 inTrackID) +{ + for (UInt32 x = 0; x < fHeader.fNumTracks; x++) + { + if (fTrackInfo[x].fID == inTrackID) + return fTrackInfo[x].fTimescale; + } + Assert(0); + return 0; +} + +UInt64 RTPFile::GetTrackBytes(UInt32 inTrackID) +{ + for (UInt32 x = 0; x < fHeader.fNumTracks; x++) + { + if (fTrackInfo[x].fID == inTrackID) + return fTrackInfo[x].fBytesInTrack; + } + Assert(0); + return 0; +} + +Bool16 RTPFile::TrackExists(UInt32 inTrackID) +{ + for (UInt32 x = 0; x < fHeader.fNumTracks; x++) + { + if (fTrackInfo[x].fID == inTrackID) + return true; + } + return false; +} + diff --git a/APIModules/QTSSRTPFileModule/RTPFileSession.h b/APIModules/QTSSRTPFileModule/RTPFileSession.h new file mode 100644 index 0000000..8964844 --- /dev/null +++ b/APIModules/QTSSRTPFileModule/RTPFileSession.h @@ -0,0 +1,227 @@ +/* + * + * @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: RTPFileSession.h + + Contains: + + + +*/ + +#ifndef __RTPFILESESSIONH__ +#define __RTPFILESESSIONH__ + +#include "RTPFileDefs.h" +#include "OSHeaders.h" +#include "OSRef.h" +#include "QTSS.h" // This object uses QTSS API file I/O +#include "SDPSourceInfo.h" + +class RTPFile; + +class RTPFileSession +{ + public: + + // One per client + + RTPFileSession(); + ~RTPFileSession(); + + // + // Class error codes + enum ErrorCode + { + errNoError = 0, + errFileNotFound = 1, + errTrackAlreadyAdded = 2, + errTrackDoesntExist = 3, + errSeekToNonexistentTime = 4 + }; + + // + // INITIALIZE + ErrorCode Initialize(StrPtrLen& inFilePath, Float32 inBufferSeconds); + + // + // ACCESSORS + + // + // Global information + inline Float64 GetMovieDuration(); + inline StrPtrLen* GetSDPFile(); + inline SourceInfo* GetSourceInfo(); + inline StrPtrLen* GetMoviePath(); + UInt64 GetAddedTracksRTPBytes() { return fAddedTracksRTPBytes; } + + + // + // Track functions + inline Float64 GetTrackDuration(UInt32 inTrackID); + inline UInt32 GetTrackTimeScale(UInt32 inTrackID); + + UInt16 GetNextTrackSequenceNumber(UInt32 inTrackID); + UInt32 GetSeekTimestamp(UInt32 TrackID); + + // + // MODIFIERS + + // + // Track modifiers + ErrorCode AddTrack(UInt32 inTrackID); + void SetTrackSSRC(UInt32 inTrackID, UInt32 inSSRC) { fTrackInfo[inTrackID].fSSRC = inSSRC; } + void SetTrackCookie(UInt32 inTrackID, void *inCookie){ fTrackInfo[inTrackID].fCookie = inCookie; } + + // Seek to a time + ErrorCode Seek(Float64 inTime); + + // GetNextPacket. Returns the transmit time for this packet + Float64 GetNextPacket(UInt8** outPacket, UInt32* outPacketLength, void** outCookie); + + private: + + // Utility functions + void SkipToNextPacket(RTPFilePacket* inCurPacket); + void ReadAndAdvise(); + UInt32 PowerOf2Floor(UInt32 inNumToFloor); + + enum + { + kMaxDataBufferSize = 262144 + }; + + struct RTPFileSessionTrackInfo + { + Bool16 fEnabled; + Bool16 fMarked; // is the seek timestamp, seq num recorded? + UInt32 fSSRC; + UInt32 fSeekTimestamp; + UInt16 fSeekSeqNumber; + void* fCookie; + }; + + QTSS_Object fFileSource; + //OSFileSource fFileSource; + UInt64 fFileLength; + UInt64 fCurrentPosition; + + RTPFile* fFile; + RTPFileSessionTrackInfo* fTrackInfo; + UInt32 fNumTracksEnabled; + + UInt8* fReadBuffer; // Buffer that file data gets read into. + // Usually same as fDataBuffer + UInt32 fReadBufferOffset; + + UInt8* fDataBuffer; // Buffer for file data + UInt32 fDataBufferSize; + UInt32 fDataBufferLen; + UInt8* fCurrentPacket; + UInt64 fAddedTracksRTPBytes; + + friend class RTPFile; +}; + + + +class RTPFile +{ + public: + + // One per file + + RTPFile(); + ~RTPFile(); + + RTPFileSession::ErrorCode Initialize(const StrPtrLen& inFilePath); + + Float64 GetMovieDuration() { return fHeader.fMovieDuration; } + + Float64 GetTrackDuration(UInt32 inTrackID); + UInt32 GetTrackTimeScale(UInt32 inTrackID); + UInt64 GetTrackBytes(UInt32 inTrackID); + Bool16 TrackExists(UInt32 inTrackID); + UInt32 GetBytesPerSecond() { return fBytesPerSecond; } + UInt32 GetMaxTrackNumber() { return fMaxTrackNumber; } + + StrPtrLen* GetSDPFile() { return &fSDPData; } + SourceInfo* GetSourceInfo() { return &fSourceInfo; } + OSRef* GetRef() { return &fRef; } + + // Returns the location in the file corresponding to this time, + // rounded to the nearest start of block. + SInt64 GetBlockLocation(Float64 inTimeInSecs); + + private: + + RTPFileTrackInfo* fTrackInfo; + RTPFileHeader fHeader; + UInt8* fBlockMap; + StrPtrLen fSDPData; + SDPSourceInfo fSourceInfo; + UInt32 fBytesPerSecond; + UInt32 fMaxTrackNumber; + + OSRef fRef; + StrPtrLen fFilePath; + + friend class RTPFileSession; +}; + + +inline StrPtrLen* RTPFileSession::GetSDPFile() +{ + return fFile->GetSDPFile(); +} + +inline SourceInfo* RTPFileSession::GetSourceInfo() +{ + return &fFile->fSourceInfo; +} + +inline StrPtrLen* RTPFileSession::GetMoviePath() +{ + return &fFile->fFilePath; +} + +inline Float64 RTPFileSession::GetMovieDuration() +{ + return fFile->fHeader.fMovieDuration; +} + +inline Float64 RTPFileSession::GetTrackDuration(UInt32 inTrackID) +{ + return fFile->GetTrackDuration(inTrackID); +} + +inline UInt32 RTPFileSession::GetTrackTimeScale(UInt32 inTrackID) +{ + return fFile->GetTrackTimeScale(inTrackID); +} + + +#endif + diff --git a/APIModules/QTSSRawFileModule.bproj/QTSSRawFileModule.cpp b/APIModules/QTSSRawFileModule.bproj/QTSSRawFileModule.cpp new file mode 100644 index 0000000..f5a6c3e --- /dev/null +++ b/APIModules/QTSSRawFileModule.bproj/QTSSRawFileModule.cpp @@ -0,0 +1,380 @@ +/* + * + * @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: QTSSRawFileModule.cpp + + Contains: Implementation of Raw File module + + + +*/ + +#include "QTSSRawFileModule.h" +#include "OSHeaders.h" +#include "StrPtrLen.h" +#include "OSArrayObjectDeleter.h" +#include "QTSSModuleUtils.h" +#include "OSMemory.h" +#include "ev.h" +#include "QTSSMemoryDeleter.h" + +#define RAWFILE_FILE_ASYNC 1 +#define RAW_FILE_DEBUGGING 0 + + +// ATTRIBUTES IDs + +static QTSS_AttributeID sStateAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFileAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFileBufferAttr = qtssIllegalAttrID; +static QTSS_AttributeID sReadOffsetAttr = qtssIllegalAttrID; +static QTSS_AttributeID sWriteOffsetAttr = qtssIllegalAttrID; + +// STATIC DATA + +static StrPtrLen sRawSuffix(".raw"); + +static const UInt32 kReadingBufferState = 0; +static const UInt32 kWritingBufferState = 1; + +// FUNCTIONS + +static QTSS_Error QTSSRawFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Preprocess(QTSS_StandardRTSP_Params* inParams); + + + +QTSS_Error QTSSRawFileModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSRawFileModuleDispatch); +} + + +QTSS_Error QTSSRawFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_RTSPPreProcessor_Role: + return Preprocess(&inParams->rtspPreProcessorParams); + } + return QTSS_NoErr; +} + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + (void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role); + + static char* sStateName = "QTSSRawFileModuleState"; + static char* sFileName = "QTSSRawFileModuleFile"; + static char* sFileBufferName = "QTSSRawFileModuleFileBuffer"; + static char* sReadOffsetName = "QTSSRawFileModuleReadOffset"; + static char* sWriteOffsetName = "QTSSRawFileModuleWriteOffset"; + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sStateName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sStateName, &sStateAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileName, &sFileAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileBufferName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileBufferName, &sFileBufferAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sReadOffsetName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sReadOffsetName, &sReadOffsetAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sWriteOffsetName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sWriteOffsetName, &sWriteOffsetAttr); + + // Tell the server our name! + static char* sModuleName = "QTSSRawFileModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Preprocess(QTSS_StandardRTSP_Params* inParams) +{ + static UInt32 sFileBufSize = 32768; + static UInt32 sInitialState = kReadingBufferState; + static UInt32 sZero = 0; + + UInt32 theLen = 0; + UInt32* theStateP = NULL; + QTSS_Error theErr = QTSS_NoErr; + + QTSS_Object theFile = NULL; + + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sStateAttr, 0, (void**)&theStateP, &theLen); + if ((theStateP == NULL) || (theLen != sizeof(UInt32))) + { + // Initial state. We haven't started sending the file yet, so + // check to see if this is our request, and if it is, set everything up. + + // Only operate if this is a DESCRIBE + QTSS_RTSPMethod* theMethod = NULL; + UInt32 theLen = 0; + if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0, + (void**)&theMethod, &theLen) != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod))) + { + Assert(0); + return QTSS_RequestFailed; + } + + if (*theMethod != qtssDescribeMethod) + return QTSS_RequestFailed; + + // Check to see if this is a raw file request + char* theFilePath = NULL; + (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFilePath); + QTSSCharArrayDeleter theFilePathDeleter(theFilePath); + theLen = ::strlen(theFilePath); + + // Copy the full path, and append a ".raw" + OSCharArrayDeleter rawPath(NEW char[theLen + sRawSuffix.Len + 4]); + ::memcpy(rawPath.GetObject(), theFilePath, theLen); + ::strcpy(rawPath.GetObject() + theLen, sRawSuffix.Ptr); + +#if RAWFILE_FILE_ASYNC + theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile); +#else + theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile); +#endif + + // If the file doesn't exist, and if this is a path with a '.raw' at the end, + // check to see if the path without the extra .raw exists + if (theErr != QTSS_NoErr) + { + theFile = NULL; + rawPath.GetObject()[theLen] = '\0'; + + if (theLen > sRawSuffix.Len) + { + StrPtrLen comparer((theFilePath + theLen) - sRawSuffix.Len, sRawSuffix.Len); + if (comparer.Equal(sRawSuffix)) + { +#if RAWFILE_FILE_ASYNC + theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile); +#else + theErr = QTSS_OpenFileObject(rawPath.GetObject(), kOpenFileNoFlags, &theFile); +#endif + } + } + } + + // If the file doesn't exist, we should probably return a 404 not found. + if (theErr != QTSS_NoErr) + return QTSS_RequestFailed; + + // Before sending any response, set keep alive to off for this connection + // Regardless of what the client sends, the server always closes the connection after sending the file + static Bool16 sFalse = false; + (void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse)); + + // We have a real file. Setup all the dictionary values we need + (void)QTSS_SetValue(inParams->inRTSPSession, sFileAttr, 0, &theFile, sizeof(theFile)); + + // Create a buffer to store data. + char* theFileBuffer = NEW char[sFileBufSize]; + (void)QTSS_SetValue(inParams->inRTSPSession, sFileBufferAttr, 0, &theFileBuffer, sizeof(theFileBuffer)); + + // Store our initial state + (void)QTSS_SetValue(inParams->inRTSPSession, sStateAttr, 0, &sInitialState, sizeof(sInitialState)); + theStateP = &sInitialState; // so we can proceed normally + + (void)QTSS_SetValue(inParams->inRTSPSession, sReadOffsetAttr, 0, &sZero, sizeof(sZero)); + (void)QTSS_SetValue(inParams->inRTSPSession, sWriteOffsetAttr, 0, &sZero, sizeof(sZero)); + } + + // Get our attributes + char** theFileBufferP = NULL; + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileBufferAttr, 0, (void**)&theFileBufferP, &theLen); + Assert(theFileBufferP != NULL); + Assert(theLen == sizeof(char*)); + + QTSS_Object* theFileP = NULL; + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileAttr, 0, (void**)&theFileP, &theLen); + Assert(theFileP != NULL); + Assert(theLen == sizeof(QTSS_Object)); + + UInt32* theReadOffsetP = NULL; + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sReadOffsetAttr, 0, (void**)&theReadOffsetP, &theLen); + Assert(theReadOffsetP != NULL); + Assert(theLen == sizeof(UInt32)); + + UInt32* theWriteOffsetP = NULL; + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sWriteOffsetAttr, 0, (void**)&theWriteOffsetP, &theLen); + Assert(theWriteOffsetP != NULL); + Assert(theLen == sizeof(UInt32)); + + UInt32 theReadOffset = *theReadOffsetP; + UInt32 theWriteOffset = *theWriteOffsetP; + UInt32 theState = *theStateP; + + Bool16 isBlocked = false; + + // Get the length of the file onto the stack + theLen = sizeof(UInt64); + UInt64 theFileLength = 0; + theErr = QTSS_GetValue(*theFileP, qtssFlObjLength, 0, (void*)&theFileLength, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theLen == sizeof(UInt64)); + + // Get the offset in the file onto the stack + theLen = sizeof(UInt64); + UInt64 theOffset = 0; + theErr = QTSS_GetValue(*theFileP, qtssFlObjPosition, 0, (void*)&theOffset, &theLen); + Assert(theErr == QTSS_NoErr); + Assert(theLen == sizeof(UInt64)); + + while (!isBlocked) + { + // If we have less than the full buffer size left to go in the file, + // down adjust our buffer size to be the amount of data remaining in the file + UInt32 theBufferSize = sFileBufSize; + if ((theFileLength - theOffset) < sFileBufSize) + theBufferSize = (UInt32) (theFileLength - theOffset); + + switch (theState) + { + case kReadingBufferState: + { + // Read as much data as possible out of the file + UInt32 theRecvLen = 0; + (void)QTSS_Read(*theFileP, + (*theFileBufferP) + theReadOffset, + theBufferSize - theReadOffset, + &theRecvLen); + theReadOffset += theRecvLen; + theOffset += theRecvLen; +#if RAW_FILE_DEBUGGING + qtss_printf("Got %"_U32BITARG_" bytes back from file read. Now at: %"_64BITARG_"u\n", theRecvLen, theOffset); +#endif + if (theReadOffset < theBufferSize) + { +#if RAW_FILE_DEBUGGING + qtss_printf("Flow controlled on file. Waiting for read event\n"); +#endif + isBlocked = true; + break; + } + + theReadOffset = 0; + Assert(theWriteOffset == 0); + theState = kWritingBufferState; + } + case kWritingBufferState: + { + UInt32 theWrittenLen = 0; + + // for debugging purposes, construct an IOVec out of this data + iovec theVec[5]; + UInt32 units = (theBufferSize - theWriteOffset) /4; + UInt32 offset = theWriteOffset; + theVec[1].iov_base = (*theFileBufferP) + offset; + theVec[1].iov_len = units; + offset += units; + theVec[2].iov_base = (*theFileBufferP) + offset; + theVec[2].iov_len = units; + offset += units; + theVec[3].iov_base = (*theFileBufferP) + offset; + theVec[3].iov_len = units; + offset += units; + theVec[4].iov_base = (*theFileBufferP) + offset; + theVec[4].iov_len = theBufferSize - offset; + + (void)QTSS_WriteV( inParams->inRTSPSession, + theVec, 5, + theBufferSize - theWriteOffset, + &theWrittenLen); + theWriteOffset += theWrittenLen; +#if RAW_FILE_DEBUGGING + qtss_printf("Got %"_U32BITARG_" bytes back from socket write.\n", theWrittenLen); +#endif + if (theWriteOffset < theBufferSize) + { +#if RAW_FILE_DEBUGGING + qtss_printf("Flow controlled on socket. Waiting for write event.\n"); +#endif + isBlocked = true; + break; + } + + // Check to see if we're done. If we are, delete stuff and return + if (theOffset == theFileLength) + { +#if RAW_FILE_DEBUGGING + qtss_printf("File transfer complete\n"); +#endif + return QTSS_NoErr; + } + + theWriteOffset = 0; + Assert(theReadOffset == 0); + theState = kReadingBufferState; + } + } + } + + Assert(isBlocked); + + // We've reached a blocking condition for some reason. + // Save our state, request an event, and return. + (void)QTSS_SetValue(inParams->inRTSPSession, sReadOffsetAttr, 0, &theReadOffset, sizeof(theReadOffset)); + (void)QTSS_SetValue(inParams->inRTSPSession, sWriteOffsetAttr, 0, &theWriteOffset, sizeof(theWriteOffset)); + (void)QTSS_SetValue(inParams->inRTSPSession, sStateAttr, 0, &theState, sizeof(theState)); + + // If we're reading, wait for the file to become readable + if (theState == kReadingBufferState) + (void)QTSS_RequestEvent(*theFileP, QTSS_ReadableEvent); + // If we're writing, wait for the socket to become writable + else + (void)QTSS_RequestEvent(inParams->inRTSPSession, QTSS_WriteableEvent); + return QTSS_NoErr; +} + +QTSS_Error CloseRTSPSession(QTSS_RTSPSession_Params* inParams) +{ + // In this role, the allocated resources are deleted before closing the RTSP session + UInt32 theLen = 0; + + // Get our file buffer pointer and delete it + char** theFileBufferP = NULL; // File buffer pointer + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileBufferAttr, 0, (void**)&theFileBufferP, &theLen); + if (theFileBufferP != NULL) + delete [] *theFileBufferP; + + QTSS_Object* theFileP = NULL; + // Get our file pointer and delete it + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileAttr, 0, (void**)&theFileP, &theLen); + Assert(theFileP != NULL); + Assert(theLen == sizeof(QTSS_Object)); + (void)QTSS_CloseFileObject(*theFileP); + + return QTSS_NoErr; +} diff --git a/APIModules/QTSSRawFileModule.bproj/QTSSRawFileModule.h b/APIModules/QTSSRawFileModule.bproj/QTSSRawFileModule.h new file mode 100644 index 0000000..54f040c --- /dev/null +++ b/APIModules/QTSSRawFileModule.bproj/QTSSRawFileModule.h @@ -0,0 +1,47 @@ +/* + * + * @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: QTSSRawFileModule.h + + Contains: A module that returns the entire contents of a file to the client. + Only does this if the suffix of the file is .raw + + + +*/ + +#ifndef __QTSS_RAW_FILE_MODULE_H__ +#define __QTSS_RAW_FILE_MODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSRawFileModule_Main(void* inPrivateArgs); +} + +#endif // __QTSS_RAW_FILE_MODULE_H__ + + diff --git a/APIModules/QTSSRawFileModule.bproj/README-RawFileModule b/APIModules/QTSSRawFileModule.bproj/README-RawFileModule new file mode 100644 index 0000000..90f04a3 --- /dev/null +++ b/APIModules/QTSSRawFileModule.bproj/README-RawFileModule @@ -0,0 +1 @@ +Installing the QTSSRawFileModule To use QTSSRawFileModule, add it to your QTSSModules folder, and restart the QuickTime Streaming Server. What does QTSSRawFileModule do? Users of QTSSRawFileModule should be familiar with the RTSP protocol. This protocol is documented as RFC 2326. http://www.landfield.com/rfcs/rfc2236.html When an RTSP client issues a DESCRIBE, the QTSSRawFileModule will examine the request URL. If the suffix of the filename in the URL is ".raw", the module will look for a file in the location specified by the URL. If this file exists, the module will return the entire contents of the file to the client as the RTSP response. QTSSRawFileModule will not prepend RTSP headers to the response, so the "Raw File" itself must include headers. For example: Request URL: rtsp://streaming.site.com/subdir/rawfile.raw QTSSRawFileModule looks for: /subdir/rawfile.raw If there is a file at that location, QTSSRawFileModule will send its contents to the client as the RTSP response. QTSSRawFileModule also allows you to override default server behavior for URLs that don't have a ".raw" suffix. On every DESCRIBE sent to the server, QTSSRawFileModule appends a ".raw" to the end of the URL, no matter what is in the URL. If it finds a file at that location, it will return the contents of the file to the client as the RTSP response. For example: Request URL: rtsp://streaming.site.com/dir/example.mov QTSSRawFileModule looks for: /dir/example.mov.raw If there is a file at that location, QTSSRawFileModule will send its contents to the client as the RTSP response. How to use QTSSRawFileModule to send redirects: A redirect is a special type of RTSP response that tells the client to reissue the same request with a different URL. The new URL is specified in the "Location" header of the redirect response. This is extremely handy if content gets moved around on a site, and the administrator doesn't want the old links to break. The following is a sample redirect response: RTSP/1.0 302 Found CSeq: 1 Server: QTSS/2.0 Location: rtsp://streaming.site.com/new/newexample.mov To have QTSSRawFileModule issue a redirect, place the contents of the above redirect response into a text file, give it a name with a .raw suffix, and place it somewhere inside your media folder. For example, if it is called "example.mov.raw", and placed directly inside the media folder, when the client issues a DESCRIBE request for rtsp://streaming.site.com/example.mov The QTSSRawFileModule will redirect the client to the new URL, specified in the location header. The client will immediately send a DESCRIBE to the new URL. \ No newline at end of file diff --git a/APIModules/QTSSRawFileModule.bproj/sampleredirect.raw b/APIModules/QTSSRawFileModule.bproj/sampleredirect.raw new file mode 100644 index 0000000..01fbe0b --- /dev/null +++ b/APIModules/QTSSRawFileModule.bproj/sampleredirect.raw @@ -0,0 +1,5 @@ +RTSP/1.0 302 Found +CSeq: 1 +Server: QTSS/2.0 +Location: rtsp://www.myserver.com/mymovie + diff --git a/APIModules/QTSSRefMovieModule/QTSSRefMovieModule.cpp b/APIModules/QTSSRefMovieModule/QTSSRefMovieModule.cpp new file mode 100644 index 0000000..75c58b4 --- /dev/null +++ b/APIModules/QTSSRefMovieModule/QTSSRefMovieModule.cpp @@ -0,0 +1,420 @@ +/* + * + * @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: QTSSRefMovieModule.cpp + + Contains: Implements a module to create RTSP text ref movies + + +*/ + +#include "QTSSRefMovieModule.h" +#include "OS.h" +#include "OSMemory.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "OSMemory.h" +#include "OSHeaders.h" +#include "ev.h" +#include "QTFile.h" +#include "QTSSModuleUtils.h" + +#include +#include +#include +#include + +//------------------------------------------------------------------------ +// STATIC DATA +//------------------------------------------------------------------------ + +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; +static QTSS_ServerObject sServer = NULL; + +// HTTP reply header +static char* sResponseHeader = "HTTP/1.0 200 OK\r\nServer: QTSS/4.0\r\n" + "Connection: Close\r\nContent-Type: video/quicktime\r\n"; + +static Bool16 sRefMovieXferEnabled = true; +static Bool16 sDefaultRefMovieXferEnabled = true; +static UInt32 sServerIPAddr = 0x74000001; // 127.0.0.1 +static UInt16 sRTSPReplyPort = 0; +static UInt16 sDefaultRTSPReplyPort = 0; + +//------------------------------------------------------------------------ +// FUNCTION PROTOTYPES +//------------------------------------------------------------------------ + +QTSS_Error QTSSRefMovieModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error RereadPrefs(); +static QTSS_Error Filter(QTSS_Filter_Params* inParams); + +static void url_strcpy(char* dest, const char* src); +static QTSS_Error SendTheResponse(QTSS_RTSPSessionObject theSession, QTSS_StreamRef stream, StrPtrLen& movie); +static Bool16 FileExists(StrPtrLen& path, StrPtrLen& movie); +static Bool16 IsHTTPGet(StrPtrLen& theRequest); +static Bool16 IsTunneledRTSP(StrPtrLen& theRequest); +static Bool16 IsAdminURL(StrPtrLen& theUrl); +static Bool16 ParseURL(StrPtrLen& theRequest, char* outURL, UInt16 maxlen); +static Bool16 IsHomeDirURL(StrPtrLen& theUrl); + +//------------------------------------------------------------------------ +// MODULE FUNCTIONS IMPLEMENTATION +//------------------------------------------------------------------------ + +QTSS_Error QTSSRefMovieModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSRefMovieModuleDispatch); +} + +// Dispatch this module's role call back. +QTSS_Error QTSSRefMovieModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPFilter_Role: + return Filter(&inParams->rtspFilterParams); + } + return QTSS_NoErr; +} + +// Handle the QTSS_Register role call back. +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPFilter_Role); + + // Tell the server our name! + static char* sModuleName = "QTSSRefMovieModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +// Handle the QTSS_Initialize role call back. +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + QTSS_Error err = QTSS_NoErr; + UInt32 ulen = sizeof(sServerIPAddr); + + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + // Get prefs object + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + sServerPrefs = inParams->inPrefs; + sServer = inParams->inServer; + + // Get the Server's IP address for later use. + err = QTSS_GetValue(sServer, qtssSvrDefaultIPAddr, 0, &sServerIPAddr, &ulen); + + err = RereadPrefs(); + + return err; +} + +// Handle the QTSS_RereadPrefs_Role role call back. +QTSS_Error RereadPrefs() +{ + QTSSModuleUtils::GetAttribute(sPrefs, "refmovie_xfer_enabled", qtssAttrDataTypeBool16, + &sRefMovieXferEnabled, &sDefaultRefMovieXferEnabled, sizeof(sRefMovieXferEnabled)); + QTSSModuleUtils::GetAttribute(sPrefs, "refmovie_rtsp_port", qtssAttrDataTypeUInt16, + &sRTSPReplyPort, &sDefaultRTSPReplyPort, sizeof(sRTSPReplyPort)); + return QTSS_NoErr; +} + +// url_strcpy - works like strcpy except that it handles URL escape +// conversions as it copies the string. +void url_strcpy(char* dest, const char* src) +{ + int c1, c2; + while(*src) + { + if (*src == '%') + { + src++; + c1 = *src++; + if (c1 >= '0' && c1 <= '9') + c1 -= '0'; + else if (c1 >= 'A' && c1 <= 'F') + c1 -= 'A' + 10; + else if (c1 >= 'a' && c1 <= 'f') + c1 -= 'a' + 10; + else + c1 = 0; + c2 = *src; + if (c2 >= '0' && c2 <= '9') + c2 -= '0'; + else if (c2 >= 'A' && c2 <= 'F') + c2 -= 'A' + 10; + else if (c2 >= 'a' && c2 <= 'f') + c2 -= 'a' + 10; + else + c2 = 32; + *dest = (c1 * 16) + c2; + } + else + { + *dest = *src; + } + dest++; + src++; + } + *dest = '\0'; +} + +// This sends the HTTP response to the server that contains the RTSPtext Ref movie +QTSS_Error SendTheResponse(QTSS_RTSPSessionObject theSession, QTSS_StreamRef stream, StrPtrLen& movie) +{ + QTSS_Error err = QTSS_NoErr; + char theMovieFile[512]; + theMovieFile[sizeof(theMovieFile) -1] = 0; + + char tmp[600]; + tmp[sizeof(tmp) -1] = 0; + + char tmp2[80]; + tmp2[sizeof(tmp2) -1] = 0; + + UInt8 x1, x2, x3, x4; + + // send the HTTP reply header to the client + err= QTSS_Write(stream, sResponseHeader, ::strlen(sResponseHeader), NULL, qtssWriteFlagsBufferData); + if (err != QTSS_NoErr) + return QTSS_NoErr; + + UInt32 ip4address = 0; + UInt32 len = sizeof(ip4address); + err = QTSS_GetValue(theSession, qtssRTSPSesLocalAddr, 0, &ip4address, &len); + + // Format the server IP address for building the RTSP address in the reply. + x1 = (UInt8)((ip4address >> 24) & 0xff); + x2 = (UInt8)((ip4address >> 16) & 0xff); + x3 = (UInt8)((ip4address >> 8) & 0xff); + x4 = (UInt8)((ip4address) & 0xff); + + if (movie.Len > sizeof(theMovieFile) -1 ) + movie.Len = sizeof(theMovieFile) -1; + + ::memcpy(theMovieFile, movie.Ptr, movie.Len); + theMovieFile[movie.Len] = '\0'; + + UInt16 port = sRTSPReplyPort; + if (0 == port) + { + len = sizeof(port); + err = QTSS_GetValue(theSession, qtssRTSPSesLocalPort, 0, &port, &len); + } + + // construct the RTSP address reply string for the client. + qtss_snprintf(tmp,sizeof(tmp) -1, "rtsptext\r\nrtsp://%d.%d.%d.%d:%d%s\r\n", x1,x2,x3,x4, port, theMovieFile); + + // send the 'Content-Length:' part of the HTTP reply + qtss_snprintf(tmp2, sizeof(tmp2) -1, "Content-Length: %d\r\n\r\n", (int) ::strlen(tmp)); + err = QTSS_Write(stream, tmp2, ::strlen(tmp2), NULL, qtssWriteFlagsBufferData); + if (err != QTSS_NoErr) + return QTSS_NoErr; + + // send the formatted RTSP reference part of the reply + err = QTSS_Write(stream, tmp, ::strlen(tmp), NULL, qtssWriteFlagsBufferData); + if (err != QTSS_NoErr) + return QTSS_NoErr; + + // flush the pending write to the client. + err = QTSS_Flush(stream); + return err; +} + +// This determines if the specified movie file +// exists at the designated path. +Bool16 FileExists(StrPtrLen& path, StrPtrLen& movie) +{ + struct stat sb; + char fullpath[1024]; + + // if the movie path ends in a '/' then there is no movie file to be found. + // (This is probably a user error in typing the URL.) + if(movie.Ptr[movie.Len-1] == '/') + return false; + + // copy path to our local buffer + ::memcpy(fullpath, path.Ptr, path.Len); + + // remove any URL escape characters when we contruct the full path. + ::url_strcpy(fullpath+path.Len, (char*) movie.Ptr); + fullpath[path.Len+movie.Len] = '\0'; + // check for file existance with the POSIX stat() function. + if (::stat(fullpath, &sb) != 0) + return false; + else + return true; +} + +// This determines if an incoming request is an HTTP GET +// request. +Bool16 IsHTTPGet(StrPtrLen& theRequest) +{ + StrPtrLen token = theRequest; + token.Len = 3; + return token.EqualIgnoreCase(StrPtrLen("GET")); +} + +// This determines if an incoming request is actually +// an RTSP request tunneled in a HTTP request. +Bool16 IsTunneledRTSP(StrPtrLen& theRequest) +{ + if (::strstr((char*)theRequest.Ptr, "x-rtsp-tunneled") != 0) + return true; + return false; +} + +// This determines if a URL in an HTTP request is actually +// a server admin request. +Bool16 IsAdminURL(StrPtrLen& theUrl) +{ + StrPtrLen token = theUrl; + token.Len = 15; + return token.EqualIgnoreCase(StrPtrLen("/modules/admin/")); +} + + +Bool16 IsHomeDirURL(StrPtrLen& theUrl) +{ + StrPtrLen token = theUrl; + token.Len = 2; + return token.EqualIgnoreCase(StrPtrLen("/~")); +} + +// Parse out the URL from the HTTP GET line. +Bool16 ParseURL(StrPtrLen& theRequest, char* outURL, UInt16 maxlen) +{ + StringParser reqParse(&theRequest); + StrPtrLen strPtr; + + ::memset(outURL, 0, maxlen); + reqParse.ConsumeWord(&strPtr); + + if ( !strPtr.Equal(StrPtrLen("GET")) ) + { + return false; + } + reqParse.ConsumeWhitespace(); + reqParse.ConsumeUntilWhitespace(&strPtr); + if (strPtr.Len == 0) + return false; + else if ((UInt16)strPtr.Len > maxlen-1) + strPtr.Len = maxlen-1; + ::memcpy(outURL, strPtr.Ptr, strPtr.Len); + + return true; +} + +// Handle the QTSS_RTSPFilter_Role role call back. +QTSS_Error Filter(QTSS_Filter_Params* inParams) +{ + QTSS_Error err = QTSS_NoErr; + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + QTSS_RTSPSessionObject theSession = inParams->inRTSPSession; + char theURL[512]; + + // If this module is disabled do nothing but return. + if (!sRefMovieXferEnabled) + return QTSS_NoErr; + + // Get the full RTSP request from the server's attribute. + StrPtrLen theFullRequest; + err = QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len); + + if (err != QTSS_NoErr) + { + return QTSS_NoErr; + } + + // if this is not an HTTP GET then ignore it. + if (!IsHTTPGet(theFullRequest)) + return QTSS_NoErr; + + // If this is a tunneled RTSP request we ignore it. + if (IsTunneledRTSP(theFullRequest)) + { + return QTSS_NoErr; + } + + // if we can't parse out the URL then we just ignore this request. + if (!ParseURL(theFullRequest, theURL, 512)) + { + return QTSS_NoErr; + } + + // Make sure that this is not an admin request before + // we go any further. + StrPtrLen movie(theURL); + if (IsAdminURL(movie)) + { + // The file path in the URL is actually an admin request. + // Just ignore it and let the admin module handle it. + return QTSS_NoErr; + } + + Bool16 isHomeDir = IsHomeDirURL(movie); + + // Get the server's movie folder location. + char* movieFolderString = NULL; + err = QTSS_GetValueAsString (sServerPrefs, qtssPrefsMovieFolder, 0, &movieFolderString); + if (err != QTSS_NoErr) + return QTSS_NoErr; + + OSCharArrayDeleter movieFolder(movieFolderString); + StrPtrLen theMovieFolder(movieFolderString); + + if (!isHomeDir && !FileExists(theMovieFolder, movie)) + { + // we couldn't find a file at the specified location + // so we will ignore this HTTP request and let some other module + // deal with the issue. + return QTSS_NoErr; + } + else + { + // Eureka!!! We found a file at the specified location. + // We assume that it is a valid movie file and we send + // the client an RTSP text reference movie in the HTTP reply. + err = SendTheResponse(theSession,theRequest, movie); + } + + return QTSS_NoErr; +} diff --git a/APIModules/QTSSRefMovieModule/QTSSRefMovieModule.h b/APIModules/QTSSRefMovieModule/QTSSRefMovieModule.h new file mode 100644 index 0000000..eee1127 --- /dev/null +++ b/APIModules/QTSSRefMovieModule/QTSSRefMovieModule.h @@ -0,0 +1,44 @@ +/* + * + * @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: QTSSRefMovieModule.h + + Contains: A module that serves an RTSP text ref movie from an HTTP request. + + + +*/ + +#ifndef __QTSSREFMOVIEMODULE_H__ +#define __QTSSREFMOVIEMODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSRefMovieModule_Main(void* inPrivateArgs); +} + +#endif //__QTSSREFMOVIEMODULE_H__ diff --git a/APIModules/QTSSReflectorModule/QTSSReflectorModule.cpp b/APIModules/QTSSReflectorModule/QTSSReflectorModule.cpp new file mode 100644 index 0000000..780feba --- /dev/null +++ b/APIModules/QTSSReflectorModule/QTSSReflectorModule.cpp @@ -0,0 +1,2368 @@ +/* + * + * @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: QTSSReflectorModule.cpp + + Contains: Implementation of QTSSReflectorModule class. + + + +*/ + +#include "QTSSReflectorModule.h" +#include "QTSSModuleUtils.h" +#include "ReflectorSession.h" +#include "OSArrayObjectDeleter.h" +#include "QTSS_Private.h" +#include "QTSSMemoryDeleter.h" +#include "OSMemory.h" +#include "OSRef.h" +#include "IdleTask.h" +#include "Task.h" +#include "OS.h" +#include "Socket.h" +#include "SocketUtils.h" +#include "FilePrefsSource.h" +#include "ResizeableStringFormatter.h" +#include "StringParser.h" +#include "QTAccessFile.h" +#include "QTSSModuleUtils.h" +#include "QTSS3GPPModuleUtils.h" + +//ReflectorOutput objects +#include "RTPSessionOutput.h" + +//SourceInfo objects +#include "SDPSourceInfo.h" + +#include "SDPUtils.h" + +#ifndef __Win32__ + #include +#endif + +#if DEBUG +#define REFLECTOR_MODULE_DEBUGGING 0 +#else +#define REFLECTOR_MODULE_DEBUGGING 0 +#endif + +// ATTRIBUTES +static QTSS_AttributeID sOutputAttr = qtssIllegalAttrID; +static QTSS_AttributeID sSessionAttr = qtssIllegalAttrID; +static QTSS_AttributeID sStreamCookieAttr = qtssIllegalAttrID; +static QTSS_AttributeID sRequestBodyAttr = qtssIllegalAttrID; +static QTSS_AttributeID sBufferOffsetAttr = qtssIllegalAttrID; +static QTSS_AttributeID sExpectedDigitFilenameErr = qtssIllegalAttrID; +static QTSS_AttributeID sReflectorBadTrackIDErr = qtssIllegalAttrID; +static QTSS_AttributeID sDuplicateBroadcastStreamErr= qtssIllegalAttrID; +static QTSS_AttributeID sClientBroadcastSessionAttr = qtssIllegalAttrID; +static QTSS_AttributeID sRTSPBroadcastSessionAttr = qtssIllegalAttrID; +static QTSS_AttributeID sAnnounceRequiresSDPinNameErr = qtssIllegalAttrID; +static QTSS_AttributeID sAnnounceDisabledNameErr = qtssIllegalAttrID; +static QTSS_AttributeID sSDPcontainsInvalidMinimumPortErr = qtssIllegalAttrID; +static QTSS_AttributeID sSDPcontainsInvalidMaximumPortErr = qtssIllegalAttrID; +static QTSS_AttributeID sStaticPortsConflictErr = qtssIllegalAttrID; +static QTSS_AttributeID sInvalidPortRangeErr = qtssIllegalAttrID; + +static QTSS_AttributeID sKillClientsEnabledAttr = qtssIllegalAttrID; +static QTSS_AttributeID sRTPInfoWaitTimeAttr = qtssIllegalAttrID; + +// STATIC DATA + +// ref to the prefs dictionary object +static OSRefTable* sSessionMap = NULL; +static const StrPtrLen kCacheControlHeader("no-cache"); +static QTSS_PrefsObject sServerPrefs = NULL; +static QTSS_ServerObject sServer = NULL; +static QTSS_ModulePrefsObject sPrefs = NULL; + +// +// Prefs +static Bool16 sAllowNonSDPURLs = true; +static Bool16 sDefaultAllowNonSDPURLs = true; + +static Bool16 sRTPInfoDisabled = false; +static Bool16 sDefaultRTPInfoDisabled = false; + +static Bool16 sAnnounceEnabled = true; +static Bool16 sDefaultAnnounceEnabled = true; +static Bool16 sBroadcastPushEnabled = true; +static Bool16 sDefaultBroadcastPushEnabled = true; +static Bool16 sAllowDuplicateBroadcasts = false; +static Bool16 sDefaultAllowDuplicateBroadcasts = false; + +static UInt32 sMaxBroadcastAnnounceDuration = 0; +static UInt32 sDefaultMaxBroadcastAnnounceDuration = 0; +static UInt16 sMinimumStaticSDPPort = 0; +static UInt16 sDefaultMinimumStaticSDPPort = 20000; +static UInt16 sMaximumStaticSDPPort = 0; +static UInt16 sDefaultMaximumStaticSDPPort = 65535; + +static Bool16 sTearDownClientsOnDisconnect = false; +static Bool16 sDefaultTearDownClientsOnDisconnect = false; + +static Bool16 sOneSSRCPerStream = true; +static Bool16 sDefaultOneSSRCPerStream = true; + +static UInt32 sTimeoutSSRCSecs = 30; +static UInt32 sDefaultTimeoutSSRCSecs = 30; + +static UInt32 sBroadcasterSessionTimeoutSecs = 20; +static UInt32 sDefaultBroadcasterSessionTimeoutSecs = 20; +static UInt32 sBroadcasterSessionTimeoutMilliSecs = sBroadcasterSessionTimeoutSecs * 1000; + +static UInt16 sLastMax = 0; +static UInt16 sLastMin = 0; + +static Bool16 sEnforceStaticSDPPortRange = false; +static Bool16 sDefaultEnforceStaticSDPPortRange = false; + +static UInt32 sMaxAnnouncedSDPLengthInKbytes = 4; +//static UInt32 sDefaultMaxAnnouncedSDPLengthInKbytes = 4; + +static QTSS_AttributeID sIPAllowListID = qtssIllegalAttrID; +static char* sIPAllowList = NULL; +static char* sLocalLoopBackAddress = "127.0.0.*"; + +static Bool16 sAuthenticateLocalBroadcast = false; +static Bool16 sDefaultAuthenticateLocalBroadcast = false; + +static Bool16 sDisableOverbuffering = false; +static Bool16 sDefaultDisableOverbuffering = false; +static Bool16 sFalse = false; + +static Bool16 sReflectBroadcasts = true; +static Bool16 sDefaultReflectBroadcasts = true; + +static Bool16 sAnnouncedKill = true; +static Bool16 sDefaultAnnouncedKill = true; + + +static Bool16 sPlayResponseRangeHeader = true; +static Bool16 sDefaultPlayResponseRangeHeader = true; + +static Bool16 sPlayerCompatibility = true; +static Bool16 sDefaultPlayerCompatibility = true; + +static UInt32 sAdjustMediaBandwidthPercent = 100; +static UInt32 sAdjustMediaBandwidthPercentDefault = 100; + +static Bool16 sForceRTPInfoSeqAndTime = false; +static Bool16 sDefaultForceRTPInfoSeqAndTime = false; + +static char* sRedirectBroadcastsKeyword = NULL; +static char* sDefaultRedirectBroadcastsKeyword = ""; +static char* sBroadcastsRedirectDir = NULL; +static char* sDefaultBroadcastsRedirectDir = ""; // match none +static char* sDefaultBroadcastsDir = ""; // match all +static char* sDefaultsBroadcasterGroup = "broadcaster"; +static StrPtrLen sBroadcasterGroup; + +static QTSS_AttributeID sBroadcastDirListID = qtssIllegalAttrID; + +static SInt32 sWaitTimeLoopCount = 10; + +// Important strings +static StrPtrLen sSDPKillSuffix(".kill"); +static StrPtrLen sSDPSuffix(".sdp"); +static StrPtrLen sMOVSuffix(".mov"); +static StrPtrLen sSDPTooLongMessage("Announced SDP is too long"); +static StrPtrLen sSDPNotValidMessage("Announced SDP is not a valid SDP"); +static StrPtrLen sKILLNotValidMessage("Announced .kill is not a valid SDP"); +static StrPtrLen sSDPTimeNotValidMessage("SDP time is not valid or movie not available at this time."); +static StrPtrLen sBroadcastNotAllowed("Broadcast is not allowed."); +static StrPtrLen sBroadcastNotActive("Broadcast is not active."); +static StrPtrLen sTheNowRangeHeader("npt=now-"); + +const int kBuffLen = 512; + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSReflectorModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error Shutdown(); +static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error DoAnnounce(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams); +ReflectorSession* FindOrCreateSession(StrPtrLen* inPath, QTSS_StandardRTSP_Params* inParams, StrPtrLen* inData = NULL,Bool16 isPush=false, Bool16 *foundSessionPtr = NULL); +static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession); +static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams); +static void RemoveOutput(ReflectorOutput* inOutput, ReflectorSession* inSession, Bool16 killClients); +static ReflectorSession* DoSessionSetup(QTSS_StandardRTSP_Params* inParams, QTSS_AttributeID inPathType,Bool16 isPush=false,Bool16 *foundSessionPtr= NULL, char** resultFilePath = NULL); +static QTSS_Error RereadPrefs(); +static QTSS_Error ProcessRTPData(QTSS_IncomingData_Params* inParams); +static QTSS_Error ReflectorAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams); +static Bool16 InfoPortsOK(QTSS_StandardRTSP_Params* inParams, SDPSourceInfo* theInfo, StrPtrLen* inPath); +void KillCommandPathInList(); +Bool16 KillSession(StrPtrLen *sdpPath, Bool16 killClients); +QTSS_Error IntervalRole(); +static Bool16 AcceptSession(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error RedirectBroadcast(QTSS_StandardRTSP_Params* inParams); +static Bool16 AllowBroadcast(QTSS_RTSPRequestObject inRTSPRequest); +static Bool16 InBroadcastDirList(QTSS_RTSPRequestObject inRTSPRequest); +static Bool16 IsAbsolutePath(StrPtrLen *inPathPtr); + +inline void KeepSession(QTSS_RTSPRequestObject theRequest,Bool16 keep) +{ + (void)QTSS_SetValue(theRequest, qtssRTSPReqRespKeepAlive, 0, &keep, sizeof(keep)); +} + + + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSReflectorModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSReflectorModuleDispatch); +} + + +QTSS_Error QTSSReflectorModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPRoute_Role: + return RedirectBroadcast(&inParams->rtspRouteParams); + case QTSS_RTSPPreProcessor_Role: + return ProcessRTSPRequest(&inParams->rtspRequestParams); + case QTSS_RTSPIncomingData_Role: + return ProcessRTPData(&inParams->rtspIncomingDataParams); + case QTSS_ClientSessionClosing_Role: + return DestroySession(&inParams->clientSessionClosingParams); + case QTSS_Shutdown_Role: + return Shutdown(); + case QTSS_RTSPAuthorize_Role: + return ReflectorAuthorizeRTSPRequest(&inParams->rtspRequestParams); + case QTSS_Interval_Role: + return IntervalRole(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_Shutdown_Role); + (void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role); + (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); + (void)QTSS_AddRole(QTSS_RTSPIncomingData_Role); // call me with interleaved RTP streams on the RTSP session + (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPRoute_Role); + + + // Add text messages attributes + static char* sExpectedDigitFilenameName = "QTSSReflectorModuleExpectedDigitFilename"; + static char* sReflectorBadTrackIDErrName = "QTSSReflectorModuleBadTrackID"; + static char* sDuplicateBroadcastStreamName = "QTSSReflectorModuleDuplicateBroadcastStream"; + static char* sAnnounceRequiresSDPinName = "QTSSReflectorModuleAnnounceRequiresSDPSuffix"; + static char* sAnnounceDisabledName = "QTSSReflectorModuleAnnounceDisabled"; + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sDuplicateBroadcastStreamName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sDuplicateBroadcastStreamName, &sDuplicateBroadcastStreamErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sAnnounceRequiresSDPinName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sAnnounceRequiresSDPinName, &sAnnounceRequiresSDPinNameErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sAnnounceDisabledName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sAnnounceDisabledName, &sAnnounceDisabledNameErr); + + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sReflectorBadTrackIDErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sReflectorBadTrackIDErrName, &sReflectorBadTrackIDErr); + + static char* sSDPcontainsInvalidMinumumPortErrName = "QTSSReflectorModuleSDPPortMinimumPort"; + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSDPcontainsInvalidMinumumPortErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSDPcontainsInvalidMinumumPortErrName, &sSDPcontainsInvalidMinimumPortErr); + + static char* sSDPcontainsInvalidMaximumPortErrName = "QTSSReflectorModuleSDPPortMaximumPort"; + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSDPcontainsInvalidMaximumPortErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSDPcontainsInvalidMaximumPortErrName, &sSDPcontainsInvalidMaximumPortErr); + + static char* sStaticPortsConflictErrName = "QTSSReflectorModuleStaticPortsConflict"; + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sStaticPortsConflictErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sStaticPortsConflictErrName, &sStaticPortsConflictErr); + + static char* sInvalidPortRangeErrName = "QTSSReflectorModuleStaticPortPrefsBadRange"; + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sInvalidPortRangeErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sInvalidPortRangeErrName, &sInvalidPortRangeErr); + + + // Add an RTP session attribute for tracking ReflectorSession objects + static char* sOutputName = "QTSSReflectorModuleOutput"; + static char* sSessionName = "QTSSReflectorModuleSession"; + static char* sStreamCookieName = "QTSSReflectorModuleStreamCookie"; + static char* sRequestBufferName = "QTSSReflectorModuleRequestBuffer"; + static char* sRequestBufferLenName= "QTSSReflectorModuleRequestBufferLen"; + static char* sBroadcasterSessionName= "QTSSReflectorModuleBroadcasterSession"; + static char* sKillClientsEnabledName= "QTSSReflectorModuleTearDownClients"; + + static char* sRTPInfoWaitTime = "QTSSReflectorModuleRTPInfoWaitTime"; + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sRTPInfoWaitTime, NULL, qtssAttrDataTypeSInt32); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sRTPInfoWaitTime, &sRTPInfoWaitTimeAttr); + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sOutputName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sOutputName, &sOutputAttr); + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sSessionName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sSessionName, &sSessionAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sStreamCookieName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sStreamCookieName, &sStreamCookieAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPRequestObjectType, sRequestBufferName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssRTSPRequestObjectType, sRequestBufferName, &sRequestBodyAttr); + + (void)QTSS_AddStaticAttribute(qtssRTSPRequestObjectType, sRequestBufferLenName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPRequestObjectType, sRequestBufferLenName, &sBufferOffsetAttr); + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sBroadcasterSessionName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sBroadcasterSessionName, &sClientBroadcastSessionAttr); + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sKillClientsEnabledName, NULL, qtssAttrDataTypeBool16); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sKillClientsEnabledName, &sKillClientsEnabledAttr); + + // keep the same attribute name for the RTSPSessionObject as used int he ClientSessionObject + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sBroadcasterSessionName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sBroadcasterSessionName, &sRTSPBroadcastSessionAttr); + + // Reflector session needs to setup some parameters too. + ReflectorStream::Register(); + // RTPSessionOutput needs to do the same + RTPSessionOutput::Register(); + + // Tell the server our name! + static char* sModuleName = "QTSSReflectorModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + QTSS3GPPModuleUtils::Initialize(inParams); + QTAccessFile::Initialize(); + sSessionMap = NEW OSRefTable(); + sServerPrefs = inParams->inPrefs; + sServer = inParams->inServer; +#if QTSS_REFLECTOR_EXTERNAL_MODULE + // The reflector is dependent on a number of objects in the Common Utilities + // library that get setup by the server if the reflector is internal to the + // server. + // + // So, if the reflector is being built as a code fragment, it must initialize + // those pieces itself +#if !MACOSXEVENTQUEUE + ::select_startevents();//initialize the select() implementation of the event queue +#endif + OS::Initialize(); + Socket::Initialize(); + SocketUtils::Initialize(); + + const UInt32 kNumReflectorThreads = 8; + TaskThreadPool::AddThreads(kNumReflectorThreads); + IdleTask::Initialize(); + Socket::StartThread(); +#endif + + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + + // Call helper class initializers + ReflectorStream::Initialize(sPrefs); + ReflectorSession::Initialize(); + + // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN + static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod, qtssAnnounceMethod, qtssRecordMethod }; + QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 7); + + RereadPrefs(); + + return QTSS_NoErr; +} + +char *GetTrimmedKeyWord(char *prefKeyWord) +{ + StrPtrLen redirKeyWordStr(prefKeyWord); + StringParser theRequestPathParser(&redirKeyWordStr); + + // trim leading / from the keyword + while(theRequestPathParser.Expect(kPathDelimiterChar)) {}; + + StrPtrLen theKeyWordStr; + theRequestPathParser.ConsumeUntil(&theKeyWordStr, kPathDelimiterChar); // stop when we see a / and don't include + + char *keyword = NEW char[theKeyWordStr.Len +1]; + ::memcpy(keyword, theKeyWordStr.Ptr, theKeyWordStr.Len); + keyword[theKeyWordStr.Len] = 0; + + return keyword; +} + +void SetMoviesRelativeDir() +{ + char* movieFolderString = NULL; + (void) QTSS_GetValueAsString (sServerPrefs, qtssPrefsMovieFolder, 0, &movieFolderString); + OSCharArrayDeleter deleter(movieFolderString); + + ResizeableStringFormatter redirectPath(NULL,0); + redirectPath.Put(movieFolderString); + if (redirectPath.GetBytesWritten() > 0 && kPathDelimiterChar != redirectPath.GetBufPtr()[redirectPath.GetBytesWritten() -1]) + redirectPath.PutChar(kPathDelimiterChar); + redirectPath.Put(sBroadcastsRedirectDir); + + char *newMovieRelativeDir = NEW char[redirectPath.GetBytesWritten() +1]; + ::memcpy(newMovieRelativeDir, redirectPath.GetBufPtr(), redirectPath.GetBytesWritten()); + newMovieRelativeDir[redirectPath.GetBytesWritten()] = 0; + + delete [] sBroadcastsRedirectDir; + sBroadcastsRedirectDir = newMovieRelativeDir; + +} + +QTSS_Error RereadPrefs() +{ + // + // Use the standard GetPref routine to retrieve the correct values for our preferences + QTSSModuleUtils::GetAttribute(sPrefs, "disable_rtp_play_info", qtssAttrDataTypeBool16, + &sRTPInfoDisabled, &sDefaultRTPInfoDisabled, sizeof(sDefaultRTPInfoDisabled)); + + QTSSModuleUtils::GetAttribute(sPrefs, "allow_non_sdp_urls", qtssAttrDataTypeBool16, + &sAllowNonSDPURLs, &sDefaultAllowNonSDPURLs, sizeof(sDefaultAllowNonSDPURLs)); + + QTSSModuleUtils::GetAttribute(sPrefs, "enable_broadcast_announce", qtssAttrDataTypeBool16, + &sAnnounceEnabled, &sDefaultAnnounceEnabled, sizeof(sDefaultAnnounceEnabled)); + QTSSModuleUtils::GetAttribute(sPrefs, "enable_broadcast_push", qtssAttrDataTypeBool16, + &sBroadcastPushEnabled, &sDefaultBroadcastPushEnabled, sizeof(sDefaultBroadcastPushEnabled)); + QTSSModuleUtils::GetAttribute(sPrefs, "max_broadcast_announce_duration_secs", qtssAttrDataTypeUInt32, + &sMaxBroadcastAnnounceDuration, &sDefaultMaxBroadcastAnnounceDuration, sizeof(sDefaultMaxBroadcastAnnounceDuration)); + QTSSModuleUtils::GetAttribute(sPrefs, "allow_duplicate_broadcasts", qtssAttrDataTypeBool16, + &sAllowDuplicateBroadcasts, &sDefaultAllowDuplicateBroadcasts, sizeof(sDefaultAllowDuplicateBroadcasts)); + + QTSSModuleUtils::GetAttribute(sPrefs, "enforce_static_sdp_port_range", qtssAttrDataTypeBool16, + &sEnforceStaticSDPPortRange, &sDefaultEnforceStaticSDPPortRange, sizeof(sDefaultEnforceStaticSDPPortRange)); + QTSSModuleUtils::GetAttribute(sPrefs, "minimum_static_sdp_port", qtssAttrDataTypeUInt16, + &sMinimumStaticSDPPort, &sDefaultMinimumStaticSDPPort, sizeof(sDefaultMinimumStaticSDPPort)); + QTSSModuleUtils::GetAttribute(sPrefs, "maximum_static_sdp_port", qtssAttrDataTypeUInt16, + &sMaximumStaticSDPPort, &sDefaultMaximumStaticSDPPort, sizeof(sDefaultMaximumStaticSDPPort)); + + QTSSModuleUtils::GetAttribute(sPrefs, "kill_clients_when_broadcast_stops", qtssAttrDataTypeBool16, + &sTearDownClientsOnDisconnect, &sDefaultTearDownClientsOnDisconnect, sizeof(sDefaultTearDownClientsOnDisconnect)); + QTSSModuleUtils::GetAttribute(sPrefs, "use_one_SSRC_per_stream", qtssAttrDataTypeBool16, + &sOneSSRCPerStream, &sDefaultOneSSRCPerStream, sizeof(sDefaultOneSSRCPerStream)); + QTSSModuleUtils::GetAttribute(sPrefs, "timeout_stream_SSRC_secs", qtssAttrDataTypeUInt32, + &sTimeoutSSRCSecs, &sDefaultTimeoutSSRCSecs, sizeof(sDefaultTimeoutSSRCSecs)); + + QTSSModuleUtils::GetAttribute(sPrefs, "timeout_broadcaster_session_secs", qtssAttrDataTypeUInt32, + &sBroadcasterSessionTimeoutSecs, &sDefaultBroadcasterSessionTimeoutSecs, sizeof(sDefaultTimeoutSSRCSecs)); + + QTSSModuleUtils::GetAttribute(sPrefs, "authenticate_local_broadcast", qtssAttrDataTypeBool16, + &sAuthenticateLocalBroadcast, &sDefaultAuthenticateLocalBroadcast, sizeof(sDefaultAuthenticateLocalBroadcast)); + + QTSSModuleUtils::GetAttribute(sPrefs, "disable_overbuffering", qtssAttrDataTypeBool16, + &sDisableOverbuffering, &sDefaultDisableOverbuffering, sizeof(sDefaultDisableOverbuffering)); + + QTSSModuleUtils::GetAttribute(sPrefs, "allow_broadcasts", qtssAttrDataTypeBool16, + &sReflectBroadcasts, &sDefaultReflectBroadcasts, sizeof(sDefaultReflectBroadcasts)); + + QTSSModuleUtils::GetAttribute(sPrefs, "allow_announced_kill", qtssAttrDataTypeBool16, + &sAnnouncedKill, &sDefaultAnnouncedKill, sizeof(sDefaultAnnouncedKill)); + + QTSSModuleUtils::GetAttribute(sPrefs, "enable_play_response_range_header", qtssAttrDataTypeBool16, + &sPlayResponseRangeHeader, &sDefaultPlayResponseRangeHeader, sizeof(sDefaultPlayResponseRangeHeader)); + + QTSSModuleUtils::GetAttribute(sPrefs, "enable_player_compatibility", qtssAttrDataTypeBool16, + &sPlayerCompatibility, &sDefaultPlayerCompatibility, sizeof(sDefaultPlayerCompatibility)); + + QTSSModuleUtils::GetAttribute(sPrefs, "compatibility_adjust_sdp_media_bandwidth_percent", qtssAttrDataTypeUInt32, + &sAdjustMediaBandwidthPercent, &sAdjustMediaBandwidthPercentDefault, sizeof(sAdjustMediaBandwidthPercentDefault)); + + if (sAdjustMediaBandwidthPercent > 100) + sAdjustMediaBandwidthPercent = 100; + + if (sAdjustMediaBandwidthPercent < 1) + sAdjustMediaBandwidthPercent = 1; + + QTSSModuleUtils::GetAttribute(sPrefs, "force_rtp_info_sequence_and_time", qtssAttrDataTypeBool16, + &sForceRTPInfoSeqAndTime, &sDefaultForceRTPInfoSeqAndTime, sizeof(sDefaultForceRTPInfoSeqAndTime)); + + sBroadcasterGroup.Delete(); + sBroadcasterGroup.Set(QTSSModuleUtils::GetStringAttribute(sPrefs, "BroadcasterGroup", sDefaultsBroadcasterGroup)); + + delete [] sRedirectBroadcastsKeyword; + char* tempKeyWord = QTSSModuleUtils::GetStringAttribute(sPrefs, "redirect_broadcast_keyword", sDefaultRedirectBroadcastsKeyword); + + sRedirectBroadcastsKeyword = GetTrimmedKeyWord(tempKeyWord); + delete [] tempKeyWord; + + delete [] sBroadcastsRedirectDir; + sBroadcastsRedirectDir = QTSSModuleUtils::GetStringAttribute(sPrefs, "redirect_broadcasts_dir", sDefaultBroadcastsRedirectDir); + if (sBroadcastsRedirectDir && sBroadcastsRedirectDir[0] != kPathDelimiterChar) + SetMoviesRelativeDir(); + + + delete [] QTSSModuleUtils::GetStringAttribute(sPrefs, "broadcast_dir_list", sDefaultBroadcastsDir); // initialize if there isn't one + sBroadcastDirListID = QTSSModuleUtils::GetAttrID(sPrefs, "broadcast_dir_list"); + + delete [] sIPAllowList; + sIPAllowList = QTSSModuleUtils::GetStringAttribute(sPrefs, "ip_allow_list", sLocalLoopBackAddress); + sIPAllowListID = QTSSModuleUtils::GetAttrID(sPrefs, "ip_allow_list"); + + + sBroadcasterSessionTimeoutMilliSecs = sBroadcasterSessionTimeoutSecs * 1000; + + + + if (sEnforceStaticSDPPortRange) + { Bool16 reportErrors = false; + if (sLastMax != sMaximumStaticSDPPort) + { sLastMax = sMaximumStaticSDPPort; + reportErrors = true; + } + + if (sLastMin != sMinimumStaticSDPPort) + { sLastMin = sMinimumStaticSDPPort; + reportErrors = true; + } + + if (reportErrors) + { + UInt16 minServerPort = 6970; + UInt16 maxServerPort = 9999; + char min[32]; + char max[32]; + + if ( ( (sMinimumStaticSDPPort <= minServerPort) && (sMaximumStaticSDPPort >= minServerPort) ) + || ( (sMinimumStaticSDPPort >= minServerPort) && (sMinimumStaticSDPPort <= maxServerPort) ) + ) + { + qtss_sprintf(min,"%u",minServerPort); + qtss_sprintf(max,"%u",maxServerPort); + QTSSModuleUtils::LogError( qtssWarningVerbosity, sStaticPortsConflictErr, 0, min, max); + } + + if (sMinimumStaticSDPPort > sMaximumStaticSDPPort) + { + qtss_sprintf(min,"%u",sMinimumStaticSDPPort); + qtss_sprintf(max,"%u",sMaximumStaticSDPPort); + QTSSModuleUtils::LogError( qtssWarningVerbosity, sInvalidPortRangeErr, 0, min, max); + } + } + } + + KillCommandPathInList(); + + QTSS3GPPModuleUtils::ReadPrefs(); + + + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ +#if QTSS_REFLECTOR_EXTERNAL_MODULE + TaskThreadPool::RemoveThreads(); +#endif + return QTSS_NoErr; +} + +QTSS_Error IntervalRole() // not used +{ + (void) QTSS_SetIntervalRoleTimer(0); // turn off + + return QTSS_NoErr; +} + + +QTSS_Error ProcessRTPData(QTSS_IncomingData_Params* inParams) +{ + if (!sBroadcastPushEnabled) + return QTSS_NoErr; + + //qtss_printf("QTSSReflectorModule:ProcessRTPData inRTSPSession=%"_U32BITARG_" inClientSession=%"_U32BITARG_"\n",inParams->inRTSPSession, inParams->inClientSession); + ReflectorSession* theSession = NULL; + UInt32 theLen = sizeof(theSession); + QTSS_Error theErr = QTSS_GetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &theSession, &theLen); + //qtss_printf("QTSSReflectorModule.cpp:ProcessRTPData sClientBroadcastSessionAttr=%"_U32BITARG_" theSession=%"_U32BITARG_" err=%"_S32BITARG_" \n",sClientBroadcastSessionAttr, theSession,theErr); + if (theSession == NULL || theErr != QTSS_NoErr) + return QTSS_NoErr; + + // it is a broadcaster session + //qtss_printf("QTSSReflectorModule.cpp:is broadcaster session\n"); + + SourceInfo* theSoureInfo = theSession->GetSourceInfo(); + Assert(theSoureInfo != NULL); + if (theSoureInfo == NULL) + return QTSS_NoErr; + + + UInt32 numStreams = theSession->GetNumStreams(); + //qtss_printf("QTSSReflectorModule.cpp:ProcessRTPData numStreams=%"_U32BITARG_"\n",numStreams); + +{ +/* + Stream data such as RTP packets is encapsulated by an ASCII dollar + sign (24 hexadecimal), followed by a one-byte channel identifier, + followed by the length of the encapsulated binary data as a binary, + two-byte integer in network byte order. The stream data follows + immediately afterwards, without a CRLF, but including the upper-layer + protocol headers. Each $ block contains exactly one upper-layer + protocol data unit, e.g., one RTP packet. +*/ + char* packetData= inParams->inPacketData; + + UInt8 packetChannel; + packetChannel = (UInt8) packetData[1]; + + UInt16 packetDataLen; + memcpy(&packetDataLen,&packetData[2],2); + packetDataLen = ntohs(packetDataLen); + + char* rtpPacket = &packetData[4]; + + //UInt32 packetLen = inParams->inPacketLen; + //qtss_printf("QTSSReflectorModule.cpp:ProcessRTPData channel=%u theSoureInfo=%"_U32BITARG_" packetLen=%"_U32BITARG_" packetDatalen=%u\n",(UInt16) packetChannel,theSoureInfo,inParams->inPacketLen,packetDataLen); + + if (1) + { + UInt32 inIndex = packetChannel / 2; // one stream per every 2 channels rtcp channel handled below + ReflectorStream* theStream = NULL; + if (inIndex < numStreams) + { theStream = theSession->GetStreamByIndex(inIndex); + + SourceInfo::StreamInfo* theStreamInfo =theStream->GetStreamInfo(); + UInt16 serverReceivePort =theStreamInfo->fPort; + + Bool16 isRTCP =false; + if (theStream != NULL) + { if (packetChannel & 1) + { serverReceivePort ++; + isRTCP = true; + } + theStream->PushPacket(rtpPacket,packetDataLen, isRTCP); + //qtss_printf("QTSSReflectorModule.cpp:ProcessRTPData Send RTSP packet channel=%u to UDP localServerAddr=%"_U32BITARG_" serverReceivePort=%"_U32BITARG_" packetDataLen=%u \n", (UInt16) packetChannel, localServerAddr, serverReceivePort,packetDataLen); + } + } + } + +} + return theErr; +} + +QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams) +{ + OSMutexLocker locker (sSessionMap->GetMutex()); //operating on sOutputAttr + + QTSS_RTSPMethod* theMethod = NULL; + //qtss_printf("QTSSReflectorModule:ProcessRTSPRequest inClientSession=%"_U32BITARG_"\n", (UInt32) inParams->inClientSession); + UInt32 theLen = 0; + if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0, + (void**)&theMethod, &theLen) != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod))) + { + Assert(0); + return QTSS_RequestFailed; + } + + if (*theMethod == qtssAnnounceMethod) + return DoAnnounce(inParams); + if (*theMethod == qtssDescribeMethod) + return DoDescribe(inParams); + if (*theMethod == qtssSetupMethod) + return DoSetup(inParams); + + RTPSessionOutput** theOutput = NULL; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*))) // a broadcaster push session + { if (*theMethod == qtssPlayMethod || *theMethod == qtssRecordMethod) + return DoPlay(inParams, NULL); + else + return QTSS_RequestFailed; + } + + switch (*theMethod) + { + case qtssPlayMethod: + return DoPlay(inParams, (*theOutput)->GetReflectorSession()); + case qtssTeardownMethod: + // Tell the server that this session should be killed, and send a TEARDOWN response + (void)QTSS_Teardown(inParams->inClientSession); + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0); + break; + case qtssPauseMethod: + (void)QTSS_Pause(inParams->inClientSession); + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0); + break; + default: + break; + } + return QTSS_NoErr; +} + +ReflectorSession* DoSessionSetup(QTSS_StandardRTSP_Params* inParams, QTSS_AttributeID inPathType,Bool16 isPush, Bool16 *foundSessionPtr, char** resultFilePath) +{ + char* theFullPathStr = NULL; + QTSS_Error theErr = QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr); + Assert(theErr == QTSS_NoErr); + QTSSCharArrayDeleter theFullPathStrDeleter(theFullPathStr); + + if (theErr != QTSS_NoErr) + return NULL; + + StrPtrLen theFullPath(theFullPathStr); + + if (theFullPath.Len > sMOVSuffix.Len ) + { StrPtrLen endOfPath2(&theFullPath.Ptr[theFullPath.Len - sMOVSuffix.Len], sMOVSuffix.Len); + if (endOfPath2.Equal(sMOVSuffix)) // it is a .mov so it is not meant for us + { + return NULL; + } + } + + + if (sAllowNonSDPURLs && !isPush) + { + // Check and see if the full path to this file matches an existing ReflectorSession + StrPtrLen thePathPtr; + OSCharArrayDeleter sdpPath(QTSSModuleUtils::GetFullPath( inParams->inRTSPRequest, + inPathType, + &thePathPtr.Len, &sSDPSuffix)); + + thePathPtr.Ptr = sdpPath.GetObject(); + + // If the actual file path has a .sdp in it, first look for the URL without the extra .sdp + if (thePathPtr.Len > (sSDPSuffix.Len * 2)) + { + // Check and see if there is a .sdp in the file path. + // If there is, truncate off our extra ".sdp", cuz it isn't needed + StrPtrLen endOfPath(&sdpPath.GetObject()[thePathPtr.Len - (sSDPSuffix.Len * 2)], sSDPSuffix.Len); + if (endOfPath.Equal(sSDPSuffix)) + { + sdpPath.GetObject()[thePathPtr.Len - sSDPSuffix.Len] = '\0'; + thePathPtr.Len -= sSDPSuffix.Len; + } + } + if (resultFilePath != NULL) + *resultFilePath = thePathPtr.GetAsCString(); + return FindOrCreateSession(&thePathPtr, inParams); + } + else + { + if (!sDefaultBroadcastPushEnabled) + return NULL; + // + // We aren't supposed to auto-append a .sdp, so just get the URL path out of the server + //StrPtrLen theFullPath; + //QTSS_Error theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, (void**)&theFullPath.Ptr, &theFullPath.Len); + //Assert(theErr == QTSS_NoErr); + + if (theFullPath.Len > sSDPSuffix.Len) + { + // + // Check to make sure this path has a .sdp at the end. If it does, + // attempt to get a reflector session for this URL. + StrPtrLen endOfPath2(&theFullPath.Ptr[theFullPath.Len - sSDPSuffix.Len], sSDPSuffix.Len); + if (endOfPath2.Equal(sSDPSuffix)) + { if (resultFilePath != NULL) + *resultFilePath = theFullPath.GetAsCString(); + return FindOrCreateSession(&theFullPath, inParams,NULL, isPush,foundSessionPtr); + } + } + return NULL; + } + return NULL; +} + +void DoAnnounceAddRequiredSDPLines(QTSS_StandardRTSP_Params* inParams, ResizeableStringFormatter *editedSDP, char* theSDPPtr) +{ + SDPContainer checkedSDPContainer; + checkedSDPContainer.SetSDPBuffer( theSDPPtr ); + if (!checkedSDPContainer.HasReqLines()) + { + if (!checkedSDPContainer.HasLineType('v')) + { // add v line + editedSDP->Put("v=0\r\n"); + } + + if (!checkedSDPContainer.HasLineType('s')) + { // add s line + char* theSDPName = NULL; + + (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFilePath, 0, &theSDPName); + QTSSCharArrayDeleter thePathStrDeleter(theSDPName); + if (theSDPName == NULL) + editedSDP->Put("s=unknown\r\n"); + else + { + editedSDP->Put("s="); + editedSDP->Put(theSDPName); + editedSDP->PutEOL(); + } + } + + if (!checkedSDPContainer.HasLineType('t')) + { // add t line + editedSDP->Put("t=0 0\r\n"); + } + + if (!checkedSDPContainer.HasLineType('o')) + { // add o line + editedSDP->Put("o="); + char tempBuff[256] = ""; tempBuff[255] = 0; + char *nameStr = tempBuff; + UInt32 buffLen = sizeof(tempBuff) - 1; + (void)QTSS_GetValue(inParams->inClientSession, qtssCliSesFirstUserAgent, 0, nameStr, &buffLen); + for (UInt32 c = 0; c < buffLen; c++) + { + if (StringParser::sEOLWhitespaceMask[ (UInt8) nameStr[c]]) + { nameStr[c] = 0; + break; + } + } + + buffLen = ::strlen(nameStr); + if (buffLen == 0) + editedSDP->Put("announced_broadcast"); + else + editedSDP->Put(nameStr, buffLen); + + editedSDP->Put(" "); + + buffLen = sizeof(tempBuff) -1; + (void)QTSS_GetValue(inParams->inClientSession, qtssCliSesRTSPSessionID, 0, &tempBuff, &buffLen); + editedSDP->Put(tempBuff, buffLen ); + + editedSDP->Put(" "); + qtss_snprintf(tempBuff, sizeof(tempBuff) -1, "%"_64BITARG_"d", (SInt64) OS::UnixTime_Secs() + 2208988800LU); + editedSDP->Put(tempBuff); + + editedSDP->Put(" IN IP4 "); + (void)QTSS_GetValue(inParams->inClientSession, qtssCliRTSPSessRemoteAddrStr, 0, tempBuff, &buffLen); + editedSDP->Put(tempBuff, buffLen); + + editedSDP->PutEOL(); + } + } + + editedSDP->Put(theSDPPtr); + + +} + + +QTSS_Error DoAnnounce(QTSS_StandardRTSP_Params* inParams) +{ + if (!sAnnounceEnabled) + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssPreconditionFailed, sAnnounceDisabledNameErr); + + // + // If this is SDP data, the reflector has the ability to write the data + // to the file system location specified by the URL. + + // + // This is a completely stateless action. No ReflectorSession gets created (obviously). + + // + // Eventually, we should really require access control before we do this. + //qtss_printf("QTSSReflectorModule:DoAnnounce\n"); + // + // Get the full path to this file + char* theFullPathStr = NULL; + (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr); + QTSSCharArrayDeleter theFullPathStrDeleter(theFullPathStr); + StrPtrLen theFullPath(theFullPathStr); + + // Check for a .kill at the end + Bool16 pathOK = false; + Bool16 killBroadcast = false; + if (sAnnouncedKill && theFullPath.Len > sSDPKillSuffix.Len) + { + StrPtrLen endOfPath(theFullPath.Ptr + (theFullPath.Len - sSDPKillSuffix.Len), sSDPKillSuffix.Len); + if (endOfPath.Equal(sSDPKillSuffix)) + { + pathOK = true; + killBroadcast = true; + } + } + + + // Check for a .sdp at the end + if (!pathOK) + { + if (theFullPath.Len <= sSDPSuffix.Len) + { + StrPtrLen endOfPath(theFullPath.Ptr + (theFullPath.Len - sSDPSuffix.Len), sSDPSuffix.Len); + if (!endOfPath.Equal(sSDPSuffix)) + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssPreconditionFailed, sAnnounceRequiresSDPinNameErr); + + } + } + + + // + // Ok, this is an sdp file. Retreive the entire contents of the SDP. + // This has to be done asynchronously (in case the SDP stuff is fragmented across + // multiple packets. So, we have to have a simple state machine. + + // + // We need to know the content length to manage memory + UInt32 theLen = 0; + UInt32* theContentLenP = NULL; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqContentLen, 0, (void**)&theContentLenP, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(UInt32))) + { + // + // RETURN ERROR RESPONSE: ANNOUNCE WITHOUT CONTENT LENGTH + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,0); + } + + // Check if the content-length is more than the imposed maximum + // if it is then return error response + if ( (sMaxAnnouncedSDPLengthInKbytes != 0) && (*theContentLenP > (sMaxAnnouncedSDPLengthInKbytes * 1024)) ) + return QTSSModuleUtils::SendErrorResponseWithMessage( inParams->inRTSPRequest, qtssPreconditionFailed, &sSDPTooLongMessage ); + + // + // Check for the existence of 2 attributes in the request: a pointer to our buffer for + // the request body, and the current offset in that buffer. If these attributes exist, + // then we've already been here for this request. If they don't exist, add them. + UInt32 theBufferOffset = 0; + char* theRequestBody = NULL; + + theLen = sizeof(theRequestBody); + theErr = QTSS_GetValue(inParams->inRTSPRequest, sRequestBodyAttr, 0, &theRequestBody, &theLen); + + //qtss_printf("QTSSReflectorModule:DoAnnounce theRequestBody =%s\n",theRequestBody); + if (theErr != QTSS_NoErr) + { + // + // First time we've been here for this request. Create a buffer for the content body and + // shove it in the request. + theRequestBody = NEW char[*theContentLenP + 1]; + memset(theRequestBody,0,*theContentLenP + 1); + theLen = sizeof(theRequestBody); + theErr = QTSS_SetValue(inParams->inRTSPRequest, sRequestBodyAttr, 0, &theRequestBody, theLen);// SetValue creates an internal copy. + Assert(theErr == QTSS_NoErr); + + // + // Also store the offset in the buffer + theLen = sizeof(theBufferOffset); + theErr = QTSS_SetValue(inParams->inRTSPRequest, sBufferOffsetAttr, 0, &theBufferOffset, theLen); + Assert(theErr == QTSS_NoErr); + } + + theLen = sizeof(theBufferOffset); + theErr = QTSS_GetValue(inParams->inRTSPRequest, sBufferOffsetAttr, 0, &theBufferOffset, &theLen); + + // + // We have our buffer and offset. Read the data. + theErr = QTSS_Read(inParams->inRTSPRequest, theRequestBody + theBufferOffset, *theContentLenP - theBufferOffset, &theLen); + Assert(theErr != QTSS_BadArgument); + + if (theErr == QTSS_RequestFailed) + { + OSCharArrayDeleter charArrayPathDeleter(theRequestBody); + // + // NEED TO RETURN RTSP ERROR RESPONSE + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,0); + } + + if ((theErr == QTSS_WouldBlock) || (theLen < (*theContentLenP - theBufferOffset))) + { + // + // Update our offset in the buffer + theBufferOffset += theLen; + (void)QTSS_SetValue(inParams->inRTSPRequest, sBufferOffsetAttr, 0, &theBufferOffset, sizeof(theBufferOffset)); + //qtss_printf("QTSSReflectorModule:DoAnnounce Request some more data \n"); + // + // The entire content body hasn't arrived yet. Request a read event and wait for it. + // Our DoAnnounce function will get called again when there is more data. + theErr = QTSS_RequestEvent(inParams->inRTSPRequest, QTSS_ReadableEvent); + Assert(theErr == QTSS_NoErr); + return QTSS_NoErr; + } + + Assert(theErr == QTSS_NoErr); + + +// +// If we've gotten here, we have the entire content body in our buffer. +// + + if (killBroadcast) + { + theFullPath.Len -= sSDPKillSuffix.Len; + if (KillSession(&theFullPath, killBroadcast)) + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerInternal,0); + else + return QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssClientNotFound, &sKILLNotValidMessage); + } + +// ------------ Clean up missing required SDP lines + + ResizeableStringFormatter editedSDP(NULL,0); + DoAnnounceAddRequiredSDPLines(inParams, &editedSDP, theRequestBody); + StrPtrLen editedSDPSPL(editedSDP.GetBufPtr(),editedSDP.GetBytesWritten()); + +// ------------ Check the headers + + SDPContainer checkedSDPContainer; + checkedSDPContainer.SetSDPBuffer( &editedSDPSPL ); + if (!checkedSDPContainer.IsSDPBufferValid()) + { + return QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssUnsupportedMediaType, &sSDPNotValidMessage); + } + + SDPSourceInfo theSDPSourceInfo(editedSDPSPL.Ptr, editedSDPSPL.Len ); + OSCharArrayDeleter charArrayPathDeleter(theRequestBody); + + if (!InfoPortsOK(inParams,&theSDPSourceInfo,&theFullPath)) // All validity checks like this check should be done before touching the file. + { return QTSS_NoErr; // InfoPortsOK is sending back the error. + } + +// ------------ reorder the sdp headers to make them proper. + + SDPLineSorter sortedSDP(&checkedSDPContainer ); + +// ------------ Write the SDP + + char* sessionHeaders = sortedSDP.GetSessionHeaders()->GetAsCString(); + OSCharArrayDeleter sessionHeadersDeleter(sessionHeaders); + + char* mediaHeaders = sortedSDP.GetMediaHeaders()->GetAsCString(); + OSCharArrayDeleter mediaHeadersDeleter(mediaHeaders); + + // sortedSDP.GetSessionHeaders()->PrintStrEOL(); + // sortedSDP.GetMediaHeaders()->PrintStrEOL(); + + // write the file !! need error reporting + FILE* theSDPFile= ::fopen(theFullPath.Ptr, "wb");//open + if (theSDPFile != NULL) + { + qtss_fprintf(theSDPFile, "%s", sessionHeaders); + qtss_fprintf(theSDPFile, "%s", mediaHeaders); + ::fflush(theSDPFile); + ::fclose(theSDPFile); + } + else + { return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientForbidden,0); + } + + + //qtss_printf("QTSSReflectorModule:DoAnnounce SendResponse OK=200\n"); + + return QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0); +} + +void DoDescribeAddRequiredSDPLines(QTSS_StandardRTSP_Params* inParams, ReflectorSession* theSession, QTSS_TimeVal modDate, ResizeableStringFormatter *editedSDP, StrPtrLen* theSDPPtr) +{ + SDPContainer checkedSDPContainer; + checkedSDPContainer.SetSDPBuffer( theSDPPtr ); + if (!checkedSDPContainer.HasReqLines()) + { + if (!checkedSDPContainer.HasLineType('v')) + { // add v line + editedSDP->Put("v=0\r\n"); + } + + if (!checkedSDPContainer.HasLineType('s')) + { // add s line + char* theSDPName = NULL; + (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFilePath, 0, &theSDPName); + QTSSCharArrayDeleter thePathStrDeleter(theSDPName); + editedSDP->Put("s="); + editedSDP->Put(theSDPName); + editedSDP->PutEOL(); + } + + if (!checkedSDPContainer.HasLineType('t')) + { // add t line + editedSDP->Put("t=0 0\r\n"); + } + + if (!checkedSDPContainer.HasLineType('o')) + { // add o line + editedSDP->Put("o=broadcast_sdp "); + char tempBuff[256]= ""; + tempBuff[255] = 0; + qtss_snprintf(tempBuff,sizeof(tempBuff) - 1, "%"_U32BITARG_"", *(UInt32 *) &theSession); + editedSDP->Put(tempBuff); + + editedSDP->Put(" "); + // modified date is in milliseconds. Convert to NTP seconds as recommended by rfc 2327 + qtss_snprintf(tempBuff, sizeof(tempBuff) - 1, "%"_64BITARG_"d", (SInt64) (modDate/1000) + 2208988800LU); + editedSDP->Put(tempBuff); + + editedSDP->Put(" IN IP4 "); + UInt32 buffLen = sizeof(tempBuff) -1; + (void)QTSS_GetValue(inParams->inClientSession, qtssCliSesHostName, 0, &tempBuff, &buffLen); + editedSDP->Put(tempBuff, buffLen); + + editedSDP->PutEOL(); + } + } + + editedSDP->Put(*theSDPPtr); + +} + +QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams) +{ + char *theFilepath = NULL; + ReflectorSession* theSession = DoSessionSetup(inParams, qtssRTSPReqFilePath, false, NULL, &theFilepath ); + OSCharArrayDeleter tempFilePath(theFilepath); + + if (theSession == NULL) + return QTSS_RequestFailed; + + RTPSessionOutput** theOutput = NULL; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen); + + // If there already was an RTPSessionOutput attached to this Client Session, + // destroy it. + if (theErr == QTSS_NoErr && theOutput != NULL) + { RemoveOutput(*theOutput, (*theOutput)->GetReflectorSession(), false); + RTPSessionOutput* theOutput = NULL; + (void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theOutput, sizeof(theOutput)); + + } + // send the DESCRIBE response + + //above function has signalled that this request belongs to us, so let's respond + iovec theDescribeVec[3] = { {0 }}; + + Assert(theSession->GetLocalSDP()->Ptr != NULL); + + + StrPtrLen theFileData; + QTSS_TimeVal outModDate = 0; + QTSS_TimeVal inModDate = -1; + (void)QTSSModuleUtils::ReadEntireFile(theFilepath, &theFileData, inModDate, &outModDate); + OSCharArrayDeleter fileDataDeleter(theFileData.Ptr); + +// -------------- process SDP to remove connection info and add track IDs, port info, and default c= line + + StrPtrLen theSDPData; + SDPSourceInfo tempSDPSourceInfo(theFileData.Ptr, theFileData.Len); // will make a copy and delete in destructor + theSDPData.Ptr = tempSDPSourceInfo.GetLocalSDP(&theSDPData.Len); // returns a new buffer with processed sdp + OSCharArrayDeleter sdpDeleter(theSDPData.Ptr); // delete the temp sdp source info buffer returned by GetLocalSDP + + if (theSDPData.Len <= 0) // can't find it on disk or it failed to parse just use the one in the session. + { + theSDPData.Ptr = theSession->GetLocalSDP()->Ptr; // this sdp isn't ours it must not be deleted + theSDPData.Len = theSession->GetLocalSDP()->Len; + } + + +// ------------ Clean up missing required SDP lines + + ResizeableStringFormatter editedSDP(NULL,0); + DoDescribeAddRequiredSDPLines(inParams, theSession, outModDate, &editedSDP, &theSDPData); + StrPtrLen editedSDPSPL(editedSDP.GetBufPtr(),editedSDP.GetBytesWritten()); + +// ------------ Check the headers + + SDPContainer checkedSDPContainer; + checkedSDPContainer.SetSDPBuffer( &editedSDPSPL ); + if (!checkedSDPContainer.IsSDPBufferValid()) + { + return QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssUnsupportedMediaType, &sSDPNotValidMessage); + } + + +// ------------ Put SDP header lines in correct order + Float32 adjustMediaBandwidthPercent = 1.0; + Bool16 adjustMediaBandwidth = false; + + if (sPlayerCompatibility ) + adjustMediaBandwidth = QTSSModuleUtils::HavePlayerProfile(sServerPrefs,inParams,QTSSModuleUtils::kAdjustBandwidth); + + if (adjustMediaBandwidth) + adjustMediaBandwidthPercent = (Float32) sAdjustMediaBandwidthPercent / 100.0; + + ResizeableStringFormatter buffer; + SDPContainer* insertMediaLines = QTSS3GPPModuleUtils::Get3GPPSDPFeatureListCopy(buffer); + SDPLineSorter sortedSDP(&checkedSDPContainer,adjustMediaBandwidthPercent,insertMediaLines); + delete insertMediaLines; + +// ------------ Write the SDP + + UInt32 sessLen = sortedSDP.GetSessionHeaders()->Len; + UInt32 mediaLen = sortedSDP.GetMediaHeaders()->Len; + theDescribeVec[1].iov_base = sortedSDP.GetSessionHeaders()->Ptr; + theDescribeVec[1].iov_len = sortedSDP.GetSessionHeaders()->Len; + + theDescribeVec[2].iov_base = sortedSDP.GetMediaHeaders()->Ptr; + theDescribeVec[2].iov_len = sortedSDP.GetMediaHeaders()->Len; + + (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + QTSSModuleUtils::SendDescribeResponse(inParams->inRTSPRequest, inParams->inClientSession, + &theDescribeVec[0], 3, sessLen + mediaLen ); + return QTSS_NoErr; +} + +Bool16 InfoPortsOK(QTSS_StandardRTSP_Params* inParams, SDPSourceInfo* theInfo, StrPtrLen* inPath) +{ // Check the ports based on the Pref whether to enforce a static SDP port range. + + Bool16 isOK = true; + + if (sEnforceStaticSDPPortRange) + { UInt16 theInfoPort = 0; + for (UInt32 x = 0; x < theInfo->GetNumStreams(); x++) + { theInfoPort = theInfo->GetStreamInfo(x)->fPort; + QTSS_AttributeID theErrorMessageID = qtssIllegalAttrID; + if (theInfoPort != 0) + { if (theInfoPort < sMinimumStaticSDPPort) + theErrorMessageID = sSDPcontainsInvalidMinimumPortErr; + else if (theInfoPort > sMaximumStaticSDPPort) + theErrorMessageID = sSDPcontainsInvalidMaximumPortErr; + } + + if (theErrorMessageID != qtssIllegalAttrID) + { + char thePort[32]; + qtss_sprintf(thePort,"%u",theInfoPort); + + char *thePath = inPath->GetAsCString(); + OSCharArrayDeleter charArrayPathDeleter(thePath); + + char *thePathPort = NEW char[inPath->Len + 32]; + OSCharArrayDeleter charArrayPathPortDeleter(thePathPort); + + qtss_sprintf(thePathPort,"%s:%s",thePath,thePort); + (void) QTSSModuleUtils::LogError(qtssWarningVerbosity, theErrorMessageID, 0, thePathPort); + + StrPtrLen thePortStr(thePort); + (void) QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssUnsupportedMediaType, theErrorMessageID,&thePortStr); + + return false; + } + } + } + + return isOK; +} + +ReflectorSession* FindOrCreateSession(StrPtrLen* inPath, QTSS_StandardRTSP_Params* inParams, StrPtrLen* inData, Bool16 isPush, Bool16 *foundSessionPtr) +{ + // This function assumes that inPath is NULL terminated + // Ok, look for a reflector session matching this full path as the ID + OSMutexLocker locker(sSessionMap->GetMutex()); + OSRef* theSessionRef = sSessionMap->Resolve(inPath); + ReflectorSession* theSession = NULL; + + if (theSessionRef == NULL) + { + //If this URL doesn't already have a reflector session, we must make a new + //one. The first step is to create an SDPSourceInfo object. + + StrPtrLen theFileData; + StrPtrLen theFileDeleteData; + + // + // If no file data is provided by the caller, read the file data out of the file. + // If file data is provided, use that as our SDP data + if (inData == NULL) + { (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileDeleteData); + theFileData = theFileDeleteData; + } + else + theFileData = *inData; + OSCharArrayDeleter fileDataDeleter(theFileDeleteData.Ptr); + + if (theFileData.Len <= 0) + return NULL; + + SDPSourceInfo* theInfo = NEW SDPSourceInfo(theFileData.Ptr, theFileData.Len); // will make a copy + + if (!theInfo->IsReflectable()) + { delete theInfo; + return NULL; + } + + + if (!InfoPortsOK(inParams, theInfo, inPath)) + { delete theInfo; + return NULL; + } + + // Check if broadcast is allowed before doing anything else + // At this point we know it is a definitely a reflector session + // It is either incoming automatic broadcast setup or a client setup to view broadcast + // In either case, verify whether the broadcast is allowed, and send forbidden response + // back + if (!AllowBroadcast(inParams->inRTSPRequest)) + { + (void) QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssClientForbidden, &sBroadcastNotAllowed); + return NULL; + } + + // + // Setup a ReflectorSession and bind the sockets. If we are negotiating, + // make sure to let the session know that this is a Push Session so + // ports may be modified. + UInt32 theSetupFlag = ReflectorSession::kMarkSetup; + if (isPush) + theSetupFlag |= ReflectorSession::kIsPushSession; + + theSession = NEW ReflectorSession(inPath); + if (theSession == NULL) + { return NULL; + } + + theSession->SetHasBufferedStreams(true); // buffer the incoming streams for clients + + // SetupReflectorSession stores theInfo in theSession so DONT delete the Info if we fail here, leave it alone. + // deleting the session will delete the info. + QTSS_Error theErr = theSession->SetupReflectorSession(theInfo, inParams, theSetupFlag,sOneSSRCPerStream, sTimeoutSSRCSecs); + if (theErr != QTSS_NoErr) + { delete theSession; + return NULL; + } + + //qtss_printf("Created reflector session = %"_U32BITARG_" theInfo=%"_U32BITARG_" \n", (UInt32) theSession,(UInt32)theInfo); + //put the session's ID into the session map. + theErr = sSessionMap->Register(theSession->GetRef()); + Assert(theErr == QTSS_NoErr); + + //unless we do this, the refcount won't increment (and we'll delete the session prematurely + if (!isPush) + { OSRef* debug = sSessionMap->Resolve(inPath); + Assert(debug == theSession->GetRef()); + } + } + else + { + // Check if broadcast is allowed before doing anything else + // At this point we know it is a definitely a reflector session + // It is either incoming automatic broadcast setup or a client setup to view broadcast + // In either case, verify whether the broadcast is allowed, and send forbidden response + // back + if (!AllowBroadcast(inParams->inRTSPRequest)) + { + (void) QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssClientForbidden, &sBroadcastNotAllowed); + return NULL; + } + + if (foundSessionPtr) + *foundSessionPtr = true; + + StrPtrLen theFileData; + SDPSourceInfo* theInfo = NULL; + + if (inData == NULL) + (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileData); + OSCharArrayDeleter charArrayDeleter(theFileData.Ptr); + + if (theFileData.Len <= 0) + return NULL; + + theInfo = NEW SDPSourceInfo(theFileData.Ptr, theFileData.Len); + if (theInfo == NULL) + return NULL; + + if (!InfoPortsOK(inParams, theInfo, inPath)) + { delete theInfo; + return NULL; + } + + delete theInfo; + + theSession = (ReflectorSession*)theSessionRef->GetObject(); + if (isPush && theSession) + { + UInt32 theSetupFlag = ReflectorSession::kMarkSetup | ReflectorSession::kIsPushSession; + QTSS_Error theErr = theSession->SetupReflectorSession(NULL, inParams, theSetupFlag); + if (theErr != QTSS_NoErr) + { return NULL; + } + } + } + + Assert(theSession != NULL); + + return theSession; + if (theSessionRef == NULL) + { + //If this URL doesn't already have a reflector session, we must make a new + //one. The first step is to create an SDPSourceInfo object. + + StrPtrLen theFileData; + StrPtrLen theFileDeleteData; + + // + // If no file data is provided by the caller, read the file data out of the file. + // If file data is provided, use that as our SDP data + if (inData == NULL) + { (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileDeleteData); + theFileData = theFileDeleteData; + } + else + theFileData = *inData; + OSCharArrayDeleter fileDataDeleter(theFileDeleteData.Ptr); + + if (theFileData.Len <= 0) + return NULL; + + SDPSourceInfo* theInfo = NEW SDPSourceInfo(theFileData.Ptr, theFileData.Len); // will make a copy + + if (!theInfo->IsReflectable()) + { delete theInfo; + return NULL; + } + if ( !theInfo->IsActiveNow() && !isPush) + { delete theInfo; + return NULL; + } + + if (!InfoPortsOK(inParams, theInfo, inPath)) + { delete theInfo; + return NULL; + } + // + // Setup a ReflectorSession and bind the sockets. If we are negotiating, + // make sure to let the session know that this is a Push Session so + // ports may be modified. + UInt32 theSetupFlag = ReflectorSession::kMarkSetup; + if (isPush) + theSetupFlag |= ReflectorSession::kIsPushSession; + + theSession = NEW ReflectorSession(inPath); + + // SetupReflectorSession stores theInfo in theSession so DONT delete the Info if we fail here, leave it alone. + // deleting the session will delete the info. + QTSS_Error theErr = theSession->SetupReflectorSession(theInfo, inParams, theSetupFlag,sOneSSRCPerStream, sTimeoutSSRCSecs); + if (theErr != QTSS_NoErr || theSession == NULL) + { delete theSession; + return NULL; + } + + //printf("Created reflector session = %"_U32BITARG_" theInfo=%"_U32BITARG_" \n", (UInt32) theSession,(UInt32)theInfo); + //put the session's ID into the session map. + theErr = sSessionMap->Register(theSession->GetRef()); + Assert(theErr == QTSS_NoErr); + + //unless we do this, the refcount won't increment (and we'll delete the session prematurely + if (!isPush) + { OSRef* debug = sSessionMap->Resolve(inPath); + Assert(debug == theSession->GetRef()); + } + } + else + { + + if (isPush) + sSessionMap->Release(theSessionRef); // don't need if a push;// don't need if a push; A Release is necessary or we will leak ReflectorSessions. + + if (foundSessionPtr) + *foundSessionPtr = true; + + StrPtrLen theFileData; + SDPSourceInfo* theInfo = NULL; + + if (inData == NULL) + (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileData); + OSCharArrayDeleter charArrayDeleter(theFileData.Ptr); + + if (theFileData.Len <= 0) + return NULL; + + theInfo = NEW SDPSourceInfo(theFileData.Ptr, theFileData.Len); + if (theInfo == NULL) + return NULL; + + if ( !theInfo->IsActiveNow() && !isPush) + { delete theInfo; + return NULL; + } + + if (!InfoPortsOK(inParams, theInfo, inPath)) + { delete theInfo; + return NULL; + } + + delete theInfo; + + theSession = (ReflectorSession*)theSessionRef->GetObject(); + if (isPush && theSession) + { + UInt32 theSetupFlag = ReflectorSession::kMarkSetup | ReflectorSession::kIsPushSession; + QTSS_Error theErr = theSession->SetupReflectorSession(NULL, inParams, theSetupFlag,sOneSSRCPerStream, sTimeoutSSRCSecs); + if (theErr != QTSS_NoErr) + { return NULL; + } + } + } + + Assert(theSession != NULL); + + // Turn off overbuffering if the "disable_overbuffering" pref says so + if (sDisableOverbuffering) + (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesOverBufferEnabled, 0, &sFalse, sizeof(sFalse)); + + return theSession; +} + +// ONLY call when performing a setup. +void DeleteReflectorPushSession(QTSS_StandardRTSP_Params* inParams, ReflectorSession* theSession, Bool16 foundSession) +{ + ReflectorSession* stopSessionProcessing = NULL; + QTSS_Error theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &stopSessionProcessing, sizeof(stopSessionProcessing)); + Assert(theErr == QTSS_NoErr); + + if (foundSession) + return; // we didn't allocate the session so don't delete + + OSRef* theSessionRef = theSession->GetRef(); + if (theSessionRef != NULL) + { + theSession->TearDownAllOutputs(); // just to be sure because we are about to delete the session. + sSessionMap->UnRegister(theSessionRef);// we had an error while setting up-- don't let anyone get the session + delete theSession; + } +} + +QTSS_Error AddRTPStream(ReflectorSession* theSession,QTSS_StandardRTSP_Params* inParams, QTSS_RTPStreamObject *newStreamPtr) +{ + // Ok, this is completely crazy but I can't think of a better way to do this that's + // safe so we'll do it this way for now. Because the ReflectorStreams use this session's + // stream queue, we need to make sure that each ReflectorStream is not reflecting to this + // session while we call QTSS_AddRTPStream. One brutal way to do this is to grab each + // ReflectorStream's mutex, which will stop every reflector stream from running. + Assert(newStreamPtr != NULL); + + if (theSession != NULL) + for (UInt32 x = 0; x < theSession->GetNumStreams(); x++) + theSession->GetStreamByIndex(x)->GetMutex()->Lock(); + + // + // Turn off reliable UDP transport, because we are not yet equipped to + // do overbuffering. + QTSS_Error theErr = QTSS_AddRTPStream(inParams->inClientSession, inParams->inRTSPRequest, newStreamPtr, qtssASFlagsForceUDPTransport); + + if (theSession != NULL) + for (UInt32 y = 0; y < theSession->GetNumStreams(); y++) + theSession->GetStreamByIndex(y)->GetMutex()->Unlock(); + + return theErr; +} + +QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams) +{ + ReflectorSession* theSession = NULL; + //qtss_printf("QTSSReflectorModule.cpp:DoSetup \n"); + + // See if this is a push from a Broadcaster + UInt32 theLen = 0; + UInt32 *transportModePtr = NULL; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqTransportMode, 0, (void**)&transportModePtr, &theLen); + Bool16 isPush = (transportModePtr != NULL && *transportModePtr == qtssRTPTransportModeRecord) ? true : false; + Bool16 foundSession = false; + + // Check to see if we have a RTPSessionOutput for this Client Session. If we don't, + // we should make one + RTPSessionOutput** theOutput = NULL; + theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen); + if (theLen != sizeof(RTPSessionOutput*)) + { + // + // This may be an incoming data session. If that's the case, there will be a Reflector + // Session in the ClientSession + //theLen = sizeof(theSession); + //theErr = QTSS_GetValue(inParams->inClientSession, sSessionAttr, 0, &theSession, &theLen); + + if (theErr != QTSS_NoErr && !isPush) + { + // This is not an incoming data session... + // Do the standard ReflectorSession setup, create an RTPSessionOutput + theSession = DoSessionSetup(inParams, qtssRTSPReqFilePathTrunc); + if (theSession == NULL) + return QTSS_RequestFailed; + + RTPSessionOutput* theNewOutput = NEW RTPSessionOutput(inParams->inClientSession, theSession, sServerPrefs, sStreamCookieAttr ); + theSession->AddOutput(theNewOutput,true); + (void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theNewOutput, sizeof(theNewOutput)); + } + else + { + theSession = DoSessionSetup(inParams, qtssRTSPReqFilePathTrunc,isPush,&foundSession); + if (theSession == NULL) + return QTSS_RequestFailed; + + // This is an incoming data session. Set the Reflector Session in the ClientSession + theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession)); + Assert(theErr == QTSS_NoErr); + //qtss_printf("QTSSReflectorModule.cpp:SETsession sClientBroadcastSessionAttr=%"_U32BITARG_" theSession=%"_U32BITARG_" err=%"_S32BITARG_" \n",(UInt32)sClientBroadcastSessionAttr, (UInt32) theSession,theErr); + (void) QTSS_SetValue(inParams->inClientSession, qtssCliSesTimeoutMsec, 0, &sBroadcasterSessionTimeoutMilliSecs, sizeof(sBroadcasterSessionTimeoutMilliSecs)); + } + } + else + { theSession = (*theOutput)->GetReflectorSession(); + if (theSession == NULL) + return QTSS_RequestFailed; + } + + //unless there is a digit at the end of this path (representing trackID), don't + //even bother with the request + char* theDigitStr = NULL; + (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr); + QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr); + if (theDigitStr == NULL) + { + if (isPush) + DeleteReflectorPushSession(inParams,theSession, foundSession); + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,sExpectedDigitFilenameErr); + } + + UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10); + + // + // If this is an incoming data session, skip everything having to do with setting up a new + // RTP Stream. + if (isPush) + { + //qtss_printf("QTSSReflectorModule.cpp:DoSetup is push setup\n"); + + // Get info about this trackID + SourceInfo::StreamInfo* theStreamInfo = theSession->GetSourceInfo()->GetStreamInfoByTrackID(theTrackID); + // If theStreamInfo is NULL, we don't have a legit track, so return an error + if (theStreamInfo == NULL) + { + DeleteReflectorPushSession(inParams,theSession, foundSession); + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, + sReflectorBadTrackIDErr); + } + + if (!sAllowDuplicateBroadcasts && theStreamInfo->fSetupToReceive) + { + DeleteReflectorPushSession(inParams,theSession, foundSession); + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssPreconditionFailed, sDuplicateBroadcastStreamErr); + } + + UInt16 theReceiveBroadcastStreamPort = theStreamInfo->fPort; + theErr = QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqSetUpServerPort, 0, &theReceiveBroadcastStreamPort, sizeof(theReceiveBroadcastStreamPort)); + Assert(theErr == QTSS_NoErr); + + + QTSS_RTPStreamObject newStream = NULL; + theErr = AddRTPStream(theSession,inParams,&newStream); + Assert(theErr == QTSS_NoErr); + if (theErr != QTSS_NoErr) + { + DeleteReflectorPushSession(inParams,theSession, foundSession); + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, 0); + } + + //send the setup response + + (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, newStream, 0); + + theStreamInfo->fSetupToReceive = true; + // This is an incoming data session. Set the Reflector Session in the ClientSession + theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession)); + Assert(theErr == QTSS_NoErr); + + if (theSession != NULL) + theSession->AddBroadcasterClientSession(inParams); + + return QTSS_NoErr; + } + + + // Get info about this trackID + SourceInfo::StreamInfo* theStreamInfo = theSession->GetSourceInfo()->GetStreamInfoByTrackID(theTrackID); + // If theStreamInfo is NULL, we don't have a legit track, so return an error + if (theStreamInfo == NULL) + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, + sReflectorBadTrackIDErr); + + StrPtrLen* thePayloadName = &theStreamInfo->fPayloadName; + QTSS_RTPPayloadType thePayloadType = theStreamInfo->fPayloadType; + + StringParser parser(thePayloadName); + + parser.GetThru(NULL, '/'); + theStreamInfo->fTimeScale = parser.ConsumeInteger(NULL); + if (theStreamInfo->fTimeScale == 0) + theStreamInfo->fTimeScale = 90000; + + QTSS_RTPStreamObject newStream = NULL; + { + // Ok, this is completely crazy but I can't think of a better way to do this that's + // safe so we'll do it this way for now. Because the ReflectorStreams use this session's + // stream queue, we need to make sure that each ReflectorStream is not reflecting to this + // session while we call QTSS_AddRTPStream. One brutal way to do this is to grab each + // ReflectorStream's mutex, which will stop every reflector stream from running. + + for (UInt32 x = 0; x < theSession->GetNumStreams(); x++) + theSession->GetStreamByIndex(x)->GetMutex()->Lock(); + + theErr = QTSS_AddRTPStream(inParams->inClientSession, inParams->inRTSPRequest, &newStream, 0); + + for (UInt32 y = 0; y < theSession->GetNumStreams(); y++) + theSession->GetStreamByIndex(y)->GetMutex()->Unlock(); + + if (theErr != QTSS_NoErr) + return theErr; + } + + // Set up dictionary items for this stream + theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayloadName->Ptr, thePayloadName->Len); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrTimescale, 0, &theStreamInfo->fTimeScale, sizeof(theStreamInfo->fTimeScale)); + Assert(theErr == QTSS_NoErr); + + // We only want to allow over buffering to dynamic rate clients + SInt32 canDynamicRate = -1; + theLen = sizeof(canDynamicRate); + (void) QTSS_GetValue(inParams->inRTSPRequest, qtssRTSPReqDynamicRateState, 0, (void*) &canDynamicRate, &theLen); + if (canDynamicRate < 1) // -1 no rate field, 0 off + (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesOverBufferEnabled, 0, &sFalse, sizeof(sFalse)); + + // Place the stream cookie in this stream for future reference + void* theStreamCookie = theSession->GetStreamCookie(theTrackID); + Assert(theStreamCookie != NULL); + theErr = QTSS_SetValue(newStream, sStreamCookieAttr, 0, &theStreamCookie, sizeof(theStreamCookie)); + Assert(theErr == QTSS_NoErr); + + // Set the number of quality levels. + static UInt32 sNumQualityLevels = ReflectorSession::kNumQualityLevels; + theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels)); + Assert(theErr == QTSS_NoErr); + + //send the setup response + (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, newStream, qtssSetupRespDontWriteSSRC); + return QTSS_NoErr; +} + + + +Bool16 HaveStreamBuffers(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession) +{ + if (inSession == NULL || inParams == NULL) + return false; + + UInt32 firstTimeStamp = 0; + UInt16 firstSeqNum = 0; + ReflectorSender* theSender = NULL; + ReflectorStream* theReflectorStream = NULL; + QTSS_RTPStreamObject* theRef = NULL; + UInt32 theStreamIndex = 0; + UInt32 theLen = 0; + QTSS_Error theErr = QTSS_NoErr; + Bool16 haveBufferedStreams = true; // set to false and return if we can't set the packets + UInt32 y = 0; + + + SInt64 packetArrivalTime = 0; + + //lock all streams + for (y = 0; y < inSession->GetNumStreams(); y++) + inSession->GetStreamByIndex(y)->GetMutex()->Lock(); + + + for ( theStreamIndex = 0; + QTSS_GetValuePtr(inParams->inClientSession, qtssCliSesStreamObjects, theStreamIndex, (void**)&theRef, &theLen) == QTSS_NoErr; + theStreamIndex++) + { + theReflectorStream = inSession->GetStreamByIndex(theStreamIndex); + + // if (!theReflectorStream->HasFirstRTCP()) + // printf("theStreamIndex =%"_U32BITARG_" no rtcp\n", theStreamIndex); + + // if (!theReflectorStream->HasFirstRTP()) + // printf("theStreamIndex = %"_U32BITARG_" no rtp\n", theStreamIndex); + + if ((theReflectorStream == NULL) || (false == theReflectorStream->HasFirstRTP()) ) + { + haveBufferedStreams = false; + //printf("1 breaking no buffered streams\n"); + break; + } + + theSender = theReflectorStream->GetRTPSender(); + haveBufferedStreams = theSender->GetFirstPacketInfo(&firstSeqNum, &firstTimeStamp, &packetArrivalTime); + //printf("theStreamIndex= %"_U32BITARG_" haveBufferedStreams=%d, seqnum=%d, timestamp=%"_U32BITARG_"\n", theStreamIndex, haveBufferedStreams, firstSeqNum, firstTimeStamp); + + if (!haveBufferedStreams) + { + //printf("2 breaking no buffered streams\n"); + break; + } + + theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstSeqNumber, 0, &firstSeqNum, sizeof(firstSeqNum)); + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstTimestamp, 0, &firstTimeStamp, sizeof(firstTimeStamp)); + Assert(theErr == QTSS_NoErr); + + + } + //unlock all streams + for (y = 0; y < inSession->GetNumStreams(); y++) + inSession->GetStreamByIndex(y)->GetMutex()->Unlock(); + + return haveBufferedStreams; +} + +QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession) +{ + QTSS_Error theErr = QTSS_NoErr; + UInt32 flags = 0; + UInt32 theLen = 0; + Bool16 rtpInfoEnabled = false; + + if (inSession == NULL) // it is a broadcast session so store the broadcast session. + { if (!sDefaultBroadcastPushEnabled) + return QTSS_RequestFailed; + + theLen = sizeof(inSession); + theErr = QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &inSession, &theLen); + if (theErr != QTSS_NoErr) + return QTSS_RequestFailed; + + theErr = QTSS_SetValue(inParams->inClientSession, sKillClientsEnabledAttr, 0, &sTearDownClientsOnDisconnect, sizeof(sTearDownClientsOnDisconnect)); + if (theErr != QTSS_NoErr) + return QTSS_RequestFailed; + + Assert(inSession != NULL); + + theErr = QTSS_SetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &inSession, sizeof(inSession)); + if (theErr != QTSS_NoErr) + return QTSS_RequestFailed; + + + + //qtss_printf("QTSSReflectorModule:SET for att err=%"_S32BITARG_" id=%"_S32BITARG_"\n",theErr,inParams->inRTSPSession); + + // this code needs to be cleaned up + // Check and see if the full path to this file matches an existing ReflectorSession + StrPtrLen thePathPtr; + OSCharArrayDeleter sdpPath(QTSSModuleUtils::GetFullPath( inParams->inRTSPRequest, + qtssRTSPReqFilePath, + &thePathPtr.Len, &sSDPSuffix)); + + thePathPtr.Ptr = sdpPath.GetObject(); + + + // remove trackID designation from the path if it is there + char *trackStr = thePathPtr.FindString("/trackID="); + if (trackStr != NULL && *trackStr != 0) + { + *trackStr = 0; // terminate the string. + thePathPtr.Len = ::strlen(thePathPtr.Ptr); + } + + // If the actual file path has a .sdp in it, first look for the URL without the extra .sdp + if (thePathPtr.Len > (sSDPSuffix.Len * 2)) + { + // Check and see if there is a .sdp in the file path. + // If there is, truncate off our extra ".sdp", cuz it isn't needed + StrPtrLen endOfPath(&sdpPath.GetObject()[thePathPtr.Len - (sSDPSuffix.Len * 2)], sSDPSuffix.Len); + if (endOfPath.Equal(sSDPSuffix)) + { + sdpPath.GetObject()[thePathPtr.Len - sSDPSuffix.Len] = '\0'; + thePathPtr.Len -= sSDPSuffix.Len; + } + } + + // do all above so we can add the session to the map with Resolve here. + // we must only do this once. + OSRef* debug = sSessionMap->Resolve(&thePathPtr); + if (debug != inSession->GetRef()) + { + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, 0); + } + + + KeepSession(inParams->inRTSPRequest,true); + //qtss_printf("QTSSReflectorModule.cpp:DoPlay (PUSH) inRTSPSession=%"_U32BITARG_" inClientSession=%"_U32BITARG_"\n",(UInt32)inParams->inRTSPSession,(UInt32)inParams->inClientSession); + } + else// it is NOT a broadcaster session + { + + RTPSessionOutput** theOutput = NULL; + theLen = 0; + theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*)) || (theOutput == NULL)) + return QTSS_RequestFailed; + (*theOutput)->InitializeStreams(); + + + + // Tell the session what the bitrate of this reflection is. This is nice for logging, + // it also allows the server to scale the TCP buffer size appropriately if we are + // interleaving the data over TCP. This must be set before calling QTSS_Play so the + // server can use it from within QTSS_Play + UInt32 bitsPerSecond = inSession->GetBitRate(); + (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond)); + + if (sPlayResponseRangeHeader) + { + StrPtrLen temp; + theErr = QTSS_GetValuePtr(inParams->inClientSession, sRTPInfoWaitTimeAttr, 0, (void**) &temp.Ptr, &temp.Len); + if (theErr != QTSS_NoErr) + QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssRangeHeader,sTheNowRangeHeader.Ptr, sTheNowRangeHeader.Len); + + } + + + if (sPlayerCompatibility ) + rtpInfoEnabled = QTSSModuleUtils::HavePlayerProfile(sServerPrefs,inParams, QTSSModuleUtils::kRequiresRTPInfoSeqAndTime); + + if (sForceRTPInfoSeqAndTime) + rtpInfoEnabled = true; + + if (sRTPInfoDisabled ) + rtpInfoEnabled = false; + + if (rtpInfoEnabled) + { + flags = qtssPlayRespWriteTrackInfo; //write first timestampe and seq num to rtpinfo + + Bool16 haveBufferedStreams = HaveStreamBuffers(inParams,inSession); + if (haveBufferedStreams) // send the cached rtp time and seq number in the response. + { + + QTSS_Error theErr = QTSS_Play(inParams->inClientSession, inParams->inRTSPRequest, qtssPlayRespWriteTrackInfo); + if (theErr != QTSS_NoErr) + return theErr; + + } + else + { + SInt32 waitTimeLoopCount = 0; + theLen = sizeof(waitTimeLoopCount); + theErr = QTSS_GetValue(inParams->inClientSession, sRTPInfoWaitTimeAttr, 0, &waitTimeLoopCount, &theLen); + if (theErr != QTSS_NoErr) + (void)QTSS_SetValue(inParams->inClientSession, sRTPInfoWaitTimeAttr, 0, &sWaitTimeLoopCount, sizeof(sWaitTimeLoopCount)); + else + { + if (waitTimeLoopCount < 1) + return QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssClientNotFound, &sBroadcastNotActive); + + waitTimeLoopCount --; + (void)QTSS_SetValue(inParams->inClientSession, sRTPInfoWaitTimeAttr, 0, &waitTimeLoopCount, sizeof(waitTimeLoopCount)); + + } + + //qtss_printf("QTSSReflectorModule:DoPlay wait 100ms waitTimeLoopCount=%ld\n", waitTimeLoopCount); + + SInt64 interval = 1 * 100; // 100 millisecond + QTSS_SetIdleTimer( interval ); + return QTSS_NoErr; + } + + + } + else + { + QTSS_Error theErr = QTSS_Play(inParams->inClientSession, inParams->inRTSPRequest, qtssPlayFlagsAppendServerInfo); + if (theErr != QTSS_NoErr) + return theErr; + + } + + } + + + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, flags); + return QTSS_NoErr; +} + + +Bool16 KillSession(StrPtrLen *sdpPathStr, Bool16 killClients) +{ + OSRef* theSessionRef = sSessionMap->Resolve(sdpPathStr); + if (theSessionRef != NULL) + { + ReflectorSession* theSession = (ReflectorSession*)theSessionRef->GetObject(); + RemoveOutput(NULL, theSession, killClients); + (void)QTSS_Teardown(theSession->GetBroadcasterSession()); + return true; + } + return false; +} + + +void KillCommandPathInList() +{ + char filePath[128] = ""; + ResizeableStringFormatter commandPath( (char*) filePath, sizeof(filePath)); // ResizeableStringFormatter is safer and more efficient than StringFormatter for most paths. + OSMutexLocker locker (sSessionMap->GetMutex()); + + for (OSRefHashTableIter theIter(sSessionMap->GetHashTable()); !theIter.IsDone(); theIter.Next()) + { + OSRef* theRef = theIter.GetCurrent(); + if (theRef == NULL) + continue; + + commandPath.Reset(); + commandPath.Put(*(theRef->GetString())); + commandPath.Put(sSDPKillSuffix); + commandPath.PutTerminator(); + + char *theCommandPath = commandPath.GetBufPtr(); + QTSS_Object outFileObject; + QTSS_Error err = QTSS_OpenFileObject(theCommandPath, qtssOpenFileNoFlags, &outFileObject); + if (err == QTSS_NoErr) + { + (void) QTSS_CloseFileObject(outFileObject); + ::unlink(theCommandPath); + KillSession(theRef->GetString(), true); + } + } + +} + +QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams) +{ + RTPSessionOutput** theOutput = NULL; + ReflectorOutput* outputPtr = NULL; + ReflectorSession* theSession = NULL; + + OSMutexLocker locker (sSessionMap->GetMutex()); + + UInt32 theLen = sizeof(theSession); + QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, &theLen); + //qtss_printf("QTSSReflectorModule.cpp:DestroySession sClientBroadcastSessionAttr=%"_U32BITARG_" theSession=%"_U32BITARG_" err=%"_S32BITARG_" \n",(UInt32)sClientBroadcastSessionAttr, (UInt32)theSession,theErr); + if (theSession != NULL) // it is a broadcaster session + { + ReflectorSession* deletedSession = NULL; + theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &deletedSession, sizeof(deletedSession)); + + SourceInfo* theSoureInfo = theSession->GetSourceInfo(); + if (theSoureInfo == NULL) + return QTSS_NoErr; + + UInt32 numStreams = theSession->GetNumStreams(); + SourceInfo::StreamInfo* theStreamInfo = NULL; + + for (UInt32 index = 0; index < numStreams; index++) + { theStreamInfo = theSoureInfo->GetStreamInfo(index); + if (theStreamInfo != NULL) + theStreamInfo->fSetupToReceive = false; + } + + Bool16 killClients = false; // the pref as the default + UInt32 theLen = sizeof(killClients); + (void) QTSS_GetValue(inParams->inClientSession, sKillClientsEnabledAttr, 0, &killClients, &theLen); + + + //qtss_printf("QTSSReflectorModule.cpp:DestroySession broadcaster theSession=%"_U32BITARG_"\n", (UInt32) theSession); + theSession->RemoveSessionFromOutput(inParams->inClientSession); + RemoveOutput(NULL, theSession, killClients); + } + else + { + theLen = 0; + theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*)) || (theOutput == NULL) || (*theOutput == NULL)) + return QTSS_RequestFailed; + theSession = (*theOutput)->GetReflectorSession(); + + if (theOutput != NULL) + outputPtr = (ReflectorOutput*) *theOutput; + + if (outputPtr != NULL) + { + RemoveOutput(outputPtr, theSession, false); + RTPSessionOutput* theOutput = NULL; + (void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theOutput, sizeof(theOutput)); + + } + + } + + return QTSS_NoErr; +} + +void RemoveOutput(ReflectorOutput* inOutput, ReflectorSession* inSession, Bool16 killClients) +{ + //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput\n"); + //This function removes the output from the ReflectorSession, then + Assert(inSession); + if (inSession != NULL) + { + if (inOutput != NULL) + { + inSession->RemoveOutput(inOutput,true); + } + else + { // it is a Broadcaster session + //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput it is a broadcaster session\n"); + SourceInfo* theInfo = inSession->GetSourceInfo(); + Assert(theInfo); + + if (theInfo->IsRTSPControlled()) + { + FileDeleter(inSession->GetSourcePath()); + } + + + if (killClients || sTearDownClientsOnDisconnect) + { + inSession->TearDownAllOutputs(); + } + } + + //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput refcount =%"_U32BITARG_"\n", inSession->GetRef()->GetRefCount() ); + + //check if the ReflectorSession should be deleted + //it should if its ref count has dropped to 0 + OSRef* theSessionRef = inSession->GetRef(); + if (theSessionRef != NULL) + { + //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput UnRegister session =%p refcount=%"_U32BITARG_"\n", theSessionRef, theSessionRef->GetRefCount() ) ; + + for (UInt32 x = 0; x < inSession->GetNumStreams(); x++) + { + if (inSession->GetStreamByIndex(x) == NULL) + continue; + + Assert(theSessionRef->GetRefCount() > 0) //this shouldn't happen. + if (theSessionRef->GetRefCount() > 0) + sSessionMap->Release(theSessionRef); // one of the sessions on the ref is ending so decrement the count for each valid stream + } + + if (theSessionRef->GetRefCount() == 0) + { + //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput UnRegister and delete session =%p refcount=%"_U32BITARG_"\n", theSessionRef, theSessionRef->GetRefCount() ) ; + sSessionMap->UnRegister(theSessionRef); + delete inSession; + } + + } + } + + delete inOutput; +} + + +Bool16 AcceptSession(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_RTSPSessionObject inRTSPSession = inParams->inRTSPSession; + QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; + + QTSS_ActionFlags action = QTSSModuleUtils::GetRequestActions(theRTSPRequest); + if(action != qtssActionFlagsWrite) + return false; + + if (QTSSModuleUtils::UserInGroup(QTSSModuleUtils::GetUserProfileObject(theRTSPRequest), sBroadcasterGroup.Ptr, sBroadcasterGroup.Len)) + return true; // ok we are allowing this broadcaster user + + char remoteAddress[20] = {0}; + StrPtrLen theClientIPAddressStr(remoteAddress,sizeof(remoteAddress)); + QTSS_Error err = QTSS_GetValue(inRTSPSession, qtssRTSPSesRemoteAddrStr, 0, (void*)theClientIPAddressStr.Ptr, &theClientIPAddressStr.Len); + if (err != QTSS_NoErr) + return false; + + if (IPComponentStr(&theClientIPAddressStr).IsLocal()) + { + if (sAuthenticateLocalBroadcast) + return false; + else + return true; + } + + if (QTSSModuleUtils::AddressInList(sPrefs, sIPAllowListID, &theClientIPAddressStr)) + return true; + + return false; +} + +QTSS_Error ReflectorAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams) +{ + if ( AcceptSession(inParams) ) + { + Bool16 allowed = true; + QTSS_RTSPRequestObject request = inParams->inRTSPRequest; + (void) QTSSModuleUtils::AuthorizeRequest(request, &allowed, &allowed, &allowed); + return QTSS_NoErr; + } + + Bool16 allowNoAccessFiles = false; + QTSS_ActionFlags noAction = ~qtssActionFlagsWrite; //no action anything but a write + QTSS_ActionFlags authorizeAction = QTSSModuleUtils::GetRequestActions(inParams->inRTSPRequest); + //printf("ReflectorAuthorizeRTSPRequest authorizeAction=%d qtssActionFlagsWrite=%d\n", authorizeAction, qtssActionFlagsWrite); + Bool16 outAllowAnyUser = false; + Bool16 outAuthorized = false; + QTAccessFile accessFile; + accessFile.AuthorizeRequest(inParams,allowNoAccessFiles, noAction, authorizeAction, &outAuthorized, &outAllowAnyUser); + + if( (outAuthorized == false) && (authorizeAction & qtssActionFlagsWrite) ) //handle it + { + //printf("ReflectorAuthorizeRTSPRequest SET not allowed\n"); + Bool16 allowed = false; + (void) QTSSModuleUtils::AuthorizeRequest(inParams->inRTSPRequest, &allowed, &allowed, &allowed); + } + return QTSS_NoErr; +} + +QTSS_Error RedirectBroadcast(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + + char* requestPathStr; + (void)QTSS_GetValueAsString(theRequest, qtssRTSPReqFilePath, 0, &requestPathStr); + QTSSCharArrayDeleter requestPathStrDeleter(requestPathStr); + StrPtrLen theRequestPath(requestPathStr); + StringParser theRequestPathParser(&theRequestPath); + + // request path begins with a '/' for ex. /mysample.mov or /redirect_broadcast_keyword/mysample.mov + theRequestPathParser.Expect(kPathDelimiterChar); + + StrPtrLen theFirstPath; + theRequestPathParser.ConsumeUntil(&theFirstPath, kPathDelimiterChar); + Assert (theFirstPath.Len != 0); + + // If the redirect_broadcast_keyword and redirect_broadcast_dir prefs are set & the first part of the path matches the keyword + if ( (sRedirectBroadcastsKeyword && sRedirectBroadcastsKeyword[0] != 0) + && (sBroadcastsRedirectDir && sBroadcastsRedirectDir[0] != 0) + && theFirstPath.EqualIgnoreCase(sRedirectBroadcastsKeyword, ::strlen(sRedirectBroadcastsKeyword)) ) + { + // set qtssRTSPReqRootDir + (void)QTSS_SetValue(theRequest, qtssRTSPReqRootDir, 0, sBroadcastsRedirectDir, ::strlen(sBroadcastsRedirectDir)); + + // set the request file path to the new path with the keyword stripped + StrPtrLen theStrippedRequestPath; + theRequestPathParser.ConsumeLength(&theStrippedRequestPath, theRequestPathParser.GetDataRemaining()); + (void) QTSS_SetValue(theRequest, qtssRTSPReqFilePath, 0, theStrippedRequestPath.Ptr, theStrippedRequestPath.Len); + } + + return QTSS_NoErr; +} + +Bool16 AllowBroadcast(QTSS_RTSPRequestObject inRTSPRequest) +{ + // If reflection of broadcasts is disabled, return false + if (!sReflectBroadcasts) + return false; + + // If request path is not in any of the broadcast_dir paths, return false + if (!InBroadcastDirList(inRTSPRequest)) + return false; + + return true; +} + +Bool16 InBroadcastDirList(QTSS_RTSPRequestObject inRTSPRequest) +{ + Bool16 allowed = false; + + char* theURIPathStr; + (void)QTSS_GetValueAsString(inRTSPRequest, qtssRTSPReqFilePath, 0, &theURIPathStr); + QTSSCharArrayDeleter requestPathStrDeleter(theURIPathStr); + + char* theLocalPathStr; + (void)QTSS_GetValueAsString(inRTSPRequest, qtssRTSPReqLocalPath, 0, &theLocalPathStr); + StrPtrLenDel requestPath(theLocalPathStr); + + char* theRequestPathStr = NULL; + char* theBroadcastDirStr = NULL; + Bool16 isURI = true; + + UInt32 index = 0; + UInt32 numValues = 0; + + + (void) QTSS_GetNumValues(sPrefs, sBroadcastDirListID, &numValues); + + if (numValues == 0) + return true; + + while (!allowed && (index < numValues)) + { + (void) QTSS_GetValueAsString(sPrefs, sBroadcastDirListID, index, &theBroadcastDirStr); + StrPtrLen theBroadcastDir(theBroadcastDirStr); + + if (theBroadcastDir.Len == 0) // an empty dir matches all + return true; + + if (IsAbsolutePath(&theBroadcastDir)) + { + theRequestPathStr = theLocalPathStr; + isURI = false; + } + else + theRequestPathStr = theURIPathStr; + + StrPtrLen requestPath(theRequestPathStr); + StringParser requestPathParser(&requestPath); + StrPtrLen pathPrefix; + if (isURI) + requestPathParser.Expect(kPathDelimiterChar); + requestPathParser.ConsumeLength(&pathPrefix, theBroadcastDir.Len); + + // if the first part of the request path matches the broadcast_dir path, return true + if (pathPrefix.Equal(theBroadcastDir)) + allowed = true; + + (void) QTSS_Delete(theBroadcastDirStr); + + index ++; + } + + return allowed; +} + +Bool16 IsAbsolutePath(StrPtrLen *inPathPtr) +{ + StringParser thePathParser(inPathPtr); + +#ifdef __Win32__ + if ((thePathParser[1] == ':') && (thePathParser[2] == kPathDelimiterChar)) +#else + if (thePathParser.PeekFast() == kPathDelimiterChar) +#endif + return true; + + return false; +} diff --git a/APIModules/QTSSReflectorModule/QTSSReflectorModule.h b/APIModules/QTSSReflectorModule/QTSSReflectorModule.h new file mode 100644 index 0000000..17478ec --- /dev/null +++ b/APIModules/QTSSReflectorModule/QTSSReflectorModule.h @@ -0,0 +1,43 @@ +/* + * + * @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: QTSSReflectorModule.h + + Contains: QTSS API module + + +*/ + +#ifndef _QTSSREFLECTORMODULE_H_ +#define _QTSSREFLECTORMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSReflectorModule_Main(void* inPrivateArgs); +} + +#endif //_QTSSREFLECTORMODULE_H_ diff --git a/APIModules/QTSSReflectorModule/QTSSRelayModule.cpp b/APIModules/QTSSReflectorModule/QTSSRelayModule.cpp new file mode 100644 index 0000000..2d0017f --- /dev/null +++ b/APIModules/QTSSReflectorModule/QTSSRelayModule.cpp @@ -0,0 +1,1274 @@ +/* + * + * @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: QTSSReflectorModule.cpp + + Contains: Implementation of QTSSReflectorModule class. + + + +*/ + +#include "QTSSRelayModule.h" +#include "QTSSModuleUtils.h" +//#include "ReflectorSession.h" +#include "RelaySession.h" +#include "ReflectorStream.h" +#include "OSArrayObjectDeleter.h" +#include "QTSS_Private.h" + +#include "../../defaultPaths.h" +#include "OSMemory.h" +#include "OSRef.h" +#include "IdleTask.h" +#include "Task.h" +#include "OS.h" +#include "Socket.h" +#include "SocketUtils.h" +#include "XMLParser.h" +#include "OSArrayObjectDeleter.h" + +//ReflectorOutput objects +#include "RTPSessionOutput.h" +#include "RelayOutput.h" + +//SourceInfo objects +#include "SDPSourceInfo.h" +#include "RelaySDPSourceInfo.h" +#include "RCFSourceInfo.h" +#include "RTSPSourceInfo.h" + +#include +#ifndef __Win32__ +#include +#endif + +#ifndef kVersionString +#include "../../revision.h" +#endif + +#if DEBUG +#define REFLECTOR_MODULE_DEBUGGING 0 +#else +#define REFLECTOR_MODULE_DEBUGGING 0 +#endif + +// STATIC DATA + +static char* sRelayPrefs = NULL; +static char* sRelayStatsURL = NULL; +static StrPtrLen sRequestHeader; +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_ServerObject sServer = NULL; +static QTSS_Object sAttributes = NULL; +static Bool16 sSkipAuthorization = true; +static Bool16 sIsRelaySession = true; +static char* sIsRelaySessionAttrName = "QTSSRelayModuleIsRelaySession"; +static QTSS_AttributeID sIsRelaySessionAttr = qtssIllegalAttrID; + +static int sRelayPrefModDate = -1; + +// ATTRIBUTES + +static QTSS_AttributeID sRelayModulePrefParseErr = qtssIllegalAttrID; + +static char* sDefaultRelayPrefs = DEFAULTPATHS_ETC_DIR "relayconfig.xml"; + +#ifdef __MacOSX__ +#define kResponseHeader "HTTP/1.0 200 OK\r\nServer: QuickTimeStreamingServer/%s/%s\r\nConnection: Close\r\nContent-Type: text/html\r\n\r\nRelay Stats" +#else +#define kResponseHeader "HTTP/1.0 200 OK\r\nServer: DarwinStreamingServer/%s/%s\r\nConnection: Close\r\nContent-Type: text/html\r\n\r\nRelay Stats" +#endif + +static char sResponseHeader[1024]; + +static OSQueue* sSessionQueue = NULL; +static OSQueue* sAnnouncedQueue = NULL; +static OSQueue* sRTSPSourceInfoQueue = NULL; +static OSQueue* sRTSPSessionIDQueue = NULL; + +static XMLParser* sRelayPrefsFile = NULL; +static OSMutex sResolverMutex; +class DNSResolverThread; +DNSResolverThread* sResolverThread = NULL; +Bool16 sDoResolveAgain = false; + +// This struct is used when Rereading Relay Prefs +struct SourceInfoQueueElem +{ + SourceInfoQueueElem(SourceInfo* inInfo, Bool16 inRTSPInfo) :fElem(), fSourceInfo(inInfo), + fIsRTSPSourceInfo(inRTSPInfo), + fShouldDelete(true) { fElem.SetEnclosingObject(this); } + ~SourceInfoQueueElem() {} + + OSQueueElem fElem; + SourceInfo* fSourceInfo; + Bool16 fIsRTSPSourceInfo; + Bool16 fShouldDelete; +}; + +// This struct is used when setting up announced source relays +struct RTSPSessionIDQueueElem +{ + RTSPSessionIDQueueElem(UInt32 rtspSessionID) :fElem(), fRTSPSessionID(rtspSessionID) { fElem.SetEnclosingObject(this); } + ~RTSPSessionIDQueueElem() {} + + OSQueueElem fElem; + UInt32 fRTSPSessionID; +}; + + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSRelayModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error Shutdown(); +static QTSS_Error RereadPrefs(); + +static RelaySession* CreateSession(SourceInfoQueueElem* inElem); +static RelaySession* FindSession(SourceInfoQueueElem* inElem); +static RelaySession* FindNextSession(SourceInfoQueueElem* inElem, OSQueueIter* inIterPtr); + +static void AddOutputs(SourceInfo* inInfo, RelaySession* inSession, Bool16 inIsRTSPSourceInfo); +static void RemoveOutput(ReflectorOutput* inOutput, RelaySession* inSession); + +static QTSS_Error Filter(QTSS_StandardRTSP_Params* inParams); +static void FindRelaySessions(OSQueue* inSessionQueue); + +static QTSS_Error SetupAnnouncedSessions(QTSS_StandardRTSP_Params* inParams); +static RTSPSessionIDQueueElem* FindRTSPSessionIDQueueElem(UInt32 inSessionID); +static RTSPSourceInfo* FindAnnouncedSourceInfo(UInt32 inIP, StrPtrLen& inURL); +static RTSPSourceInfo* FindExistingRelayInfo(UInt32 inIP, StrPtrLen& inURL); + +static void RereadRelayPrefs(XMLParser* prefsParser); +static void FindSourceInfos(OSQueue* inSessionQueue, OSQueue* inAnnouncedQueue, XMLParser* prefsParser); +static void ClearSourceInfos(OSQueue* inQueue); + +static QTSS_Error RouteAuthorization(QTSS_StandardRTSP_Params* inParams); +static Bool16 IsRelayRequest(UInt16 inPort); + +static void ReadRelayPrefsFile(); +static Bool16 CheckDNSNames(XMLParser* prefsFile, Bool16 doResolution); +static void ResolveDNSAddr(XMLTag* tag); + + +// DNS Resolution can block a thread for a long time (there's no async version), and on X +// we only have 1 task thread, so the resolution must be on another thread. However, we +// want to do the rest of the RereadPrefs on the main task thread, so we need both a thread +// and a task. The thread resolves the addresses and fires the task, the task deletes +// the thread and is deleted itself as soon as it's done. + +class DNSResolverThread : public OSThread +{ + class RereadPrefsTask : public Task + { + + + public: + RereadPrefsTask() : Task () {this->SetTaskName("DNSResolverThread::RereadPrefsTask");} + virtual SInt64 Run() + { + RereadRelayPrefs(sRelayPrefsFile); + delete sResolverThread; + sResolverThread = NULL; + delete sRelayPrefsFile; + + // we need to see if reread prefs has been called again while we were resolving. + // If so, it just exited without doing anything, and we need to start over to pick + // up whatever changed. + sResolverMutex.Lock(); + sRelayPrefsFile = NULL; + if (sDoResolveAgain) + { + sDoResolveAgain = false; + sResolverMutex.Unlock(); + ReadRelayPrefsFile(); + } + else sResolverMutex.Unlock(); + + return -1; + } + }; + +public: + static void ResolveRelayPrefs(XMLParser* relayPrefs) + { + sResolverMutex.Lock(); + + if (sRelayPrefsFile == NULL) + { + sRelayPrefsFile = relayPrefs; + sResolverThread = new DNSResolverThread(); + sResolverThread->Start(); + } + else + { + sDoResolveAgain = true; // it's already resolving, tell it to try again when it's done + delete relayPrefs; + } + + sResolverMutex.Unlock(); + } + + virtual void Entry() + { + if (sRelayPrefsFile != NULL) + { + CheckDNSNames(sRelayPrefsFile, true); + RereadPrefsTask* tempTask = new RereadPrefsTask(); + tempTask->Signal(Task::kIdleEvent); + } + } +}; + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSRelayModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSRelayModuleDispatch); +} + + +QTSS_Error QTSSRelayModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RereadPrefs_Role: + + return RereadPrefs(); + case QTSS_RTSPFilter_Role: + return Filter(&inParams->rtspRequestParams); + case QTSS_RTSPRoute_Role: + return RouteAuthorization(&inParams->rtspRouteParams); + case QTSS_RTSPPostProcessor_Role: + return SetupAnnouncedSessions(&inParams->rtspPostProcessorParams); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_Shutdown_Role); + (void)QTSS_AddRole(QTSS_RTSPFilter_Role); + (void)QTSS_AddRole(QTSS_RTSPRoute_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + (void)QTSS_AddRole(QTSS_RTSPPostProcessor_Role); + + // get the text for the error message from the server atrribute. + static char* sRelayModulePrefParseErrName = "QTSSRelayModulePrefParseError"; + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sRelayModulePrefParseErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sRelayModulePrefParseErrName, &sRelayModulePrefParseErr); + + // add a boolean attribute for the relay session to the client session object + // we set it to true if we know that it is a relay session else we set it to false + // (set to true for those client sessions that are created for the relay when announces come in) + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sIsRelaySessionAttrName, NULL, qtssAttrDataTypeBool16); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sIsRelaySessionAttrName, &sIsRelaySessionAttr); + + RelaySession::Register(); + + RelayOutput::Register(); + // Reflector session needs to setup some parameters too. + ReflectorStream::Register(); + + // Tell the server our name! + static char* sModuleName = "QTSSRelayModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sSessionQueue = NEW OSQueue(); + sAnnouncedQueue = NEW OSQueue(); + sRTSPSourceInfoQueue = NEW OSQueue(); + sRTSPSessionIDQueue = NEW OSQueue(); + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + sServer = inParams->inServer; + sAttributes = QTSSModuleUtils::GetModuleAttributesObject(inParams->inModule); + + qtss_sprintf(sResponseHeader, kResponseHeader, kVersionString, kBuildString); + + // Call helper class initializers + // this function calls the ReflectorSession base class Initialize function + RelaySession::Initialize(sAttributes); + +#if QTSS_RELAY_EXTERNAL_MODULE + // The reflector is dependent on a number of objects in the Common Utilities + // library that get setup by the server if the reflector is internal to the + // server. + // + // So, if the reflector is being built as a code fragment, it must initialize + // those pieces itself +#if !MACOSXEVENTQUEUE + ::select_startevents();//initialize the select() implementation of the event queue +#endif + OS::Initialize(); + Socket::Initialize(); + SocketUtils::Initialize(); + + const UInt32 kNumReflectorThreads = 8; + TaskThreadPool::AddThreads(kNumReflectorThreads); + IdleTask::Initialize(); + Socket::StartThread(); +#endif + + // + // Instead of passing our own module prefs object, as one might expect, + // here we pass in the QTSSReflectorModule's, because the prefs that + // apply to ReflectorStream are stored in that module's prefs + StrPtrLen theReflectorModule("QTSSReflectorModule"); + QTSS_ModulePrefsObject theReflectorPrefs = + QTSSModuleUtils::GetModulePrefsObject(QTSSModuleUtils::GetModuleObjectByName(theReflectorModule)); + + // Call helper class initializers + ReflectorStream::Initialize(theReflectorPrefs); + RereadPrefs(); + + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ +#if QTSS_REFLECTOR_EXTERNAL_MODULE + TaskThreadPool::RemoveThreads(); +#endif + return QTSS_NoErr; +} + +QTSS_Error RereadPrefs() +{ + delete [] sRelayPrefs; + delete [] sRelayStatsURL; + + sRelayPrefs = QTSSModuleUtils::GetStringAttribute(sPrefs, "relay_prefs_file", sDefaultRelayPrefs); + sRelayStatsURL = QTSSModuleUtils::GetStringAttribute(sPrefs, "relay_stats_url", ""); + + ReadRelayPrefsFile(); + return QTSS_NoErr; +} + +RelaySession* CreateSession(SourceInfoQueueElem* inElem) +{ + RelaySession* theSession = NEW RelaySession(NULL, inElem->fSourceInfo); + QTSS_Error theErr = theSession->SetupRelaySession(inElem->fSourceInfo); + inElem->fShouldDelete = false; // SourceInfo will be deleted by the RelaySession + if (theErr != QTSS_NoErr) + { + delete theSession; + return NULL; + } + + theSession->FormatHTML(NULL); + + sSessionQueue->EnQueue(theSession->GetQueueElem()); + return theSession; +} + +RelaySession* FindSession(SourceInfoQueueElem* inElem) +{ + OSQueueIter theIter(sSessionQueue); + return FindNextSession(inElem, &theIter); +} + +RelaySession* FindNextSession(SourceInfoQueueElem* inElem, OSQueueIter* inIterPtr) +{ + RelaySession* theSession = NULL; + + for ( ;!inIterPtr->IsDone(); inIterPtr->Next()) + { + theSession = (RelaySession*)inIterPtr->GetCurrent()->GetEnclosingObject(); + if (theSession->Equal(inElem->fSourceInfo)) + return theSession; + } + + return NULL; +} + +void RemoveOutput(ReflectorOutput* inOutput, RelaySession* inSession) +{ + // This function removes the output from the RelaySession, then + // checks to see if the session should go away. If it should, this deletes it + inSession->RemoveOutput(inOutput,false); + delete inOutput; + + if (inSession->GetNumOutputs() == 0) + { + sSessionQueue->Remove(inSession->GetQueueElem()); + delete inSession; + } +} + +void RemoveAllOutputs(RelaySession* inSession) +{ + OSMutexLocker locker(RelayOutput::GetQueueMutex()); + for (OSQueueIter iter(RelayOutput::GetOutputQueue()); !iter.IsDone(); ) + { + RelayOutput* theOutput = (RelayOutput*)iter.GetCurrent()->GetEnclosingObject(); + + iter.Next(); + if(theOutput->GetRelaySession() == inSession) + { + inSession->RemoveOutput(theOutput,false); + delete theOutput; + } + } + + Assert(inSession->GetNumOutputs() == 0); + sSessionQueue->Remove(inSession->GetQueueElem()); + delete inSession; +} + +QTSS_Error Filter(QTSS_StandardRTSP_Params* inParams) +{ + UInt32 theLen = 0; + char* theFullRequest = NULL; + (void)QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest, &theLen); + + OSMutexLocker locker(RelayOutput::GetQueueMutex()); + + // Check to see if this is a request we should handle + if ((sRequestHeader.Ptr == NULL) || (sRequestHeader.Len == 0)) + return QTSS_NoErr; + if ((theFullRequest == NULL) || (theLen < sRequestHeader.Len)) + return QTSS_NoErr; + if (::memcmp(theFullRequest, sRequestHeader.Ptr, sRequestHeader.Len) != 0) + return QTSS_NoErr; + + // Keep-alive should be off! + Bool16 theFalse = false; + (void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespKeepAlive, 0, &theFalse, sizeof(theFalse)); + + (void)QTSS_Write(inParams->inRTSPRequest, sResponseHeader, ::strlen(sResponseHeader), NULL, 0); + + // First build up a queue of all RelaySessions with RelayOutputs. This way, + // we can present a list that's sorted. + OSQueue theSessionQueue; + FindRelaySessions(&theSessionQueue); + + static StrPtrLen sNoRelays("

There are no currently active relays

"); + if (theSessionQueue.GetLength() == 0) + (void)QTSS_Write(inParams->inRTSPRequest, sNoRelays.Ptr, sNoRelays.Len, NULL, 0); + + // Now go through our RelaySessionQueue, writing the info for each RelaySession, + // and all the outputs associated with that session + for (OSQueueIter iter(&theSessionQueue); !iter.IsDone(); iter.Next()) + { + RelaySession* theSession = (RelaySession*)iter.GetCurrent()->GetEnclosingObject(); + (void)QTSS_Write(inParams->inRTSPRequest, theSession->GetSourceInfoHTML()->Ptr, theSession->GetSourceInfoHTML()->Len, NULL, 0); + + for (OSQueueIter iter2(RelayOutput::GetOutputQueue()); !iter2.IsDone(); iter2.Next()) + { + RelayOutput* theOutput = (RelayOutput*)iter2.GetCurrent()->GetEnclosingObject(); + if (theSession == theOutput->GetRelaySession()) + { + (void)QTSS_Write(inParams->inRTSPRequest, theOutput->GetOutputInfoHTML()->Ptr, theOutput->GetOutputInfoHTML()->Len, NULL, 0); + + // Write current stats for this output + char theStatsBuf[1024]; + qtss_sprintf(theStatsBuf, "Current stats for this relay: %"_U32BITARG_" packets per second. %"_U32BITARG_" bits per second. %"_64BITARG_"d packets since it started. %"_64BITARG_"d bits since it started

", theOutput->GetCurPacketsPerSecond(), theOutput->GetCurBitsPerSecond(), theOutput->GetTotalPacketsSent(), theOutput->GetTotalBytesSent()); + (void)QTSS_Write(inParams->inRTSPRequest, &theStatsBuf[0], ::strlen(theStatsBuf), NULL, 0); + } + } + } + static StrPtrLen sResponseEnd(""); + (void)QTSS_Write(inParams->inRTSPRequest, sResponseEnd.Ptr, sResponseEnd.Len, NULL, 0); + + for (OSQueueIter iter3(&theSessionQueue); !iter3.IsDone(); ) + { + // Cleanup the memory we had allocated in FindRelaySessions + OSQueueElem* theElem = iter3.GetCurrent(); + iter3.Next(); + theSessionQueue.Remove(theElem); + delete theElem; + } + return QTSS_NoErr; +} + +void FindRelaySessions(OSQueue* inSessionQueue) +{ + for (OSQueueIter theIter(RelayOutput::GetOutputQueue()); !theIter.IsDone(); theIter.Next()) + { + RelayOutput* theOutput = (RelayOutput*)theIter.GetCurrent()->GetEnclosingObject(); + Bool16 found = false; + + // Check to see if we've already seen this RelaySession + for (OSQueueIter theIter2(inSessionQueue); !theIter2.IsDone(); theIter2.Next()) + { + if (theOutput->GetRelaySession() == theIter2.GetCurrent()->GetEnclosingObject()) + { + found = true; + break; + } + } + if (!found) + { + // We haven't seen this one yet, so put it on the queue. + OSQueueElem* theElem = NEW OSQueueElem(theOutput->GetRelaySession()); + inSessionQueue->EnQueue(theElem); + } + } +} + +QTSS_Error SetupAnnouncedSessions(QTSS_StandardRTSP_Params* inParams) +{ + + QTSS_Error theErr = QTSS_NoErr; + Bool16 setup = false; + Bool16 play = false; + + // Get the RTSP Method + QTSS_RTSPMethod* theMethod = NULL; + UInt32 theLen = 0; + theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0, (void**)&theMethod, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod))) + { + Assert(0); + return QTSS_NoErr; + } + + // Get the SETUP Transport Mode + QTSS_RTPTransportMode* theMode = NULL; + theLen = 0; + theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqTransportMode, 0, (void**)&theMode, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(QTSS_RTPTransportMode))) + { + Assert(0); + return QTSS_NoErr; + } + + if ( (*theMethod == qtssSetupMethod) && (*theMode == qtssRTPTransportModeRecord) ) + setup = true; + else if (*theMethod == qtssPlayMethod || *theMethod == qtssRecordMethod) + play = true; + + // We are only interested in SETUP mode=receive or mode=record and PLAY or RECORD requests + if (!(setup || play)) + return QTSS_NoErr; + + // Get the RTSP Status Code + QTSS_RTSPStatusCode* theStatusCode = NULL; + theLen = 0; + theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqStatusCode, 0, (void**)&theStatusCode, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPStatusCode))) + { + Assert(0); + return QTSS_NoErr; + } + + // We are only interested in 200 OK requests, ofcourse + if(*theStatusCode != qtssSuccessOK) + return QTSS_NoErr; + + // Now get the RTSP Session ID + UInt32* theSessionID = 0; + theLen = 0; + theErr = QTSS_GetValuePtr(inParams->inRTSPSession, qtssRTSPSesID, 0, (void**)&theSessionID, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(UInt32))) + { + Assert(0); + return QTSS_NoErr; + } + + OSMutexLocker locker(RelayOutput::GetQueueMutex()); + + RTSPSessionIDQueueElem* theElem = FindRTSPSessionIDQueueElem(*theSessionID); + + // If the RTSPSessionID is not in the queue + if (theElem == NULL) + { + // If it's a SETUP mode=receive or record add the RTSPSessionID to the queue + if(setup) + { + RTSPSessionIDQueueElem* idElem = NEW RTSPSessionIDQueueElem(*theSessionID); + sRTSPSessionIDQueue->EnQueue(&idElem->fElem); + } + + // Nothing more to do + return QTSS_NoErr; + } + + // If theElem is not NULL + if(setup) // No need to add it to the queue twice + return QTSS_NoErr; + + // At this point it has to be a PLAY request with the session ID in the queue + + // Remove the session ID from the queue + sRTSPSessionIDQueue->Remove(&theElem->fElem); + delete theElem; + theElem = 0; + + // Get the Remote IP address + UInt32* theIP = 0; + theLen = 0; + theErr = QTSS_GetValuePtr(inParams->inRTSPSession, qtssRTSPSesRemoteAddr, 0, (void**)&theIP, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(UInt32))) + { + Assert(0); + return QTSS_NoErr; + } + + // Get the URI + theLen = 0; + char* theURLStr = NULL; + theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqURI, 0, (void**)&theURLStr, &theLen); + if ((theErr != QTSS_NoErr) || (theURLStr == NULL)) + { + Assert(0); + return QTSS_NoErr; + } + + StrPtrLen theURL(theURLStr, theLen); + + // Check if a source info exists for this ANNOUNCED URL + RTSPSourceInfo* info = FindAnnouncedSourceInfo(*theIP, theURL); + if(info == NULL) // if a source info does not exists, we don't have to do anything + return QTSS_NoErr; + + // Check if a relay has already been set up for this + // if it has, remove all the outputs, delete the session + // before creating a new source info etc... + RTSPSourceInfo* existingInfo = FindExistingRelayInfo(*theIP, theURL); + if(existingInfo != NULL) + { + RelaySession* session = existingInfo->GetRelaySession(); + if(session == NULL) + delete existingInfo; + else + RemoveAllOutputs(session); + } + + RTSPSourceInfo* newInfo = NEW RTSPSourceInfo(*info); + newInfo->SetAnnounceActualIP(*theIP); // this is used later along with the source url to check if a relay is already set up + + UInt16 thePort = 0; + theLen = sizeof(UInt16); + theErr = QTSS_GetValue(sServer, qtssSvrRTSPPorts, 0, (void *) &thePort, &theLen); + + if ((theErr != QTSS_NoErr) || (theLen != sizeof(UInt16))) + { + delete newInfo; + return QTSS_NoErr; + } + + newInfo->SetSourceParameters(INADDR_LOOPBACK, thePort, theURL); + newInfo->StartSessionCreatorTask(sSessionQueue, sRTSPSourceInfoQueue); + + return QTSS_NoErr; +} + +RTSPSessionIDQueueElem* FindRTSPSessionIDQueueElem(UInt32 inSessionID) +{ + for (OSQueueIter iter(sRTSPSessionIDQueue); !iter.IsDone(); iter.Next()) + { + RTSPSessionIDQueueElem* theElem = (RTSPSessionIDQueueElem*)iter.GetCurrent()->GetEnclosingObject(); + + if (theElem->fRTSPSessionID == inSessionID) + return theElem; + } + + return NULL; +} + +RTSPSourceInfo* FindAnnouncedSourceInfo(UInt32 inIP, StrPtrLen& inURL) +{ + RTSPSourceInfo* info = NULL; + Bool16 ipMatchfound = false; + + // check to see if the ip address + URL match any of the source infos in the sAnnouncedQueue. + // in this order: + // 1. IP + URL match + // 2. IP match + // 3. All IPs+URLs match (meaning all announced broadcasts) + + for (OSQueueIter iter(sAnnouncedQueue); !iter.IsDone(); iter.Next()) + { + SourceInfo* theInfo = ((SourceInfoQueueElem*)iter.GetCurrent()->GetEnclosingObject())->fSourceInfo; + +// RTSPSourceInfo* announceInfo = dynamic_cast(theInfo); + RTSPSourceInfo* announceInfo = (RTSPSourceInfo *)(theInfo); +// if ((announceInfo == NULL) || (!announceInfo->IsAnnounce())) +// continue; + + UInt32 infoIP = announceInfo->GetAnnounceIP(); + StrPtrLen infoURL(announceInfo->GetAnnounceURL()); + + // if there is a source info for ANNOUNCE ALL, keep this info until a better one is found + if (!ipMatchfound && (infoIP == 0) && ((infoURL.Ptr == NULL) || (infoURL.Len == 0))) + { + info = announceInfo; + continue; + } + + // if there is a source info for ANNOUNCE any URL from an IP, keep this info until a better one is found + if ((infoIP == inIP) && ((infoURL.Ptr == NULL) || (infoURL.Len == 0))) + { + info = announceInfo; + ipMatchfound = true; + continue; + } + + // if there is a source info for ANNOUNCE for the URL + IP combo, return this as the source info + // strip leading path delimiters + UInt32 count = 0; + while (count < inURL.Len && inURL.Ptr[count] == '/') + { count ++; + } + StrPtrLen announcedURL(&inURL.Ptr[count],inURL.Len - count); + + // strip leading path delimiters + count = 0; + while (count < infoURL.Len && infoURL.Ptr[count] == '/') + { count ++; + } + StrPtrLen configMountPoint(&infoURL.Ptr[count],infoURL.Len - count); + + if ((infoIP == inIP) && (configMountPoint.Equal(announcedURL))) + { + info = announceInfo; + break; + } + } + + // cast the source info to RTSPSourceInfo because all the elements of the sAnnouncedQueue + // are RTSPSourceInfos anyway. + // Don't really like this but I suppose it will do for now + return info; +} + +RTSPSourceInfo* FindExistingRelayInfo(UInt32 inIP, StrPtrLen& inURL) +{ + if (inURL.Ptr == NULL) + return NULL; + + for (OSQueueIter iter(sRTSPSourceInfoQueue); !iter.IsDone(); iter.Next()) + { + RTSPSourceInfo* info = (RTSPSourceInfo*)iter.GetCurrent()->GetEnclosingObject(); + + if ((inIP == info->GetAnnounceActualIP()) && inURL.Equal(info->GetSourceURL())) + return info; + } + + return NULL; +} + +void RereadRelayPrefs(XMLParser* prefsParser) +{ + // Construct a SourceInfo object for each relayable thingy + OSQueue sourceInfoQueue; + ClearSourceInfos(sAnnouncedQueue); + FindSourceInfos(&sourceInfoQueue, sAnnouncedQueue, prefsParser); + + // Ok, we have all our SourceInfo objects. Now, lets alter the list of Relay + // outputs to match + + // Go through the queue of Relay outputs, removing the source Infos that + // are already going + OSMutexLocker locker(RelayOutput::GetQueueMutex()); + + // We must reread the relay stats URL here, because we have the RelayOutput + // mutex so we know that Filter can't be filtering now + delete [] sRequestHeader.Ptr; + sRequestHeader.Ptr = NULL; + sRequestHeader.Len = 0; + + if ((sRelayStatsURL != NULL) && (::strlen(sRelayStatsURL) > 0)) + { + // sRequestHeader line should look like: GET /sRelayStatsURL HTTP + sRequestHeader.Ptr = NEW char[::strlen(sRelayStatsURL) + 15]; + ::strcpy(sRequestHeader.Ptr, "GET /"); + ::strcat(sRequestHeader.Ptr, sRelayStatsURL); + ::strcat(sRequestHeader.Ptr, " HTTP"); + sRequestHeader.Len = ::strlen(sRequestHeader.Ptr); + } + + for (OSQueueIter iter(RelayOutput::GetOutputQueue()); !iter.IsDone(); ) + { + RelayOutput* theOutput = (RelayOutput*)iter.GetCurrent()->GetEnclosingObject(); + + Bool16 stillActive = false; + + // Check to see if this RelayOutput matches one of the streams in the + // SourceInfo queue. If it does, this has 2 implications: + // + // 1) The stream in the SourceInfo that matches should NOT be setup as a + // new RelayOutput, because it already exists. (Equal will set a flag in + // the StreamInfo object saying as much) + // + // 2) This particular RelayOutput should remain (not be deleted). + for (OSQueueIter iter2(&sourceInfoQueue); !iter2.IsDone(); iter2.Next()) + { + SourceInfo* theInfo = + ((SourceInfoQueueElem*)iter2.GetCurrent()->GetEnclosingObject())->fSourceInfo; + if (theOutput->Equal(theInfo)) + { + stillActive = true; + break; + } + } + + // Also check if this RelayOutput matches one of the streams in the + // AnnouncedInfo queue. If it does, the implications are same as above. + // Perform this check only if it is not already found in the first queue + if(!stillActive) + { + for (OSQueueIter iter3(sAnnouncedQueue); !iter3.IsDone(); iter3.Next()) + { + SourceInfo* theInfo = + ((SourceInfoQueueElem*)iter3.GetCurrent()->GetEnclosingObject())->fSourceInfo; + if (theOutput->Equal(theInfo)) + { + stillActive = true; + break; + } + } + } + + iter.Next(); + + // This relay output is no longer on our list + if (!stillActive) + RemoveOutput(theOutput, theOutput->GetRelaySession()); + } + + // We've pruned the list of RelayOutputs of all outputs that are no longer + // going on. Now, all that is left to do is to create any new outputs (and sessions) + // that are just starting up + + for (OSQueueIter iter4(&sourceInfoQueue); !iter4.IsDone(); iter4.Next()) + { + SourceInfoQueueElem* theElem = (SourceInfoQueueElem*)iter4.GetCurrent()->GetEnclosingObject(); + if (theElem->fSourceInfo->GetNumNewOutputs() == 0) // Because we've already checked for Outputs + continue; // That already exist, we know which ones are new + + RelaySession* theSession = FindSession(theElem); + + if (theSession == NULL) + { + if (theElem->fIsRTSPSourceInfo) + { + theElem->fShouldDelete = false; +// RTSPSourceInfo* theInfo = dynamic_cast(theElem->fSourceInfo); + RTSPSourceInfo* theInfo = (RTSPSourceInfo *)(theElem->fSourceInfo); + theInfo->StartSessionCreatorTask(sSessionQueue, sRTSPSourceInfoQueue); + } + else + { + theSession = CreateSession(theElem); + if (theSession != NULL) + AddOutputs(theElem->fSourceInfo, theSession, theElem->fIsRTSPSourceInfo); + } + } + else + { + AddOutputs(theElem->fSourceInfo, theSession, theElem->fIsRTSPSourceInfo); + } + } + + // For the announced source infos, find a session that is already active, and add any + // output that isn't already set up. If a session isn't found, don't create it! + for (OSQueueIter iter5(sAnnouncedQueue); !iter5.IsDone(); iter5.Next()) + { + SourceInfoQueueElem* theElem = (SourceInfoQueueElem*)iter5.GetCurrent()->GetEnclosingObject(); + if (theElem->fSourceInfo->GetNumNewOutputs() == 0) // Because we've already checked for Outputs + continue; // That already exist, we know which ones are new + + for (OSQueueIter iter6(sSessionQueue); !iter6.IsDone(); ) + { + + RelaySession* theSession = FindNextSession(theElem, &iter6); + if(!iter6.IsDone()) + iter6.Next(); + + if (theSession == NULL) + continue; + + AddOutputs(theElem->fSourceInfo, theSession, theElem->fIsRTSPSourceInfo); + } + } + + // Clear only the first source info queue. The announced source info queue + // must be kept around so that we know which announced broadcasts to relay + ClearSourceInfos(&sourceInfoQueue); +} + +void AddOutputs(SourceInfo* inInfo, RelaySession* inSession, Bool16 inIsRTSPSourceInfo) +{ + for (UInt32 x = 0; x < inInfo->GetNumOutputs(); x++) + { + SourceInfo::OutputInfo* theOutputInfo = inInfo->GetOutputInfo(x); + if (theOutputInfo->fAlreadySetup) + continue; + + RelayOutput* theOutput = NEW RelayOutput(inInfo, x, inSession, inIsRTSPSourceInfo); + if (theOutput->IsValid()) + inSession->AddOutput(theOutput, false); + else + delete theOutput; + } +} + +void FindSourceInfos(OSQueue* inSessionQueue, OSQueue* inAnnouncedQueue, XMLParser* prefsParser) +{ + SourceInfoQueueElem* theElem = NULL; + + XMLTag* tag = prefsParser->GetRootTag(); + XMLTag* relayTag; + UInt32 index = 0; + while ((relayTag = tag->GetEmbeddedTagByNameAndAttr("OBJECT", "TYPE", "relay", index)) != NULL) + { + index++; + + XMLTag* enabledTag = relayTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "enabled"); + if ((enabledTag != NULL) && (enabledTag->GetValue() != NULL) && !strcmp(enabledTag->GetValue(), "false")) + continue; // this relay pref is disabled + + XMLTag* sourceTag = relayTag->GetEmbeddedTagByNameAndAttr("OBJECT", "CLASS", "source"); + if (sourceTag == NULL) + continue; + + char* type = sourceTag->GetAttributeValue("TYPE"); + if (type == NULL) + continue; + + if (!strcmp(type, "relay_sdp")) + { + XMLTag* fileNameTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "sdp_path"); + if (!fileNameTag) + continue; + + char* fileName = fileNameTag->GetValue(); + if (fileName == NULL) + continue; + + StrPtrLen theFileData; + (void)QTSSModuleUtils::ReadEntireFile(fileName, &theFileData); + if (theFileData.Len > 0) // There is a Relay SDP file! + { + RelaySDPSourceInfo* theInfo = NEW RelaySDPSourceInfo(&theFileData); + + // Only keep this sdp file around if there are input streams & + // output streams described in it. + if ((theInfo->GetNumStreams() == 0) || (theInfo->GetNumOutputs() == 0)) + delete theInfo; + else + { + theElem = NEW SourceInfoQueueElem(theInfo, false); + inSessionQueue->EnQueue(&theElem->fElem); + } + delete [] theFileData.Ptr;// We don't need the file data anymore + } + } + else if (!strcmp(type, "udp_source")) + { + RCFSourceInfo* theRCFInfo = NEW RCFSourceInfo(relayTag); + // Only keep this sdp file around if there are input streams & + // output streams described in it. + if ((theRCFInfo->GetNumStreams() == 0) || (theRCFInfo->GetNumOutputs() == 0)) + delete theRCFInfo; + else + { + theElem = NEW SourceInfoQueueElem(theRCFInfo, false); + inSessionQueue->EnQueue(&theElem->fElem); + } + } + else if (!strcmp(type, "rtsp_source")) + { + // This is an rtsp source, so go through the describe, setup, play connect + // process before moving on. + RTSPSourceInfo* theRTSPInfo = NEW RTSPSourceInfo(0); + QTSS_Error theErr = theRTSPInfo->ParsePrefs(relayTag, false); + if (theErr == QTSS_NoErr) + { + // We have to do this *after* doing the DESCRIBE because parsing + // the relay destinations depends on information in the SDP + theRTSPInfo->ParseRelayDestinations(relayTag); + + if (theRTSPInfo->GetNumOutputs() == 0) + delete theRTSPInfo; + else + { + theElem = NEW SourceInfoQueueElem(theRTSPInfo, true); + inSessionQueue->EnQueue(&theElem->fElem); + } + } + else + delete theRTSPInfo; + } + else if (!strcmp(type, "announced_source")) + { + // This is an announced rtsp source, so just set up an RTSPSourceInfo object and parse + // through the destinations. Leave the rest for later. + RTSPSourceInfo* theRTSPInfo = NEW RTSPSourceInfo(true); + QTSS_Error theErr = theRTSPInfo->ParsePrefs(relayTag, true); + if (theErr == QTSS_NoErr) + { + theRTSPInfo->ParseRelayDestinations(relayTag); + + if (theRTSPInfo->GetNumOutputs() == 0) + delete theRTSPInfo; + else + { + theElem = NEW SourceInfoQueueElem(theRTSPInfo, true); + // put this sourceinfo elem on the global sAnnouncedQueue so that + // its session can be set up when an ANNOUNCE is received + inAnnouncedQueue->EnQueue(&theElem->fElem); + } + } + else + delete theRTSPInfo; + } + } +} + +void ClearSourceInfos(OSQueue* inQueue) +{ + for (OSQueueIter theIter(inQueue); !theIter.IsDone(); ) + { + // Get the current queue element, and make sure to move onto the + // next one, as the current one is going away. + SourceInfoQueueElem* theElem = (SourceInfoQueueElem*)theIter.GetCurrent()->GetEnclosingObject(); + + theIter.Next(); + + inQueue->Remove(&theElem->fElem); + + // If we're supposed to delete this SourceInfo, then do so + if (theElem->fShouldDelete) + delete theElem->fSourceInfo; + delete theElem; + } +} + +QTSS_Error RouteAuthorization(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_Error theErr = QTSS_NoErr; + UInt32 theLen = 0; + + // Get the Remote IP address + UInt32* theIP = 0; + theErr = QTSS_GetValuePtr(inParams->inRTSPSession, qtssRTSPSesRemoteAddr, 0, (void**)&theIP, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(UInt32))) + { + Assert(0); + return QTSS_NoErr; + } + + if (*theIP != INADDR_LOOPBACK) + return QTSS_NoErr; + + // Get the Remote Port + UInt16* thePort = 0; + theLen = 0; + theErr = QTSS_GetValuePtr(inParams->inRTSPSession, qtssRTSPSesRemotePort, 0, (void**)&thePort, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(UInt16))) + { + Assert(0); + return QTSS_NoErr; + } + + // Check to see if any of the outputs has an announced source + // whose source URI and request port match theURL and thePort + if (IsRelayRequest(*thePort)) + { + // ask server to skip authorization + if (QTSS_NoErr != QTSS_SetValue(inParams->inRTSPRequest,qtssRTSPReqSkipAuthorization, 0, &sSkipAuthorization, sizeof(sSkipAuthorization))) + return QTSS_RequestFailed; // bail on the request if the SetValue call fails! + + // set the client session QTSSRelayModuleIsRelaySession attribute to true + if (QTSS_NoErr != QTSS_SetValue(inParams->inClientSession, sIsRelaySessionAttr, 0,(void *)&sIsRelaySession, sizeof(sIsRelaySession))) + return QTSS_RequestFailed; // bail on the request if the SetValue call fails! + } + + return QTSS_NoErr; +} + +Bool16 IsRelayRequest(UInt16 inPort) +{ + for (OSQueueIter iter(sRTSPSourceInfoQueue); !iter.IsDone(); iter.Next()) + { + RTSPSourceInfo* info = (RTSPSourceInfo*)iter.GetCurrent()->GetEnclosingObject(); + + if (inPort == (info->GetClientSocket())->GetLocalPort()) + return true; + } + + return false; +} + +void ReadRelayPrefsFile() +{ + // check to see if file has changed + struct stat buf; + if (::stat(sRelayPrefs, &buf) >= 0) + { + if (sRelayPrefModDate == buf.st_mtime) + return; + + sRelayPrefModDate = buf.st_mtime; + } + + XMLParser* xmlFile = new XMLParser(sRelayPrefs); + + if (!xmlFile->DoesFileExist()) + { + delete xmlFile; + return; // just return with no error logged + } + + char errorBuffer[1000]; + if (!xmlFile->ParseFile(errorBuffer, sizeof(errorBuffer))) + { + // log this error to the error log + QTSSModuleUtils::LogError( qtssWarningVerbosity, sRelayModulePrefParseErr, 0, NULL, NULL); + delete xmlFile; + return; // file was invalid, so no source infos + } + + if (!CheckDNSNames(xmlFile, false)) + { + // do reread prefs stuff now + RereadRelayPrefs(xmlFile); + delete xmlFile; + return; + } + + // otherwise we need to do the DNS resolution on another thread + DNSResolverThread::ResolveRelayPrefs(xmlFile); // will keep or delete xmlFile +} + +Bool16 CheckDNSNames(XMLParser* prefsFile, Bool16 doResolution) +{ + XMLTag* tag = prefsFile->GetRootTag(); + XMLTag* relayTag; + XMLTag* prefTag; + UInt32 index = 0; + while ((relayTag = tag->GetEmbeddedTagByNameAndAttr("OBJECT", "TYPE", "relay", index)) != NULL) + { + index++; + + XMLTag* enabledTag = relayTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "enabled"); + if ((enabledTag != NULL) && (enabledTag->GetValue() != NULL) && !strcmp(enabledTag->GetValue(), "false")) + continue; // this relay pref is disabled + + XMLTag* sourceTag = relayTag->GetEmbeddedTagByNameAndAttr("OBJECT", "CLASS", "source"); + if (sourceTag == NULL) + continue; + + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "in_addr"); + if (prefTag != NULL) + { + char* destAddrStr = prefTag->GetValue(); + if (destAddrStr != NULL) + if (SocketUtils::ConvertStringToAddr(destAddrStr) == INADDR_NONE) + { + if (doResolution) + ResolveDNSAddr(prefTag); + else + return true; + } + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "source_addr"); + if (prefTag != NULL) + { + char* srcAddrStr = prefTag->GetValue(); + if (srcAddrStr != NULL) + if (SocketUtils::ConvertStringToAddr(srcAddrStr) == INADDR_NONE) + { + if (doResolution) + ResolveDNSAddr(prefTag); + else + return true; + } + } + + UInt32 numTags = relayTag->GetNumEmbeddedTags(); + for (UInt32 y = 0; y < numTags; y++) + { + XMLTag* destTag = relayTag->GetEmbeddedTagByNameAndAttr("OBJECT", "CLASS", "destination", y); + if (destTag == NULL) + break; + + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "out_addr"); + if (prefTag != NULL) + { + char* outAddrStr = prefTag->GetValue(); + if (outAddrStr != NULL) + if (SocketUtils::ConvertStringToAddr(outAddrStr) == INADDR_NONE) + { + if (doResolution) + ResolveDNSAddr(prefTag); + else + return true; + } + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "dest_addr"); + if (prefTag != NULL) + { + char* destAddrStr = prefTag->GetValue(); + if (destAddrStr != NULL) + if (SocketUtils::ConvertStringToAddr(destAddrStr) == INADDR_NONE) + { + if (doResolution) + ResolveDNSAddr(prefTag); + else + return true; + } + } + } + } + + return false; +} + +void ResolveDNSAddr(XMLTag* tag) +{ + struct in_addr inAddr; + struct hostent* theHostent = ::gethostbyname(tag->GetValue()); + if (theHostent != NULL) + { + char buffer[50]; + StrPtrLen temp(buffer); + inAddr.s_addr = *(UInt32*)(theHostent->h_addr_list[0]); + SocketUtils::ConvertAddrToString(inAddr, &temp); + tag->SetValue(buffer); + } +} + diff --git a/APIModules/QTSSReflectorModule/QTSSRelayModule.h b/APIModules/QTSSReflectorModule/QTSSRelayModule.h new file mode 100644 index 0000000..a2dcd94 --- /dev/null +++ b/APIModules/QTSSReflectorModule/QTSSRelayModule.h @@ -0,0 +1,44 @@ +/* + * + * @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: QTSSReflectorModule.h + + Contains: QTSS API module + + + +*/ + +#ifndef _QTSSRELAYMODULE_H_ +#define _QTSSRELAYMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSRelayModule_Main(void* inPrivateArgs); +} + +#endif //_QTSSRELAYMODULE_H_ diff --git a/APIModules/QTSSReflectorModule/QTSSSplitterModule.cpp b/APIModules/QTSSReflectorModule/QTSSSplitterModule.cpp new file mode 100644 index 0000000..576c411 --- /dev/null +++ b/APIModules/QTSSReflectorModule/QTSSSplitterModule.cpp @@ -0,0 +1,667 @@ +/* + * + * @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: QTSSSplitterModule.cpp + + Contains: Implementation of QTSSSplitterModule class. + + + + + +*/ + +#include "QTSSSplitterModule.h" +#include "QTSSModuleUtils.h" +#include "ReflectorSession.h" +#include "OSArrayObjectDeleter.h" +#include "OSMemory.h" + +//ReflectorOutput objects +#include "RTPSessionOutput.h" +#include "RelayOutput.h" + +//SourceInfo objects +#include "RTSPSourceInfo.h" + +// Fixes +#include "QTSSMemoryDeleter.h" +#include "FilePrefsSource.h" + +// ATTRIBUTES + +static QTSS_AttributeID sOutputAttr = qtssIllegalAttrID; +static QTSS_AttributeID sSessionAttr = qtssIllegalAttrID; +static QTSS_AttributeID sStreamCookieAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sRemoteHostRespondedWithAnErrorErr = qtssIllegalAttrID; +static QTSS_AttributeID sRemoteHostRefusedConnectionErr = qtssIllegalAttrID; +static QTSS_AttributeID sExpectedDigitFilenameErr = qtssIllegalAttrID; +static QTSS_AttributeID sBadTrackIDErr = qtssIllegalAttrID; + +// STATIC DATA + +static const UInt32 kSessionStartingIdleTimeInMsec = 20; +static const StrPtrLen kCacheControlHeader("no-cache"); +static QTSS_PrefsObject sServerPrefs = NULL; + +static OSRefTable* sSessionMap = NULL; + +// Important strings +static StrPtrLen sRCFSuffix(".rcf"); +static StrPtrLen sRTSPSourceStr("relay_source"); + +// FUNCTION PROTOTYPES + +static QTSS_Error QTSSSplitterModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error Shutdown(); +static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams); +static ReflectorSession* FindOrCreateSession(StrPtrLen* inPath, QTSS_StandardRTSP_Params* inParams); +static QTSS_Error HandleSourceInfoErr(QTSS_Error rtspSourceInfoErr, QTSS_StandardRTSP_Params* inParams, + ReflectorSession* inSession, RTSPClient* inClient); +static void DeleteSessionOnError(ReflectorSession* inSession, QTSS_ClientSessionObject inCliSession); +static void NullOutSessionAttr(QTSS_ClientSessionObject inSession); +static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession); +static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession); +static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams); +static void IssueTeardown(ReflectorSession* inSession); +static void RequestSocketEvent(QTSS_StreamRef inStream, UInt32 inEventMask); + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSSplitterModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSSplitterModuleDispatch); +} + + +QTSS_Error QTSSSplitterModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RTSPPreProcessor_Role: + return ProcessRTSPRequest(&inParams->rtspRequestParams); + case QTSS_ClientSessionClosing_Role: + return DestroySession(&inParams->clientSessionClosingParams); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_Shutdown_Role); + (void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role); + (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); + + // Add text messages attributes + static char* sRemoteHostRespondedWithAnErrorName = "QTSSSplitterModuleRemoteHostError"; + static char* sRemoteHostRefusedConnectionName = "QTSSSplitterModuleRemoteHostRefused"; + static char* sExpectedDigitFilenameName = "QTSSSplitterModuleExpectedDigitFilename"; + static char* sBadTrackIDErrName = "QTSSSplitterModuleBadTrackID"; + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sRemoteHostRespondedWithAnErrorName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sRemoteHostRespondedWithAnErrorName, &sRemoteHostRespondedWithAnErrorErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sRemoteHostRefusedConnectionName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sRemoteHostRefusedConnectionName, &sRemoteHostRefusedConnectionErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadTrackIDErrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadTrackIDErrName, &sBadTrackIDErr); + + // Add an RTP session attribute for tracking ReflectorSession objects + static char* sOutputName = "QTSSSplitterModuleOutput"; + static char* sSessionName= "QTSSSplitterModuleSession"; + static char* sStreamCookieName = "QTSSSplitterModuleStreamCookie"; + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sOutputName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sOutputName, &sOutputAttr); + + (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sSessionName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssClientSessionObjectType, sSessionName, &sSessionAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sStreamCookieName, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sStreamCookieName, &sStreamCookieAttr); + + // Reflector stream needs to setup some parameters too. + ReflectorStream::Register(); + // RTPSessionOutput needs to do the same + RTPSessionOutput::Register(); + + // Tell the server our name! + static char* sModuleName = "QTSSSplitterModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sSessionMap = NEW OSRefTable(); + sServerPrefs = inParams->inPrefs; + + // + // Instead of passing our own module prefs object, as one might expect, + // here we pass in the QTSSReflectorModule's, because the prefs that + // apply to ReflectorStream are stored in that module's prefs + StrPtrLen theReflectorModule("QTSSReflectorModule"); + QTSS_ModulePrefsObject theReflectorPrefs = + QTSSModuleUtils::GetModulePrefsObject(QTSSModuleUtils::GetModuleObjectByName(theReflectorModule)); + + // Call helper class initializers + ReflectorStream::Initialize(theReflectorPrefs); + ReflectorSession::Initialize(); + + // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN + static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod }; + QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5); + + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + return QTSS_NoErr; +} + +QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_RTSPMethod* theMethod = NULL; + UInt32 theLen = 0; + if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0, + (void**)&theMethod, &theLen) != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod))) + { + Assert(0); + return QTSS_RequestFailed; + } + + if (*theMethod == qtssDescribeMethod) + return DoDescribe(inParams); + + RTPSessionOutput** theOutput = NULL; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*))) + return QTSS_RequestFailed; + + switch (*theMethod) + { + case qtssSetupMethod: + return DoSetup(inParams, (*theOutput)->GetReflectorSession()); + case qtssPlayMethod: + return DoPlay(inParams, (*theOutput)->GetReflectorSession()); + case qtssTeardownMethod: + // Tell the server that this session should be killed, and send a TEARDOWN response + (void)QTSS_Teardown(inParams->inClientSession); + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0); + break; + case qtssPauseMethod: + (void)QTSS_Pause(inParams->inClientSession); + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0); + break; + default: + break; + } + return QTSS_NoErr; +} + + +QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams) +{ + QTSS_Error theErr = QTSS_NoErr; + + // If this URL doesn't end with an .rcf, don't even bother. Ah, if only the QTSSReflectorModule + // could make this same check as well + StrPtrLen theURI; + (void)QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqURI, 0, (void**)&theURI.Ptr, &theURI.Len); + if ((theURI.Len < sRCFSuffix.Len) || (!sRCFSuffix.EqualIgnoreCase(&theURI.Ptr[theURI.Len - sRCFSuffix.Len], sRCFSuffix.Len))) + return QTSS_NoErr; + + // Check and see if we are in the process of setting this connection up already + ReflectorSession* theSession = NULL; + UInt32 theLen = sizeof(ReflectorSession*); + (void)QTSS_GetValue(inParams->inClientSession, sSessionAttr, 0, (void*)&theSession, &theLen); + + if (theSession == NULL) + { + // If we are not already in the process of initializing and setting up this ReflectorSession, + // attempt to create one or attach to one. + + // Check and see if the full path to this file matches an existing ReflectorSession + StrPtrLen thePathPtr; + OSCharArrayDeleter rcfPath(QTSSModuleUtils::GetFullPath( inParams->inRTSPRequest, + qtssRTSPReqFilePath, + &thePathPtr.Len, NULL)); + + thePathPtr.Ptr = rcfPath.GetObject(); + theSession = FindOrCreateSession(&thePathPtr, inParams); + // If this function returned an error, this request shouldn't be handled by this module + if (theSession == NULL) + return QTSS_NoErr; + } + if (!((RTSPSourceInfo*)theSession->GetSourceInfo())->IsDescribeComplete()) + { + // Only the session owner need worry about this code. this is an easy. + // Way of checking this. + Assert(theLen == sizeof(ReflectorSession*)); + + // If we haven't finished the describe yet, call + // Describe again on the RTSPSourceInfo object. + QTSS_Error theErr = ((RTSPSourceInfo*)theSession->GetSourceInfo())->Describe(); + if (theErr != QTSS_NoErr) + return HandleSourceInfoErr(theErr, inParams, theSession, + ((RTSPSourceInfo*)theSession->GetSourceInfo())->GetRTSPClient()); + else + { + // Describe has completed. At this point we can setup the ReflectorSession. + // However, tell it not to consider this session completely setup yet, as + theErr = theSession->SetupReflectorSession(theSession->GetSourceInfo(), inParams, ReflectorSession::kDontMarkSetup); + if (theErr != QTSS_NoErr) + { + // If we get an error here, for some reason we couldn't bind the ports, etc, etc. + // Just abort + DeleteSessionOnError(theSession, inParams->inClientSession); + return theErr; + } + } + } + + if (!theSession->IsSetup()) + { + // Only the session owner need worry about this code. this is an easy. + // Way of checking this. + Assert(theLen == sizeof(ReflectorSession*)); + + // If we get here, the DESCRIBE has completed, but if we are the owner that isn't enough. + // We need to make sure that the SETUP and PLAY requests execute as well. + theErr = ((RTSPSourceInfo*)theSession->GetSourceInfo())->SetupAndPlay(); + if (theErr != QTSS_NoErr) + return HandleSourceInfoErr(theErr, inParams, theSession, + ((RTSPSourceInfo*)theSession->GetSourceInfo())->GetRTSPClient()); + + // We've completed the SETUP and PLAY process if we are here. The ReflectorSession + // is completely setup. + theSession->ManuallyMarkSetup(); + + // NULL out the sSessionAttr, we don't need it anymore. + NullOutSessionAttr(inParams->inClientSession); + } + + //ok, we've found or setup the proper reflector session, create an RTPSessionOutput object, + //and add it to the session's list of outputs + RTPSessionOutput* theNewOutput = NEW RTPSessionOutput(inParams->inClientSession, theSession, sServerPrefs, sStreamCookieAttr ); + theSession->AddOutput(theNewOutput, true); + + // And vice-versa, store this reflector session in the RTP session. + (void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theNewOutput, sizeof(theNewOutput)); + + // Finally, send the DESCRIBE response + + //above function has signalled that this request belongs to us, so let's respond + iovec theDescribeVec[2] = { 0 }; + + Assert(theSession->GetLocalSDP()->Ptr != NULL); + theDescribeVec[1].iov_base = theSession->GetLocalSDP()->Ptr; + theDescribeVec[1].iov_len = theSession->GetLocalSDP()->Len; + (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + QTSSModuleUtils::SendDescribeResponse(inParams->inRTSPRequest, inParams->inClientSession, + &theDescribeVec[0], 2, theSession->GetLocalSDP()->Len); + return QTSS_NoErr; +} + +ReflectorSession* FindOrCreateSession(StrPtrLen* inPath, QTSS_StandardRTSP_Params* inParams) +{ + // This function assumes that inPath is NULL terminated + + StrPtrLen theFileData; + RTSPSourceInfo* theInfo = NULL; + + (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileData); + if (theFileData.Len > 0) + theInfo = NEW RTSPSourceInfo(Socket::kNonBlockingSocketType); + else + return NULL; + + // We need to interpret this file as a standard prefs file, so let the + // FilePrefsSource object parse it, then call ParsePrefs on the RTSPSourceInfo object, + // which will parse out the RCF metadata. + + FilePrefsSource thePrefsSource(true);// Allow duplicates + (void)thePrefsSource.InitFromConfigFile(inPath->Ptr); + QTSS_Error theErr = theInfo->ParsePrefs(&thePrefsSource, 0); + if (theErr != QTSS_NoErr) + { + delete theInfo; + return NULL; + } + + // Ok, look for a reflector session matching the URL specified in the RCF file. + // A unique broadcast is defined by the URL, the URL is the argument to resolve. + + OSMutexLocker locker(sSessionMap->GetMutex()); + OSRef* theSessionRef = sSessionMap->Resolve(theInfo->GetRTSPClient()->GetURL()); + ReflectorSession* theSession = NULL; + + if (theSessionRef == NULL) + { + //If this URL doesn't already have a reflector session, we must make a new + //one. We already have the proper sourceInfo object, so we only need to construct the session + + theSession = NEW ReflectorSession(theInfo->GetRTSPClient()->GetURL(), theInfo); + + //put the session's ID into the session map. + theErr = sSessionMap->Register(theSession->GetRef()); + Assert(theErr == QTSS_NoErr); + + //unless we do this, the refcount won't increment (and we'll delete the session prematurely + OSRef* debug = sSessionMap->Resolve(theInfo->GetRTSPClient()->GetURL()); + Assert(debug == theSession->GetRef()); + + // Create a socket stream for the TCP socket in the RTSPClient object. The socket stream will + // allow this module to receive events on the socket + QTSS_StreamRef theSockStream = NULL; + theErr = QTSS_CreateStreamFromSocket(theInfo->GetRTSPClient()->GetSocket()->GetSocket()->GetSocketFD(), &theSockStream); + Assert(theErr == QTSS_NoErr); + Assert(theSockStream != NULL); + + // Store the socket stream in the Reflector Session so we can get at it easily later on + theSession->SetSocketStream(theSockStream); + + // This RTSP session is the "owner" of this ReflectorSession, and will be responsible + // for setting it up properly, so we should make sure this attribute gets set + UInt32 theLen = sizeof(theSession); + theErr = QTSS_SetValue(inParams->inClientSession, sSessionAttr, 0, (void*)&theSession, theLen); + Assert(theErr == QTSS_NoErr); + } + else + { + // We aren't the owner of this ReflectorSession, and only the owner needs to keep this + // RTSPSourceInfo object around. + delete theInfo; + + theSession = (ReflectorSession*)theSessionRef->GetObject(); + + if (!theSession->IsSetup()) + { + // We are not the creator of this session, and it may not be setup yet. If it isn't, + // we should simply wait for it to be setup. + sSessionMap->Release(theSession->GetRef()); + + // Give the owner some time to finish setting it up. + (void)QTSS_SetIdleTimer(kSessionStartingIdleTimeInMsec); + return NULL; // There isn't a completed session... yet............. + } + } + + Assert(theSession != NULL); + return theSession; +} + +QTSS_Error HandleSourceInfoErr(QTSS_Error rtspSourceInfoErr, QTSS_StandardRTSP_Params* inParams, + ReflectorSession* inSession, RTSPClient* inClient) +{ + // If we get an EAGAIN here, the DESCRIBE hasn't completed yet + if ((rtspSourceInfoErr == EAGAIN) || (rtspSourceInfoErr == EINPROGRESS)) + { + // We're making an assumption here that inClient only uses one socket to connect to + // the server. We only have one stream, so we have to make that assumption. + + // Note that it is not necessary to have any kind of timeout here, because the server + // naturally times out idle connections. If the server doesn't respond for awhile, + // this session will naturally go away + inClient->GetSocket()->GetSocket()->DontAutoCleanup(); + RequestSocketEvent(inSession->GetSocketStream(), inClient->GetSocket()->GetEventMask()); + return QTSS_NoErr; // We'll get called in the same method again when there is more work to do + } + + // We've encountered a fatal error for this session, so delete it. + DeleteSessionOnError(inSession, inParams->inClientSession); + + if (rtspSourceInfoErr == QTSS_RequestFailed) + { + // This happens if the remote host responded with an error. + char tempBuf[20]; + qtss_sprintf(tempBuf, "%"_U32BITARG_"", inClient->GetStatus()); + StrPtrLen tempBufPtr(&tempBuf[0]); + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerGatewayTimeout, + sRemoteHostRespondedWithAnErrorErr, &tempBufPtr); + } + else + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerGatewayTimeout, + sRemoteHostRefusedConnectionErr); +} + +void DeleteSessionOnError(ReflectorSession* inSession, QTSS_ClientSessionObject inCliSession) +{ + // Make sure to destroy the socket stream as well + Assert(inSession->GetSocketStream() != NULL); + (void)QTSS_DestroySocketStream(inSession->GetSocketStream()); + + OSMutexLocker locker (sSessionMap->GetMutex()); + //decrement the ref count + sSessionMap->Release(inSession->GetRef()); + + // We are here if we are the owner of this session and we encountered an error + // while trying to setup the session. We have the session map mutex, so the + // refcount at this point *must* be 0. + Assert(inSession->GetRef()->GetRefCount() == 0); + sSessionMap->UnRegister(inSession->GetRef()); + delete inSession; + + // Make sure the session is NULLd out, because it's deleted now! + NullOutSessionAttr(inCliSession); +} + +void NullOutSessionAttr(QTSS_ClientSessionObject inSession) +{ + ReflectorSession* theNull = NULL; + UInt32 theLen = sizeof(theNull); + QTSS_Error theErr = QTSS_SetValue(inSession, sSessionAttr, 0, (void*)&theNull, theLen); + Assert(theErr == QTSS_NoErr); +} + +QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession) +{ + //unless there is a digit at the end of this path (representing trackID), don't + //even bother with the request + char* theDigitStr = NULL; + (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr); + QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr); + if (theDigitStr == NULL) + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, sExpectedDigitFilenameErr); + + UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10); + + QTSS_Error theErr = QTSS_NoErr; + + // Get info about this trackID + SourceInfo::StreamInfo* theStreamInfo = inSession->GetSourceInfo()->GetStreamInfoByTrackID(theTrackID); + // If theStreamInfo is NULL, we don't have a legit track, so return an error + if (theStreamInfo == NULL) + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, + sBadTrackIDErr); + + StrPtrLen* thePayloadName = &theStreamInfo->fPayloadName; + QTSS_RTPPayloadType thePayloadType = theStreamInfo->fPayloadType; + + QTSS_RTPStreamObject newStream = NULL; + { + // Ok, this is completely crazy but I can't think of a better way to do this that's + // safe so we'll do it this way for now. Because the ReflectorStreams use this session's + // stream queue, we need to make sure that each ReflectorStream is not reflecting to this + // session while we call QTSS_AddRTPStream. One brutal way to do this is to grab each + // ReflectorStream's mutex, which will stop every reflector stream from running. + + for (UInt32 x = 0; x < inSession->GetNumStreams(); x++) + inSession->GetStreamByIndex(x)->GetMutex()->Lock(); + + theErr = QTSS_AddRTPStream(inParams->inClientSession, inParams->inRTSPRequest, &newStream, 0); + + for (UInt32 y = 0; y < inSession->GetNumStreams(); y++) + inSession->GetStreamByIndex(y)->GetMutex()->Unlock(); + + if (theErr != QTSS_NoErr) + return theErr; + } + + // Set up dictionary items for this stream + theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayloadName->Ptr, thePayloadName->Len); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType)); + Assert(theErr == QTSS_NoErr); + theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID)); + Assert(theErr == QTSS_NoErr); + + // Place the stream cookie in this stream for future reference + void* theStreamCookie = inSession->GetStreamCookie(theTrackID); + Assert(theStreamCookie != NULL); + theErr = QTSS_SetValue(newStream, sStreamCookieAttr, 0, &theStreamCookie, sizeof(theStreamCookie)); + Assert(theErr == QTSS_NoErr); + + // Set the number of quality levels. + static UInt32 sNumQualityLevels = ReflectorSession::kNumQualityLevels; + theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels)); + Assert(theErr == QTSS_NoErr); + + //send the setup response + (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader, + kCacheControlHeader.Ptr, kCacheControlHeader.Len); + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, newStream, 0); + return QTSS_NoErr; +} + + +QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession) +{ + // Tell the session what the bitrate of this reflection is. This is nice for logging, + // it also allows the server to scale the TCP buffer size appropriately if we are + // interleaving the data over TCP. This must be set before calling QTSS_Play so the + // server can use it from within QTSS_Play + UInt32 bitsPerSecond = inSession->GetBitRate(); + (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond)); + + //Server shouldn't send RTCP (reflector does it), but the server should append the server info app packet + QTSS_Error theErr = QTSS_Play(inParams->inClientSession, inParams->inRTSPRequest, qtssPlayFlagsAppendServerInfo); + if (theErr != QTSS_NoErr) + return theErr; + + //and send a standard play response + (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0); + return QTSS_NoErr; +} + +QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams) +{ + // Check and see if we are in the process of tearing down this connection already + ReflectorSession* theSession = NULL; + UInt32 theLen = sizeof(ReflectorSession*); + (void)QTSS_GetValue(inParams->inClientSession, sSessionAttr, 0, (void*)&theSession, &theLen); + + if (theSession != NULL) + IssueTeardown(theSession); + else + { + RTPSessionOutput** theOutput = NULL; + QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen); + if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*)) || (theOutput == NULL)) + return QTSS_RequestFailed; + + // This function removes the output from the ReflectorSession, then + // checks to see if the session should go away. If it should, this deletes it + theSession = (*theOutput)->GetReflectorSession(); + theSession->RemoveOutput(*theOutput, true); + delete (*theOutput); + + //check if the ReflectorSession should be deleted + //(it should if its ref count has dropped to 0) + OSMutexLocker locker (sSessionMap->GetMutex()); + //decrement the ref count + sSessionMap->Release(theSession->GetRef()); + if (theSession->GetRef()->GetRefCount() == 0) + { + sSessionMap->UnRegister(theSession->GetRef()); + + theLen = sizeof(theSession); + theErr = QTSS_SetValue(inParams->inClientSession, sSessionAttr, 0, (void*)&theSession, theLen); + if (theErr != QTSS_NoErr) + delete theSession; + else + IssueTeardown(theSession); + } + } + return QTSS_NoErr; +} + +void IssueTeardown(ReflectorSession* inSession) +{ + // Tell the RTSPSourceInfo object to initiate or continue the Teardown process + QTSS_Error theErr = ((RTSPSourceInfo*)inSession->GetSourceInfo())->Teardown(); + if ((theErr == EAGAIN) || (theErr == EINPROGRESS)) + { + RTSPClient* theClient = ((RTSPSourceInfo*)inSession->GetSourceInfo())->GetRTSPClient(); + theClient->GetSocket()->GetSocket()->DontAutoCleanup(); + RequestSocketEvent(inSession->GetSocketStream(), theClient->GetSocket()->GetEventMask()); + } + else + { + // Make sure to destroy the socket stream as well + Assert(inSession->GetSocketStream() != NULL); + (void)QTSS_DestroySocketStream(inSession->GetSocketStream()); + + delete inSession; + } +} + +void RequestSocketEvent(QTSS_StreamRef inStream, UInt32 inEventMask) +{ + // + // Job of this function is to convert a CommonUtilitiesLib event mask to a QTSS Event mask + QTSS_EventType theEvent = 0; + + if (inEventMask & EV_RE) + theEvent |= QTSS_ReadableEvent; + if (inEventMask & EV_WR) + theEvent |= QTSS_WriteableEvent; + + QTSS_Error theErr = QTSS_RequestEvent(inStream, theEvent); + Assert(theErr == QTSS_NoErr); +} diff --git a/APIModules/QTSSReflectorModule/QTSSSplitterModule.h b/APIModules/QTSSReflectorModule/QTSSSplitterModule.h new file mode 100644 index 0000000..34e2e52 --- /dev/null +++ b/APIModules/QTSSReflectorModule/QTSSSplitterModule.h @@ -0,0 +1,43 @@ +/* + * + * @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: QTSSSplitterModule.h + + Contains: QTSS API module + + +*/ + +#ifndef _QTSSSPLITTERMODULE_H_ +#define _QTSSSPLITTERMODULE_H_ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSSplitterModule_Main(void* inPrivateArgs); +} + +#endif //_QTSSSPLITTERMODULE_H_ diff --git a/APIModules/QTSSReflectorModule/RCFSourceInfo.cpp b/APIModules/QTSSReflectorModule/RCFSourceInfo.cpp new file mode 100644 index 0000000..cc4d245 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RCFSourceInfo.cpp @@ -0,0 +1,248 @@ +/* + * + * @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: RCFSourceInfo.cpp + + Contains: Implementation of object defined in .h file + + Copyright: © 1998 by Apple Computer, Inc., all rights reserved. + + + +*/ + +#include "RCFSourceInfo.h" +#include "PrefsSource.h" +#include "StringParser.h" +#include "OSMemory.h" +#include "SocketUtils.h" + +void RCFSourceInfo::SetName(const char* inName) +{ + if (inName != NULL) + { + fName = NEW char[::strlen(inName) + 1]; + ::strcpy(fName, inName); + } +} + +RCFSourceInfo::~RCFSourceInfo() +{ + if (fName != NULL) + delete fName; + + // Not necessary anymore as the destructor of the base class will take care + // of deleting all allocated memory for fOutputArray and fStreamArray + /* + if (fOutputArray != NULL) + { + for (UInt32 x = 0; x < fNumOutputs; x++) + delete [] fOutputArray[x].fPortArray; + + char* theOutputArray = (char*)fOutputArray; + delete [] theOutputArray; + } + if (fStreamArray != NULL) + { + char* theStreamArray = (char*)fStreamArray; + delete [] theStreamArray; + } + */ +} + +void RCFSourceInfo::Parse(XMLTag* relayTag) +{ + XMLTag* sourceTag = relayTag->GetEmbeddedTagByNameAndAttr("OBJECT", "CLASS", "source"); + if (sourceTag == NULL) + return; + + fNumStreams = 0; + UInt32 destIPAddr = 0; + UInt32 srcIPAddr = 0; + UInt16 ttl = 0; + + XMLTag* prefTag; + + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "in_addr"); + if (prefTag != NULL) + { + char* destAddrStr = prefTag->GetValue(); + if (destAddrStr != NULL) + destIPAddr = SocketUtils::ConvertStringToAddr(destAddrStr); + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "source_addr"); + if (prefTag != NULL) + { + char* srcAddrStr = prefTag->GetValue(); + if (srcAddrStr != NULL) + srcIPAddr = SocketUtils::ConvertStringToAddr(srcAddrStr); + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "ttl"); + if (prefTag != NULL) + { + char* ttlStr = prefTag->GetValue(); + if (ttlStr != NULL) + ttl = atoi(ttlStr); + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("LIST-PREF", "NAME", "udp_ports"); + if (prefTag != NULL) + { + fNumStreams = prefTag->GetNumEmbeddedTags(); + + // Allocate a proper sized stream array + fStreamArray = NEW StreamInfo[fNumStreams]; + + for (UInt32 x = 0; x < fNumStreams; x++) + { + XMLTag* portTag = prefTag->GetEmbeddedTagByName("VALUE", x); + int port = 0; + if (portTag != NULL) + { + char* portStr = portTag->GetValue(); + if (portStr != NULL) + port = atoi(portStr); + } + + // Setup all the StreamInfo structures + fStreamArray[x].fSrcIPAddr = srcIPAddr; + fStreamArray[x].fDestIPAddr = destIPAddr; + fStreamArray[x].fPort = port; + fStreamArray[x].fTimeToLive = ttl; + fStreamArray[x].fPayloadType = qtssUnknownPayloadType; + fStreamArray[x].fTrackID = x+1; + } + } + + // Now go through all the relay_destination lines (starting from the next line after the + // relay_source line. + this->ParseRelayDestinations(relayTag); +} + +void RCFSourceInfo::ParseRelayDestinations(XMLTag* relayTag) +{ + // parse the NAME attribute of the relay tag and store it in the relayname attribute + char* name = relayTag->GetAttributeValue("NAME"); + if (name != NULL) + { + fName = NEW char[::strlen(name) + 1]; + ::strcpy(fName, name); + } + + UInt32 numTags = relayTag->GetNumEmbeddedTags(); + AllocateOutputArray(numTags); // not all these are relay tags, but most are + + // Now actually go through and figure out what to put into these OutputInfo structures, + // based on what's on the relay_destination line + fNumOutputs = 0; + for (UInt32 y = 0; y < numTags; y++) + { + XMLTag* destTag = relayTag->GetEmbeddedTagByNameAndAttr("OBJECT", "CLASS", "destination", y); + if (destTag == NULL) + return; + + char* destType = destTag->GetAttributeValue("TYPE"); + if (destType == NULL) + return; + + if (!strcmp(destType, "udp_destination")) + ParseDestination(destTag, y); + else if (!strcmp(destType, "announced_destination")) + ParseAnnouncedDestination(destTag, y); + + fNumOutputs++; + } +} + +void RCFSourceInfo::ParseDestination(XMLTag* destTag, UInt32 index) +{ + XMLTag* prefTag; + + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "out_addr"); + if (prefTag != NULL) + { + char* outAddrStr = prefTag->GetValue(); + if (outAddrStr != NULL) + fOutputArray[index].fLocalAddr = SocketUtils::ConvertStringToAddr(outAddrStr); + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "dest_addr"); + if (prefTag != NULL) + { + char* destAddrStr = prefTag->GetValue(); + if (destAddrStr != NULL) + fOutputArray[index].fDestAddr = SocketUtils::ConvertStringToAddr(destAddrStr); + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "ttl"); + if (prefTag != NULL) + { + char* ttlStr = prefTag->GetValue(); + if (ttlStr != NULL) + fOutputArray[index].fTimeToLive = atoi(ttlStr); + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("LIST-PREF", "NAME", "udp_ports"); + if (prefTag != NULL) + { + fOutputArray[index].fNumPorts = prefTag->GetNumEmbeddedTags(); + + fOutputArray[index].fPortArray = NEW UInt16[fOutputArray[index].fNumPorts]; + ::memset(fOutputArray[index].fPortArray, 0, fOutputArray[index].fNumPorts * sizeof(UInt16)); + + for (UInt32 x = 0; x < fOutputArray[index].fNumPorts; x++) + { + XMLTag* portTag = prefTag->GetEmbeddedTagByName("VALUE", x); + if (portTag != NULL) + { + char* portStr = portTag->GetValue(); + if (portStr != NULL) + { + fOutputArray[index].fPortArray[x] = atoi(portStr); + } + } + } + } + else + { + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "udp_base_port"); + if(prefTag == NULL) + qtss_printf("Missing both 'udp_base_port' and 'udp_ports' tags.\n Cannot set up the destination!\n"); + else + { + char* basePortStr = prefTag->GetValue(); + if (basePortStr != NULL) + fOutputArray[index].fBasePort = atoi(basePortStr); + } + } +} + +void RCFSourceInfo::ParseAnnouncedDestination(XMLTag* destTag, UInt32 index) +{ + // should log some sort of error + // can't announce without an sdp +} + +void RCFSourceInfo::AllocateOutputArray(UInt32 numOutputs) +{ + // Allocate the proper number of relay outputs + fOutputArray = NEW OutputInfo[numOutputs]; +} diff --git a/APIModules/QTSSReflectorModule/RCFSourceInfo.h b/APIModules/QTSSReflectorModule/RCFSourceInfo.h new file mode 100644 index 0000000..f2b1ba9 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RCFSourceInfo.h @@ -0,0 +1,72 @@ +/* + * + * @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: RCFSourceInfo.h + + Contains: This object takes input RCF data, and uses it to support the SourceInfo + API. + + + +*/ + +#ifndef __RCF_SOURCE_INFO_H__ +#define __RCF_SOURCE_INFO_H__ + +#include "StrPtrLen.h" +#include "SourceInfo.h" +#include "XMLParser.h" +#include "StringParser.h" + +class RCFSourceInfo : public SourceInfo +{ + public: + + // Uses the SDP Data to build up the StreamInfo structures + RCFSourceInfo() : fName(NULL) {} + RCFSourceInfo(XMLTag* relayTag) : fName(NULL) { Parse(relayTag); } + RCFSourceInfo(const RCFSourceInfo& copy):SourceInfo(copy) { this->SetName(copy.fName); } + virtual ~RCFSourceInfo(); + + // Parses out the SDP file provided, sets up the StreamInfo structures + void Parse(XMLTag* relayTag); + + // Parses relay_destination lines and builds OutputInfo structs + void ParseRelayDestinations(XMLTag* relayTag); + + void SetName(const char* inName); + char* Name() { return fName; } + + protected: + virtual void ParseDestination(XMLTag* destTag, UInt32 index); + virtual void ParseAnnouncedDestination(XMLTag* destTag, UInt32 index); + virtual void AllocateOutputArray(UInt32 numOutputs); + + char* fName; + +}; +#endif // __SDP_SOURCE_INFO_H__ + + diff --git a/APIModules/QTSSReflectorModule/RTPSessionOutput.cpp b/APIModules/QTSSReflectorModule/RTPSessionOutput.cpp new file mode 100644 index 0000000..91aaa71 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RTPSessionOutput.cpp @@ -0,0 +1,779 @@ +/* + * + * @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: RTSPReflectorOutput.cpp + + Contains: Implementation of object in .h file + + +*/ + + +#include "RTPSessionOutput.h" +#include "ReflectorStream.h" + +#include + + +#if DEBUG +#define RTP_SESSION_DEBUGGING 0 +#else +#define RTP_SESSION_DEBUGGING 0 +#endif + +// ATTRIBUTES +static QTSS_AttributeID sStreamPacketCountAttr = qtssIllegalAttrID; + + +static QTSS_AttributeID sNextSeqNumAttr = qtssIllegalAttrID; +static QTSS_AttributeID sSeqNumOffsetAttr = qtssIllegalAttrID; +static QTSS_AttributeID sLastQualityChangeAttr = qtssIllegalAttrID; +static QTSS_AttributeID sLastRTPPacketIDAttr = qtssIllegalAttrID; +static QTSS_AttributeID sLastRTCPPacketIDAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sFirstRTCPCurrentTimeAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFirstRTCPArrivalTimeAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFirstRTCPTimeStampAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sFirstRTPCurrentTimeAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFirstRTPArrivalTimeAttr = qtssIllegalAttrID; +static QTSS_AttributeID sFirstRTPTimeStampAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sBaseRTPTimeStampAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sBaseArrivalTimeStampAttr = qtssIllegalAttrID; +static QTSS_AttributeID sStreamSSRCAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sStreamByteCountAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sLastRTPTimeStampAttr = qtssIllegalAttrID; + +static QTSS_AttributeID sLastRTCPTransmitAttr = qtssIllegalAttrID; + +RTPSessionOutput::RTPSessionOutput(QTSS_ClientSessionObject inClientSession, ReflectorSession* inReflectorSession, + QTSS_Object serverPrefs, QTSS_AttributeID inCookieAddrID) +: fClientSession(inClientSession), + fReflectorSession(inReflectorSession), + fCookieAttrID(inCookieAddrID), + fBufferDelayMSecs(ReflectorStream::sOverBufferInMsec), + fBaseArrivalTime(0), + fIsUDP(false), + fTransportInitialized(false), + fMustSynch(true), + fPreFilter(true) +{ + // create a bookmark for each stream we'll reflect + this->InititializeBookmarks( inReflectorSession->GetNumStreams() ); + +} + +void RTPSessionOutput::Register() +{ + // Add some attributes to QTSS_RTPStream dictionary + static char* sNextSeqNum = "qtssNextSeqNum"; + static char* sSeqNumOffset = "qtssSeqNumOffset"; + static char* sLastQualityChange = "qtssLastQualityChange"; + + static char* sLastRTPPacketID = "qtssReflectorStreamLastRTPPacketID"; + static char* sLastRTCPPacketID = "qtssReflectorStreamLastRTCPPacketID"; + + + static char* sFirstRTCPArrivalTime = "qtssReflectorStreamStartRTCPArrivalTime"; + static char* sFirstRTCPTimeStamp = "qtssReflectorStreamStartRTCPTimeStamp"; + static char* sFirstRTCPCurrentTime = "qtssReflectorStreamStartRTCPCurrent"; + + static char* sFirstRTPArrivalTime = "qtssReflectorStreamStartRTPArrivalTime"; + static char* sFirstRTPTimeStamp = "qtssReflectorStreamStartRTPTimeStamp"; + static char* sFirstRTPCurrentTime = "qtssReflectorStreamStartRTPCurrent"; + + static char* sBaseRTPTimeStamp = "qtssReflectorStreamBaseRTPTimeStamp"; + static char* sBaseArrivalTimeStamp = "qtssReflectorStreamBaseArrivalTime"; + + static char* sLastRTPTimeStamp = "qtssReflectorStreamLastRTPTimeStamp"; + static char* sLastRTCPTransmit = "qtssReflectorStreamLastRTCPTransmit"; + + static char* sStreamSSRC = "qtssReflectorStreamSSRC"; + static char* sStreamPacketCount = "qtssReflectorStreamPacketCount"; + static char* sStreamByteCount = "qtssReflectorStreamByteCount"; + + + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sLastRTCPTransmit, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sLastRTCPTransmit, &sLastRTCPTransmitAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sNextSeqNum, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sNextSeqNum, &sNextSeqNumAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sSeqNumOffset, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sSeqNumOffset, &sSeqNumOffsetAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sLastQualityChange, NULL, qtssAttrDataTypeSInt64); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sLastQualityChange, &sLastQualityChangeAttr); + + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sLastRTPPacketID, NULL, qtssAttrDataTypeUInt64); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sLastRTPPacketID, &sLastRTPPacketIDAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sLastRTCPPacketID, NULL, qtssAttrDataTypeUInt64); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sLastRTCPPacketID, &sLastRTCPPacketIDAttr); + + + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sLastRTPTimeStamp, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sLastRTPTimeStamp, &sLastRTPTimeStampAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sFirstRTCPArrivalTime, NULL, qtssAttrDataTypeSInt64); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sFirstRTCPArrivalTime, &sFirstRTCPArrivalTimeAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sFirstRTCPTimeStamp, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sFirstRTCPTimeStamp, &sFirstRTCPTimeStampAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sFirstRTCPCurrentTime, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sFirstRTCPCurrentTime, &sFirstRTCPCurrentTimeAttr); + + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sFirstRTPCurrentTime, NULL, qtssAttrDataTypeSInt64); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sFirstRTPCurrentTime, &sFirstRTPCurrentTimeAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sFirstRTPArrivalTime, NULL, qtssAttrDataTypeSInt64); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sFirstRTPArrivalTime, &sFirstRTPArrivalTimeAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sFirstRTPTimeStamp, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sFirstRTPTimeStamp, &sFirstRTPTimeStampAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sBaseRTPTimeStamp, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sBaseRTPTimeStamp, &sBaseRTPTimeStampAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sBaseArrivalTimeStamp, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sBaseArrivalTimeStamp, &sBaseArrivalTimeStampAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sStreamSSRC, NULL, qtssAttrDataTypeVoidPointer); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sStreamSSRC, &sStreamSSRCAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sStreamPacketCount, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sStreamPacketCount, &sStreamPacketCountAttr); + + (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sStreamByteCount, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sStreamByteCount, &sStreamByteCountAttr); + +} + +Bool16 RTPSessionOutput::IsPlaying() +{ + QTSS_RTPSessionState* theState = NULL; + UInt32 theLen = 0; + + if (!fClientSession) + return false; + + (void)QTSS_GetValuePtr(fClientSession, qtssCliSesState, 0, (void**)&theState, &theLen); + if (theLen == 0 || theState == NULL || *theState != qtssPlayingState) + return false; + + + return true; +} + +void RTPSessionOutput::InitializeStreams() +{ + + UInt32 theLen = 0; + QTSS_RTPStreamObject* theStreamPtr = NULL; + UInt32 packetCountInitValue = 0; + + for (SInt16 z = 0; QTSS_GetValuePtr(fClientSession, qtssCliSesStreamObjects, z, (void**)&theStreamPtr, &theLen) == QTSS_NoErr; z++) + { + (void) QTSS_SetValue(*theStreamPtr, sStreamPacketCountAttr, 0, &packetCountInitValue, sizeof(UInt32)); + } + +} + + + +Bool16 RTPSessionOutput::IsUDP() +{ + if (fTransportInitialized) + return fIsUDP; + + + QTSS_RTPSessionState* theState = NULL; + UInt32 theLen = 0; + (void)QTSS_GetValuePtr(fClientSession, qtssCliSesState, 0, (void**)&theState, &theLen); + if (*theState != qtssPlayingState) + return true; + + QTSS_RTPStreamObject *theStreamPtr = NULL; + QTSS_RTPTransportType *theTransportTypePtr = NULL; + for (SInt16 z = 0; QTSS_GetValuePtr(fClientSession, qtssCliSesStreamObjects, z, (void**)&theStreamPtr, &theLen) == QTSS_NoErr; z++) + { + (void) QTSS_GetValuePtr(*theStreamPtr, qtssRTPStrTransportType, 0, (void**) &theTransportTypePtr, &theLen); + if (theTransportTypePtr && *theTransportTypePtr == qtssRTPTransportTypeUDP) + { + fIsUDP = true; + break; // treat entire session UDP + } + else + { + fIsUDP = false; + } + } + + //if (fIsUDP) printf("RTPSessionOutput::RTPSessionOutput Standard UDP client\n"); + //else printf("RTPSessionOutput::RTPSessionOutput Buffered Client\n"); + + fTransportInitialized = true; + return fIsUDP; +} + + +Bool16 RTPSessionOutput::FilterPacket(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacket) +{ + + UInt32* packetCountPtr = NULL; + UInt32 theLen = 0; + + //see if we started sending and if so then just keep sending (reset on a play) + QTSS_Error writeErr = QTSS_GetValuePtr(*theStreamPtr, sStreamPacketCountAttr, 0,(void**) &packetCountPtr,&theLen); + if (writeErr == QTSS_NoErr && theLen > 0 && *packetCountPtr > 0) + return false; + + Assert(theStreamPtr); + Assert(inPacket); + + UInt16 seqnum = this->GetPacketSeqNumber(inPacket); + UInt16 firstSeqNum = 0; + theLen = sizeof(firstSeqNum); + + if ( QTSS_NoErr != QTSS_GetValue(*theStreamPtr, qtssRTPStrFirstSeqNumber, 0, &firstSeqNum, &theLen) ) + return true; + + if ( seqnum < firstSeqNum ) + { + //printf("RTPSessionOutput::FilterPacket don't send packet = %u < first=%lu\n", seqnum, firstSeqNum); + return true; + } + + //printf("RTPSessionOutput::FilterPacket found first packet = %u \n", firstSeqNum); + + fPreFilter = false; + return fPreFilter; +} + + +Bool16 RTPSessionOutput::PacketAlreadySent(QTSS_RTPStreamObject *theStreamPtr, UInt32 inFlags, UInt64* packetIDPtr) +{ + Assert(theStreamPtr); + Assert(packetIDPtr); + + UInt32 theLen = 0; + UInt64 *lastPacketIDPtr = NULL; + Bool16 packetSent = false; + + if (inFlags & qtssWriteFlagsIsRTP) + { + if ( (QTSS_NoErr == QTSS_GetValuePtr(*theStreamPtr, sLastRTPPacketIDAttr, 0, (void**)&lastPacketIDPtr, &theLen)) + && (*packetIDPtr <= *lastPacketIDPtr) + ) + { + //printf("RTPSessionOutput::WritePacket Don't send RTP packet id =%qu\n", *packetIDPtr); + packetSent = true; + } + + } else if (inFlags & qtssWriteFlagsIsRTCP) + { + if ( QTSS_NoErr == QTSS_GetValuePtr(*theStreamPtr, sLastRTCPPacketIDAttr, 0, (void**)&lastPacketIDPtr, &theLen) + && (*packetIDPtr <= *lastPacketIDPtr) + ) + { + //printf("RTPSessionOutput::WritePacket Don't send RTCP packet id =%qu last packet sent id =%qu\n", *packetIDPtr,*lastPacketIDPtr); + packetSent = true; + } + } + + return packetSent; +} + +Bool16 RTPSessionOutput::PacketReadyToSend(QTSS_RTPStreamObject *theStreamPtr,SInt64 *currentTimePtr, UInt32 inFlags, UInt64* packetIDPtr, SInt64* timeToSendThisPacketAgainPtr) +{ + return true; + +} + +QTSS_Error RTPSessionOutput::TrackRTCPBaseTime(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64* packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr) +{ + Bool16 haveBaseTime = false; + Bool16 haveAllFirstRTPs = true; + + UInt32 streamTimeScale = 0; + UInt32 theLen = sizeof(streamTimeScale); + QTSS_Error writeErr = QTSS_GetValue(*theStreamPtr, qtssRTPStrTimescale, 0, (void *) &streamTimeScale, &theLen); + Assert(writeErr == QTSS_NoErr); + + UInt32 baseTimeStamp = 0; + theLen = sizeof(baseTimeStamp); + if (!fMustSynch || QTSS_NoErr == QTSS_GetValue(*theStreamPtr, sBaseRTPTimeStampAttr, 0, (void*)&baseTimeStamp, &theLen) ) // we need a starting stream time that is synched + { + haveBaseTime = true; + } + else + { + UInt64 earliestArrivalTime = ~(UInt64) 0; //max value + UInt32 firstStreamTime = 0; + SInt64 firstStreamArrivalTime = 0; + QTSS_RTPStreamObject *findStream = NULL; + + if (fMustSynch || QTSS_NoErr != QTSS_GetValuePtr(*theStreamPtr, sBaseArrivalTimeStampAttr, 0, (void**)&fBaseArrivalTime, &theLen) ) + { // we don't have a base arrival time for the session see if we can set one now. + + for (SInt32 z = 0; QTSS_GetValuePtr(fClientSession, qtssCliSesStreamObjects, z, (void**)&findStream, &theLen) == QTSS_NoErr; z++) + { + SInt64* firstArrivalTimePtr = NULL; + if (QTSS_NoErr != QTSS_GetValuePtr(*findStream, sFirstRTPArrivalTimeAttr, 0, (void**)&firstArrivalTimePtr, &theLen)) + {// no packet on this stream yet + haveAllFirstRTPs = false; // not enough info to calc a base time + break; + } + else + { // we have an arrival time see if it is the first for all streams + if ( (UInt64) *firstArrivalTimePtr < earliestArrivalTime ) + { + earliestArrivalTime = *firstArrivalTimePtr; + } + } + + } + + if (haveAllFirstRTPs) // we can now create a base arrival time and base stream time from that + { + + writeErr = QTSS_SetValue(*theStreamPtr, sBaseArrivalTimeStampAttr, 0, &earliestArrivalTime, sizeof(SInt64)); + Assert(writeErr == QTSS_NoErr); + fBaseArrivalTime = (SInt64) earliestArrivalTime; + } + } + + if (haveAllFirstRTPs)//sBaseRTPTimeStamp + { // we don't have a base stream time but we have a base session time so calculate the base stream time. + theLen = sizeof(firstStreamTime); + if (QTSS_NoErr != QTSS_GetValue(*theStreamPtr, sFirstRTPTimeStampAttr, 0, (void*)&firstStreamTime, &theLen)) + return QTSS_NoErr; + + theLen = sizeof(firstStreamArrivalTime); + if (QTSS_NoErr != QTSS_GetValue(*theStreamPtr, sFirstRTPArrivalTimeAttr, 0, (void*)&firstStreamArrivalTime, &theLen)) + return QTSS_NoErr; + + SInt64 arrivalTimeDiffMSecs = (firstStreamArrivalTime - fBaseArrivalTime);// + fBufferDelayMSecs;//add the buffer delay !! not sure about faster than real time arrival times.... + UInt32 timeDiffStreamTime = (UInt32)( ( (Float64) arrivalTimeDiffMSecs/(Float64) 1000.0) * (Float64) streamTimeScale ); + baseTimeStamp = firstStreamTime - timeDiffStreamTime; + if (QTSS_NoErr == QTSS_SetValue(*theStreamPtr, sBaseRTPTimeStampAttr, 0, (void*)&baseTimeStamp, sizeof(baseTimeStamp))) + haveBaseTime = true; + + (void) QTSS_SetValue(*theStreamPtr, qtssRTPStrFirstTimestamp, 0, &baseTimeStamp, sizeof(baseTimeStamp)); + + fMustSynch = false; + //printf("fBaseArrivalTime =%qd baseTimeStamp %"_U32BITARG_" streamStartTime=%qd diff =%qd\n", fBaseArrivalTime, baseTimeStamp, firstStreamArrivalTime, arrivalTimeDiffMSecs); + } + } + + return writeErr; + +} + +QTSS_Error RTPSessionOutput::RewriteRTCP(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64* packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr) +{ + UInt32 theLen; + + SInt64 firstRTPCurrentTime = 0; + theLen = sizeof(firstRTPCurrentTime); + QTSS_GetValue(*theStreamPtr, sFirstRTPCurrentTimeAttr, 0, (void*)&firstRTPCurrentTime, &theLen); + + SInt64 firstRTPArrivalTime = 0; + theLen = sizeof(firstRTPArrivalTime); + QTSS_GetValue(*theStreamPtr, sFirstRTPArrivalTimeAttr, 0, (void*)&firstRTPArrivalTime, &theLen); + + + UInt32 rtpTime = 0; + theLen = sizeof(rtpTime); + QTSS_GetValue(*theStreamPtr, sFirstRTPTimeStampAttr, 0, (void*)&rtpTime, &theLen); + + + UInt32* theReport = (UInt32*) inPacketStrPtr->Ptr; + theReport+=2; // point to the NTP time stamp + SInt64* theNTPTimestampP = (SInt64*)theReport; + *theNTPTimestampP = OS::HostToNetworkSInt64(OS::TimeMilli_To_1900Fixed64Secs(*currentTimePtr)); // time now + + UInt32 baseTimeStamp = 0; + theLen = sizeof(baseTimeStamp); + (void) QTSS_GetValue(*theStreamPtr, sBaseRTPTimeStampAttr, 0, (void*)&baseTimeStamp, &theLen); // we need a starting stream time that is synched + + UInt32 streamTimeScale = 0; + theLen = sizeof(streamTimeScale); + QTSS_GetValue(*theStreamPtr, qtssRTPStrTimescale, 0, (void *) &streamTimeScale, &theLen); + + SInt64 packetOffset = *currentTimePtr - fBaseArrivalTime; // real time that has passed + packetOffset -= (firstRTPCurrentTime - firstRTPArrivalTime); // less the initial buffer delay for this stream + if (packetOffset < 0) + packetOffset = 0; + + Float64 rtpTimeFromStart = (Float64) packetOffset / (Float64) 1000.0; + UInt32 rtpTimeFromStartInScale = (UInt32) (Float64) ((Float64) streamTimeScale * rtpTimeFromStart); + //printf("rtptime offset time =%f in scale =%"_U32BITARG_"\n", rtpTimeFromStart, rtpTimeFromStartInScale ); + + theReport += 2; // point to the rtp time stamp of "now" synched and scaled in stream time + *theReport = htonl(baseTimeStamp + rtpTimeFromStartInScale); + + theLen = sizeof(UInt32); + UInt32 packetCount = 0; + (void) QTSS_GetValue(*theStreamPtr, sStreamPacketCountAttr, 0, &packetCount,&theLen); + theReport += 1; // point to the rtp packets sent + *theReport = htonl(ntohl(*theReport) * 2); + + UInt32 byteCount = 0; + (void) QTSS_GetValue(*theStreamPtr, sStreamByteCountAttr, 0, &byteCount,&theLen); + theReport += 1; // point to the rtp payload bytes sent + *theReport = htonl(ntohl(*theReport) * 2); + + return QTSS_NoErr; +} + +QTSS_Error RTPSessionOutput::TrackRTCPPackets(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64* packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr) +{ + QTSS_Error writeErr = QTSS_NoErr; + + Assert(inFlags & qtssWriteFlagsIsRTCP); + + if (!(inFlags & qtssWriteFlagsIsRTCP)) + return -1; + + this->TrackRTCPBaseTime(theStreamPtr,inPacketStrPtr,currentTimePtr,inFlags, packetLatenessInMSec,timeToSendThisPacketAgain, packetIDPtr,arrivalTimeMSecPtr); + + this->RewriteRTCP(theStreamPtr,inPacketStrPtr,currentTimePtr,inFlags, packetLatenessInMSec,timeToSendThisPacketAgain, packetIDPtr,arrivalTimeMSecPtr); + + + return writeErr; +} + +QTSS_Error RTPSessionOutput::TrackRTPPackets(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64* packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr) +{ + QTSS_Error writeErr = QTSS_NoErr; + + Assert(inFlags & qtssWriteFlagsIsRTP); + + if (!(inFlags & qtssWriteFlagsIsRTP)) + return QTSS_NoErr; + + ReflectorPacket packetContainer; + packetContainer.SetPacketData(inPacketStrPtr->Ptr, inPacketStrPtr->Len); + packetContainer.fIsRTCP = false; + SInt64 *theTimePtr = NULL; + UInt32 theLen = 0; + + if (QTSS_NoErr != QTSS_GetValuePtr(*theStreamPtr, sFirstRTPArrivalTimeAttr, 0, (void**)&theTimePtr, &theLen)) + { + UInt32 theSSRC = packetContainer.GetSSRC(packetContainer.fIsRTCP); + (void) QTSS_SetValue(*theStreamPtr, sStreamSSRCAttr, 0, &theSSRC, sizeof(theSSRC)); + + UInt32 rtpTime = packetContainer.GetPacketRTPTime(); + writeErr = QTSS_SetValue(*theStreamPtr, sFirstRTPTimeStampAttr, 0, &rtpTime, sizeof(rtpTime)); + Assert(writeErr == QTSS_NoErr); + + writeErr = QTSS_SetValue(*theStreamPtr, sFirstRTPArrivalTimeAttr, 0, arrivalTimeMSecPtr, sizeof(SInt64)); + Assert(writeErr == QTSS_NoErr); + + writeErr = QTSS_SetValue(*theStreamPtr, sFirstRTPCurrentTimeAttr, 0, currentTimePtr, sizeof(SInt64)); + Assert(writeErr == QTSS_NoErr); + + UInt32 initValue = 0; + writeErr = QTSS_SetValue(*theStreamPtr, sStreamByteCountAttr, 0, &initValue, sizeof(UInt32)); + Assert(writeErr == QTSS_NoErr); + + //printf("first rtp on stream stream=%"_U32BITARG_" ssrc=%"_U32BITARG_" rtpTime=%"_U32BITARG_" arrivalTimeMSecPtr=%qd currentTime=%qd\n",(UInt32) theStreamPtr, theSSRC, rtpTime, *arrivalTimeMSecPtr, *currentTimePtr); + + } + else + { + UInt32* packetCountPtr = NULL; + UInt32* byteCountPtr = NULL; + UInt32 theLen = 0; + + writeErr = QTSS_GetValuePtr(*theStreamPtr, sStreamByteCountAttr, 0, (void**) &byteCountPtr,&theLen); + if (writeErr == QTSS_NoErr && theLen > 0) + *byteCountPtr += inPacketStrPtr->Len - 12;// 12 header bytes + + + UInt32* theSSRCPtr = 0; + (void) QTSS_GetValuePtr(*theStreamPtr, sStreamSSRCAttr, 0, (void**)&theSSRCPtr, &theLen); + if (*theSSRCPtr != packetContainer.GetSSRC(packetContainer.fIsRTCP)) + { + + + (void) QTSS_RemoveValue(*theStreamPtr,sFirstRTPArrivalTimeAttr,0); + (void) QTSS_RemoveValue(*theStreamPtr,sFirstRTPTimeStampAttr,0); + (void) QTSS_RemoveValue(*theStreamPtr,sFirstRTPCurrentTimeAttr,0); + (void) QTSS_RemoveValue(*theStreamPtr,sStreamPacketCountAttr,0); + (void) QTSS_RemoveValue(*theStreamPtr,sStreamByteCountAttr,0); + fMustSynch = true; + + //printf("found different ssrc =%"_U32BITARG_" packetssrc=%"_U32BITARG_"\n",*theSSRCPtr, packetContainer.GetSSRC(packetContainer.fIsRTCP)); + + } + + + + } + + return writeErr; + +} + +QTSS_Error RTPSessionOutput::TrackPackets(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64* packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr) +{ + if (this->IsUDP()) + return QTSS_NoErr; + + if (inFlags & qtssWriteFlagsIsRTCP) + (void) this->TrackRTCPPackets(theStreamPtr, inPacketStrPtr, currentTimePtr,inFlags, packetLatenessInMSec, timeToSendThisPacketAgain, packetIDPtr,arrivalTimeMSecPtr); + else if (inFlags & qtssWriteFlagsIsRTP) + (void) this->TrackRTPPackets(theStreamPtr, inPacketStrPtr, currentTimePtr,inFlags, packetLatenessInMSec, timeToSendThisPacketAgain, packetIDPtr,arrivalTimeMSecPtr); + + return QTSS_NoErr; +} + + +QTSS_Error RTPSessionOutput::WritePacket(StrPtrLen* inPacket, void* inStreamCookie, UInt32 inFlags, SInt64 packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr, Bool16 firstPacket) +{ + QTSS_RTPSessionState* theState = NULL; + UInt32 theLen = 0; + QTSS_Error writeErr = QTSS_NoErr; + SInt64 currentTime = OS::Milliseconds(); + + if (inPacket == NULL || inPacket->Len == 0) + return QTSS_NoErr; + + + (void)QTSS_GetValuePtr(fClientSession, qtssCliSesState, 0, (void**)&theState, &theLen); + if (theLen == 0 || theState == NULL || *theState != qtssPlayingState) + { //qtss_printf("QTSS_WouldBlock *theState=%d qtssPlayingState=%d\n", *theState , qtssPlayingState); + return QTSS_WouldBlock; + } + + //make sure all RTP streams with this ID see this packet + QTSS_RTPStreamObject *theStreamPtr = NULL; + + for (UInt32 z = 0; QTSS_GetValuePtr(fClientSession, qtssCliSesStreamObjects, z, (void**)&theStreamPtr, &theLen) == QTSS_NoErr; z++) + { + if (this->PacketMatchesStream(inStreamCookie, theStreamPtr)) + { + if ( (inFlags & qtssWriteFlagsIsRTP) && this->FilterPacket(theStreamPtr, inPacket) ) + return QTSS_NoErr; // keep looking at packets + + if (this->PacketAlreadySent(theStreamPtr,inFlags, packetIDPtr)) + return QTSS_NoErr; // keep looking at packets + + if (!this->PacketReadyToSend(theStreamPtr,¤tTime, inFlags, packetIDPtr, timeToSendThisPacketAgain)) + { //qtss_printf("QTSS_WouldBlock\n"); + return QTSS_WouldBlock; // stop not ready to send packets now + } + + + // TrackPackets below is for re-writing the rtcps we don't use it right now-- shouldn't need to + // (void) this->TrackPackets(theStreamPtr, inPacket, ¤tTime,inFlags, &packetLatenessInMSec, timeToSendThisPacketAgain, packetIDPtr,arrivalTimeMSecPtr); + + QTSS_PacketStruct thePacket; + thePacket.packetData = inPacket->Ptr; + SInt64 delayMSecs = fBufferDelayMSecs - (currentTime - *arrivalTimeMSecPtr); + thePacket.packetTransmitTime = (currentTime - packetLatenessInMSec); + if (fBufferDelayMSecs > 0 ) + thePacket.packetTransmitTime += delayMSecs; // add buffer time where oldest buffered packet as now == 0 and newest is entire buffer time in the future. + + writeErr = QTSS_Write(*theStreamPtr, &thePacket, inPacket->Len, NULL, inFlags | qtssWriteFlagsWriteBurstBegin); + if (writeErr == QTSS_WouldBlock) + { + //qtss_printf("QTSS_Write == QTSS_WouldBlock\n"); + // + // We are flow controlled. See if we know when flow control will be lifted and report that + *timeToSendThisPacketAgain = thePacket.suggestedWakeupTime; + + if (firstPacket) + { fBufferDelayMSecs = (currentTime - *arrivalTimeMSecPtr); + //qtss_printf("firstPacket fBufferDelayMSecs =%lu \n", fBufferDelayMSecs); + } + } + else + { + fLastIntervalMilliSec = currentTime - fLastPacketTransmitTime; + if (fLastIntervalMilliSec > 100) //reset interval maybe first packet or it has been blocked for awhile + fLastIntervalMilliSec = 5; + fLastPacketTransmitTime = currentTime; + + if (inFlags & qtssWriteFlagsIsRTP) + { + (void) QTSS_SetValue (*theStreamPtr, sLastRTPPacketIDAttr, 0, packetIDPtr, sizeof(UInt64)); + } + else if (inFlags & qtssWriteFlagsIsRTCP) + { + (void) QTSS_SetValue (*theStreamPtr, sLastRTCPPacketIDAttr, 0, packetIDPtr, sizeof(UInt64)); + (void) QTSS_SetValue (*theStreamPtr, sLastRTCPTransmitAttr, 0, ¤tTime, sizeof(UInt64)); + } + + + + { // increment packet counts + UInt32* packetCountPtr = NULL; + UInt32 theLen = 0; + + (void) QTSS_GetValuePtr(*theStreamPtr, sStreamPacketCountAttr, 0,(void**) &packetCountPtr,&theLen); + if (theLen > 0) + { *packetCountPtr += 1; + //printf("SET sStreamPacketCountAttr =%lu\n", *packetCountPtr); + } + } + } + } + + if ( writeErr != QTSS_NoErr ) + break; + } + + return writeErr; +} + +UInt16 RTPSessionOutput::GetPacketSeqNumber(StrPtrLen* inPacket) +{ + if (inPacket->Len < 4) + return 0; + + //The RTP seq number is the second short of the packet + UInt16* seqNumPtr = (UInt16*)inPacket->Ptr; + return ntohs(seqNumPtr[1]); +} + +void RTPSessionOutput::SetPacketSeqNumber(StrPtrLen* inPacket, UInt16 inSeqNumber) +{ + if (inPacket->Len < 4) + return; + + //The RTP seq number is the second short of the packet + UInt16* seqNumPtr = (UInt16*)inPacket->Ptr; + seqNumPtr[1] = htons(inSeqNumber); +} + +// this routine is not used +Bool16 RTPSessionOutput::PacketShouldBeThinned(QTSS_RTPStreamObject inStream, StrPtrLen* inPacket) +{ + return false; // function is disabled. + + static UInt16 sZero = 0; + //This function determines whether the packet should be dropped. + //It also adjusts the sequence number if necessary + + if (inPacket->Len < 4) + return false; + + UInt16 curSeqNum = this->GetPacketSeqNumber(inPacket); + UInt32* curQualityLevel = NULL; + UInt16* nextSeqNum = NULL; + UInt16* theSeqNumOffset = NULL; + SInt64* lastChangeTime = NULL; + + UInt32 theLen = 0; + (void)QTSS_GetValuePtr(inStream, qtssRTPStrQualityLevel, 0, (void**)&curQualityLevel, &theLen); + if ((curQualityLevel == NULL) || (theLen != sizeof(UInt32))) + return false; + (void)QTSS_GetValuePtr(inStream, sNextSeqNumAttr, 0, (void**)&nextSeqNum, &theLen); + if ((nextSeqNum == NULL) || (theLen != sizeof(UInt16))) + { + nextSeqNum = &sZero; + (void)QTSS_SetValue(inStream, sNextSeqNumAttr, 0, nextSeqNum, sizeof(UInt16)); + } + (void)QTSS_GetValuePtr(inStream, sSeqNumOffsetAttr, 0, (void**)&theSeqNumOffset, &theLen); + if ((theSeqNumOffset == NULL) || (theLen != sizeof(UInt16))) + { + theSeqNumOffset = &sZero; + (void)QTSS_SetValue(inStream, sSeqNumOffsetAttr, 0, theSeqNumOffset, sizeof(UInt16)); + } + UInt16 newSeqNumOffset = *theSeqNumOffset; + + (void)QTSS_GetValuePtr(inStream, sLastQualityChangeAttr, 0, (void**)&lastChangeTime, &theLen); + if ((lastChangeTime == NULL) || (theLen != sizeof(SInt64))) + { static SInt64 startTime = 0; + lastChangeTime = &startTime; + (void)QTSS_SetValue(inStream, sLastQualityChangeAttr, 0, lastChangeTime, sizeof(SInt64)); + } + + SInt64 timeNow = OS::Milliseconds(); + if (*lastChangeTime == 0 || *curQualityLevel == 0) + *lastChangeTime =timeNow; + + if (*curQualityLevel > 0 && ((*lastChangeTime + 30000) < timeNow) ) // 30 seconds between reductions + { *curQualityLevel -= 1; // reduce quality value. If we quality doesn't change then we may have hit some steady state which we can't get out of without thinning or increasing the quality + *lastChangeTime =timeNow; + //qtss_printf("RTPSessionOutput set quality to %"_U32BITARG_"\n",*curQualityLevel); + } + + //Check to see if we need to drop to audio only + if ((*curQualityLevel >= ReflectorSession::kAudioOnlyQuality) && + (*nextSeqNum == 0)) + { +#if REFLECTOR_THINNING_DEBUGGING || RTP_SESSION_DEBUGGING + qtss_printf(" *** Reflector Dropping to audio only *** \n"); +#endif + //All we need to do in this case is mark the sequence number of the first dropped packet + (void)QTSS_SetValue(inStream, sNextSeqNumAttr, 0, &curSeqNum, sizeof(UInt16)); + *lastChangeTime =timeNow; + } + + + //Check to see if we can reinstate video + if ((*curQualityLevel == ReflectorSession::kNormalQuality) && (*nextSeqNum != 0)) + { + //Compute the offset amount for each subsequent sequence number. This offset will + //alter the sequence numbers so that they increment normally (providing the illusion to the + //client that there are no missing packets) + newSeqNumOffset = (*theSeqNumOffset) + (curSeqNum - (*nextSeqNum)); + (void)QTSS_SetValue(inStream, sSeqNumOffsetAttr, 0, &newSeqNumOffset, sizeof(UInt16)); + (void)QTSS_SetValue(inStream, sNextSeqNumAttr, 0, &sZero, sizeof(UInt16)); + } + + //tell the caller whether to drop this packet or not. + if (*curQualityLevel >= ReflectorSession::kAudioOnlyQuality) + return true; + else + { + //Adjust the sequence number of the current packet based on the offset, if any + curSeqNum -= newSeqNumOffset; + this->SetPacketSeqNumber(inPacket, curSeqNum); + return false; + } +} + +void RTPSessionOutput::TearDown() +{ + QTSS_CliSesTeardownReason reason = qtssCliSesTearDownBroadcastEnded; + (void)QTSS_SetValue(fClientSession, qtssCliTeardownReason, 0, &reason, sizeof(reason)); + (void)QTSS_Teardown(fClientSession); +} + + diff --git a/APIModules/QTSSReflectorModule/RTPSessionOutput.h b/APIModules/QTSSReflectorModule/RTPSessionOutput.h new file mode 100644 index 0000000..1124fee --- /dev/null +++ b/APIModules/QTSSReflectorModule/RTPSessionOutput.h @@ -0,0 +1,111 @@ +/* + * + * @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: RTSPReflectorOutput.h + + Contains: Derived from ReflectorOutput, this implements the WritePacket + method in terms of the QTSS API (that is, it writes to a client + using the QTSS_RTPSessionObject + + + +*/ + +#ifndef __RTSP_REFLECTOR_OUTPUT_H__ +#define __RTSP_REFLECTOR_OUTPUT_H__ + +#include "ReflectorOutput.h" +#include "ReflectorSession.h" +#include "QTSS.h" + +class RTPSessionOutput : public ReflectorOutput +{ + public: + + // Adds some dictionary attributes + static void Register(); + + RTPSessionOutput(QTSS_ClientSessionObject inRTPSession, ReflectorSession* inReflectorSession, + QTSS_Object serverPrefs, QTSS_AttributeID inCookieAddrID); + virtual ~RTPSessionOutput() {} + + ReflectorSession* GetReflectorSession() { return fReflectorSession; } + void InitializeStreams(); + + // This writes the packet out to the proper QTSS_RTPStreamObject. + // If this function returns QTSS_WouldBlock, timeToSendThisPacketAgain will + // be set to # of msec in which the packet can be sent, or -1 if unknown + virtual QTSS_Error WritePacket(StrPtrLen* inPacketData, void* inStreamCookie, UInt32 inFlags, SInt64 packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSec,Bool16 firstPacket ); + virtual void TearDown(); + + SInt64 GetReflectorSessionInitTime() { return fReflectorSession->GetInitTimeMS(); } + + virtual Bool16 IsUDP(); + + virtual Bool16 IsPlaying(); + + void SetBufferDelay (UInt32 delay) { fBufferDelayMSecs = delay; } + + private: + + QTSS_ClientSessionObject fClientSession; + ReflectorSession* fReflectorSession; + QTSS_AttributeID fCookieAttrID; + UInt32 fBufferDelayMSecs; + SInt64 fBaseArrivalTime; + Bool16 fIsUDP; + Bool16 fTransportInitialized; + Bool16 fMustSynch; + Bool16 fPreFilter; + + UInt16 GetPacketSeqNumber(StrPtrLen* inPacket); + void SetPacketSeqNumber(StrPtrLen* inPacket, UInt16 inSeqNumber); + Bool16 PacketShouldBeThinned(QTSS_RTPStreamObject inStream, StrPtrLen* inPacket); + Bool16 FilterPacket(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacket); + + UInt32 GetPacketRTPTime(StrPtrLen* packetStrPtr); +inline Bool16 PacketMatchesStream(void* inStreamCookie, QTSS_RTPStreamObject *theStreamPtr); + Bool16 PacketReadyToSend(QTSS_RTPStreamObject *theStreamPtr,SInt64 *currentTimePtr, UInt32 inFlags, UInt64* packetIDPtr, SInt64* timeToSendThisPacketAgainPtr); + Bool16 PacketAlreadySent(QTSS_RTPStreamObject *theStreamPtr, UInt32 inFlags, UInt64* packetIDPtr); + QTSS_Error TrackRTCPBaseTime(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64 *packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr); + QTSS_Error RewriteRTCP(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64 *packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr); + QTSS_Error TrackRTPPackets(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64 *packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr); + QTSS_Error TrackRTCPPackets(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64 *packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr); + QTSS_Error TrackPackets(QTSS_RTPStreamObject *theStreamPtr, StrPtrLen* inPacketStrPtr, SInt64 *currentTimePtr, UInt32 inFlags, SInt64 *packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSecPtr); +}; + + +Bool16 RTPSessionOutput::PacketMatchesStream(void* inStreamCookie, QTSS_RTPStreamObject *theStreamPtr) +{ + void** theStreamCookie = NULL; + UInt32 theLen = 0; + (void) QTSS_GetValuePtr(*theStreamPtr, fCookieAttrID, 0, (void**)&theStreamCookie, &theLen); + + if ((theStreamCookie != NULL) && (*theStreamCookie == inStreamCookie)) + return true; + + return false; +} +#endif //__RTSP_REFLECTOR_OUTPUT_H__ diff --git a/APIModules/QTSSReflectorModule/RTSPSourceInfo.cpp b/APIModules/QTSSReflectorModule/RTSPSourceInfo.cpp new file mode 100644 index 0000000..ed086b4 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RTSPSourceInfo.cpp @@ -0,0 +1,750 @@ +/* + * + * @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: RTSPSourceInfo.cpp + + Contains: + + + +*/ + +#include "RTSPSourceInfo.h" +#include "StringParser.h" +#include "SDPSourceInfo.h" +#include "OSMemory.h" +#include "StringFormatter.h" +#include "SocketUtils.h" +#include "RelayOutput.h" +#include "StringTranslator.h" +#include "SDPUtils.h" +#include "OSArrayObjectDeleter.h" + +StrPtrLen RTSPSourceInfo::sKeyString("rtsp_source"); +StrPtrLen RTSPSourceInfo::sAnnouncedKeyString("announced_rtsp_source"); + +// StrPtrLen's for various keywords on the relay_source & relay_destination lines +static StrPtrLen sOutAddr("out_addr"); +static StrPtrLen sDestAddr("dest_addr"); +static StrPtrLen sDestPorts("dest_ports"); +static StrPtrLen sTtl("ttl"); + +char* RTSPOutputInfo::CopyString(const char* srcStr) +{ + char* dstStr = NULL; + + if(srcStr != NULL) + { + UInt32 len = ::strlen(srcStr); + dstStr = NEW char[len + 1]; + ::memcpy(dstStr, srcStr, len); + dstStr[len] = '\0'; + } + + return dstStr; +} + +void RTSPOutputInfo::Copy(const RTSPOutputInfo& copy) +{ + fIsAnnounced = copy.fIsAnnounced; + fAnnouncePort = copy.fAnnouncePort; + + if(copy.fDestURl != NULL) + fDestURl = RTSPOutputInfo::CopyString(copy.fDestURl); + if(copy.fUserName != NULL) + fUserName = RTSPOutputInfo::CopyString(copy.fUserName); + if(copy.fPassword != NULL) + fPassword = RTSPOutputInfo::CopyString(copy.fPassword); +} + +RTSPSourceInfo::RTSPSourceInfo(const RTSPSourceInfo& copy) + :RCFSourceInfo(copy), + fHostAddr(copy.fHostAddr), fHostPort(copy.fHostPort), fLocalAddr(copy.fLocalAddr), + fNumSetupsComplete(copy.fNumSetupsComplete), fDescribeComplete(copy.fDescribeComplete), + fAnnounce(copy.fAnnounce), fAnnounceIP(copy.fAnnounceIP), fAnnounceActualIP(copy.fAnnounceIP),fQueueElem() +{ + fQueueElem.SetEnclosingObject(this); + fSourceURL = RTSPOutputInfo::CopyString(copy.fSourceURL); + fUserName = RTSPOutputInfo::CopyString(copy.fUserName); + fPassword = RTSPOutputInfo::CopyString(copy.fPassword); + fAnnounceURL = RTSPOutputInfo::CopyString(copy.fAnnounceURL); + + fRTSPInfoArray = NEW RTSPOutputInfo[fNumOutputs]; + for (UInt32 index=0; index < fNumOutputs; index++) + fRTSPInfoArray[index].Copy(copy.fRTSPInfoArray[index]); + + // These aren't set anyway and shouldn't be copied around + fClientSocket = NULL; + fClient = NULL; + fRelaySessionCreatorTask = NULL; + fSession = NULL; + fSessionQueue = NULL; + + if ((copy.fLocalSDP).Ptr != NULL) + fLocalSDP.Set((copy.fLocalSDP).GetAsCString(), (copy.fLocalSDP).Len); + +} + +RTSPSourceInfo::~RTSPSourceInfo() +{ + OSMutexLocker locker(RelayOutput::GetQueueMutex()); + + if (fRelaySessionCreatorTask != NULL) + fRelaySessionCreatorTask->fInfo = NULL; + + if (fDescribeComplete) + { + Assert(fClientSocket != NULL); + Assert(fClient != NULL); + // the task will delete these objects when it is done with the teardown + TeardownTask* task = new TeardownTask(fClientSocket, fClient); + task->Signal(Task::kStartEvent); + } + else + { + if (fClientSocket != NULL) delete fClientSocket; + if (fClient != NULL) delete fClient; + } + + if (fQueueElem.IsMemberOfAnyQueue()) + fQueueElem.Remove(); + + if (fLocalSDP.Ptr != NULL) delete fLocalSDP.Ptr; + fLocalSDP.Len = 0; + + if (fSourceURL != NULL) delete fSourceURL; + if (fAnnounceURL != NULL) delete fAnnounceURL; + + if (fRTSPInfoArray != NULL) delete [] fRTSPInfoArray; +} + +void RTSPSourceInfo::InitClient(UInt32 inSocketType) +{ + fClientSocket = NEW TCPClientSocket(inSocketType); + fClient = NEW RTSPClient(fClientSocket, false, RelaySession::sRelayUserAgent); +} + +void RTSPSourceInfo::SetClientInfo(UInt32 inAddr, UInt16 inPort, char* inURL, UInt32 inLocalAddr) +{ + if (fClientSocket != NULL) + fClientSocket->Set(inAddr, inPort); + + StrPtrLen inURLPtrLen(inURL); + + if (fClient != NULL) + fClient->Set(inURLPtrLen); + + if (inLocalAddr != 0) + fClientSocket->GetSocket()->Bind(inLocalAddr, 0); +} + +QTSS_Error RTSPSourceInfo::ParsePrefs(XMLTag* relayTag, Bool16 inAnnounce) +{ + XMLTag* prefTag; + UInt32 localAddr = 0; + UInt32 theHostAddr = 0; + UInt16 theHostPort = 554; + char* userName = NULL; + char* password = NULL; + StrPtrLen theURL; + + fAnnounce = inAnnounce; + + XMLTag* sourceTag = relayTag->GetEmbeddedTagByNameAndAttr("OBJECT", "CLASS", "source"); + if (sourceTag == NULL) + return QTSS_ValueNotFound; + + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "in_addr"); + if (prefTag != NULL) + { + char* inAddrStr = prefTag->GetValue(); + if (inAddrStr != NULL) + localAddr = SocketUtils::ConvertStringToAddr(inAddrStr); + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "source_addr"); + if (prefTag != NULL) + { + char* destAddrStr = prefTag->GetValue(); + if (destAddrStr != NULL) + theHostAddr = SocketUtils::ConvertStringToAddr(destAddrStr); + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "rtsp_port"); + if (prefTag != NULL) + { + char* portStr = prefTag->GetValue(); + if (portStr != NULL) + theHostPort = atoi(portStr); + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "name"); + if (prefTag != NULL) + { + userName = prefTag->GetValue(); + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "password"); + if (prefTag != NULL) + { + password = prefTag->GetValue(); + } + prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "url"); + char urlBuff[1024]; + if (prefTag != NULL) + { + char* urlString = prefTag->GetValue(); + StringTranslator::EncodeURL(urlString, strlen(urlString) + 1, urlBuff, sizeof(urlBuff)); + theURL.Set(urlBuff); + } + + if (fAnnounce) + { + fAnnounceURL = theURL.GetAsCString(); + fAnnounceIP = theHostAddr; + } + else + { + fHostAddr = theHostAddr; + fHostPort = theHostPort; + fSourceURL = theURL.GetAsCString(); + fUserName = RTSPOutputInfo::CopyString(userName); + fPassword = RTSPOutputInfo::CopyString(password); + } + + return QTSS_NoErr; +} + +QTSS_Error RTSPSourceInfo::Describe() +{ + QTSS_Error theErr = QTSS_NoErr; + + if (!fDescribeComplete) + { + // Work on the describe + theErr = fClient->SendDescribe(); + if (theErr != QTSS_NoErr) + return theErr; + if (fClient->GetStatus() != 200) + return QTSS_RequestFailed; + + // If the above function returns QTSS_NoErr, we've gotten the describe response, + // so process it. + SDPSourceInfo theSourceInfo(fClient->GetContentBody(), fClient->GetContentLength()); + + // Copy the Source Info into our local SourceInfo. + fNumStreams = theSourceInfo.GetNumStreams(); + fStreamArray = NEW StreamInfo[fNumStreams]; + + for (UInt32 x = 0; x < fNumStreams; x++) + { + // Copy fPayloadType, fPayloadName, fTrackID, fBufferDelay + fStreamArray[x].Copy(*theSourceInfo.GetStreamInfo(x)); + + // Copy all stream info data. Also set fSrcIPAddr to be the host addr + fStreamArray[x].fSrcIPAddr = fClientSocket->GetHostAddr(); + fStreamArray[x].fDestIPAddr = fClientSocket->GetLocalAddr(); + fStreamArray[x].fPort = 0; + fStreamArray[x].fTimeToLive = 0; + } + } + + // Ok, describe is complete, copy out the SDP information. + + fLocalSDP.Ptr = NEW char[fClient->GetContentLength() + 1]; + + // Look for an "a=range" line in the SDP. If there is one, remove it. + + static StrPtrLen sRangeStr("a=range:"); + StrPtrLen theSDPPtr(fClient->GetContentBody(), fClient->GetContentLength()); + StringParser theSDPParser(&theSDPPtr); + + do + { + // Loop until we reach the end of the SDP or hit a a=range line. + StrPtrLen theSDPLine(theSDPParser.GetCurrentPosition(), theSDPParser.GetDataRemaining()); + if ((theSDPLine.Len > sRangeStr.Len) && (theSDPLine.NumEqualIgnoreCase(sRangeStr.Ptr, sRangeStr.Len))) + break; + + } while (theSDPParser.GetThruEOL(NULL)); + + // Copy what we have so far + ::memcpy(fLocalSDP.Ptr, fClient->GetContentBody(), theSDPParser.GetDataParsedLen()); + fLocalSDP.Len = theSDPParser.GetDataParsedLen(); + + // Skip over the range (if it exists) + (void)theSDPParser.GetThruEOL(NULL); + + // Copy the rest of the SDP + ::memcpy(fLocalSDP.Ptr + fLocalSDP.Len, theSDPParser.GetCurrentPosition(), theSDPParser.GetDataRemaining()); + fLocalSDP.Len += theSDPParser.GetDataRemaining(); + +#define _WRITE_SDP_ 0 + +#if _WRITE_SDP_ + FILE* outputFile = ::fopen("rtspclient.sdp", "w"); + if (outputFile != NULL) + { + fLocalSDP.Ptr[fLocalSDP.Len] = '\0'; + qtss_fprintf(outputFile, "%s", fLocalSDP.Ptr); + ::fclose(outputFile); + qtss_printf("Wrote sdp to rtspclient.sdp\n"); + } + else + qtss_printf("Failed to write sdp\n"); +#endif + fDescribeComplete = true; + return QTSS_NoErr; +} + +QTSS_Error RTSPSourceInfo::SetupAndPlay() +{ + QTSS_Error theErr = QTSS_NoErr; + + // Do all the setups. This is async, so when a setup doesn't complete + // immediately, return an error, and we'll pick up where we left off. + while (fNumSetupsComplete < fNumStreams) + { + theErr = fClient->SendUDPSetup(fStreamArray[fNumSetupsComplete].fTrackID, fStreamArray[fNumSetupsComplete].fPort); + if (theErr != QTSS_NoErr) + return theErr; + else if (fClient->GetStatus() != 200) + return QTSS_RequestFailed; + else + fNumSetupsComplete++; + } + + // We've done all the setups. Now send a play. + theErr = fClient->SendPlay(0); + if (theErr != QTSS_NoErr) + return theErr; + if (fClient->GetStatus() != 200) + return QTSS_RequestFailed; + + return QTSS_NoErr; +} + +QTSS_Error RTSPSourceInfo::Teardown() +{ + return (QTSS_Error)fClient->SendTeardown(); +} + +char* RTSPSourceInfo::GetLocalSDP(UInt32* newSDPLen) +{ + *newSDPLen = fLocalSDP.Len; + return fLocalSDP.GetAsCString(); +} + +char* RTSPSourceInfo::GetAnnounceSDP(UInt32 ipAddr, UInt32* newSDPLen) +{ + char *announceSDP = NEW char[fLocalSDP.Len * 2]; + StringFormatter announceSDPFormatter(announceSDP, fLocalSDP.Len * 2); + + StrPtrLen sdpLine; + StringParser sdpParser(&fLocalSDP); + bool added = false; + + while (sdpParser.GetDataRemaining() > 0) + { + //stop when we reach an empty line. + sdpParser.GetThruEOL(&sdpLine); + if (sdpLine.Len == 0) + continue; + + switch (*sdpLine.Ptr) + { + case 'c': + break; // remove any existing c lines + case 'm': + { + if (!added) + { + added = true; + // add a c line before the first m line + char ipStr[50]; + char buff[50]; + StrPtrLen temp(buff); + + struct in_addr theIPAddr; + theIPAddr.s_addr = htonl(ipAddr); + SocketUtils::ConvertAddrToString(theIPAddr, &temp); + + qtss_sprintf(ipStr, "c=IN IP4 %s", buff); + StrPtrLen tempLine(ipStr); + announceSDPFormatter.Put(tempLine); + announceSDPFormatter.PutEOL(); + } + + announceSDPFormatter.Put(sdpLine); + announceSDPFormatter.PutEOL(); + break;//ignore connection information + } + default: + { + announceSDPFormatter.Put(sdpLine); + announceSDPFormatter.PutEOL(); + } + } + } + + *newSDPLen = (UInt32)announceSDPFormatter.GetCurrentOffset(); + announceSDP[*newSDPLen] = 0; + + StrPtrLen theSDPStr(announceSDP); + SDPContainer rawSDPContainer; + if (!rawSDPContainer.SetSDPBuffer( &theSDPStr )) + { return NULL; // it is screwed up do nothing the sdp will be deleted automatically + } + + SDPLineSorter sortedSDP(&rawSDPContainer); + return sortedSDP.GetSortedSDPCopy(); // return a new copy of the sorted SDP +} + +void RTSPSourceInfo::ParseAnnouncedDestination(XMLTag* destTag, UInt32 index) +{ + XMLTag* prefTag; + + fRTSPInfoArray[index].fIsAnnounced = true; + + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "out_addr"); + if (prefTag != NULL) + { + char* outAddrStr = prefTag->GetValue(); + if (outAddrStr != NULL) + fOutputArray[index].fLocalAddr = SocketUtils::ConvertStringToAddr(outAddrStr); + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "dest_addr"); + if (prefTag != NULL) + { + char* destAddrStr = prefTag->GetValue(); + if (destAddrStr != NULL) + fOutputArray[index].fDestAddr = SocketUtils::ConvertStringToAddr(destAddrStr); + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "rtsp_port"); + if (prefTag != NULL) + { + char* portStr = prefTag->GetValue(); + if (portStr != NULL) + fRTSPInfoArray[index].fAnnouncePort = atoi(portStr); + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "name"); + if (prefTag != NULL) + { + StrPtrLen userName(prefTag->GetValue()); + fRTSPInfoArray[index].fUserName = userName.GetAsCString(); + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "url"); + if (prefTag != NULL) + { + char urlBuff[1024]; + char* urlString = prefTag->GetValue(); + StringTranslator::EncodeURL(urlString, strlen(urlString) + 1, urlBuff, sizeof(urlBuff)); + StrPtrLen destURL(urlBuff); + fRTSPInfoArray[index].fDestURl = destURL.GetAsCString(); + } + prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "password"); + if (prefTag != NULL) + { + StrPtrLen password(prefTag->GetValue()); + fRTSPInfoArray[index].fPassword = password.GetAsCString(); + } +} + +void RTSPSourceInfo::AllocateOutputArray(UInt32 numOutputs) +{ + // Allocate the proper number of relay outputs + RCFSourceInfo::AllocateOutputArray(numOutputs); + fRTSPInfoArray = new RTSPOutputInfo[numOutputs]; +} + +Bool16 RTSPSourceInfo::Equal(SourceInfo* inInfo) +{ + if (!inInfo->IsRTSPSourceInfo()) + return false; + +// RTSPSourceInfo* info = dynamic_cast(inInfo); + RTSPSourceInfo* info = (RTSPSourceInfo *)(inInfo); + +// if (info == NULL) +// return false; + + if (!fAnnounce) + { + StrPtrLen source(fSourceURL); + if( source.Equal(info->GetSourceURL()) && (fHostAddr == info->GetHostAddr()) + && (fHostPort == info->GetHostPort()) ) + return true; + } + else + { + StrPtrLen announceURL(fAnnounceURL); + if ((fAnnounceIP == info->GetAnnounceIP()) && announceURL.Equal(info->GetAnnounceURL())) + return true; + } + + return false; +} + +void RTSPSourceInfo::SetSourceParameters(UInt32 inHostAddr, UInt16 inHostPort, StrPtrLen& inURL) +{ + fHostAddr = inHostAddr; + fHostPort = inHostPort; + fSourceURL = inURL.GetAsCString(); +} + +void RTSPSourceInfo::StartSessionCreatorTask(OSQueue* inSessionQueue, OSQueue* inSourceQueue) +{ + InitClient(Socket::kNonBlockingSocketType); + SetClientInfo(fHostAddr, fHostPort, fSourceURL, fLocalAddr); + if (fUserName != NULL) + fClient->SetName(fUserName); + if (fPassword != NULL) + fClient->SetPassword(fPassword); + + fSessionQueue = inSessionQueue; + + if(fAnnounce) + inSourceQueue->EnQueue(&fQueueElem); + + fSessionCreationState = kSendingDescribe; + fRelaySessionCreatorTask = NEW RelaySessionCreator(this); + fRelaySessionCreatorTask->Signal(Task::kStartEvent); +} + +SInt64 RTSPSourceInfo::RelaySessionCreator::Run() +{ + OSMutexLocker locker(RelayOutput::GetQueueMutex()); + SInt64 result = -1; + if (fInfo != NULL) + result = fInfo->RunCreateSession(); + + return result; +} + +SInt64 RTSPSourceInfo::RunCreateSession() +{ + OS_Error osErr = OS_NoErr; + SInt64 result = 500; + + if (fSessionCreationState == kSendingDescribe) + { + if (!fDescribeComplete) + { + osErr = fClient->SendDescribe(); + + if (osErr == OS_NoErr) + { + if (fClient->GetStatus() == 200) + { + // we've gotten the describe response, so process it. + SDPSourceInfo theSourceInfo(fClient->GetContentBody(), fClient->GetContentLength()); + + // Copy the Source Info into our local SourceInfo. + fNumStreams = theSourceInfo.GetNumStreams(); + fStreamArray = NEW StreamInfo[fNumStreams]; + + for (UInt32 x = 0; x < fNumStreams; x++) + { + // Copy fPayloadType, fPayloadName, fTrackID, fBufferDelay + fStreamArray[x].Copy(*theSourceInfo.GetStreamInfo(x)); + + // Copy all stream info data. Also set fSrcIPAddr to be the host addr + fStreamArray[x].fSrcIPAddr = fClientSocket->GetHostAddr(); + fStreamArray[x].fDestIPAddr = fClientSocket->GetLocalAddr(); + fStreamArray[x].fPort = 0; + fStreamArray[x].fTimeToLive = 0; + } + } + else + osErr = ENOTCONN; + } + } + + //describe is complete + if(osErr == OS_NoErr) + { + //copy out the SDP information + fLocalSDP.Ptr = NEW char[fClient->GetContentLength() + 1]; + + // Look for an "a=range" line in the SDP. If there is one, remove it. + static StrPtrLen sRangeStr("a=range:"); + StrPtrLen theSDPPtr(fClient->GetContentBody(), fClient->GetContentLength()); + StringParser theSDPParser(&theSDPPtr); + + do + { + // Loop until we reach the end of the SDP or hit a a=range line. + StrPtrLen theSDPLine(theSDPParser.GetCurrentPosition(), theSDPParser.GetDataRemaining()); + if ((theSDPLine.Len > sRangeStr.Len) && (theSDPLine.NumEqualIgnoreCase(sRangeStr.Ptr, sRangeStr.Len))) + break; + } while (theSDPParser.GetThruEOL(NULL)); + + // Copy what we have so far + ::memcpy(fLocalSDP.Ptr, fClient->GetContentBody(), theSDPParser.GetDataParsedLen()); + fLocalSDP.Len = theSDPParser.GetDataParsedLen(); + + // Skip over the range (if it exists) + (void)theSDPParser.GetThruEOL(NULL); + + // Copy the rest of the SDP + ::memcpy(fLocalSDP.Ptr + fLocalSDP.Len, theSDPParser.GetCurrentPosition(), theSDPParser.GetDataRemaining()); + fLocalSDP.Len += theSDPParser.GetDataRemaining(); + +#define _WRITE_SDP_ 0 + +#if _WRITE_SDP_ + FILE* outputFile = ::fopen("rtspclient.sdp", "w"); + if (outputFile != NULL) + { + fLocalSDP.Ptr[fLocalSDP.Len] = '\0'; + qtss_fprintf(outputFile, "%s", fLocalSDP.Ptr); + ::fclose(outputFile); + qtss_printf("Wrote sdp to rtspclient.sdp\n"); + } + else + qtss_printf("Failed to write sdp\n"); +#endif + fDescribeComplete = true; + + fSession = NEW RelaySession(NULL, this); + if (fSession->SetupRelaySession(this) == OS_NoErr) + { + fSessionCreationState = kSendingSetup; + } + else + { + osErr = ENOTCONN; + } + } + } + + while ((fSessionCreationState == kSendingSetup) && (osErr == OS_NoErr)) + { + osErr = fClient->SendUDPSetup(fStreamArray[fNumSetupsComplete].fTrackID, fStreamArray[fNumSetupsComplete].fPort); + if(osErr == OS_NoErr) + { + if(fClient->GetStatus() == 200) + { + fNumSetupsComplete++; + if (fNumSetupsComplete == fNumStreams) + fSessionCreationState = kSendingPlay; + } + else + osErr = ENOTCONN; + } + } + + if (fSessionCreationState == kSendingPlay) + { + osErr = fClient->SendPlay(0); + if (osErr == OS_NoErr) + { + if (fClient->GetStatus() == 200) + fSessionCreationState = kDone; + else + osErr = ENOTCONN; + } + } + + if (fSessionCreationState == kDone) + { + // If session was correctly set up, + // add the outputs + if(fSession != NULL) + { + // Format SourceInfo HTML for the stats web page + fSession->FormatHTML(fClient->GetURL()); + + OSMutexLocker locker(RelayOutput::GetQueueMutex()); + fSessionQueue->EnQueue(fSession->GetQueueElem()); + + for (UInt32 x = 0; x < fNumOutputs; x++) + { + SourceInfo::OutputInfo* theOutputInfo = GetOutputInfo(x); + if (theOutputInfo->fAlreadySetup) + continue; // shouldn't ever happen + + RelayOutput* theOutput = NEW RelayOutput(this, x, fSession, true); + if (theOutput->IsValid()) + fSession->AddOutput(theOutput, false); + else + delete theOutput; + } + } + fClientSocket->GetSocket()->SetTask(NULL); //detach the task from the socket + result = -1; // let the task die + fRelaySessionCreatorTask = NULL; + } + + if ((osErr == EINPROGRESS) || (osErr == EAGAIN)) + { + // Request an async event + fClientSocket->GetSocket()->SetTask(fRelaySessionCreatorTask); + fClientSocket->GetSocket()->RequestEvent(fClientSocket->GetEventMask() ); + } + else if (osErr != OS_NoErr) + { + // We encountered some fatal error with the socket. Record this as a connection failure + // delete the session + // delete the session + if(fSession != NULL) + { + delete fSession; + fSession = NULL; + } + + fClientSocket->GetSocket()->SetTask(NULL); //detach the task from the socket + result = -1; // let the task die + fRelaySessionCreatorTask = NULL; + } + + return result; +} + + +RTSPSourceInfo::TeardownTask::TeardownTask(TCPClientSocket* clientSocket, RTSPClient* client) +{ + this->SetTaskName("RTSPSourceInfo::TeardownTask"); + fClientSocket = clientSocket; + fClient = client; +} + +RTSPSourceInfo::TeardownTask::~TeardownTask() +{ + delete fClientSocket; + delete fClient; +} + +SInt64 RTSPSourceInfo::TeardownTask::Run() +{ + OS_Error err = fClient->SendTeardown(); + + if ((err == EINPROGRESS) || (err == EAGAIN)) + { + // Request an async event + fClientSocket->GetSocket()->SetTask(this); + fClientSocket->GetSocket()->RequestEvent(fClientSocket->GetEventMask() ); + return 250; + } + fClientSocket->GetSocket()->SetTask(NULL); //detach the task from the socket + return -1; // we're out of here, this will cause the destructor to be called +} + diff --git a/APIModules/QTSSReflectorModule/RTSPSourceInfo.h b/APIModules/QTSSReflectorModule/RTSPSourceInfo.h new file mode 100644 index 0000000..5748e1f --- /dev/null +++ b/APIModules/QTSSReflectorModule/RTSPSourceInfo.h @@ -0,0 +1,243 @@ +/* + * + * @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: RTSPSourceInfo.h + + Contains: + + +*/ + +#ifndef __RTSP_SOURCE_INFO_H__ +#define __RTSP_SOURCE_INFO_H__ + +#include "QTSS.h" +#include "StrPtrLen.h" +#include "RCFSourceInfo.h" +#include "RTSPClient.h" +#include "XMLParser.h" +#include "ClientSocket.h" +#include "RelaySession.h" + +class RelaySessionCreator; + +class RTSPOutputInfo +{ +public: + RTSPOutputInfo() : fIsAnnounced(false), + fAnnouncePort(554), + fDestURl(NULL), + fUserName(NULL), + fPassword(NULL) {} + + ~RTSPOutputInfo() + { + if (fDestURl != NULL) delete fDestURl; + if (fUserName != NULL) delete fUserName; + if (fPassword != NULL) delete fPassword; + } + + static char* CopyString(const char* srcStr); + void Copy(const RTSPOutputInfo& copy); // copies dynamically allocated data too + Bool16 Equal(const RTSPOutputInfo* inInfo) + { return ((inInfo != NULL) && (fIsAnnounced == inInfo->fIsAnnounced) && (fAnnouncePort == inInfo->fAnnouncePort) + && (strcmp(fDestURl, inInfo->fDestURl) == 0)); } + + + Bool16 fIsAnnounced; + UInt16 fAnnouncePort; + char* fDestURl; + char* fUserName; + char* fPassword; +}; + +class RTSPSourceInfo : public RCFSourceInfo +{ + public: + + // Specify whether the client should be blocking or non-blocking + RTSPSourceInfo(Bool16 inAnnounce) : fSourceURL(NULL), + fHostAddr(0), + fHostPort(0), + fLocalAddr(0), + fUserName(NULL), + fPassword(NULL), + fRTSPInfoArray(NULL), + fClientSocket(NULL), + fClient(NULL), + fNumSetupsComplete(0), + fDescribeComplete(false), + fAnnounce(inAnnounce), + fAnnounceURL(NULL), + fAnnounceIP(0), + fAnnounceActualIP(0), + fRelaySessionCreatorTask(NULL), + fSession(NULL), + fSessionQueue(NULL), + fQueueElem() { fQueueElem.SetEnclosingObject(this); } + + RTSPSourceInfo(const RTSPSourceInfo& copy); // Does copy dynamically allocated data + // Doesn't copy fClientSocket and fClient ptrs + + virtual ~RTSPSourceInfo(); + + // Call this before calling ParsePrefs / Describe + void InitClient(UInt32 inSocketType); + + void SetClientInfo(UInt32 inAddr, UInt16 inPort, char* inURL, UInt32 inLocalAddr = 0); + + // Call this immediately after the constructor. This object will parse + // the config file and extract the necessary information to connect to an rtsp server. + // Specify the config file line index where the "rtsp_source" line resides + QTSS_Error ParsePrefs(XMLTag* relayTag, Bool16 inAnnounce); + + // Connects, sends a DESCRIBE, and parses the incoming SDP data. After this + // function completes sucessfully, GetLocalSDP returns the data, and the + // SourceInfo & DestInfo arrays will be set up. Also sends SETUPs for all the + // tracks, and finishes by issuing a PLAY. + // + // These functions return QTSS_NoErr if the transaction has completed + // successfully. Otherwise, they return: + // + // EAGAIN: the transaction is still in progress, the call should be reissued + // QTSS_RequestFailed: the remote host responded with an error. + // Any other error means that the remote host was unavailable or refused the connection + QTSS_Error Describe(); + QTSS_Error SetupAndPlay(); + + // This function works the same way as the above ones, and should be + // called before destroying the object to let the remote host know that + // we are going away. + QTSS_Error Teardown(); + + // This function uses the Parsed SDP file, and strips out all the network information, + // producing an SDP file that appears to be local. + virtual char* GetLocalSDP(UInt32* newSDPLen); + virtual char* GetAnnounceSDP(UInt32 ipAddr, UInt32* newSDPLen); + virtual StrPtrLen* GetSourceID() { return fClient->GetURL(); } + + // This object looks for this keyword in the FilePrefsSource, where it + // expects the IP address, port, and URL. + static StrPtrLen& GetRTSPSourceString() { return sKeyString; } + + RTSPClient* GetRTSPClient() { return fClient; } + TCPClientSocket* GetClientSocket() { return fClientSocket; } + + Bool16 IsDescribeComplete(){ return fDescribeComplete; } + + RTSPOutputInfo* GetRTSPOutputInfo(UInt32 index) { return &fRTSPInfoArray[index]; } + char* GetSourceURL() { return fSourceURL; } + + virtual Bool16 IsRTSPSourceInfo() { return true; } + virtual Bool16 Equal(SourceInfo* inInfo); + + Bool16 IsAnnounce() { return fAnnounce; } + + char* GetAnnounceURL() { return fAnnounceURL; } + UInt32 GetAnnounceIP() { return fAnnounceIP; } + + UInt32 GetAnnounceActualIP() { return fAnnounceActualIP; } + void SetAnnounceActualIP(UInt32 inActualIP) { fAnnounceActualIP = inActualIP; } + + UInt32 GetHostAddr() { return fHostAddr; } + UInt32 GetHostPort() { return fHostPort; } + + char* GetUsername() { return fUserName; } + char* GetPassword() { return fPassword; } + + RelaySession* GetRelaySession() { return fSession; } + + void SetSourceParameters(UInt32 inHostAddr, UInt16 inHostPort, StrPtrLen& inURL); + + void StartSessionCreatorTask(OSQueue* inSessionQueue, OSQueue* inSourceQueue); + + SInt64 RunCreateSession(); + + protected: + virtual void ParseAnnouncedDestination(XMLTag* destTag, UInt32 index); + virtual void AllocateOutputArray(UInt32 numOutputs); + + private: + class RelaySessionCreator : public Task + { + public: + RelaySessionCreator(RTSPSourceInfo* inInfo) : fInfo(inInfo) {this->SetTaskName("RTSPSourceInfo::RelaySessionCreator");} + + virtual SInt64 Run(); + + RTSPSourceInfo* fInfo; + }; + + class TeardownTask : public Task + { + public: + TeardownTask(TCPClientSocket* clientSocket, RTSPClient* client); + virtual ~TeardownTask(); + + virtual SInt64 Run(); + + private: + TCPClientSocket* fClientSocket; + RTSPClient* fClient; + }; + + char* fSourceURL; + UInt32 fHostAddr; + UInt16 fHostPort; + UInt32 fLocalAddr; + char* fUserName; + char* fPassword; + RTSPOutputInfo* fRTSPInfoArray; + TCPClientSocket* fClientSocket; + RTSPClient* fClient; + UInt32 fNumSetupsComplete; + Bool16 fDescribeComplete; + StrPtrLen fLocalSDP; + + Bool16 fAnnounce; + char* fAnnounceURL; + UInt32 fAnnounceIP; + UInt32 fAnnounceActualIP; + RelaySessionCreator* fRelaySessionCreatorTask; + + enum // relay session creation states + { + kSendingDescribe = 0, + kSendingSetup = 1, + kSendingPlay = 2, + kDone = 3 + }; + UInt32 fSessionCreationState; + + RelaySession* fSession; + OSQueue* fSessionQueue; + + OSQueueElem fQueueElem; + + static StrPtrLen sKeyString; + static StrPtrLen sAnnouncedKeyString; +}; +#endif // __RTSP_SOURCE_INFO_H__ + diff --git a/APIModules/QTSSReflectorModule/ReflectorOutput.h b/APIModules/QTSSReflectorModule/ReflectorOutput.h new file mode 100644 index 0000000..20d5e6e --- /dev/null +++ b/APIModules/QTSSReflectorModule/ReflectorOutput.h @@ -0,0 +1,167 @@ +/* + * + * @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: ReflectorOutput.h + + Contains: VERY simple abstract base class that defines one virtual method, WritePacket. + This is extremely useful to the reflector, which, using one of these objects, + can transparently reflect a packet, not being aware of how it will actually be + written to the network + + + +*/ + +#ifndef __REFLECTOR_OUTPUT_H__ +#define __REFLECTOR_OUTPUT_H__ + +#include "QTSS.h" +#include "StrPtrLen.h" +#include "OSHeaders.h" +#include "MyAssert.h" +#include "OS.h" +#include "OSQueue.h" + + +class ReflectorOutput +{ + public: + + ReflectorOutput() : fBookmarkedPacketsElemsArray(NULL), fNumBookmarks(0), fAvailPosition(0), fLastIntervalMilliSec(5), fLastPacketTransmitTime(0) {} + + virtual ~ReflectorOutput() + { + if ( fBookmarkedPacketsElemsArray ) + { ::memset( fBookmarkedPacketsElemsArray, 0, sizeof ( OSQueueElem* ) * fNumBookmarks ); + delete [] fBookmarkedPacketsElemsArray; + } + } + + // an array of packet elements ( from fPacketQueue in ReflectorSender ) + // possibly one for each ReflectorSender that sends data to this ReflectorOutput + OSQueueElem **fBookmarkedPacketsElemsArray; + UInt32 fNumBookmarks; + SInt32 fAvailPosition; + QTSS_TimeVal fLastIntervalMilliSec; + QTSS_TimeVal fLastPacketTransmitTime; + + Bool16 fNewOutput; +inline OSQueueElem* GetBookMarkedPacket(OSQueue *thePacketQueue); +inline Bool16 SetBookMarkPacket(OSQueueElem* thePacketElemPtr); + + // WritePacket + // + // Pass in the packet contents, the cookie of the stream to which it will be written, + // and the QTSS API write flags (this should either be qtssWriteFlagsIsRTP or IsRTCP + // packetLateness is how many MSec's late this packet is in being delivered ( will be < 0 if its early ) + // If this function returns QTSS_WouldBlock, timeToSendThisPacketAgain will + // be set to # of msec in which the packet can be sent, or -1 if unknown + virtual QTSS_Error WritePacket(StrPtrLen* inPacket, void* inStreamCookie, UInt32 inFlags, SInt64 packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTimeMSec, Bool16 firstPacket ) = 0; + + virtual void TearDown() = 0; + virtual Bool16 IsUDP() = 0; + virtual Bool16 IsPlaying() = 0; + + enum { kWaitMilliSec = 5, kMaxWaitMilliSec = 1000 }; + + protected: + void InititializeBookmarks( UInt32 numStreams ) + { + // need 2 bookmarks for each stream ( include RTCPs ) + UInt32 numBookmarks = numStreams * 2; + + fBookmarkedPacketsElemsArray = new OSQueueElem*[numBookmarks]; + ::memset( fBookmarkedPacketsElemsArray, 0, sizeof ( OSQueueElem* ) * numBookmarks ); + + fNumBookmarks = numBookmarks; + } + +}; + +Bool16 ReflectorOutput::SetBookMarkPacket(OSQueueElem* thePacketElemPtr) +{ + if (fAvailPosition != -1 && thePacketElemPtr) + { + fBookmarkedPacketsElemsArray[fAvailPosition] = thePacketElemPtr; + + for (UInt32 i = 0; i < fNumBookmarks; i++) + { + if (fBookmarkedPacketsElemsArray[i] == NULL) + { + fAvailPosition = i; + return true; + } + } + } + + return false; + +} + +OSQueueElem* ReflectorOutput::GetBookMarkedPacket(OSQueue *thePacketQueue) +{ + Assert(thePacketQueue != NULL); + + OSQueueElem* packetElem = NULL; + UInt32 curBookmark = 0; + + fAvailPosition = -1; + + Assert( curBookmark < fNumBookmarks ); + + // see if we've bookmarked a held packet for this Sender in this Output + while ( curBookmark < fNumBookmarks ) + { + OSQueueElem* bookmarkedElem = fBookmarkedPacketsElemsArray[curBookmark]; + + if ( bookmarkedElem ) // there may be holes in this array + { + if ( bookmarkedElem->IsMember( *thePacketQueue ) ) + { + // this packet was previously bookmarked for this specific queue + // remove if from the bookmark list and use it + // to jump ahead into the Sender's over all packet queue + fBookmarkedPacketsElemsArray[curBookmark] = NULL; + fAvailPosition = curBookmark; + packetElem = bookmarkedElem; + break; + } + } + else + { + fAvailPosition = curBookmark; + } + + curBookmark++; + + } + + return packetElem; +} + + + + +#endif //__REFLECTOR_OUTPUT_H__ diff --git a/APIModules/QTSSReflectorModule/ReflectorSession.cpp b/APIModules/QTSSReflectorModule/ReflectorSession.cpp new file mode 100644 index 0000000..f7ff13f --- /dev/null +++ b/APIModules/QTSSReflectorModule/ReflectorSession.cpp @@ -0,0 +1,404 @@ +/* + * + * @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: ReflectorSession.cpp + + Contains: Implementation of object defined in ReflectorSession.h. + + + +*/ + + +#include "ReflectorSession.h" +#include "RTCPPacket.h" +#include "SocketUtils.h" +#include "EventContext.h" + +#include "OSMemory.h" +#include "OS.h" +#include "atomic.h" + +#include "QTSSModuleUtils.h" + +#include + + +#ifndef __Win32__ + #include +#endif + +#if DEBUG +#define REFLECTOR_SESSION_DEBUGGING 0 +#else +#define REFLECTOR_SESSION_DEBUGGING 0 +#endif + + +FileDeleter::FileDeleter(StrPtrLen* inSDPPath) +{ + Assert (inSDPPath); + fFilePath.Len = inSDPPath->Len; + fFilePath.Ptr = NEW char[inSDPPath->Len + 1]; + Assert (fFilePath.Ptr); + memcpy(fFilePath.Ptr, inSDPPath->Ptr,inSDPPath->Len); + fFilePath.Ptr[inSDPPath->Len] = 0; +} + + +FileDeleter::~FileDeleter() +{ + //qtss_printf("FileDeleter::~FileDeleter delete = %s \n",fFilePath.Ptr); + ::unlink(fFilePath.Ptr); + delete fFilePath.Ptr; + fFilePath.Ptr = NULL; + fFilePath.Len = 0; +} + + + +static OSRefTable* sStreamMap = NULL; + + + +void ReflectorSession::Initialize() +{ + if (sStreamMap == NULL) + sStreamMap = NEW OSRefTable(); +} + +ReflectorSession::ReflectorSession(StrPtrLen* inSourceID, SourceInfo* inInfo) +: fIsSetup(false), + fQueueElem(), + fNumOutputs(0), + fStreamArray(NULL), + fFormatter(fHTMLBuf, kMaxHTMLSize), + fSourceInfo(inInfo), + fSocketStream(NULL), + fBroadcasterSession(NULL), + fInitTimeMS(OS::Milliseconds()), + fHasBufferedStreams(false) +{ + + fQueueElem.SetEnclosingObject(this); + if (inSourceID != NULL) + { + fSourceID.Ptr = NEW char[inSourceID->Len + 1]; + ::memcpy(fSourceID.Ptr, inSourceID->Ptr, inSourceID->Len); + fSourceID.Len = inSourceID->Len; + fRef.Set(fSourceID, this); + } +} + + +ReflectorSession::~ReflectorSession() +{ +#if REFLECTOR_SESSION_DEBUGGING + qtss_printf("Removing ReflectorSession: %s\n", fSourceInfoHTML.Ptr); +#endif + + // For each stream, check to see if the ReflectorStream should be deleted + OSMutexLocker locker (sStreamMap->GetMutex()); + for (UInt32 x = 0; x < fSourceInfo->GetNumStreams(); x++) + { + if (fStreamArray[x] == NULL) + continue; + + UInt32 refCount = fStreamArray[x]->GetRef()->GetRefCount(); + Bool16 unregisterNow = (refCount == 1) ? true : false; + + //qtss_printf("ReflectorSession::~ReflectorSession stream index=%"_U32BITARG_" refcount=%"_U32BITARG_"\n",x,refCount); + //decrement the ref count + + if (refCount > 0) // Refcount may be 0 if there was some error setting up the stream + sStreamMap->Release(fStreamArray[x]->GetRef()); // decrement the refcount + + refCount = fStreamArray[x]->GetRef()->GetRefCount(); + if (refCount == 0) + { // Delete this stream if the refcount has dropped to 0 + if (unregisterNow) + sStreamMap->UnRegister(fStreamArray[x]->GetRef()); // Refcount may be 0 if there was some error setting up the stream + //qtss_printf("delete stream index=%"_U32BITARG_" refcount=%"_U32BITARG_"\n",x,refCount); + delete fStreamArray[x]; + fStreamArray[x] = NULL; + } + } + + // We own this object when it is given to us, so delete it now + delete [] fStreamArray; + delete fSourceInfo; + fLocalSDP.Delete(); + fSourceID.Delete(); +} + +QTSS_Error ReflectorSession::SetupReflectorSession(SourceInfo* inInfo, QTSS_StandardRTSP_Params* inParams, UInt32 inFlags, Bool16 filterState, UInt32 filterTimeout) +{ + if (inInfo == NULL) // use the current SourceInfo + inInfo = fSourceInfo; + + // Store a reference to this sourceInfo permanently + Assert((fSourceInfo == NULL) || (inInfo == fSourceInfo)); + fSourceInfo = inInfo; + + fLocalSDP.Delete();// this must be set to the new SDP. + fLocalSDP.Ptr = inInfo->GetLocalSDP(&fLocalSDP.Len); + + // Allocate all our ReflectorStreams, using the SourceInfo + + if (fStreamArray != NULL) + { for (UInt32 x = 0; x < fSourceInfo->GetNumStreams(); x++) + if (fSourceInfo->GetStreamInfo(x)->fPort > 0 && fStreamArray[x] != NULL) + sStreamMap->Release(fStreamArray[x]->GetRef()); + } + delete fStreamArray; // keep the array list synchronized with the source info. + fStreamArray = NEW ReflectorStream*[fSourceInfo->GetNumStreams()]; + ::memset(fStreamArray, 0, fSourceInfo->GetNumStreams() * sizeof(ReflectorStream*)); + + for (UInt32 x = 0; x < fSourceInfo->GetNumStreams(); x++) + { + // For each ReflectorStream, check and see if there is one that matches + // this stream ID + char theStreamID[ReflectorStream::kStreamIDSize]; + StrPtrLen theStreamIDPtr(theStreamID, ReflectorStream::kStreamIDSize); + ReflectorStream::GenerateSourceID(fSourceInfo->GetStreamInfo(x), &theStreamID[0]); + + OSMutexLocker locker(sStreamMap->GetMutex()); + OSRef* theStreamRef = NULL; + + if (false && (inFlags & kIsPushSession)) // always setup our own ports when pushed. + fSourceInfo->GetStreamInfo(x)->fPort = 0; + else + // If the port # of this stream is 0, that means "any port". + // We don't know what the dest port of this stream is yet (this + // can happen while acting as an RTSP client). Never share these streams. + // This can also happen if the incoming data is interleaved in the TCP connection or a dynamic UDP port is requested + if (fSourceInfo->GetStreamInfo(x)->fPort > 0) + { theStreamRef = sStreamMap->Resolve(&theStreamIDPtr); + #if REFLECTOR_SESSION_DEBUGGING + if (theStreamRef != NULL) + { + ReflectorStream* theRef = (ReflectorStream*)theStreamRef->GetObject(); + UInt32 refCount = theRef->GetRef()->GetRefCount(); + qtss_printf("stream has port stream index=%"_U32BITARG_" refcount=%"_U32BITARG_"\n",x,refCount); + } + #endif + } + + if (theStreamRef == NULL) + { + fStreamArray[x] = NEW ReflectorStream(fSourceInfo->GetStreamInfo(x)); + // Obviously, we may encounter an error binding the reflector sockets. + // If that happens, we'll just abort here, which will leave the ReflectorStream + // array in an inconsistent state, so we need to make sure in our cleanup + // code to check for NULL. + QTSS_Error theError = fStreamArray[x]->BindSockets(inParams,inFlags, filterState, filterTimeout); + if (theError != QTSS_NoErr) + { + delete fStreamArray[x]; + fStreamArray[x] = NULL; + return theError; + } + fStreamArray[x]->SetEnableBuffer(this->fHasBufferedStreams);// buffering is done by the stream's sender + + // If the port was 0, update it to reflect what the actual RTP port is. + fSourceInfo->GetStreamInfo(x)->fPort = fStreamArray[x]->GetStreamInfo()->fPort; + //qtss_printf("ReflectorSession::SetupReflectorSession fSourceInfo->GetStreamInfo(x)->fPort= %u\n",fSourceInfo->GetStreamInfo(x)->fPort); + + ReflectorStream::GenerateSourceID(fSourceInfo->GetStreamInfo(x), &theStreamID[0]); + + theError = sStreamMap->Register(fStreamArray[x]->GetRef()); + Assert(theError == QTSS_NoErr); + + //unless we do this, the refcount won't increment (and we'll delete the session prematurely + OSRef* debug = sStreamMap->Resolve(&theStreamIDPtr); + Assert(debug == fStreamArray[x]->GetRef()); + + //UInt32 refCount = fStreamArray[x]->GetRef()->GetRefCount(); + //qtss_printf("stream index=%"_U32BITARG_" refcount=%"_U32BITARG_"\n",x,refCount); + + } + else + fStreamArray[x] = (ReflectorStream*)theStreamRef->GetObject(); + + } + + + if (inFlags & kMarkSetup) + fIsSetup = true; + + return QTSS_NoErr; +} + +void ReflectorSession::AddBroadcasterClientSession(QTSS_StandardRTSP_Params* inParams) +{ + if (NULL == fStreamArray || NULL == inParams) + return; + + for (UInt32 x = 0; x < fSourceInfo->GetNumStreams(); x++) + { + if (fStreamArray[x] != NULL) + { //qtss_printf("AddBroadcasterSession=%"_U32BITARG_"\n",inParams->inClientSession); + ((ReflectorSocket*)fStreamArray[x]->GetSocketPair()->GetSocketA())->AddBroadcasterSession(inParams->inClientSession); + ((ReflectorSocket*)fStreamArray[x]->GetSocketPair()->GetSocketB())->AddBroadcasterSession(inParams->inClientSession); + } + } + fBroadcasterSession = inParams->inClientSession; +} +void ReflectorSession::FormatHTML(StrPtrLen* inURL) +{ + // Begin writing our source description HTML (used by the relay) + // Line looks like: Relay Source: 17.221.98.239, Ports: 5430 5432 5434 + static StrPtrLen sHTMLStart("

Relay Source: "); + static StrPtrLen sPorts(", Ports: "); + static StrPtrLen sHTMLEnd("


"); + + // Begin writing the HTML + fFormatter.Put(sHTMLStart); + + if (inURL == NULL) + { + // If no URL is provided, format the source IP addr as a string. + char theIPAddrBuf[20]; + StrPtrLen theIPAddr(theIPAddrBuf, 20); + struct in_addr theAddr; + theAddr.s_addr = htonl(fSourceInfo->GetStreamInfo(0)->fSrcIPAddr); + SocketUtils::ConvertAddrToString(theAddr, &theIPAddr); + fFormatter.Put(theIPAddr); + } + else + fFormatter.Put(*inURL); + + fFormatter.Put(sPorts); + + for (UInt32 x = 0; x < fSourceInfo->GetNumStreams(); x++) + { + fFormatter.Put(fSourceInfo->GetStreamInfo(x)->fPort); + fFormatter.PutSpace(); + } + fFormatter.Put(sHTMLEnd); + + // Setup the StrPtrLen to point to the right stuff + fSourceInfoHTML.Ptr = fFormatter.GetBufPtr(); + fSourceInfoHTML.Len = fFormatter.GetCurrentOffset(); + + fFormatter.PutTerminator(); +} + + +void ReflectorSession::AddOutput(ReflectorOutput* inOutput, Bool16 isClient) +{ + Assert(fSourceInfo->GetNumStreams() > 0); + + // We need to make sure that this output goes into the same bucket for each ReflectorStream. + SInt32 bucket = -1; + SInt32 lastBucket = -1; + + while (true) + { + UInt32 x = 0; + for ( ; x < fSourceInfo->GetNumStreams(); x++) + { + bucket = fStreamArray[x]->AddOutput(inOutput, bucket); + if (bucket == -1) // If this output couldn't be added to this bucket, + break; // break and try again + else + { + lastBucket = bucket; // Remember the last successful bucket placement. + if (isClient) + fStreamArray[x]->IncEyeCount(); + } + } + + if (bucket == -1) + { + // If there was some kind of conflict adding this output to this bucket, + // we need to remove it from the streams to which it was added. + for (UInt32 y = 0; y < x; y++) + { + fStreamArray[y]->RemoveOutput(inOutput); + if (isClient) + fStreamArray[y]->DecEyeCount(); + } + + // Because there was an error, we need to start the whole process over again, + // this time starting from a higher bucket + lastBucket = bucket = lastBucket + 1; + } + else + break; + } + (void)atomic_add(&fNumOutputs, 1); +} + +void ReflectorSession::RemoveOutput(ReflectorOutput* inOutput, Bool16 isClient) +{ + (void)atomic_sub(&fNumOutputs, 1); + for (UInt32 y = 0; y < fSourceInfo->GetNumStreams(); y++) + { + fStreamArray[y]->RemoveOutput(inOutput); + if (isClient) + fStreamArray[y]->DecEyeCount(); + } +} + +void ReflectorSession::TearDownAllOutputs() +{ + for (UInt32 y = 0; y < fSourceInfo->GetNumStreams(); y++) + fStreamArray[y]->TearDownAllOutputs(); +} + +void ReflectorSession::RemoveSessionFromOutput(QTSS_ClientSessionObject inSession) +{ + for (UInt32 x = 0; x < fSourceInfo->GetNumStreams(); x++) + { ((ReflectorSocket*)fStreamArray[x]->GetSocketPair()->GetSocketA())->RemoveBroadcasterSession(inSession); + ((ReflectorSocket*)fStreamArray[x]->GetSocketPair()->GetSocketB())->RemoveBroadcasterSession(inSession); + } + fBroadcasterSession = NULL; +} + + +UInt32 ReflectorSession::GetBitRate() +{ + UInt32 retval = 0; + for (UInt32 x = 0; x < fSourceInfo->GetNumStreams(); x++) + retval += fStreamArray[x]->GetBitRate(); + return retval; +} + +Bool16 ReflectorSession::Equal(SourceInfo* inInfo) +{ + return fSourceInfo->Equal(inInfo); +} + +void* ReflectorSession::GetStreamCookie(UInt32 inStreamID) +{ + for (UInt32 x = 0; x < fSourceInfo->GetNumStreams(); x++) + { + if (fSourceInfo->GetStreamInfo(x)->fTrackID == inStreamID) + return fStreamArray[x]->GetStreamCookie(); + } + return NULL; +} + diff --git a/APIModules/QTSSReflectorModule/ReflectorSession.h b/APIModules/QTSSReflectorModule/ReflectorSession.h new file mode 100644 index 0000000..69a14a4 --- /dev/null +++ b/APIModules/QTSSReflectorModule/ReflectorSession.h @@ -0,0 +1,201 @@ +/* + * + * @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: ReflectorSession.h + + Contains: This object supports reflecting an RTP multicast stream to N + RTPStreams. It spaces out the packet send times in order to + maximize the randomness of the sending pattern and smooth + the stream. + + + +*/ + + + +#include "QTSS.h" +#include "OSRef.h" +#include "StrPtrLen.h" +#include "ResizeableStringFormatter.h" +#include "MyAssert.h" + +#include "ReflectorStream.h" +#include "SourceInfo.h" +#include "OSArrayObjectDeleter.h" + + +#ifndef _FILE_DELETER_ +#define _FILE_DELETER_ + +class FileDeleter +{ public: + FileDeleter(StrPtrLen* inSDPPath); + ~FileDeleter(); + + private: + StrPtrLen fFilePath; +}; + +#endif + + +#ifndef __REFLECTOR_SESSION__ +#define __REFLECTOR_SESSION__ +class ReflectorSession +{ + public: + + // Public interface to generic RTP packet forwarding engine + + // + // Initialize + // + // Call initialize before calling any other function in this class + static void Initialize(); + + // Create one of these ReflectorSessions per source broadcast. For mapping purposes, + // the object can be constructred using an optional source ID. + // + // Caller may also provide a SourceInfo object, though it is not needed and + // will also need to be provided to SetupReflectorSession when that is called. + ReflectorSession(StrPtrLen* inSourceID, SourceInfo* inInfo = NULL); + virtual ~ReflectorSession(); + + // + // MODIFIERS + + // Call this to initialize and setup the source sockets. Once this function + // completes sucessfully, Outputs may be added through the function calls below. + // + // The SourceInfo object passed in here will be owned by the ReflectorSession. Do not + // delete it. + + enum + { + kMarkSetup = 0, //After SetupReflectorSession is called, IsSetup returns true + kDontMarkSetup = 1, //After SetupReflectorSession is called, IsSetup returns false + kIsPushSession = 2 // When setting up streams handle port conflicts by allocating. + }; + + QTSS_Error SetupReflectorSession(SourceInfo* inInfo, QTSS_StandardRTSP_Params* inParams, + UInt32 inFlags = kMarkSetup, Bool16 filterState = true, UInt32 filterTimeout = 30); + + // Packets get forwarded by attaching ReflectorOutput objects to a ReflectorSession. + + void AddOutput(ReflectorOutput* inOutput, Bool16 isClient); + void RemoveOutput(ReflectorOutput* inOutput, Bool16 isClient); + void TearDownAllOutputs(); + void RemoveSessionFromOutput(QTSS_ClientSessionObject inSession); + void ManuallyMarkSetup() { fIsSetup = true; } + + // For the Relay's status, a ReflectorSession can format an informative bit of + // HTML to describe the source. This must be called after the ReflectorSession + // is all setup. + + void FormatHTML(StrPtrLen* inURL); + + // + // ACCESSORS + + OSRef* GetRef() { return &fRef; } + OSQueueElem* GetQueueElem() { return &fQueueElem; } + UInt32 GetNumOutputs() { return fNumOutputs; } + UInt32 GetNumStreams() { return fSourceInfo->GetNumStreams(); } + StrPtrLen* GetSourceInfoHTML() { return &fSourceInfoHTML; } + SourceInfo* GetSourceInfo() { return fSourceInfo; } + StrPtrLen* GetLocalSDP() { return &fLocalSDP; } + StrPtrLen* GetSourcePath() { return &fSourceID; } + Bool16 IsSetup() { return fIsSetup; } + ReflectorStream*GetStreamByIndex(UInt32 inIndex) { return fStreamArray[inIndex]; } + void AddBroadcasterClientSession(QTSS_StandardRTSP_Params* inParams); + QTSS_ClientSessionObject GetBroadcasterSession() { return fBroadcasterSession;} + + // For the QTSSSplitterModule, this object can cache a QTSS_StreamRef + void SetSocketStream(QTSS_StreamRef inStream) { fSocketStream = inStream; } + QTSS_StreamRef GetSocketStream() { return fSocketStream; } + + // A ReflectorSession keeps track of the aggregate bit rate each + // stream is reflecting (RTP only). Initially, this will return 0 + // until enough time passes to compute an accurate average. + UInt32 GetBitRate(); + + // Returns true if this SourceInfo structure is equivalent to this + // ReflectorSession. + Bool16 Equal(SourceInfo* inInfo); + + // Each stream has a cookie associated with it. When the stream writes a packet + // to an output, this cookie is used to identify which stream is writing the packet. + // The below function is useful so outputs can get the cookie value for a stream ID, + // and therefore mux the cookie to the right output stream. + void* GetStreamCookie(UInt32 inStreamID); + + //Reflector quality levels: + enum + { + kMaxHTMLSize = 128, + kAudioOnlyQuality = 1, //UInt32 + kNormalQuality = 0, //UInt32 + kNumQualityLevels = 2 //UInt32 + }; + + SInt64 GetInitTimeMS() { return fInitTimeMS; } + + void SetHasBufferedStreams(Bool16 enableBuffer) { fHasBufferedStreams = enableBuffer; } + + private: + + // Is this session setup? + Bool16 fIsSetup; + + // For storage in the session map + OSRef fRef; + StrPtrLen fSourceID; + OSQueueElem fQueueElem; // Relay uses this. + + unsigned int fNumOutputs; + + ReflectorStream** fStreamArray; + + char fHTMLBuf[kMaxHTMLSize]; + StrPtrLen fSourceInfoHTML; + ResizeableStringFormatter fFormatter; + + // The reflector session needs to hang onto the source info object + // for it's entire lifetime. Right now, this is used for reflector-as-client. + SourceInfo* fSourceInfo; + StrPtrLen fLocalSDP; + + // For the QTSSSplitterModule, this object can cache a QTSS_StreamRef + QTSS_StreamRef fSocketStream; + QTSS_ClientSessionObject fBroadcasterSession; + SInt64 fInitTimeMS; + + Bool16 fHasBufferedStreams; + +}; + +#endif + diff --git a/APIModules/QTSSReflectorModule/ReflectorStream.cpp b/APIModules/QTSSReflectorModule/ReflectorStream.cpp new file mode 100644 index 0000000..705074b --- /dev/null +++ b/APIModules/QTSSReflectorModule/ReflectorStream.cpp @@ -0,0 +1,1617 @@ +/* + * + * @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: ReflectorStream.cpp + + Contains: Implementation of object defined in ReflectorStream.h. + + + +*/ + +#include "ReflectorStream.h" +#include "QTSSModuleUtils.h" +#include "OSMemory.h" +#include "SocketUtils.h" +#include "atomic.h" +#include "RTCPPacket.h" +#include "ReflectorSession.h" + + +#if DEBUG +#define REFLECTOR_STREAM_DEBUGGING 0 +#else +#define REFLECTOR_STREAM_DEBUGGING 0 +#endif + + +static ReflectorSocketPool sSocketPool; + +// ATTRIBUTES + +static QTSS_AttributeID sCantBindReflectorSocketErr = qtssIllegalAttrID; +static QTSS_AttributeID sCantJoinMulticastGroupErr = qtssIllegalAttrID; + +// PREFS + +static UInt32 sDefaultOverBufferInSec = 10; +static UInt32 sDefaultBucketDelayInMsec = 73; +static Bool16 sDefaultUsePacketReceiveTime = false; +static UInt32 sDefaultMaxFuturePacketTimeSec = 60; +static UInt32 sDefaultFirstPacketOffsetMsec = 500; + +UInt32 ReflectorStream::sBucketSize = 16; +UInt32 ReflectorStream::sOverBufferInMsec = 10000; // more or less what the client over buffer will be +UInt32 ReflectorStream::sMaxFuturePacketMSec = 60000; // max packet future time +UInt32 ReflectorStream::sMaxPacketAgeMSec = 10000; + +UInt32 ReflectorStream::sMaxFuturePacketSec = 60; // max packet future time +UInt32 ReflectorStream::sOverBufferInSec = 10; +UInt32 ReflectorStream::sBucketDelayInMsec = 73; +Bool16 ReflectorStream::sUsePacketReceiveTime = false; +UInt32 ReflectorStream::sFirstPacketOffsetMsec = 500; + +void ReflectorStream::Register() +{ + // Add text messages attributes + static char* sCantBindReflectorSocket= "QTSSReflectorModuleCantBindReflectorSocket"; + static char* sCantJoinMulticastGroup = "QTSSReflectorModuleCantJoinMulticastGroup"; + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sCantBindReflectorSocket, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sCantBindReflectorSocket, &sCantBindReflectorSocketErr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sCantJoinMulticastGroup, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sCantJoinMulticastGroup, &sCantJoinMulticastGroupErr); +} + +void ReflectorStream::Initialize(QTSS_ModulePrefsObject inPrefs) +{ + + QTSSModuleUtils::GetAttribute(inPrefs, "reflector_bucket_offset_delay_msec", qtssAttrDataTypeUInt32, + &ReflectorStream::sBucketDelayInMsec, &sDefaultBucketDelayInMsec, sizeof(sBucketDelayInMsec)); + + QTSSModuleUtils::GetAttribute(inPrefs, "reflector_buffer_size_sec", qtssAttrDataTypeUInt32, + &ReflectorStream::sOverBufferInSec, &sDefaultOverBufferInSec, sizeof(sDefaultOverBufferInSec)); + + QTSSModuleUtils::GetAttribute(inPrefs, "reflector_use_in_packet_receive_time", qtssAttrDataTypeBool16, + &ReflectorStream::sUsePacketReceiveTime, &sDefaultUsePacketReceiveTime, sizeof(sDefaultUsePacketReceiveTime)); + + QTSSModuleUtils::GetAttribute(inPrefs, "reflector_in_packet_max_receive_sec", qtssAttrDataTypeUInt32, + &ReflectorStream::sMaxFuturePacketSec, &sDefaultMaxFuturePacketTimeSec, sizeof(sDefaultMaxFuturePacketTimeSec)); + + QTSSModuleUtils::GetAttribute(inPrefs, "reflector_rtp_info_offset_msec", qtssAttrDataTypeUInt32, + &ReflectorStream::sFirstPacketOffsetMsec, &sDefaultFirstPacketOffsetMsec, sizeof(sDefaultFirstPacketOffsetMsec)); + + ReflectorStream::sOverBufferInMsec = sOverBufferInSec * 1000; + ReflectorStream::sMaxFuturePacketMSec = sMaxFuturePacketSec * 1000; + ReflectorStream::sMaxPacketAgeMSec = (UInt32) (sOverBufferInMsec * 1.5); //allow a little time before deleting. + +} + +void ReflectorStream::GenerateSourceID(SourceInfo::StreamInfo* inInfo, char* ioBuffer) +{ + + ::memcpy(ioBuffer, &inInfo->fSrcIPAddr, sizeof(inInfo->fSrcIPAddr)); + ::memcpy(&ioBuffer[sizeof(inInfo->fSrcIPAddr)], &inInfo->fPort, sizeof(inInfo->fPort)); +} + + +ReflectorStream::ReflectorStream(SourceInfo::StreamInfo* inInfo) +: fPacketCount(0), + fSockets(NULL), + fRTPSender(NULL, qtssWriteFlagsIsRTP), + fRTCPSender(NULL, qtssWriteFlagsIsRTCP), + fOutputArray(NULL), + fNumBuckets(kMinNumBuckets), + fNumElements(0), + fBucketMutex(), + + fDestRTCPAddr(0), + fDestRTCPPort(0), + + fCurrentBitRate(0), + fLastBitRateSample(OS::Milliseconds()), // don't calculate our first bit rate until kBitRateAvgIntervalInMilSecs has passed! + fBytesSentInThisInterval(0), + + fRTPChannel(-1), + fRTCPChannel(-1), + fHasFirstRTCPPacket(false), + fHasFirstRTPPacket(false), + fEnableBuffer(false), + fEyeCount(0), + fFirst_RTCP_RTP_Time(0), + fFirst_RTCP_Arrival_Time(0) +{ + + fRTPSender.fStream = this; + fRTCPSender.fStream = this; + + fStreamInfo.Copy(*inInfo); + + // ALLOCATE BUCKET ARRAY + this->AllocateBucketArray(fNumBuckets); + + // WRITE RTCP PACKET + + //write as much of the RTCP RR as is possible right now (most of it never changes) + UInt32 theSsrc = (UInt32)::rand(); + char theTempCName[RTCPSRPacket::kMaxCNameLen]; + UInt32 cNameLen = RTCPSRPacket::GetACName(theTempCName); + + //write the RR (just header + ssrc) + UInt32* theRRWriter = (UInt32*)&fReceiverReportBuffer[0]; + *theRRWriter = htonl(0x80c90001); + theRRWriter++; + *theRRWriter = htonl(theSsrc); + theRRWriter++; + + //SDES length is the length of the CName, plus 2 32bit words, minus 1 + *theRRWriter = htonl(0x81ca0000 + (cNameLen >> 2) + 1); + theRRWriter++; + *theRRWriter = htonl(theSsrc); + theRRWriter++; + ::memcpy(theRRWriter, theTempCName, cNameLen); + theRRWriter += cNameLen >> 2; + + //APP packet format, QTSS specific stuff + *theRRWriter = htonl(0x80cc0008); + theRRWriter++; + *theRRWriter = htonl(theSsrc); + theRRWriter++; + *theRRWriter = htonl(FOUR_CHARS_TO_INT('Q','T','S','S')); + theRRWriter++; + *theRRWriter = htonl(0); + theRRWriter++; + *theRRWriter = htonl(0x00000004); + theRRWriter++; + *theRRWriter = htonl(0x6579000c); + theRRWriter++; + + fEyeLocation = theRRWriter; + fReceiverReportSize = kReceiverReportSize + kAppSize + cNameLen; + + // If the source is a multicast, we should send our receiver reports + // to the multicast address + if (SocketUtils::IsMulticastIPAddr(fStreamInfo.fDestIPAddr)) + { + fDestRTCPAddr = fStreamInfo.fDestIPAddr; + fDestRTCPPort = fStreamInfo.fPort + 1; + } +} + + +ReflectorStream::~ReflectorStream() +{ + Assert(fNumElements == 0); + + if (fSockets != NULL) + { + //first things first, let's take this stream off the socket's queue + //of streams. This will basically ensure that no reflecting activity + //can happen on this stream. + ((ReflectorSocket*)fSockets->GetSocketA())->RemoveSender(&fRTPSender); + ((ReflectorSocket*)fSockets->GetSocketB())->RemoveSender(&fRTCPSender); + + //leave the multicast group. Because this socket is shared amongst several + //potential multicasts, we don't want to remain a member of a stale multicast + if (SocketUtils::IsMulticastIPAddr(fStreamInfo.fDestIPAddr)) + { + fSockets->GetSocketA()->LeaveMulticast(fStreamInfo.fDestIPAddr); + fSockets->GetSocketB()->LeaveMulticast(fStreamInfo.fDestIPAddr); + } + //now release the socket pair + sSocketPool.ReleaseUDPSocketPair(fSockets); + } + + //qtss_printf("Deleting stream %x\n", this); + + //delete every client Bucket + for (UInt32 y = 0; y < fNumBuckets; y++) + delete [] fOutputArray[y]; + delete [] fOutputArray; +} + +void ReflectorStream::AllocateBucketArray(UInt32 inNumBuckets) +{ + Bucket* oldArray = fOutputArray; + //allocate the 2-dimensional array + fOutputArray = NEW Bucket[inNumBuckets]; + for (UInt32 x = 0; x < inNumBuckets; x++) + { + fOutputArray[x] = NEW ReflectorOutput*[sBucketSize]; + ::memset(fOutputArray[x], 0, sizeof(ReflectorOutput*) * sBucketSize); + } + + //copy over the old information if there was an old array + if (oldArray != NULL) + { + Assert(inNumBuckets > fNumBuckets); + for (UInt32 y = 0; y < fNumBuckets; y++) + { + ::memcpy(fOutputArray[y],oldArray[y], sBucketSize * sizeof(ReflectorOutput*)); + delete [] oldArray[y]; + } + delete [] oldArray; + } + fNumBuckets = inNumBuckets; +} + + +SInt32 ReflectorStream::AddOutput(ReflectorOutput* inOutput, SInt32 putInThisBucket) +{ + OSMutexLocker locker(&fBucketMutex); + +#if DEBUG + // We should never be adding an output twice to a stream + for (UInt32 dOne = 0; dOne < fNumBuckets; dOne++) + for (UInt32 dTwo = 0; dTwo < sBucketSize; dTwo++) + Assert(fOutputArray[dOne][dTwo] != inOutput); +#endif + + // If caller didn't specify a bucket, find a bucket + if (putInThisBucket < 0) + putInThisBucket = this->FindBucket(); + + Assert(putInThisBucket >= 0); + + if (fNumBuckets <= (UInt32)putInThisBucket) + this->AllocateBucketArray(putInThisBucket * 2); + + for(UInt32 y = 0; y < sBucketSize; y++) + { + if (fOutputArray[putInThisBucket][y] == NULL) + { + fOutputArray[putInThisBucket][y] = inOutput; +#if REFLECTOR_STREAM_DEBUGGING + qtss_printf("Adding new output (0x%lx) to bucket %"_S32BITARG_", index %"_S32BITARG_",\nnum buckets %li bucketSize: %li \n",(SInt32)inOutput, putInThisBucket, y, (SInt32)fNumBuckets, (SInt32)sBucketSize); +#endif + fNumElements++; + return putInThisBucket; + } + } + // There was no empty spot in the specified bucket. Return an error + return -1; +} + +SInt32 ReflectorStream::FindBucket() +{ + // If we need more buckets, allocate them. + if (fNumElements == (sBucketSize * fNumBuckets)) + this->AllocateBucketArray(fNumBuckets * 2); + + //find the first open spot in the array + for (SInt32 putInThisBucket = 0; (UInt32)putInThisBucket < fNumBuckets; putInThisBucket++) + { + for(UInt32 y = 0; y < sBucketSize; y++) + if (fOutputArray[putInThisBucket][y] == NULL) + return putInThisBucket; + } + Assert(0); + return 0; +} + +void ReflectorStream::RemoveOutput(ReflectorOutput* inOutput) +{ + OSMutexLocker locker(&fBucketMutex); + Assert(fNumElements > 0); + + //look at all the indexes in the array + for (UInt32 x = 0; x < fNumBuckets; x++) + { + for (UInt32 y = 0; y < sBucketSize; y++) + { + //The array may have blank spaces! + if (fOutputArray[x][y] == inOutput) + { + fOutputArray[x][y] = NULL;//just clear out the pointer + +#if REFLECTOR_STREAM_DEBUGGING + qtss_printf("Removing output %x from bucket %"_S32BITARG_", index %"_S32BITARG_"\n",inOutput,x,y); +#endif + fNumElements--; + return; + } + } + } + Assert(0); +} + +void ReflectorStream::TearDownAllOutputs() +{ + + OSMutexLocker locker(&fBucketMutex); + + //look at all the indexes in the array + for (UInt32 x = 0; x < fNumBuckets; x++) + { + for (UInt32 y = 0; y < sBucketSize; y++) + { ReflectorOutput* theOutputPtr= fOutputArray[x][y]; + //The array may have blank spaces! + if (theOutputPtr != NULL) + { theOutputPtr->TearDown(); +#if REFLECTOR_STREAM_DEBUGGING + qtss_printf("TearDownAllOutputs Removing output from bucket %"_S32BITARG_", index %"_S32BITARG_"\n",x,y); +#endif + } + } + } +} + + +QTSS_Error ReflectorStream::BindSockets(QTSS_StandardRTSP_Params* inParams, UInt32 inReflectorSessionFlags, Bool16 filterState, UInt32 timeout) +{ + // If the incoming data is RTSP interleaved, we don't need to do anything here + if (inReflectorSessionFlags & ReflectorSession::kIsPushSession) + fStreamInfo.fSetupToReceive = true; + + QTSS_RTSPRequestObject inRequest = NULL; + if (inParams != NULL) + inRequest = inParams->inRTSPRequest; + + // Set the transport Type a Broadcaster + QTSS_RTPTransportType transportType = qtssRTPTransportTypeUDP; + if (inParams != NULL) + { UInt32 theLen = sizeof(transportType); + (void) QTSS_GetValue(inParams->inRTSPRequest, qtssRTSPReqTransportType, 0, (void*)&transportType, &theLen); + } + + // get a pair of sockets. The socket must be bound on INADDR_ANY because we don't know + // which interface has access to this broadcast. If there is a source IP address + // specified by the source info, we can use that to demultiplex separate broadcasts on + // the same port. If the src IP addr is 0, we cannot do this and must dedicate 1 port per + // broadcast + + // changing INADDR_ANY to fStreamInfo.fDestIPAddr to deal with NATs (need to track this change though) + // change submitted by denis@berlin.ccc.de + Bool16 isMulticastDest = (SocketUtils::IsMulticastIPAddr(fStreamInfo.fDestIPAddr)); + if (isMulticastDest) { + fSockets = sSocketPool.GetUDPSocketPair(INADDR_ANY, fStreamInfo.fPort, fStreamInfo.fSrcIPAddr, 0); + } else { + fSockets = sSocketPool.GetUDPSocketPair(fStreamInfo.fDestIPAddr, fStreamInfo.fPort, fStreamInfo.fSrcIPAddr, 0); + } + + if ((fSockets == NULL) && fStreamInfo.fSetupToReceive) + { + fStreamInfo.fPort = 0; + if (isMulticastDest) { + fSockets = sSocketPool.GetUDPSocketPair(INADDR_ANY, fStreamInfo.fPort, fStreamInfo.fSrcIPAddr, 0); + } else { + fSockets = sSocketPool.GetUDPSocketPair(fStreamInfo.fDestIPAddr, fStreamInfo.fPort, fStreamInfo.fSrcIPAddr, 0); + } + } + if (fSockets == NULL) + return QTSSModuleUtils::SendErrorResponse(inRequest, qtssServerInternal, + sCantBindReflectorSocketErr); + + // If we know the source IP address of this broadcast, we can demux incoming traffic + // on the same port by that source IP address. If we don't know the source IP addr, + // it is impossible for us to demux, and therefore we shouldn't allow multiple + // broadcasts on the same port. + if (((ReflectorSocket*)fSockets->GetSocketA())->HasSender() && (fStreamInfo.fSrcIPAddr == 0)) + return QTSSModuleUtils::SendErrorResponse(inRequest, qtssServerInternal, + sCantBindReflectorSocketErr); + + //also put this stream onto the socket's queue of streams + ((ReflectorSocket*)fSockets->GetSocketA())->AddSender(&fRTPSender); + ((ReflectorSocket*)fSockets->GetSocketB())->AddSender(&fRTCPSender); + + // A broadcaster is setting up a UDP session so let the sockets update the session + if (fStreamInfo.fSetupToReceive && qtssRTPTransportTypeUDP == transportType && inParams != NULL) + { ((ReflectorSocket*)fSockets->GetSocketA())->AddBroadcasterSession(inParams->inClientSession); + ((ReflectorSocket*)fSockets->GetSocketB())->AddBroadcasterSession(inParams->inClientSession); + } + + ((ReflectorSocket*)fSockets->GetSocketA())->SetSSRCFilter(filterState, timeout); + ((ReflectorSocket*)fSockets->GetSocketB())->SetSSRCFilter(filterState, timeout); + +#if 1 + // Always set the Rcv buf size for the sockets. This is important because the + // server is going to be getting many packets on these sockets. + fSockets->GetSocketA()->SetSocketRcvBufSize(512 * 1024); + fSockets->GetSocketB()->SetSocketRcvBufSize(512 * 1024); +#endif + + //If the broadcaster is sending RTP directly to us, we don't + //need to join a multicast group because we're not using multicast + if (isMulticastDest) + { + QTSS_Error err = fSockets->GetSocketA()->JoinMulticast(fStreamInfo.fDestIPAddr); + if (err == QTSS_NoErr) + err = fSockets->GetSocketB()->JoinMulticast(fStreamInfo.fDestIPAddr); + // If we get an error when setting the TTL, this isn't too important (TTL on + // these sockets is only useful for RTCP RRs. + if (err == QTSS_NoErr) + (void)fSockets->GetSocketA()->SetTtl(fStreamInfo.fTimeToLive); + if (err == QTSS_NoErr) + (void)fSockets->GetSocketB()->SetTtl(fStreamInfo.fTimeToLive); + if (err != QTSS_NoErr) + return QTSSModuleUtils::SendErrorResponse(inRequest, qtssServerInternal, + sCantJoinMulticastGroupErr); + } + + // If the port is 0, update the port to be the actual port value + fStreamInfo.fPort = fSockets->GetSocketA()->GetLocalPort(); + + //finally, register these sockets for events + fSockets->GetSocketA()->RequestEvent(EV_RE); + fSockets->GetSocketB()->RequestEvent(EV_RE); + + // Copy the source ID and setup the ref + StrPtrLen theSourceID(fSourceIDBuf, kStreamIDSize); + ReflectorStream::GenerateSourceID(&fStreamInfo, fSourceIDBuf); + fRef.Set(theSourceID, this); + return QTSS_NoErr; +} + +void ReflectorStream::SendReceiverReport() +{ + // Check to see if our destination RTCP addr & port are setup. They may + // not be if the source is unicast and we haven't gotten any incoming packets yet + if (fDestRTCPAddr == 0) + return; + + UInt32 theEyeCount = this->GetEyeCount(); + UInt32* theEyeWriter = fEyeLocation; + *theEyeWriter = htonl(theEyeCount) & 0x7fffffff;//no idea why we do this! + theEyeWriter++; + *theEyeWriter = htonl(theEyeCount) & 0x7fffffff; + theEyeWriter++; + *theEyeWriter = htonl(0) & 0x7fffffff; + + //send the packet to the multicast RTCP addr & port for this stream + (void)fSockets->GetSocketB()->SendTo(fDestRTCPAddr, fDestRTCPPort, fReceiverReportBuffer, fReceiverReportSize); +} + +void ReflectorStream::PushPacket(char *packet, UInt32 packetLen, Bool16 isRTCP) +{ + + if (packetLen > 0) + { + ReflectorPacket* thePacket = NULL; + if (isRTCP) + { //qtss_printf("ReflectorStream::PushPacket RTCP packetlen = %"_U32BITARG_"\n",packetLen); + thePacket = ((ReflectorSocket*)fSockets->GetSocketB())->GetPacket(); + if (thePacket == NULL) + { //qtss_printf("ReflectorStream::PushPacket RTCP GetPacket() is NULL\n"); + return; + } + + OSMutexLocker locker( ((ReflectorSocket*)(fSockets->GetSocketB()) )->GetDemuxer()->GetMutex()); + thePacket->SetPacketData(packet, packetLen); + ((ReflectorSocket*)fSockets->GetSocketB())->ProcessPacket(OS::Milliseconds(),thePacket,0,0); + ((ReflectorSocket*)fSockets->GetSocketB())->Signal(Task::kIdleEvent); + } + else + { //qtss_printf("ReflectorStream::PushPacket RTP packetlen = %"_U32BITARG_"\n",packetLen); + thePacket = ((ReflectorSocket*)fSockets->GetSocketA())->GetPacket(); + if (thePacket == NULL) + { //qtss_printf("ReflectorStream::PushPacket GetPacket() is NULL\n"); + return; + } + + OSMutexLocker locker(((ReflectorSocket*)(fSockets->GetSocketA()))->GetDemuxer()->GetMutex()); + thePacket->SetPacketData(packet, packetLen); + ((ReflectorSocket*)fSockets->GetSocketA())->ProcessPacket(OS::Milliseconds(),thePacket,0,0); + ((ReflectorSocket*)fSockets->GetSocketA())->Signal(Task::kIdleEvent); + } + } +} + + + + +ReflectorSender::ReflectorSender(ReflectorStream* inStream, UInt32 inWriteFlag) +: fStream(inStream), + fWriteFlag(inWriteFlag), + fFirstNewPacketInQueue(NULL), + fFirstPacketInQueueForNewOutput(NULL), + fHasNewPackets(false), + fNextTimeToRun(0), + fLastRRTime(0), + fSocketQueueElem() +{ + fSocketQueueElem.SetEnclosingObject(this); +} + +ReflectorSender::~ReflectorSender() +{ + //dequeue and delete every buffer + while (fPacketQueue.GetLength() > 0) + { + ReflectorPacket* packet = (ReflectorPacket*)fPacketQueue.DeQueue()->GetEnclosingObject(); + delete packet; + } +} + + +Bool16 ReflectorSender::ShouldReflectNow(const SInt64& inCurrentTime, SInt64* ioWakeupTime) +{ + Assert(ioWakeupTime != NULL); + //check to make sure there actually is work to do for this stream. + if ((!fHasNewPackets) && ((fNextTimeToRun == 0) || (inCurrentTime < fNextTimeToRun))) + { + //We don't need to do work right now, but + //this stream must still communicate when it needs to be woken up next + SInt64 theWakeupTime = fNextTimeToRun + inCurrentTime; + //qtss_printf("ReflectorSender::ShouldReflectNow theWakeupTime=%qd newWakeUpTime=%qd ioWakepTime=%qd\n", theWakeupTime, fNextTimeToRun + inCurrentTime,*ioWakeupTime); + if ((fNextTimeToRun > 0) && (theWakeupTime < *ioWakeupTime)) + *ioWakeupTime = theWakeupTime; + return false; + } + return true; +} + +UInt32 ReflectorSender::GetOldestPacketRTPTime(Bool16 *foundPtr) +{ + if (foundPtr != NULL) + *foundPtr = false; + OSMutexLocker locker(&fStream->fBucketMutex); + OSQueueElem* packetElem = this->GetClientBufferStartPacket(); + if (packetElem == NULL) + return 0; + + ReflectorPacket* thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + if (thePacket == NULL) + return 0; + + if (foundPtr != NULL) + *foundPtr = true; + + return thePacket->GetPacketRTPTime(); +} + +UInt16 ReflectorSender::GetFirstPacketRTPSeqNum(Bool16 *foundPtr) +{ + if (foundPtr != NULL) + *foundPtr = false; + + UInt16 resultSeqNum = 0; + OSMutexLocker locker(&fStream->fBucketMutex); + OSQueueElem* packetElem = this->GetClientBufferStartPacket(); + + if (packetElem == NULL) + return 0; + + ReflectorPacket* thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + if (thePacket == NULL) + return 0; + + if (foundPtr != NULL) + *foundPtr = true; + + resultSeqNum = thePacket->GetPacketRTPSeqNum(); + + return resultSeqNum; +} + +OSQueueElem* ReflectorSender::GetClientBufferNextPacketTime(UInt32 inRTPTime) +{ + + OSQueueIter qIter(&fPacketQueue);// start at oldest packet in q + OSQueueElem* requestedPacket = NULL; + OSQueueElem* elem = NULL; + + while ( !qIter.IsDone() ) // start at oldest packet in q + { + elem = qIter.GetCurrent(); + + if (requestedPacket == NULL) + requestedPacket = elem; + + if (requestedPacket == NULL) + break; + + ReflectorPacket* thePacket = (ReflectorPacket*)elem->GetEnclosingObject(); + Assert( thePacket ); + + if (thePacket->GetPacketRTPTime() > inRTPTime) + { + requestedPacket = elem; // return the first packet we have that has a later time + break; // found the packet we need: done processing + } + qIter.Next(); + + + } + + return requestedPacket; +} + +Bool16 ReflectorSender::GetFirstRTPTimePacket(UInt16* outSeqNumPtr, UInt32* outRTPTimePtr, SInt64* outArrivalTimePtr) +{ + OSMutexLocker locker(&fStream->fBucketMutex); + OSQueueElem* packetElem = this->GetClientBufferStartPacketOffset(ReflectorStream::sFirstPacketOffsetMsec); + + if (packetElem == NULL) + return false; + + ReflectorPacket* thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + if (thePacket == NULL) + return false; + + packetElem = GetClientBufferNextPacketTime(thePacket->GetPacketRTPTime()); + if (packetElem == NULL) + return false; + + thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + if (thePacket == NULL) + return false; + + if (outSeqNumPtr) + *outSeqNumPtr = thePacket->GetPacketRTPSeqNum(); + + if (outRTPTimePtr) + *outRTPTimePtr = thePacket->GetPacketRTPTime(); + + if (outArrivalTimePtr) + *outArrivalTimePtr = thePacket->fTimeArrived; + + return true; +} + +Bool16 ReflectorSender::GetFirstPacketInfo(UInt16* outSeqNumPtr, UInt32* outRTPTimePtr, SInt64* outArrivalTimePtr) +{ + OSMutexLocker locker(&fStream->fBucketMutex); + OSQueueElem* packetElem = this->GetClientBufferStartPacketOffset(ReflectorStream::sFirstPacketOffsetMsec); +// OSQueueElem* packetElem = this->GetClientBufferStartPacket(); + + if (packetElem == NULL) + return false; + + ReflectorPacket* thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + if (thePacket == NULL) + return false; + + if (outSeqNumPtr) + *outSeqNumPtr = thePacket->GetPacketRTPSeqNum(); + + if (outRTPTimePtr) + *outRTPTimePtr = thePacket->GetPacketRTPTime(); + + if (outArrivalTimePtr) + *outArrivalTimePtr = thePacket->fTimeArrived; + + thePacket->fNeededByOutput = true; + + return true; +} + + +#if REFLECTOR_STREAM_DEBUGGING +static UInt16 DGetPacketSeqNumber(StrPtrLen* inPacket) +{ + if (inPacket->Len < 4) + return 0; + + //The RTP seq number is the second short of the packet + UInt16* seqNumPtr = (UInt16*)inPacket->Ptr; + return ntohs(seqNumPtr[1]); +} + + + +#endif + + +void ReflectorSender::ReflectRelayPackets(SInt64* ioWakeupTime, OSQueue* inFreeQueue) +{ + //Most of this code is useless i.e. buckets and bookmarks. This code will get cleaned up eventually + + //printf("ReflectorSender::ReflectPackets %qd %qd\n",*ioWakeupTime,fNextTimeToRun); +#if DEBUG + Assert(ioWakeupTime != NULL); +#endif + #if REFLECTOR_STREAM_DEBUGGING > 2 + Bool16 printQueueLenOnExit = false; + #endif + + SInt64 currentTime = OS::Milliseconds(); + + //make sure to reset these state variables + fHasNewPackets = false; + fNextTimeToRun = 1000; // init to 1 secs + + //determine if we need to send a receiver report to the multicast source + if ((fWriteFlag == qtssWriteFlagsIsRTCP) && (currentTime > (fLastRRTime + kRRInterval))) + { + fLastRRTime = currentTime; + fStream->SendReceiverReport(); + #if REFLECTOR_STREAM_DEBUGGING > 2 + printQueueLenOnExit = true; + printf( "fPacketQueue len %li\n", (SInt32)fPacketQueue.GetLength() ); + #endif + } + + //the rest of this function must be atomic wrt the ReflectorSession, because + //it involves iterating through the RTPSession array, which isn't thread safe + OSMutexLocker locker(&fStream->fBucketMutex); + + // Check to see if we should update the session's bitrate average + if ((fStream->fLastBitRateSample + ReflectorStream::kBitRateAvgIntervalInMilSecs) < currentTime) + { + unsigned int intervalBytes = fStream->fBytesSentInThisInterval; + (void)atomic_sub(&fStream->fBytesSentInThisInterval, intervalBytes); + + // Multiply by 1000 to convert from milliseconds to seconds, and by 8 to convert from bytes to bits + Float32 bps = (Float32)(intervalBytes * 8) / (Float32)(currentTime - fStream->fLastBitRateSample); + bps *= 1000; + fStream->fCurrentBitRate = (UInt32)bps; + + // Don't check again for awhile! + fStream->fLastBitRateSample = currentTime; + } + + for (UInt32 bucketIndex = 0; bucketIndex < fStream->fNumBuckets; bucketIndex++) + { + for (UInt32 bucketMemberIndex = 0; bucketMemberIndex < fStream->sBucketSize; bucketMemberIndex++) + { + ReflectorOutput* theOutput = fStream->fOutputArray[bucketIndex][bucketMemberIndex]; + + + if (theOutput != NULL) + { + SInt32 availBookmarksPosition = -1; // -1 == invalid position + OSQueueElem* packetElem = NULL; + UInt32 curBookmark = 0; + + Assert( curBookmark < theOutput->fNumBookmarks ); + + // see if we've bookmarked a held packet for this Sender in this Output + while ( curBookmark < theOutput->fNumBookmarks ) + { + OSQueueElem* bookmarkedElem = theOutput->fBookmarkedPacketsElemsArray[curBookmark]; + + if ( bookmarkedElem ) // there may be holes in this array + { + if ( bookmarkedElem->IsMember( fPacketQueue ) ) + { + // this packet was previously bookmarked for this specific queue + // remove if from the bookmark list and use it + // to jump ahead into the Sender's over all packet queue + theOutput->fBookmarkedPacketsElemsArray[curBookmark] = NULL; + availBookmarksPosition = curBookmark; + packetElem = bookmarkedElem; + break; + } + + } + else + { + availBookmarksPosition = curBookmark; + } + + curBookmark++; + + } + + Assert( availBookmarksPosition != -1 ); + + #if REFLECTOR_STREAM_DEBUGGING > 1 + if ( packetElem ) // show 'em what we got johnny + { ReflectorPacket* thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + printf("Bookmarked packet time: %li, packetSeq %i\n", (SInt32)thePacket->fTimeArrived, DGetPacketSeqNumber( &thePacket->fPacketPtr ) ); + } + #endif + + // the output did not have a bookmarked packet if it's own + // so show it the first new packet we have in this sender. + // ( since TCP flow control may delay the sending of packets, this may not + // be the same as the first packet in the queue + if ( packetElem == NULL ) + { + packetElem = fFirstNewPacketInQueue; + + #if REFLECTOR_STREAM_DEBUGGING > 1 + if ( packetElem ) // show 'em what we got johnny + { + ReflectorPacket* thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + printf("1st NEW packet from Sender sess 0x%lx time: %li, packetSeq %i\n", (SInt32)theOutput, (SInt32)thePacket->fTimeArrived, DGetPacketSeqNumber( &thePacket->fPacketPtr ) ); + } + else + printf("no new packets\n" ); + #endif + } + + OSQueueIter qIter(&fPacketQueue, packetElem); // starts from beginning if packetElem == NULL, else from packetElem + + Bool16 dodBookmarkPacket = false; + + while ( !qIter.IsDone() ) + { + packetElem = qIter.GetCurrent(); + + ReflectorPacket* thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + QTSS_Error err = QTSS_NoErr; + + #if REFLECTOR_STREAM_DEBUGGING > 2 + printf("packet time: %li, packetSeq %i\n", (SInt32)thePacket->fTimeArrived, DGetPacketSeqNumber( &thePacket->fPacketPtr ) ); + #endif + + // once we see a packet we cant' send, we need to stop trying + // during this pass mark remaining as still needed + if ( !dodBookmarkPacket ) + { + SInt64 packetLateness = currentTime - thePacket->fTimeArrived - (ReflectorStream::sBucketDelayInMsec * (SInt64)bucketIndex); + // packetLateness measures how late this packet it after being corrected for the bucket delay + + #if REFLECTOR_STREAM_DEBUGGING > 2 + printf("packetLateness %li, seq# %li\n", (SInt32)packetLateness, (SInt32) DGetPacketSeqNumber( &thePacket->fPacketPtr ) ); + #endif + + SInt64 timeToSendPacket = -1; + err = theOutput->WritePacket(&thePacket->fPacketPtr, fStream, fWriteFlag, packetLateness, &timeToSendPacket, NULL, NULL, false); + + if ( err == QTSS_WouldBlock ) + { + #if REFLECTOR_STREAM_DEBUGGING > 2 + printf("EAGAIN bookmark: %li, packetSeq %i\n", (SInt32)packetLateness, DGetPacketSeqNumber( &thePacket->fPacketPtr ) ); + #endif + // tag it and bookmark it + thePacket->fNeededByOutput = true; + + Assert( availBookmarksPosition != -1 ); + if ( availBookmarksPosition != -1 ) + theOutput->fBookmarkedPacketsElemsArray[availBookmarksPosition] = packetElem; + + dodBookmarkPacket = true; + + // call us again in # ms to retry on an EAGAIN + if ((timeToSendPacket > 0) && (fNextTimeToRun > timeToSendPacket )) + fNextTimeToRun = timeToSendPacket; + if ( timeToSendPacket == -1 ) + this->SetNextTimeToRun(5); // keep in synch with delay on would block for on-demand lower is better for high-bit rate movies. + + } + } + else + { + if ( thePacket->fNeededByOutput ) // optimization: if the packet is already marked, another Output has been through this already + break; + thePacket->fNeededByOutput = true; + } + + qIter.Next(); + } + + } + } + } + + // reset our first new packet bookmark + fFirstNewPacketInQueue = NULL; + + // iterate one more through the senders queue to clear out + // the unneeded packets + OSQueueIter removeIter(&fPacketQueue); + while ( !removeIter.IsDone() ) + { + OSQueueElem* elem = removeIter.GetCurrent(); + Assert( elem ); + + //at this point, move onto the next queue element, because we may be altering + //the queue itself in the code below + removeIter.Next(); + + ReflectorPacket* thePacket = (ReflectorPacket*)elem->GetEnclosingObject(); + Assert( thePacket ); + + if ( thePacket->fNeededByOutput == false ) + { + thePacket->fNeededByOutput = true; + fPacketQueue.Remove( elem ); + inFreeQueue->EnQueue( elem ); + + } + else // reset for next call to ReflectPackets + { + thePacket->fNeededByOutput = false; + } + } + + //Don't forget that the caller also wants to know when we next want to run + if (*ioWakeupTime == 0) + *ioWakeupTime = fNextTimeToRun; + else if ((fNextTimeToRun > 0) && (*ioWakeupTime > fNextTimeToRun)) + *ioWakeupTime = fNextTimeToRun; + // exit with fNextTimeToRun in real time, not relative time. + fNextTimeToRun += currentTime; + + #if REFLECTOR_STREAM_DEBUGGING > 2 + if ( printQueueLenOnExit ) + printf( "EXIT fPacketQueue len %li\n", (SInt32)fPacketQueue.GetLength() ); + #endif +} + +/*********************************************************************************************** +/ ReflectorSender::ReflectPackets +/ +/ There are n ReflectorSender's for n output streams per presentation. +/ +/ Each sender is associated with an array of ReflectorOutput's. Each +/ output represents a client connection. Each output has # RTPStream's. +/ +/ When we write a packet to the ReflectorOutput he matches it's payload +/ to one of his streams and sends it there. +/ +/ To smooth the bandwitdth (server, not user) requirements of the reflected streams, the Sender +/ groups the ReflectorOutput's into buckets. The input streams are reflected to +/ each bucket progressively later in time. So rather than send a single packet +/ to say 1000 clients all at once, we send it to just the first 16, then then next 16 +/ 100 ms later and so on. +/ +/ +/ intputs ioWakeupTime - relative time to call us again in MSec +/ inFreeQueue - queue of free packets. +*/ + +void ReflectorSender::ReflectPackets(SInt64* ioWakeupTime, OSQueue* inFreeQueue) +{ + if (!fStream->BufferEnabled()) // Call old routine for relays; they don't want buffering. + { + this->ReflectRelayPackets(ioWakeupTime,inFreeQueue); + return; + } + + SInt64 currentTime = OS::Milliseconds(); + + //make sure to reset these state variables + fHasNewPackets = false; + + fNextTimeToRun = 1000; // init to 1 secs + + if (fWriteFlag == qtssWriteFlagsIsRTCP) + fNextTimeToRun = 1000; + + //determine if we need to send a receiver report to the multicast source + if ((fWriteFlag == qtssWriteFlagsIsRTCP) && (currentTime > (fLastRRTime + kRRInterval))) + { + fLastRRTime = currentTime; + fStream->SendReceiverReport(); + } + + //the rest of this function must be atomic wrt the ReflectorSession, because + //it involves iterating through the RTPSession array, which isn't thread safe + OSMutexLocker locker(&fStream->fBucketMutex); + + // Check to see if we should update the session's bitrate average + fStream->UpdateBitRate(currentTime); + + // where to start new clients in the q + fFirstPacketInQueueForNewOutput = this->GetClientBufferStartPacketOffset(0); + +#if (0) //test code + if (NULL != fFirstPacketInQueueForNewOutput) + printf("ReflectorSender::ReflectPackets SET first packet fFirstPacketInQueueForNewOutput %d \n", DGetPacketSeqNumber( &( (ReflectorPacket*) ( fFirstPacketInQueueForNewOutput->GetEnclosingObject()))->fPacketPtr )); + + ReflectorPacket* thePacket = NULL; + if (fFirstPacketInQueueForNewOutput != NULL) + thePacket = (ReflectorPacket*) fFirstPacketInQueueForNewOutput->GetEnclosingObject(); + if (thePacket == NULL) + { printf("fFirstPacketInQueueForNewOutput is NULL \n"); + + } + +#endif + + Bool16 firstPacket =false; + + for (UInt32 bucketIndex = 0; bucketIndex < fStream->fNumBuckets; bucketIndex++) + { + for (UInt32 bucketMemberIndex = 0; bucketMemberIndex < fStream->sBucketSize; bucketMemberIndex++) + { + ReflectorOutput* theOutput = fStream->fOutputArray[bucketIndex][bucketMemberIndex]; + if (theOutput != NULL) + { + if ( false == theOutput->IsPlaying() ) + continue; + + OSQueueElem* packetElem = theOutput->GetBookMarkedPacket(&fPacketQueue); + if ( packetElem == NULL ) // should only be a new output + { + packetElem = fFirstPacketInQueueForNewOutput; // everybody starts at the oldest packet in the buffer delay or uses a bookmark + firstPacket = true; + theOutput->fNewOutput = false; + //if (packetElem) printf("ReflectorSender::ReflectPackets Sending first packet in Queue packetElem=fFirstPacketInQueueForNewOutput %d \n", ( (ReflectorPacket*) (packetElem->GetEnclosingObject() ) )->GetPacketRTPSeqNum()); + + } + + SInt64 bucketDelay = ReflectorStream::sBucketDelayInMsec * (SInt64)bucketIndex; + packetElem = this->SendPacketsToOutput(theOutput, packetElem,currentTime, bucketDelay, firstPacket); + if (packetElem) + { + ReflectorPacket* thePacket = (ReflectorPacket*)packetElem->GetEnclosingObject(); + thePacket->fNeededByOutput = true; // flag to prevent removal in RemoveOldPackets + (void) theOutput->SetBookMarkPacket(packetElem); // store a reference to the packet + } + } + } + } + + this->RemoveOldPackets(inFreeQueue); + fFirstNewPacketInQueue = NULL; + + //Don't forget that the caller also wants to know when we next want to run + if (*ioWakeupTime == 0) + *ioWakeupTime = fNextTimeToRun; + else if ((fNextTimeToRun > 0) && (*ioWakeupTime > fNextTimeToRun)) + *ioWakeupTime = fNextTimeToRun; + // exit with fNextTimeToRun in real time, not relative time. + fNextTimeToRun += currentTime; + + // qtss_printf("SetNextTimeToRun fNextTimeToRun=%qd + currentTime=%qd\n", fNextTimeToRun, currentTime); + // qtss_printf("ReflectorSender::ReflectPackets *ioWakeupTime = %qd\n", *ioWakeupTime); + +} + +OSQueueElem* ReflectorSender::SendPacketsToOutput(ReflectorOutput* theOutput, OSQueueElem* currentPacket, SInt64 currentTime, SInt64 bucketDelay, Bool16 firstPacket) +{ + OSQueueElem* lastPacket = currentPacket; + OSQueueIter qIter(&fPacketQueue, currentPacket); // starts from beginning if currentPacket == NULL, else from currentPacket + + UInt32 count = 0; + QTSS_Error err = QTSS_NoErr; + while ( !qIter.IsDone() ) + { + currentPacket = qIter.GetCurrent(); + lastPacket = currentPacket; + + ReflectorPacket* thePacket = (ReflectorPacket*)currentPacket->GetEnclosingObject(); + SInt64 packetLateness = bucketDelay; + SInt64 timeToSendPacket = -1; + + //printf("packetLateness %qd, seq# %li\n", packetLateness, (SInt32) DGetPacketSeqNumber( &thePacket->fPacketPtr ) ); + + err = theOutput->WritePacket(&thePacket->fPacketPtr, fStream, fWriteFlag, packetLateness, &timeToSendPacket,&thePacket->fStreamCountID,&thePacket->fTimeArrived, firstPacket ); + + if (err == QTSS_WouldBlock) + { // call us again in # ms to retry on an EAGAIN + + if ((timeToSendPacket > 0) && ( (fNextTimeToRun + currentTime) > timeToSendPacket )) // blocked but we are scheduled to wake up later + fNextTimeToRun = timeToSendPacket - currentTime; + + if (theOutput->fLastIntervalMilliSec < 5 ) + theOutput->fLastIntervalMilliSec = 5; + + if ( timeToSendPacket < 0 ) // blocked and we are behind + { //qtss_printf("fNextTimeToRun = theOutput->fLastIntervalMilliSec=%qd;\n", theOutput->fLastIntervalMilliSec); // Use the last packet interval + this->SetNextTimeToRun(theOutput->fLastIntervalMilliSec); + } + + if (fNextTimeToRun > 100) //don't wait that long + { //qtss_printf("fNextTimeToRun = %qd now 100;\n", fNextTimeToRun); + this->SetNextTimeToRun(100); + } + + if (fNextTimeToRun < 5) //wait longer + { //qtss_printf("fNextTimeToRun = 5;\n"); + this->SetNextTimeToRun(5); + } + + if (theOutput->fLastIntervalMilliSec >= 100) // allow up to 1 second max -- allow some time for the socket to clear and don't go into a tight loop if the client is gone. + theOutput->fLastIntervalMilliSec = 100; + else + theOutput->fLastIntervalMilliSec *= 2; // scale upwards over time + + //qtss_printf ( "Blocked ReflectorSender::SendPacketsToOutput timeToSendPacket=%qd fLastIntervalMilliSec=%qd fNextTimeToRun=%qd \n", timeToSendPacket, theOutput->fLastIntervalMilliSec, fNextTimeToRun); + + break; + } + + count++; + qIter.Next(); + + } + + return lastPacket; +} + +OSQueueElem* ReflectorSender::GetClientBufferStartPacketOffset(SInt64 offsetMsec) +{ + + OSQueueIter qIter(&fPacketQueue);// start at oldest packet in q + SInt64 theCurrentTime = OS::Milliseconds(); + SInt64 packetDelay = 0; + OSQueueElem* oldestPacketInClientBufferTime = NULL; + + + while ( !qIter.IsDone() ) // start at oldest packet in q + { + OSQueueElem* elem = qIter.GetCurrent(); + Assert( elem ); + qIter.Next(); + + ReflectorPacket* thePacket = (ReflectorPacket*)elem->GetEnclosingObject(); + Assert( thePacket ); + + packetDelay = theCurrentTime - thePacket->fTimeArrived; + if (offsetMsec > ReflectorStream::sOverBufferInMsec) + offsetMsec = ReflectorStream::sOverBufferInMsec; + + if ( packetDelay <= (ReflectorStream::sOverBufferInMsec - offsetMsec) ) + { + oldestPacketInClientBufferTime = &thePacket->fQueueElem; + break; // found the packet we need: done processing + } + + } + + return oldestPacketInClientBufferTime; +} + +void ReflectorSender::RemoveOldPackets(OSQueue* inFreeQueue) +{ + +// Iterate through the senders queue to clear out packets +// Start at the oldest packet and walk forward to the newest packet +// + OSQueueIter removeIter(&fPacketQueue); + SInt64 theCurrentTime = OS::Milliseconds(); + SInt64 packetDelay = 0; + SInt64 currentMaxPacketDelay = ReflectorStream::sMaxPacketAgeMSec; + + + while ( !removeIter.IsDone() ) + { + OSQueueElem* elem = removeIter.GetCurrent(); + Assert( elem ); + + //at this point, move onto the next queue element, because we may be altering + //the queue itself in the code below + removeIter.Next(); + + ReflectorPacket* thePacket = (ReflectorPacket*)elem->GetEnclosingObject(); + Assert( thePacket ); + //printf("ReflectorSender::RemoveOldPackets Packet %d in queue is %qd milliseconds old\n", DGetPacketSeqNumber( &thePacket->fPacketPtr ) ,theCurrentTime - thePacket->fTimeArrived); + + + packetDelay = theCurrentTime - thePacket->fTimeArrived; + + // walk q and remove packets that are too old + if ( !thePacket->fNeededByOutput && packetDelay > currentMaxPacketDelay) // delete based on late tolerance and whether a client is blocked on the packet + { // not needed and older than our required buffer + thePacket->Reset(); + fPacketQueue.Remove( elem ); + inFreeQueue->EnQueue( elem ); + } + else + { // we want to keep all of these but we should reset the ones that should be aged out unless marked + // as need the next time through reflect packets. + + thePacket->fNeededByOutput = false; //mark not needed.. will be set next time through reflect packets + if (packetDelay <= currentMaxPacketDelay) // this packet is going to be kept around as well as the ones that follow. + break; + } + } + + +} + +void ReflectorSocketPool::SetUDPSocketOptions(UDPSocketPair* inPair) +{ + // Fix add ReuseAddr for compatibility with MPEG4IP broadcaster which likes to use the same + //sockets. + + //Make sure this works with PlaylistBroadcaster + //inPair->GetSocketA()->ReuseAddr(); + //inPair->GetSocketA()->ReuseAddr(); + +} + + +UDPSocketPair* ReflectorSocketPool::ConstructUDPSocketPair() +{ + return NEW UDPSocketPair + (NEW ReflectorSocket(), NEW ReflectorSocket()); +} + +void ReflectorSocketPool::DestructUDPSocket(ReflectorSocket* socket) +{ + if (socket) // allocated + { + if (socket->GetLocalPort() > 0) // bound and active + { //The socket's run function may be executing RIGHT NOW! So we can't + //just delete the thing, we need to send the sockets kill events. + //qtss_printf("ReflectorSocketPool::DestructUDPSocketPair Signal kKillEvent socket=%p\n",socket); + socket->Signal(Task::kKillEvent); + } + else // not bound ok to delete + { //qtss_printf("ReflectorSocketPool::DestructUDPSocketPair delete socket=%p\n",socket); + delete socket; + } + } +} + +void ReflectorSocketPool::DestructUDPSocketPair(UDPSocketPair *inPair) +{ + //qtss_printf("ReflectorSocketPool::DestructUDPSocketPair inPair=%p socketA=%p\n", inPair,(ReflectorSocket*)inPair->GetSocketA()); + this->DestructUDPSocket((ReflectorSocket*)inPair->GetSocketA()); + + //qtss_printf("ReflectorSocketPool::DestructUDPSocketPair inPair=%p socketB=%p\n", inPair,(ReflectorSocket*)inPair->GetSocketB()); + this->DestructUDPSocket((ReflectorSocket*)inPair->GetSocketB()); + + delete inPair; +} + +ReflectorSocket::ReflectorSocket() +: IdleTask(), + UDPSocket(NULL, Socket::kNonBlockingSocketType | UDPSocket::kWantsDemuxer), + fBroadcasterClientSession(NULL), + fLastBroadcasterTimeOutRefresh(0), + fSleepTime(0), + fValidSSRC(0), + fLastValidSSRCTime(0), + fFilterSSRCs(true), + fTimeoutSecs(30), + fHasReceiveTime(false), + fFirstReceiveTime(0), + fFirstArrivalTime(0), + fCurrentSSRC(0) + +{ + //construct all the preallocated packets + this->SetTaskName("ReflectorSocket"); + this->SetTask(this); + + for (UInt32 numPackets = 0; numPackets < kNumPreallocatedPackets; numPackets++) + { + //If the local port # of this socket is odd, then all the packets + //used for this socket are rtcp packets. + ReflectorPacket* packet = NEW ReflectorPacket(); + fFreeQueue.EnQueue(&packet->fQueueElem);//put this packet onto the free queue + } +} + +ReflectorSocket::~ReflectorSocket() +{ + //printf("ReflectorSocket::~ReflectorSocket\n"); + while (fFreeQueue.GetLength() > 0) + { + ReflectorPacket* packet = (ReflectorPacket*)fFreeQueue.DeQueue()->GetEnclosingObject(); + delete packet; + } +} + +void ReflectorSocket::AddSender(ReflectorSender* inSender) +{ + OSMutexLocker locker(this->GetDemuxer()->GetMutex()); + QTSS_Error err = this->GetDemuxer()->RegisterTask(inSender->fStream->fStreamInfo.fSrcIPAddr, 0, inSender); + Assert(err == QTSS_NoErr); + fSenderQueue.EnQueue(&inSender->fSocketQueueElem); +} + +void ReflectorSocket::RemoveSender(ReflectorSender* inSender) +{ + OSMutexLocker locker(this->GetDemuxer()->GetMutex()); + fSenderQueue.Remove(&inSender->fSocketQueueElem); + QTSS_Error err = this->GetDemuxer()->UnregisterTask(inSender->fStream->fStreamInfo.fSrcIPAddr, 0, inSender); + Assert(err == QTSS_NoErr); +} + +SInt64 ReflectorSocket::Run() +{ + //We want to make sure we can't get idle events WHILE we are inside + //this function. That will cause us to run the queues unnecessarily + //and just get all confused. + this->CancelTimeout(); + + Task::EventFlags theEvents = this->GetEvents(); + //if we have been told to delete ourselves, do so. + if (theEvents & Task::kKillEvent) + return -1; + + OSMutexLocker locker(this->GetDemuxer()->GetMutex()); + SInt64 theMilliseconds = OS::Milliseconds(); + + //Only check for data on the socket if we've actually been notified to that effect + if (theEvents & Task::kReadEvent) + this->GetIncomingData(theMilliseconds); + +#if DEBUG + //make sure that we haven't gotten here prematurely! This wouldn't mess + //anything up, but it would waste CPU. + if (theEvents & Task::kIdleEvent) + { + SInt32 temp = (SInt32)(fSleepTime - theMilliseconds); + char tempBuf[20]; + qtss_sprintf(tempBuf,"%"_S32BITARG_"",temp); + WarnV(fSleepTime <= theMilliseconds, tempBuf); + } +#endif + + fSleepTime = 0; + //Now that we've gotten all available packets, have the streams reflect + for (OSQueueIter iter2(&fSenderQueue); !iter2.IsDone(); iter2.Next()) + { + ReflectorSender* theSender2 = (ReflectorSender*)iter2.GetCurrent()->GetEnclosingObject(); + if (theSender2 != NULL && theSender2->ShouldReflectNow(theMilliseconds, &fSleepTime)) + theSender2->ReflectPackets(&fSleepTime, &fFreeQueue); + } + +#if DEBUG + theMilliseconds = OS::Milliseconds(); +#endif + + //For smoothing purposes, the streams can mark when they want to wakeup. + if (fSleepTime > 0) + this->SetIdleTimer(fSleepTime); +#if DEBUG + //The debugging check above expects real time. + fSleepTime += theMilliseconds; +#endif + + return 0; +} + + +void ReflectorSocket::FilterInvalidSSRCs(ReflectorPacket* thePacket,Bool16 isRTCP) +{ // assume the first SSRC we see is valid and all others are to be ignored. + if ( thePacket->fPacketPtr.Len > 0) do + { + SInt64 currentTime = OS::Milliseconds() / 1000; + if (0 == fValidSSRC) + { fValidSSRC = thePacket->GetSSRC(isRTCP); // SSRC of 0 is allowed + fLastValidSSRCTime = currentTime; + //qtss_printf("socket=%"_U32BITARG_" FIRST PACKET fValidSSRC=%"_U32BITARG_" \n", (UInt32) this,fValidSSRC); + break; + } + + UInt32 packetSSRC = thePacket->GetSSRC(isRTCP); + if (packetSSRC != 0) + { + if (packetSSRC == fValidSSRC) + { fLastValidSSRCTime = currentTime; + //qtss_printf("socket=%"_U32BITARG_" good packet\n", (UInt32) this ); + break; + } + + //qtss_printf("socket=%"_U32BITARG_" bad packet packetSSRC= %"_U32BITARG_" fValidSSRC=%"_U32BITARG_" \n", (UInt32) this,packetSSRC,fValidSSRC); + thePacket->fPacketPtr.Len = 0; // ignore this packet wrong SSRC + } + + // this executes whenever an invalid SSRC is found -- maybe the original stream ended and a new one is now active + if ( (fLastValidSSRCTime + fTimeoutSecs) < currentTime) // fValidSSRC timed out --no packets with this SSRC seen for awhile + { fValidSSRC = 0; // reset the valid SSRC with the next packet's SSRC + //qtss_printf("RESET fValidSSRC\n"); + } + + }while (false); +} + +Bool16 ReflectorSocket::ProcessPacket(const SInt64& inMilliseconds,ReflectorPacket* thePacket,UInt32 theRemoteAddr,UInt16 theRemotePort) +{ + Bool16 done = false; // stop when result is true + if (thePacket != NULL) do + { + if (GetLocalPort() & 1) + thePacket->fIsRTCP = true; + else + thePacket->fIsRTCP = false; + + if (fBroadcasterClientSession != NULL) // alway refresh timeout even if we are filtering. + { if ( (inMilliseconds - fLastBroadcasterTimeOutRefresh) > kRefreshBroadcastSessionIntervalMilliSecs) + { QTSS_RefreshTimeOut(fBroadcasterClientSession); + fLastBroadcasterTimeOutRefresh = inMilliseconds; + } + } + + + if (thePacket->fPacketPtr.Len == 0) + { + //put the packet back on the free queue, because we didn't actually + //get any data here. + fFreeQueue.EnQueue(&thePacket->fQueueElem); + this->RequestEvent(EV_RE); + done = true; + //qtss_printf("ReflectorSocket::ProcessPacket no more packets on this socket!\n"); + break;//no more packets on this socket! + } + + if (thePacket->IsRTCP()) + { + //if this is a new RTCP packet, check to see if it is a sender report. + //We should only reflect sender reports. Because RTCP packets can't have both + //an SR & an RR, and because the SR & the RR must be the first packet in a + //compound RTCP packet, all we have to do to determine this is look at the + //packet type of the first packet in the compound packet. + RTCPPacket theRTCPPacket; + if ((!theRTCPPacket.ParsePacket((UInt8*)thePacket->fPacketPtr.Ptr, thePacket->fPacketPtr.Len)) || + (theRTCPPacket.GetPacketType() != RTCPSRPacket::kSRPacketType)) + { + //pretend as if we never got this packet + fFreeQueue.EnQueue(&thePacket->fQueueElem); + done = true; + break; + } + } + + // Only reflect one SSRC stream at a time. + // Pass the packet and whether it is an RTCP or RTP packet based on the port number. + if (fFilterSSRCs) + this->FilterInvalidSSRCs(thePacket,GetLocalPort() & 1);// thePacket->fPacketPtr.Len is set to 0 for invalid SSRCs. + + // Find the appropriate ReflectorSender for this packet. + ReflectorSender* theSender = (ReflectorSender*)this->GetDemuxer()->GetTask(theRemoteAddr, 0); + // If there is a generic sender for this socket, use it. + if (theSender == NULL) + theSender = (ReflectorSender*)this->GetDemuxer()->GetTask(0, 0); + + if (theSender == NULL) + { + //UInt16* theSeqNumberP = (UInt16*)thePacket->fPacketPtr.Ptr; + //qtss_printf("ReflectorSocket::ProcessPacket no sender found for packet! sequence number=%d\n",ntohs(theSeqNumberP[1])); + fFreeQueue.EnQueue(&thePacket->fQueueElem); // don't process the packet + done = true; + break; + } + + Assert(theSender != NULL); // at this point we have a sender + + const UInt32 maxQSize = 4000; + + // Check to see if we need to set the remote RTCP address + // for this stream. This will be necessary if the source is unicast. +#ifdef NAT_WORKAROUND + if ((theRemoteAddr != 0) && ((theSender->fStream->fDestRTCPAddr == 0) || (thePacket->IsRTCP()))) // Submitted fix from denis@berlin.ccc.de + { + Assert(!SocketUtils::IsMulticastIPAddr(theSender->fStream->fStreamInfo.fDestIPAddr)); + Assert(theRemotePort != 0); + theSender->fStream->fDestRTCPAddr = theRemoteAddr; + theSender->fStream->fDestRTCPPort = theRemotePort; + + // RTCPs are always on odd ports, so check to see if this port is an + // RTP port, and if so, just add 1. + if (!(thePacket->IsRTCP()) && !(theRemotePort & 1)) + theSender->fStream->fDestRTCPPort++; + } +#else + if ((theRemoteAddr != 0) && (theSender->fStream->fDestRTCPAddr == 0)) + { + // If the source is multicast, this shouldn't be necessary + Assert(!SocketUtils::IsMulticastIPAddr(theSender->fStream->fStreamInfo.fDestIPAddr)); + Assert(theRemotePort != 0); + theSender->fStream->fDestRTCPAddr = theRemoteAddr; + theSender->fStream->fDestRTCPPort = theRemotePort; + + // RTCPs are always on odd ports, so check to see if this port is an + // RTP port, and if so, just add 1. + if (!(theRemotePort & 1)) + theSender->fStream->fDestRTCPPort++; + } +#endif //NAT_WORKAROUND + thePacket->fStreamCountID = ++(theSender->fStream->fPacketCount); + thePacket->fBucketsSeenThisPacket = 0; + thePacket->fTimeArrived = inMilliseconds; + theSender->fPacketQueue.EnQueue(&thePacket->fQueueElem); + if ( theSender->fFirstNewPacketInQueue == NULL ) + theSender->fFirstNewPacketInQueue = &thePacket->fQueueElem; + theSender->fHasNewPackets = true; + + + if (!(thePacket->IsRTCP())) + { + // don't check for duplicate packets, they may be needed to keep in sync. + // Because this is an RTP packet make sure to atomic add this because + // multiple sockets can be adding to this variable simultaneously + (void)atomic_add(&theSender->fStream->fBytesSentInThisInterval, thePacket->fPacketPtr.Len); + //printf("ReflectorSocket::ProcessPacket received RTP id=%qu\n", thePacket->fStreamCountID); + theSender->fStream->SetHasFirstRTP(true); + } + else + { + //printf("ReflectorSocket::ProcessPacket received RTCP id=%qu\n", thePacket->fStreamCountID); + theSender->fStream->SetHasFirstRTCP(true); + theSender->fStream->SetFirst_RTCP_RTP_Time(thePacket->GetPacketRTPTime()); + theSender->fStream->SetFirst_RTCP_Arrival_Time(thePacket->fTimeArrived); + } + + + if (ReflectorStream::sUsePacketReceiveTime && thePacket->fPacketPtr.Len > 12) + { + UInt32 offset = thePacket->fPacketPtr.Len; + char* theTag = ((char*) thePacket->fPacketPtr.Ptr + offset) - 12; + UInt64* theValue = (UInt64*) ((char*) ( (char*) thePacket->fPacketPtr.Ptr + offset) - 8); + + if (0 == ::strncmp(theTag,"aktt",4)) + { + UInt64 theReceiveTime = OS::NetworkToHostSInt64(*theValue); + UInt32 theSSRC = thePacket->GetSSRC(theRemotePort & 1); // use to check if broadcast has restarted so we can reset + + if ( !this->fHasReceiveTime || (this->fCurrentSSRC != theSSRC) ) + { + this->fCurrentSSRC = theSSRC; + this->fFirstArrivalTime = thePacket->fTimeArrived; + this->fFirstReceiveTime = theReceiveTime; + this->fHasReceiveTime = true; + } + + + SInt64 packetOffsetFromStart = theReceiveTime - this->fFirstReceiveTime; // packets arrive at time 0 and fill forward into the future + thePacket->fTimeArrived = this->fFirstArrivalTime + packetOffsetFromStart; // offset starts negative by over buffer amount + thePacket->fPacketPtr.Len -= 12; + + SInt64 arrivalTimeOffset = thePacket->fTimeArrived - inMilliseconds; + if ( arrivalTimeOffset > ReflectorStream::sMaxFuturePacketMSec ) // way out in the future. + thePacket->fTimeArrived = inMilliseconds + ReflectorStream::sMaxFuturePacketMSec; //keep it but only for sMaxFuturePacketMSec = (sMaxPacketAgeMSec <-- current --> sMaxFuturePacketMSec) + + // if it was in the past we leave it alone because it will be deleted after processing. + + + //printf("ReflectorSocket::ProcessPacket packetOffsetFromStart=%f\n", (Float32) packetOffsetFromStart / 1000); + } + + } + + //printf("ReflectorSocket::GetIncomingData has packet from time=%qd src addr=%"_U32BITARG_" src port=%u packetlen=%"_U32BITARG_"\n",inMilliseconds, theRemoteAddr,theRemotePort,thePacket->fPacketPtr.Len); + if (0) //turn on / off buffer size checking -- pref can go here if we find we need to adjust this + if (theSender->fPacketQueue.GetLength() > maxQSize) //don't grow memory too big + { + char outMessage[256]; + sprintf(outMessage,"Packet Queue for port=%d qsize = %"_S32BITARG_" hit max qSize=%"_U32BITARG_"", theRemotePort,theSender->fPacketQueue.GetLength(), maxQSize); + WarnV(false, outMessage); + } + + + + } while(false); + + return done; +} + + +void ReflectorSocket::GetIncomingData(const SInt64& inMilliseconds) +{ + OSMutexLocker locker(this->GetDemuxer()->GetMutex()); + UInt32 theRemoteAddr = 0; + UInt16 theRemotePort = 0; + //get all the outstanding packets for this socket + while (true) + { + //get a packet off the free queue. + ReflectorPacket* thePacket = this->GetPacket(); + + thePacket->fPacketPtr.Len = 0; + (void)this->RecvFrom(&theRemoteAddr, &theRemotePort, thePacket->fPacketPtr.Ptr, + ReflectorPacket::kMaxReflectorPacketSize, &thePacket->fPacketPtr.Len); + + if (this->ProcessPacket(inMilliseconds,thePacket,theRemoteAddr, theRemotePort)) + break; + + //printf("ReflectorSocket::GetIncomingData \n"); + } + +} + + + + +ReflectorPacket* ReflectorSocket::GetPacket() +{ + OSMutexLocker locker(this->GetDemuxer()->GetMutex()); + if (fFreeQueue.GetLength() == 0) + //if the port number of this socket is odd, this packet is an RTCP packet. + return NEW ReflectorPacket(); + else + return (ReflectorPacket*)fFreeQueue.DeQueue()->GetEnclosingObject(); +} diff --git a/APIModules/QTSSReflectorModule/ReflectorStream.h b/APIModules/QTSSReflectorModule/ReflectorStream.h new file mode 100644 index 0000000..0313cd1 --- /dev/null +++ b/APIModules/QTSSReflectorModule/ReflectorStream.h @@ -0,0 +1,513 @@ +/* + * + * @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: ReflectorStream.h + + Contains: This object supports reflecting an RTP multicast stream to N + RTPStreams. It spaces out the packet send times in order to + maximize the randomness of the sending pattern and smooth + the stream. + + + +*/ + +#ifndef _REFLECTOR_STREAM_H_ +#define _REFLECTOR_STREAM_H_ + +#include "QTSS.h" + +#include "IdleTask.h" +#include "SourceInfo.h" + +#include "UDPSocket.h" +#include "UDPSocketPool.h" +#include "UDPDemuxer.h" +#include "EventContext.h" +#include "SequenceNumberMap.h" + +#include "OSMutex.h" +#include "OSQueue.h" +#include "OSRef.h" + +#include "RTCPSRPacket.h" +#include "ReflectorOutput.h" +#include "atomic.h" + +//This will add some printfs that are useful for checking the thinning +#define REFLECTOR_THINNING_DEBUGGING 0 + +//Define to use new potential workaround for NAT problems +#define NAT_WORKAROUND 1 + +class ReflectorPacket; +class ReflectorSender; +class ReflectorStream; +class RTPSessionOutput; + +class ReflectorPacket +{ + public: + + ReflectorPacket() : fQueueElem() { fQueueElem.SetEnclosingObject(this); this->Reset();} + void Reset() { // make packet ready to reuse fQueueElem is always in use + fBucketsSeenThisPacket = 0; + fTimeArrived = 0; + //fQueueElem -- should be set to this + fPacketPtr.Set(fPacketData, 0); + fIsRTCP = false; + fStreamCountID = 0; + fNeededByOutput = false; + } + + ~ReflectorPacket() {} + + void SetPacketData(char *data, UInt32 len) { Assert(kMaxReflectorPacketSize > len); if (len > 0) memcpy(this->fPacketPtr.Ptr,data,len); this->fPacketPtr.Len = len;} + Bool16 IsRTCP() { return fIsRTCP; } +inline UInt32 GetPacketRTPTime(); +inline UInt16 GetPacketRTPSeqNum(); +inline UInt32 GetSSRC(Bool16 isRTCP); +inline SInt64 GetPacketNTPTime(); + + private: + + enum + { + kMaxReflectorPacketSize = 2060 //jm 5/02 increased from 2048 by 12 bytes for test bytes appended to packets + }; + + UInt32 fBucketsSeenThisPacket; + SInt64 fTimeArrived; + OSQueueElem fQueueElem; + char fPacketData[kMaxReflectorPacketSize]; + StrPtrLen fPacketPtr; + Bool16 fIsRTCP; + Bool16 fNeededByOutput; // is this packet still needed for output? + UInt64 fStreamCountID; + + friend class ReflectorSender; + friend class ReflectorSocket; + friend class RTPSessionOutput; + + +}; + +UInt32 ReflectorPacket::GetSSRC(Bool16 isRTCP) +{ + if (fPacketPtr.Ptr == NULL || fPacketPtr.Len < 8) + return 0; + + UInt32* theSsrcPtr = (UInt32*)fPacketPtr.Ptr; + if (isRTCP)// RTCP + return ntohl(theSsrcPtr[1]); + + if (fPacketPtr.Len < 12) + return 0; + + return ntohl(theSsrcPtr[2]); // RTP SSRC +} + +UInt32 ReflectorPacket::GetPacketRTPTime() +{ + + UInt32 timestamp = 0; + if (!fIsRTCP) + { + //The RTP timestamp number is the second long of the packet + if (fPacketPtr.Ptr == NULL || fPacketPtr.Len < 8) + return 0; + timestamp = ntohl( ((UInt32*)fPacketPtr.Ptr)[1]); + } + else + { + if (fPacketPtr.Ptr == NULL || fPacketPtr.Len < 20) + return 0; + timestamp = ntohl( ((UInt32*)fPacketPtr.Ptr)[4]); + } + return timestamp; +} + +UInt16 ReflectorPacket::GetPacketRTPSeqNum() +{ + Assert(!fIsRTCP); // not a supported type + + if (fPacketPtr.Ptr == NULL || fPacketPtr.Len < 4 || fIsRTCP) + return 0; + + UInt16 sequence = ntohs( ((UInt16*)fPacketPtr.Ptr)[1]); //The RTP sequenc number is the second short of the packet + return sequence; +} + + +SInt64 ReflectorPacket::GetPacketNTPTime() +{ + Assert(fIsRTCP); // not a supported type + if (fPacketPtr.Ptr == NULL || fPacketPtr.Len < 16 || !fIsRTCP) + return 0; + + UInt32* theReport = (UInt32*)fPacketPtr.Ptr; + theReport +=2; + SInt64 ntp = 0; + ::memcpy(&ntp, theReport, sizeof(SInt64)); + + return OS::Time1900Fixed64Secs_To_TimeMilli(OS::NetworkToHostSInt64(ntp)); + + +} + + +//Custom UDP socket classes for doing reflector packet retrieval, socket management +class ReflectorSocket : public IdleTask, public UDPSocket +{ + public: + + ReflectorSocket(); + virtual ~ReflectorSocket(); + void AddBroadcasterSession(QTSS_ClientSessionObject inSession) { OSMutexLocker locker(this->GetDemuxer()->GetMutex()); fBroadcasterClientSession = inSession; } + void RemoveBroadcasterSession(QTSS_ClientSessionObject inSession){ OSMutexLocker locker(this->GetDemuxer()->GetMutex()); if (inSession == fBroadcasterClientSession) fBroadcasterClientSession = NULL; } + void AddSender(ReflectorSender* inSender); + void RemoveSender(ReflectorSender* inStreamElem); + Bool16 HasSender() { return (this->GetDemuxer()->GetHashTable()->GetNumEntries() > 0); } + Bool16 ProcessPacket(const SInt64& inMilliseconds,ReflectorPacket* thePacket,UInt32 theRemoteAddr,UInt16 theRemotePort); + ReflectorPacket* GetPacket(); + virtual SInt64 Run(); + void SetSSRCFilter(Bool16 state, UInt32 timeoutSecs) { fFilterSSRCs = state; fTimeoutSecs = timeoutSecs;} + private: + + //virtual SInt64 Run(); + void GetIncomingData(const SInt64& inMilliseconds); + void FilterInvalidSSRCs(ReflectorPacket* thePacket,Bool16 isRTCP); + + //Number of packets to allocate when the socket is first created + enum + { + kNumPreallocatedPackets = 20, //UInt32 + kRefreshBroadcastSessionIntervalMilliSecs = 10000, + kSSRCTimeOut = 30000 // milliseconds before clearing the SSRC if no new ssrcs have come in + }; + QTSS_ClientSessionObject fBroadcasterClientSession; + SInt64 fLastBroadcasterTimeOutRefresh; + // Queue of available ReflectorPackets + OSQueue fFreeQueue; + // Queue of senders + OSQueue fSenderQueue; + SInt64 fSleepTime; + + UInt32 fValidSSRC; + SInt64 fLastValidSSRCTime; + Bool16 fFilterSSRCs; + UInt32 fTimeoutSecs; + + Bool16 fHasReceiveTime; + UInt64 fFirstReceiveTime; + SInt64 fFirstArrivalTime; + UInt32 fCurrentSSRC; + +}; + + +class ReflectorSocketPool : public UDPSocketPool +{ + public: + + ReflectorSocketPool() {} + virtual ~ReflectorSocketPool() {} + + virtual UDPSocketPair* ConstructUDPSocketPair(); + virtual void DestructUDPSocketPair(UDPSocketPair *inPair); + virtual void SetUDPSocketOptions(UDPSocketPair* inPair); + void DestructUDPSocket( ReflectorSocket* socket); + + +}; + +class ReflectorSender : public UDPDemuxerTask +{ + public: + ReflectorSender(ReflectorStream* inStream, UInt32 inWriteFlag); + virtual ~ReflectorSender(); + // Queue of senders + OSQueue fSenderQueue; + SInt64 fSleepTime; + + //Used for adjusting sequence numbers in light of thinning + UInt16 GetPacketSeqNumber(const StrPtrLen& inPacket); + void SetPacketSeqNumber(const StrPtrLen& inPacket, UInt16 inSeqNumber); + Bool16 PacketShouldBeThinned(QTSS_RTPStreamObject inStream, const StrPtrLen& inPacket); + + //We want to make sure that ReflectPackets only gets invoked when there + //is actually work to do, because it is an expensive function + Bool16 ShouldReflectNow(const SInt64& inCurrentTime, SInt64* ioWakeupTime); + + //This function gets data from the multicast source and reflects. + //Returns the time at which it next needs to be invoked + void ReflectPackets(SInt64* ioWakeupTime, OSQueue* inFreeQueue); + + //this is the old way of doing reflect packets. It is only here until the relay code can be cleaned up. + void ReflectRelayPackets(SInt64* ioWakeupTime, OSQueue* inFreeQueue); + + OSQueueElem* SendPacketsToOutput(ReflectorOutput* theOutput, OSQueueElem* currentPacket, SInt64 currentTime, SInt64 bucketDelay, Bool16 firstPacket); + + UInt32 GetOldestPacketRTPTime(Bool16 *foundPtr); + UInt16 GetFirstPacketRTPSeqNum(Bool16 *foundPtr); + Bool16 GetFirstPacketInfo(UInt16* outSeqNumPtr, UInt32* outRTPTimePtr, SInt64* outArrivalTimePtr); + + OSQueueElem*GetClientBufferNextPacketTime(UInt32 inRTPTime); + Bool16 GetFirstRTPTimePacket(UInt16* outSeqNumPtr, UInt32* outRTPTimePtr, SInt64* outArrivalTimePtr); + + void RemoveOldPackets(OSQueue* inFreeQueue); + OSQueueElem* GetClientBufferStartPacketOffset(SInt64 offsetMsec); + OSQueueElem* GetClientBufferStartPacket() { return this->GetClientBufferStartPacketOffset(0); }; + + ReflectorStream* fStream; + UInt32 fWriteFlag; + + OSQueue fPacketQueue; + OSQueueElem* fFirstNewPacketInQueue; + OSQueueElem* fFirstPacketInQueueForNewOutput; + + //these serve as an optimization, keeping track of when this + //sender needs to run so it doesn't run unnecessarily + + inline void SetNextTimeToRun(SInt64 nextTime) { fNextTimeToRun = nextTime; + //qtss_printf("SetNextTimeToRun =%"_64BITARG_"d\n", fNextTimeToRun); + } + + Bool16 fHasNewPackets; + SInt64 fNextTimeToRun; + + //how often to send RRs to the source + enum + { + kRRInterval = 5000 //SInt64 (every 5 seconds) + }; + + SInt64 fLastRRTime; + OSQueueElem fSocketQueueElem; + + friend class ReflectorSocket; + friend class ReflectorStream; +}; + +class ReflectorStream +{ + public: + + enum + { + // A ReflectorStream is uniquely identified by the + // destination IP address & destination port of the broadcast. + // This ID simply contains that information. + // + // A unicast broadcast can also be identified by source IP address. If + // you are attempting to demux by source IP, this ID will not guarentee + // uniqueness and special care should be used. + kStreamIDSize = sizeof(UInt32) + sizeof(UInt16) + }; + + // Uses a StreamInfo to generate a unique ID + static void GenerateSourceID(SourceInfo::StreamInfo* inInfo, char* ioBuffer); + + ReflectorStream(SourceInfo::StreamInfo* inInfo); + ~ReflectorStream(); + + // + // SETUP + // + // Call Register from the Register role, as this object has some QTSS API + // attributes to setup + static void Register(); + static void Initialize(QTSS_ModulePrefsObject inPrefs); + + // + // MODIFIERS + + // Call this to initialize the reflector sockets. Uses the QTSS_RTSPRequestObject + // if provided to report any errors that occur + // Passes the QTSS_ClientSessionObject to the socket so the socket can update the session if needed. + QTSS_Error BindSockets(QTSS_StandardRTSP_Params* inParams, UInt32 inReflectorSessionFlags, Bool16 filterState, UInt32 timeout); + + // This stream reflects packets from the broadcast to specific ReflectorOutputs. + // You attach outputs to ReflectorStreams this way. You can force the ReflectorStream + // to put this output into a certain bucket by passing in a certain bucket index. + // Pass in -1 if you don't care. AddOutput returns the bucket index this output was + // placed into, or -1 on an error. + + SInt32 AddOutput(ReflectorOutput* inOutput, SInt32 putInThisBucket); + + // Removes the specified output from this ReflectorStream. + void RemoveOutput(ReflectorOutput* inOutput); // Removes this output from all tracks + + void TearDownAllOutputs(); // causes a tear down and then a remove + + // If the incoming data is RTSP interleaved, packets for this stream are identified + // by channel numbers + void SetRTPChannelNum(SInt16 inChannel) { fRTPChannel = inChannel; } + void SetRTCPChannelNum(SInt16 inChannel) { fRTCPChannel = inChannel; } + void PushPacket(char *packet, UInt32 packetLen, Bool16 isRTCP); + + // + // ACCESSORS + + OSRef* GetRef() { return &fRef; } + UInt32 GetBitRate() { return fCurrentBitRate; } + SourceInfo::StreamInfo* GetStreamInfo() { return &fStreamInfo; } + OSMutex* GetMutex() { return &fBucketMutex; } + void* GetStreamCookie() { return this; } + SInt16 GetRTPChannel() { return fRTPChannel; } + SInt16 GetRTCPChannel() { return fRTCPChannel; } + UDPSocketPair* GetSocketPair() { return fSockets;} + ReflectorSender* GetRTPSender() { return &fRTPSender; } + ReflectorSender* GetRTCPSender() { return &fRTCPSender; } + + void SetHasFirstRTCP(Bool16 hasPacket) { fHasFirstRTCPPacket = hasPacket; } + Bool16 HasFirstRTCP() { return fHasFirstRTCPPacket; } + + void SetFirst_RTCP_RTP_Time(UInt32 time) { fFirst_RTCP_RTP_Time = time; } + UInt32 GetFirst_RTCP_RTP_Time() { return fFirst_RTCP_RTP_Time; } + + void SetFirst_RTCP_Arrival_Time(SInt64 time) { fFirst_RTCP_Arrival_Time = time; } + SInt64 GetFirst_RTCP_Arrival_Time() { return fFirst_RTCP_Arrival_Time; } + + + void SetHasFirstRTP(Bool16 hasPacket) { fHasFirstRTPPacket = hasPacket; } + Bool16 HasFirstRTP() { return fHasFirstRTPPacket; } + + UInt32 GetBufferDelay() { return ReflectorStream::sOverBufferInMsec; } + UInt32 GetTimeScale() { return fStreamInfo.fTimeScale; } + UInt64 fPacketCount; + + void SetEnableBuffer(Bool16 enableBuffer) { fEnableBuffer = enableBuffer; } + Bool16 BufferEnabled() { return fEnableBuffer; } +inline void UpdateBitRate(SInt64 currentTime); + static UInt32 sOverBufferInMsec; + + void IncEyeCount() { OSMutexLocker locker(&fBucketMutex); fEyeCount ++; } + void DecEyeCount() { OSMutexLocker locker(&fBucketMutex); fEyeCount --; } + UInt32 GetEyeCount() { OSMutexLocker locker(&fBucketMutex); return fEyeCount; } + + private: + + //Sends an RTCP receiver report to the broadcast source + void SendReceiverReport(); + void AllocateBucketArray(UInt32 inNumBuckets); + SInt32 FindBucket(); + // Unique ID & OSRef. ReflectorStreams can be mapped & shared + OSRef fRef; + char fSourceIDBuf[kStreamIDSize]; + + // Reflector sockets, retrieved from the socket pool + UDPSocketPair* fSockets; + + ReflectorSender fRTPSender; + ReflectorSender fRTCPSender; + SequenceNumberMap fSequenceNumberMap; //for removing duplicate packets + + // All the necessary info about this stream + SourceInfo::StreamInfo fStreamInfo; + + enum + { + kReceiverReportSize = 16, //UInt32 + kAppSize = 36, //UInt32 + kMinNumBuckets = 16, //UInt32 + kBitRateAvgIntervalInMilSecs = 30000 // time between bitrate averages + }; + + // BUCKET ARRAY + + //ReflectorOutputs are kept in a 2-dimensional array, "Buckets" + typedef ReflectorOutput** Bucket; + Bucket* fOutputArray; + + UInt32 fNumBuckets; //Number of buckets currently + UInt32 fNumElements; //Number of reflector outputs in the array + + //Bucket array can't be modified while we are sending packets. + OSMutex fBucketMutex; + + // RTCP RR information + + char fReceiverReportBuffer[kReceiverReportSize + kAppSize + + RTCPSRPacket::kMaxCNameLen]; + UInt32* fEyeLocation;//place in the buffer to write the eye information + UInt32 fReceiverReportSize; + + // This is the destination address & port for RTCP + // receiver reports. + UInt32 fDestRTCPAddr; + UInt16 fDestRTCPPort; + + // Used for calculating average bit rate + UInt32 fCurrentBitRate; + SInt64 fLastBitRateSample; + unsigned int fBytesSentInThisInterval;// unsigned int because we need to atomic_add + + // If incoming data is RTSP interleaved + SInt16 fRTPChannel; //These will be -1 if not set to anything + SInt16 fRTCPChannel; + + Bool16 fHasFirstRTCPPacket; + Bool16 fHasFirstRTPPacket; + + Bool16 fEnableBuffer; + UInt32 fEyeCount; + + UInt32 fFirst_RTCP_RTP_Time; + SInt64 fFirst_RTCP_Arrival_Time; + + static UInt32 sBucketSize; + static UInt32 sMaxPacketAgeMSec; + static UInt32 sMaxFuturePacketSec; + + static UInt32 sMaxFuturePacketMSec; + static UInt32 sOverBufferInSec; + static UInt32 sBucketDelayInMsec; + static Bool16 sUsePacketReceiveTime; + static UInt32 sFirstPacketOffsetMsec; + + friend class ReflectorSocket; + friend class ReflectorSender; +}; + + +void ReflectorStream::UpdateBitRate(SInt64 currentTime) +{ + if ((fLastBitRateSample + ReflectorStream::kBitRateAvgIntervalInMilSecs) < currentTime) + { + unsigned int intervalBytes = fBytesSentInThisInterval; + (void)atomic_sub(&fBytesSentInThisInterval, intervalBytes); + + // Multiply by 1000 to convert from milliseconds to seconds, and by 8 to convert from bytes to bits + Float32 bps = (Float32)(intervalBytes * 8) / (Float32)(currentTime - fLastBitRateSample); + bps *= 1000; + fCurrentBitRate = (UInt32)bps; + + // Don't check again for awhile! + fLastBitRateSample = currentTime; + } +} +#endif //_REFLECTOR_SESSION_H_ + diff --git a/APIModules/QTSSReflectorModule/RelayOutput.cpp b/APIModules/QTSSReflectorModule/RelayOutput.cpp new file mode 100644 index 0000000..fc45878 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RelayOutput.cpp @@ -0,0 +1,580 @@ +/* + * + * @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: RelayOutput.cpp + + Contains: Implementation of object described in .h file + + + +*/ + +#include "RelayOutput.h" + +#include "OSMemory.h" +#include "SocketUtils.h" +#include "RTSPSourceInfo.h" + +static StrPtrLen sUDPDestStr("udp_destination"); +static StrPtrLen sAnnouncedDestStr("announced_destination"); + +// STATIC DATA +OSQueue RelayOutput::sRelayOutputQueue; +OSMutex RelayOutput::sQueueMutex; + +QTSS_ObjectType RelayOutput::qtssRelayOutputObjectType; + +QTSS_AttributeID RelayOutput::sOutputType = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputDestAddr = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputLocalAddr = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputUDPPorts = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputRTSPPort = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputURL = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputTTL = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputCurPacketsPerSec = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputCurBitsPerSec = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputTotalPacketsSent = qtssIllegalAttrID; +QTSS_AttributeID RelayOutput::sOutputTotalBytesSent = qtssIllegalAttrID; + +static char* sOutputTypeName = "output_type"; +static char* sOutputDestAddrName = "output_dest_addr"; +static char* sOutputLocalAddrName = "output_local_addr"; +static char* sOutputUDPPortsName = "output_udp_ports"; +static char* sOutputRTSPPortName = "output_rtsp_port"; +static char* sOutputURLName = "output_url"; +static char* sOutputTTLName = "output_ttl"; + +static char* sOutputCurPacketsPerSecName = "output_cur_packetspersec"; +static char* sOutputCurBitsPerSecName = "output_cur_bitspersec"; +static char* sOutputTotalPacketsSentName = "output_total_packets_sent"; +static char* sOutputTotalBytesSentName = "output_total_bytes_sent"; + +void RelayOutput::Register() +{ + // Create the relay output object type + (void)QTSS_CreateObjectType(&qtssRelayOutputObjectType); + + // Add the static attributes to the qtssRelayOutputObjectType object + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputTypeName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputTypeName, &sOutputType); // dest type + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputDestAddrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputDestAddrName, &sOutputDestAddr); // dest addr + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputLocalAddrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputLocalAddrName, &sOutputLocalAddr); // interface addr + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputUDPPortsName, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputUDPPortsName, &sOutputUDPPorts); // udp ports + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputRTSPPortName, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputRTSPPortName, &sOutputRTSPPort); // rtsp port + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputURLName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputURLName, &sOutputURL); // url + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputTTLName, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputTTLName, &sOutputTTL); // ttl + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputCurPacketsPerSecName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputCurPacketsPerSecName, &sOutputCurPacketsPerSec);// cur packets/sec + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputCurBitsPerSecName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputCurBitsPerSecName, &sOutputCurBitsPerSec); // cur bits/sec + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputTotalPacketsSentName, NULL, qtssAttrDataTypeUInt64); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputTotalPacketsSentName, &sOutputTotalPacketsSent);// total packets + + (void)QTSS_AddStaticAttribute(qtssRelayOutputObjectType, sOutputTotalBytesSentName, NULL, qtssAttrDataTypeUInt64); + (void)QTSS_IDForAttr(qtssRelayOutputObjectType, sOutputTotalBytesSentName, &sOutputTotalBytesSent); // total bytes + +} + +RelayOutput::RelayOutput(SourceInfo* inInfo, UInt32 inWhichOutput, RelaySession* inRelaySession, Bool16 isRTSPSourceInfo) +: fRelaySession(inRelaySession), + fOutputSocket(NULL, Socket::kNonBlockingSocketType), + fNumStreams(inRelaySession->GetSourceInfo()->GetNumStreams()), // use the reflector session's source info + fOutputInfo(*inInfo->GetOutputInfo(inWhichOutput)), + fQueueElem(), + fFormatter(&fHTMLBuf[0], kMaxHTMLSize), + fPacketsPerSecond(0), + fBitsPerSecond(0), + fLastUpdateTime(0), + fTotalPacketsSent(0), + fTotalBytesSent(0), + fLastPackets(0), + fLastBytes(0), + fClientSocket(NULL), + fClient(NULL), + fDoingAnnounce(false), + fValid(true), + fOutgoingSDP(NULL), + fAnnounceTask(NULL), + fRTSPOutputInfo(NULL) +{ + Assert(fNumStreams > 0); + + fQueueElem.SetEnclosingObject(this); + fStreamCookieArray = NEW void*[fNumStreams]; + fTrackIDArray = NEW UInt32[fNumStreams]; + fOutputInfo.fPortArray = NEW UInt16[fNumStreams];//copy constructor doesn't do this + ::memset(fOutputInfo.fPortArray, 0, fNumStreams * sizeof(UInt16)); + + // create a bookmark for each stream we'll reflect + this->InititializeBookmarks( inRelaySession->GetNumStreams() ); + + // Copy out all the track IDs for each stream + for (UInt32 x = 0; x < fNumStreams; x++) + { + fTrackIDArray[x] = inRelaySession->GetSourceInfo()->GetStreamInfo(x)->fTrackID; + fStreamCookieArray[x] = inRelaySession->GetStreamCookie(fTrackIDArray[x]); + } + + // Copy the contents of the output port array + + if (inInfo->GetOutputInfo(inWhichOutput)->fPortArray != NULL) + { + UInt32 copySize = fNumStreams; + if (fOutputInfo.fNumPorts < fNumStreams) + copySize = fOutputInfo.fNumPorts; + ::memcpy(fOutputInfo.fPortArray, inInfo->GetOutputInfo(inWhichOutput)->fPortArray, copySize * sizeof(UInt16)); + } + else if (fOutputInfo.fBasePort != 0) + { + for (UInt32 y = 0; y < fNumStreams; y++) + fOutputInfo.fPortArray[y] = (UInt16) (fOutputInfo.fBasePort + (y * 2) ); + } + + OS_Error err = BindSocket(); + if (err != OS_NoErr) + { + fValid = false; + return; + } + + RTSPOutputInfo* rtspInfo = NULL; + if (isRTSPSourceInfo) + { + // in the case of the announce source info, the passed in source info will have the new + // output information, but only the session's source info will have the sdp and url. + RTSPSourceInfo* rtspSourceInfo = (RTSPSourceInfo *)(inInfo); + Assert(rtspSourceInfo != NULL); + RTSPSourceInfo* sessionSourceInfo = (RTSPSourceInfo *)(inRelaySession->GetSourceInfo()); + Assert(sessionSourceInfo != NULL); + + rtspInfo = rtspSourceInfo->GetRTSPOutputInfo(inWhichOutput); + if (rtspInfo->fIsAnnounced) + { + fRTSPOutputInfo = NEW RTSPOutputInfo(); + fRTSPOutputInfo->Copy(*rtspInfo); + + fDoingAnnounce = true; + // set up rtsp socket and client + fClientSocket = new TCPClientSocket(Socket::kNonBlockingSocketType); + fClient = new RTSPClient(fClientSocket, false, RelaySession::sRelayUserAgent); + + // set up the outgoing socket + fClientSocket->Set(fOutputInfo.fDestAddr, rtspInfo->fAnnouncePort); + int sndBufSize = 32 * 1024; + int rcvBufSize=1024; + fClientSocket->SetOptions(sndBufSize,rcvBufSize); + + // set up the client object + StrPtrLen url; + if ((rtspInfo->fDestURl != NULL) && (strlen(rtspInfo->fDestURl) > 0)) + url.Set(rtspInfo->fDestURl); + else + url.Set(sessionSourceInfo->GetSourceURL()); + + fClient->Set(url); + + fClient->SetTransportMode(RTSPClient::kPushMode); + fClient->SetName(rtspInfo->fUserName); + fClient->SetPassword(rtspInfo->fPassword); + + UInt32 len; + fOutgoingSDP = sessionSourceInfo->GetAnnounceSDP(fOutputInfo.fDestAddr, &len); + fAnnounceState = kSendingAnnounce; + fCurrentSetup = 0; + fAnnounceTask = new RelayAnnouncer(this); // this will now go and run the async announce + fAnnounceTask->Signal(Task::kStartEvent); + } + } + + // Write the Output HTML + // Looks like: Relaying to: 229.49.52.102, Ports: 16898 16900 Time to live: 15 + static StrPtrLen sHTMLStart("Relaying to: "); + static StrPtrLen sPorts(", Ports: "); + static StrPtrLen sTimeToLive(" Time to live: "); + static StrPtrLen sHTMLEnd("
"); + + // First, format the destination addr as a dotted decimal string + char theIPAddrBuf[20]; + StrPtrLen theIPAddr(theIPAddrBuf, 20); + struct in_addr theAddr; + theAddr.s_addr = htonl(fOutputInfo.fDestAddr); + SocketUtils::ConvertAddrToString(theAddr, &theIPAddr); + + // Begin writing the HTML + fFormatter.Put(sHTMLStart); + fFormatter.Put(theIPAddr); + fFormatter.Put(sPorts); + + for (UInt32 y = 0; y < fNumStreams; y++) + { + // Write all the destination ports + fFormatter.Put(fOutputInfo.fPortArray[y]); + fFormatter.PutSpace(); + } + + if (SocketUtils::IsMulticastIPAddr(inInfo->GetOutputInfo(inWhichOutput)->fDestAddr)) + { + // Put the time to live if this is a multicast destination + fFormatter.Put(sTimeToLive); + fFormatter.Put(fOutputInfo.fTimeToLive); + } + fFormatter.Put(sHTMLEnd); + + // Setup the StrPtrLen to point to the right stuff + fOutputInfoHTML.Ptr = fFormatter.GetBufPtr(); + fOutputInfoHTML.Len = fFormatter.GetCurrentOffset(); + + OSMutexLocker locker(&sQueueMutex); + sRelayOutputQueue.EnQueue(&fQueueElem); + + SetupRelayOutputObject(rtspInfo); +} + +RelayOutput::~RelayOutput() +{ + OSMutexLocker locker(&sQueueMutex); + sRelayOutputQueue.Remove(&fQueueElem); + + if (fClientSocket) + delete fClientSocket; + if (fClient) + delete fClient; + + delete [] fStreamCookieArray; + + delete fOutgoingSDP; + fOutgoingSDP = NULL; + + if (fAnnounceTask != NULL) + fAnnounceTask->fOutput = NULL; + + if (fRTSPOutputInfo != NULL) + delete fRTSPOutputInfo; + + QTSS_Object outputObject; + UInt32 len = sizeof(QTSS_Object); + + for (int x = 0; QTSS_GetValue(fRelaySessionObject, RelaySession::sRelayOutputObject, x, &outputObject, &len) == QTSS_NoErr; x++) + { + Assert(outputObject != NULL); + Assert(len == sizeof(QTSS_Object)); + + if (outputObject == fRelayOutputObject) + { + (void)QTSS_RemoveValue(fRelaySessionObject, RelaySession::sRelayOutputObject, x); + break; + } + } +} + + +OS_Error RelayOutput::BindSocket() +{ + OS_Error theErr = fOutputSocket.Open(); + if (theErr != OS_NoErr) + return theErr; + + // We don't care what local port we bind to + theErr = fOutputSocket.Bind(fOutputInfo.fLocalAddr, 0); + if (theErr != OS_NoErr) + return theErr; + + // Set the ttl to be the proper value + return fOutputSocket.SetTtl(fOutputInfo.fTimeToLive); +} + +Bool16 RelayOutput::Equal(SourceInfo* inInfo) +{ + // First check if the Source Info matches this RelaySession + if (!fRelaySession->Equal(inInfo)) + return false; + for (UInt32 x = 0; x < inInfo->GetNumOutputs(); x++) + { + if (inInfo->GetOutputInfo(x)->Equal(fOutputInfo)) + { + RTSPOutputInfo* rtspOutputInfo = NULL; + if (inInfo->IsRTSPSourceInfo()) + { + rtspOutputInfo = ((RTSPSourceInfo*)inInfo)->GetRTSPOutputInfo(x); + if (!rtspOutputInfo->fIsAnnounced) + rtspOutputInfo = NULL; + } + + if (fRTSPOutputInfo != NULL) // announced output + { + if (!fRTSPOutputInfo->Equal(rtspOutputInfo)) // doesn't match the output + continue; + } + else if (rtspOutputInfo != NULL) + continue; + + // This is a rather special purpose function... here we set this + // flag marking this particular output as a duplicate, because + // we know it is equal to this object. + // (This is used in QTSSReflectorModule.cpp:RereadRelayPrefs) + inInfo->GetOutputInfo(x)->fAlreadySetup = true; + return true; + } + } + return false; +} + +QTSS_Error RelayOutput::WritePacket(StrPtrLen* inPacket, void* inStreamCookie, UInt32 inFlags, SInt64 /*packetLatenessInMSec*/, SInt64* /*timeToSendThisPacketAgain*/, UInt64* packetIDPtr, SInt64* /*arrivalTimeMSec*/, Bool16 /*firstPacket */ ) +{ + + if (!fValid || fDoingAnnounce) + return OS_NoErr; // Not done setting up or we had an error setting up + + // we don't use packetLateness becuase relays don't need to worry about TCP flow control induced transmit delay + + // Look for the matching streamID + for (UInt32 x = 0; x < fNumStreams; x++) + { + if (inStreamCookie == fStreamCookieArray[x]) + { + UInt16 theDestPort = fOutputInfo.fPortArray[x]; + Assert((theDestPort & 1) == 0); //this should always be an RTP port (even) + if (inFlags & qtssWriteFlagsIsRTCP) + theDestPort++; + + (void)fOutputSocket.SendTo(fOutputInfo.fDestAddr, theDestPort, + inPacket->Ptr, inPacket->Len); + + // Update our totals + fTotalPacketsSent++; + fTotalBytesSent += inPacket->Len; + break; + } + } + + // If it is time to recalculate statistics, do so + SInt64 curTime = OS::Milliseconds(); + if ((fLastUpdateTime + kStatsIntervalInMilSecs) < curTime) + { + // Update packets per second + Float64 packetsPerSec = (Float64)((SInt64)fTotalPacketsSent - (SInt64)fLastPackets); + packetsPerSec *= 1000; + packetsPerSec /= (Float64)(curTime - fLastUpdateTime); + fPacketsPerSecond = (UInt32)packetsPerSec; + + // Update bits per second. Win32 doesn't implement UInt64 -> Float64. + Float64 bitsPerSec = (Float64)((SInt64)fTotalBytesSent - (SInt64)fLastBytes); + bitsPerSec *= 1000 * 8;//convert from seconds to milsecs, bytes to bits + bitsPerSec /= (Float64)(curTime - fLastUpdateTime); + fBitsPerSecond = (UInt32)bitsPerSec; + + fLastUpdateTime = curTime; + fLastPackets = fTotalPacketsSent; + fLastBytes = fTotalBytesSent; + } + + return QTSS_NoErr; +} + +SInt64 RelayOutput::RelayAnnouncer::Run() +{ + OSMutexLocker locker(RelayOutput::GetQueueMutex()); + SInt64 result = -1; + if (fOutput != NULL) + result = fOutput->RunAnnounce(); + + return result; +} + +SInt64 RelayOutput::RunAnnounce() +{ + OS_Error err = OS_NoErr; + SInt64 result = 1000; + + if (fAnnounceState == kSendingAnnounce) + { + if (fOutgoingSDP == NULL || ::strlen(fOutgoingSDP) == 0) + err = ENOTCONN; + else + { + err = fClient->SendAnnounce(fOutgoingSDP); + if (err == OS_NoErr) + { + delete fOutgoingSDP; + fOutgoingSDP = NULL; + if (fClient->GetStatus() == 200) + fAnnounceState = kSendingSetup; + else + err = ENOTCONN; + } + } + } + + while ((fAnnounceState == kSendingSetup) && (err == OS_NoErr)) + { + err = fClient->SendUDPSetup(fTrackIDArray[fCurrentSetup], 10000); + if (err == OS_NoErr) + { + if (fClient->GetStatus() == 200) + { + fOutputInfo.fPortArray[fCurrentSetup] = fClient->GetServerPort(); // this got set from the Setup reply + fCurrentSetup++; + if (fCurrentSetup == fNumStreams) + fAnnounceState = kSendingPlay; + } + else + err = ENOTCONN; + } + } + + if (fAnnounceState == kSendingPlay) + { + err = fClient->SendPlay(0); + if (err == OS_NoErr) + { + if (fClient->GetStatus() == 200) + fAnnounceState = kDone; + else + err = ENOTCONN; + } + } + + if (fAnnounceState == kDone) + { + for (UInt32 index = 0; index < fNumStreams; index++) // source udp ports + { + UInt16 udpPort = fOutputInfo.fPortArray[index]; + err = QTSS_SetValue (fRelayOutputObject, sOutputUDPPorts, index, &udpPort, sizeof(udpPort)); + Assert(err == QTSS_NoErr); + } + + fDoingAnnounce = false; + result = -1; // let the task die + fAnnounceTask = NULL; + } + + if ((err == EINPROGRESS) || (err == EAGAIN)) + { + // Request an async event + fClientSocket->GetSocket()->SetTask(fAnnounceTask); + fClientSocket->GetSocket()->RequestEvent(fClientSocket->GetEventMask() ); + } + else if (err != OS_NoErr) + { + // We encountered some fatal error with the socket. Record this as a connection failure + fValid = false; + result = -1; // let the task die + fAnnounceTask = NULL; + } + + if ( (-1 == result) && (NULL != fClientSocket) && (NULL != fClientSocket->GetSocket() ) ) + fClientSocket->GetSocket()->SetTask(NULL); //remove fAnnounceTask from event handling code. The task can be safely deleted after detaching from the socket. + + return result; +} + +void RelayOutput::SetupRelayOutputObject(RTSPOutputInfo* inRTSPInfo) +{ + fRelaySessionObject = fRelaySession->GetRelaySessionObject(); + QTSS_Error theErr = QTSS_NoErr; + UInt32 outIndex = 0; + + theErr = QTSS_LockObject (fRelaySessionObject); + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_CreateObjectValue (fRelaySessionObject , RelaySession::sRelayOutputObject, qtssRelayOutputObjectType, &outIndex, &fRelayOutputObject); + Assert(theErr == QTSS_NoErr); + + if ((inRTSPInfo == NULL) || !inRTSPInfo->fIsAnnounced) // output type + { + theErr = QTSS_SetValue (fRelayOutputObject, sOutputType, 0, (void*)sUDPDestStr.Ptr, sUDPDestStr.Len); + Assert(theErr == QTSS_NoErr); + } + else + { + theErr = QTSS_SetValue (fRelayOutputObject, sOutputType, 0, (void*)sAnnouncedDestStr.Ptr, sAnnouncedDestStr.Len); // output type + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValue (fRelayOutputObject, sOutputRTSPPort, 0, &inRTSPInfo->fAnnouncePort, sizeof(inRTSPInfo->fAnnouncePort)); // rtsp port + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValue (fRelayOutputObject, sOutputURL, 0, (fClient->GetURL())->Ptr, (fClient->GetURL())->Len); // output url + Assert(theErr == QTSS_NoErr); + } + + char theIPAddrBuf[20]; + StrPtrLen theIPAddr(theIPAddrBuf, 20); + + struct in_addr theDestAddr; // output destination address + theDestAddr.s_addr = htonl(fOutputInfo.fDestAddr); + SocketUtils::ConvertAddrToString(theDestAddr, &theIPAddr); + + theErr = QTSS_SetValue (fRelayOutputObject, sOutputDestAddr, 0, (void*)theIPAddr.Ptr, theIPAddr.Len); + Assert(theErr == QTSS_NoErr); + + struct in_addr theLocalAddr; // output local address + theLocalAddr.s_addr = htonl(fOutputInfo.fLocalAddr); + SocketUtils::ConvertAddrToString(theLocalAddr, &theIPAddr); + + theErr = QTSS_SetValue (fRelayOutputObject, sOutputLocalAddr, 0, (void*)theIPAddr.Ptr, theIPAddr.Len); + Assert(theErr == QTSS_NoErr); + + + for (UInt32 index = 0; index < fNumStreams; index++) // output udp ports + { + UInt16 udpPort = fOutputInfo.fPortArray[index]; + theErr = QTSS_SetValue (fRelayOutputObject, sOutputUDPPorts, index, &udpPort, sizeof(udpPort)); + Assert(theErr == QTSS_NoErr); + } + + theErr = QTSS_SetValue (fRelayOutputObject, sOutputTTL, 0, &(fOutputInfo.fTimeToLive), sizeof(fOutputInfo.fTimeToLive)); + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValuePtr (fRelayOutputObject, sOutputCurPacketsPerSec, &fPacketsPerSecond, sizeof(fPacketsPerSecond)); + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValuePtr (fRelayOutputObject, sOutputCurBitsPerSec, &fBitsPerSecond, sizeof(fBitsPerSecond)); + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValuePtr (fRelayOutputObject, sOutputTotalPacketsSent, &fTotalPacketsSent, sizeof(fTotalPacketsSent)); + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValuePtr (fRelayOutputObject, sOutputTotalBytesSent, &fTotalBytesSent, sizeof(fTotalBytesSent)); + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_UnlockObject (fRelaySessionObject); + Assert(theErr == QTSS_NoErr); + +} diff --git a/APIModules/QTSSReflectorModule/RelayOutput.h b/APIModules/QTSSReflectorModule/RelayOutput.h new file mode 100644 index 0000000..f7c5046 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RelayOutput.h @@ -0,0 +1,182 @@ +/* + * + * @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: RelayOutput.h + + Contains: An implementation of the ReflectorOutput abstract base class, + that just writes data out to UDP sockets. + +*/ + +#ifndef __RELAY_OUTPUT_H__ +#define __RELAY_OUTPUT_H__ + +#include "ReflectorOutput.h" +#include "RelaySession.h" +#include "SourceInfo.h" +#include "RTSPClient.h" +#include "ClientSocket.h" + +#include "OSQueue.h" +#include "OSMutex.h" + +class RelayAnnouncer; + +class RelayOutput : public ReflectorOutput +{ + public: + + // Call Register in the Relay Module's Register Role + static void Register(); + + RelayOutput(SourceInfo* inInfo, UInt32 inWhichOutput, RelaySession* inSession, Bool16 isRTSPSourceInfo); + virtual ~RelayOutput(); + + // Returns true if this output matches one of the Outputs in the SourceInfo. + // Also marks the proper SourceInfo::OutputInfo "fAlreadySetup" flag as true + Bool16 Equal(SourceInfo* inInfo); + + // Call this to setup this object's output socket + OS_Error BindSocket(); + + // Writes the packet directly to a UDP socket + virtual QTSS_Error WritePacket(StrPtrLen* inPacket, void* inStreamCookie, UInt32 inFlags, SInt64 packetLatenessInMSec, SInt64* timeToSendThisPacketAgain, UInt64* packetIDPtr, SInt64* arrivalTime, Bool16 firstPacket); + + virtual Bool16 IsUDP() { return true; } + + virtual Bool16 IsPlaying() {if (!fValid || fDoingAnnounce) return false; return true; } + + // ACCESSORS + + RelaySession* GetRelaySession() { return fRelaySession; } + StrPtrLen* GetOutputInfoHTML() { return &fOutputInfoHTML; } + + UInt32 GetCurPacketsPerSecond() { return fPacketsPerSecond; } + UInt32 GetCurBitsPerSecond() { return fBitsPerSecond; } + UInt64& GetTotalPacketsSent() { return fTotalPacketsSent; } + UInt64& GetTotalBytesSent() { return fTotalBytesSent; } + Bool16 IsValid() { return fValid; } + + // Use these functions to iterate over all RelayOutputs + static OSMutex* GetQueueMutex() { return &sQueueMutex; } + static OSQueue* GetOutputQueue(){ return &sRelayOutputQueue; } + void TearDown() {}; + + SInt64 RunAnnounce(); + + private: + + void SetupRelayOutputObject(RTSPOutputInfo* inRTSPInfo); + + class RelayAnnouncer : public Task + { + public: + RelayAnnouncer(RelayOutput* output) : fOutput(output) {this->SetTaskName("RelayAnnouncer");} + + virtual SInt64 Run(); + RelayOutput* fOutput; + }; + + enum + { + kMaxHTMLSize = 255, // Note, this may be too short and we don't protect! + kStatsIntervalInMilSecs = 10000 // Update "current" statistics every 10 seconds + }; + + RelaySession* fRelaySession; + + // Relay streams all share this one socket for writing. + UDPSocket fOutputSocket; + UInt32 fNumStreams; + SourceInfo::OutputInfo fOutputInfo; + void** fStreamCookieArray;//Each stream has a cookie + UInt32* fTrackIDArray; + + OSQueueElem fQueueElem; + + char fHTMLBuf[kMaxHTMLSize]; + StrPtrLen fOutputInfoHTML; + ResizeableStringFormatter fFormatter; + + // Statistics + UInt32 fPacketsPerSecond; + UInt32 fBitsPerSecond; + + SInt64 fLastUpdateTime; + UInt64 fTotalPacketsSent; + UInt64 fTotalBytesSent; + UInt64 fLastPackets; + UInt64 fLastBytes; + + TCPClientSocket* fClientSocket; + RTSPClient* fClient; + Bool16 fDoingAnnounce; + Bool16 fValid; + char* fOutgoingSDP; + RelayAnnouncer* fAnnounceTask; + + RTSPOutputInfo* fRTSPOutputInfo; + + enum // anounce states + { + kSendingAnnounce = 0, + kSendingSetup = 1, + kSendingPlay = 2, + kDone = 3 + }; + UInt32 fAnnounceState; + UInt32 fCurrentSetup; + + // Queue of all current RelayReflectorOutput objects, for use in the + static OSQueue sRelayOutputQueue; + static OSMutex sQueueMutex; + + QTSS_Object fRelaySessionObject; + QTSS_Object fRelayOutputObject; + + // attributes of the qtssRelayOutputObjectType + static QTSS_ObjectType qtssRelayOutputObjectType; + + static QTSS_AttributeID sOutputType; + static QTSS_AttributeID sOutputDestAddr; + static QTSS_AttributeID sOutputLocalAddr; + static QTSS_AttributeID sOutputUDPPorts; + static QTSS_AttributeID sOutputRTSPPort; + static QTSS_AttributeID sOutputURL; + static QTSS_AttributeID sOutputTTL; + static QTSS_AttributeID sOutputCurPacketsPerSec; + static QTSS_AttributeID sOutputCurBitsPerSec; + static QTSS_AttributeID sOutputTotalPacketsSent; + static QTSS_AttributeID sOutputTotalBytesSent; + + + + + + +}; + + +#endif //__RELAY_OUTPUT_H__ diff --git a/APIModules/QTSSReflectorModule/RelaySDPSourceInfo.cpp b/APIModules/QTSSReflectorModule/RelaySDPSourceInfo.cpp new file mode 100644 index 0000000..9ce32d3 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RelaySDPSourceInfo.cpp @@ -0,0 +1,202 @@ +/* + * + * @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: RelaySDPSourceInfo.cpp + + Contains: Implementation of object defined in .h file + + + +*/ + +#include "RelaySDPSourceInfo.h" +#include "SDPSourceInfo.h" + +#include "MyAssert.h" +#include "StringParser.h" +#include "OSMemory.h" + +#ifndef __Win32__ +#include +#endif + +RelaySDPSourceInfo::~RelaySDPSourceInfo() +{ + // Not necessary anymore as the destructor of the base class will take care + // of deleting all allocated memory for fOutputArray and fStreamArray + /* + if (fOutputArray != NULL) + { + for (UInt32 x = 0; x < fNumOutputs; x++) + { + delete [] fOutputArray[x].fPortArray; + fOutputArray[x].fNumPorts = 0; + } + + char* theOutputArray = (char*)fOutputArray; + delete [] theOutputArray; + } + if (fStreamArray != NULL) + { + char* theStreamArray = (char*)fStreamArray; + delete [] theStreamArray; + } + */ +} + + +void RelaySDPSourceInfo::Parse(StrPtrLen* inSDPData) +{ + // These are the lines of the SDP file that we are interested in + static StrPtrLen sRelayAddr("a=x-qt-relay-addr"); + static StrPtrLen sRelayPort("a=x-qt-relay-port"); + + Assert(fOutputArray == NULL); + Assert(fStreamArray == NULL); + + StrPtrLen sdpLine; + StrPtrLen outputAddrs; + + StringParser trackCounter(inSDPData); + + UInt32 theDestIPAddr = 0; + UInt16 theDestTtl = 0; + + // + // FIRST WALK THROUGH SDP + // The first walk is to count up the number of StreamInfo & OutputInfo + // objects that we will need. + while (true) + { + // grab a line + trackCounter.ConsumeUntil(&sdpLine, StringParser::sEOLMask); + + if (sdpLine.NumEqualIgnoreCase(sRelayAddr.Ptr, sRelayAddr.Len)) + { + // there is a x-qt-relay-addr line, look for all IP addrs + StringParser relayAddrParser(&sdpLine); + relayAddrParser.ConsumeUntil(NULL, StringParser::sDigitMask); + + // The first IP addr on this line is the destination IP addr of the + // source broadcast. + theDestIPAddr = SDPSourceInfo::GetIPAddr(&relayAddrParser, ' '); + relayAddrParser.ConsumeWhitespace(); + + // Store this position so we can later go back to it + outputAddrs.Ptr = relayAddrParser.GetCurrentPosition(); + outputAddrs.Len = relayAddrParser.GetDataRemaining(); + + StrPtrLen theTtl; + while (relayAddrParser.GetDataRemaining() > 0) + { + relayAddrParser.ConsumeUntil(&theTtl, ' '); + relayAddrParser.ConsumeWhitespace(); + fNumOutputs++; + } + fNumOutputs--;// Don't count the ttl as an output! + + StringParser ttlParser(&theTtl); + theDestTtl = (UInt16) ttlParser.ConsumeInteger(NULL); + } + // Each x=qt-relay-port line corresponds to one source stream. + else if (sdpLine.NumEqualIgnoreCase(sRelayPort.Ptr, sRelayPort.Len)) + fNumStreams++; + + //stop when we reach an empty line. + if (!trackCounter.ExpectEOL()) + break; + } + + // No relay info in this file! + if ((fNumStreams == 0) || (fNumOutputs == 0)) + return; + + // x-qt-relay-port lines should always be in pairs (RTP & RTCP) + if ((fNumStreams & 1) != 0) + return; + fNumStreams /= 2; + + // + // CONSTRUCT fStreamInfo AND fOutputInfo ARRAYS + fStreamArray = NEW StreamInfo[fNumStreams]; + fOutputArray = NEW OutputInfo[fNumOutputs]; + + // + // FILL IN ARRAYS + + // Filling in the output addresses is easy because the outputAddrs + // StrPtrLen points right at the data we want + StringParser theOutputAddrParser(&outputAddrs); + for (UInt32 x = 0; x < fNumOutputs; x++) + { + fOutputArray[x].fDestAddr = SDPSourceInfo::GetIPAddr(&theOutputAddrParser, ' '); + fOutputArray[x].fLocalAddr = INADDR_ANY; + fOutputArray[x].fTimeToLive = theDestTtl; + fOutputArray[x].fPortArray = NEW UInt16[fNumStreams];//Each output has one port per stream + fOutputArray[x].fNumPorts = fNumStreams; + ::memset(fOutputArray[x].fPortArray, 0, fNumStreams * sizeof(UInt16)); + fOutputArray[x].fAlreadySetup = false; + theOutputAddrParser.ConsumeWhitespace(); + Assert(fOutputArray[x].fDestAddr > 0); + } + + StringParser sdpParser(inSDPData); + + // Now go through and find all the port information on all the x-qt-relay-port lines + for (UInt32 theStreamIndex = 0; theStreamIndex < fNumStreams; ) + { + sdpParser.ConsumeUntil(&sdpLine, StringParser::sEOLMask); + + // parse through all the x-qt-relay-port lines + if (sdpLine.NumEqualIgnoreCase(sRelayPort.Ptr, sRelayPort.Len)) + { + // Begin parsing... find the first port on the line + StringParser relayAddrParser(&sdpLine); + relayAddrParser.ConsumeUntil(NULL, StringParser::sDigitMask); + + // The first port is the source port for this stream + fStreamArray[theStreamIndex].fPort = (UInt16) relayAddrParser.ConsumeInteger(NULL); + if (fStreamArray[theStreamIndex].fPort & 1) + continue; //we only care about RTP ports + + // Fill in all the fields we can for this stream + fStreamArray[theStreamIndex].fDestIPAddr = theDestIPAddr; + fStreamArray[theStreamIndex].fTimeToLive = theDestTtl; + fStreamArray[theStreamIndex].fTrackID = theStreamIndex + 1; + + // Now fill in all the output ports for this stream + for (UInt32 x = 0; x < fNumOutputs; x++) + { + relayAddrParser.ConsumeWhitespace(); + fOutputArray[x].fPortArray[theStreamIndex] = (UInt16) relayAddrParser.ConsumeInteger(NULL); + } + + theStreamIndex++; + } + //stop when we reach an empty line. + if (!sdpParser.ExpectEOL()) + break; + } +} diff --git a/APIModules/QTSSReflectorModule/RelaySDPSourceInfo.h b/APIModules/QTSSReflectorModule/RelaySDPSourceInfo.h new file mode 100644 index 0000000..12d9481 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RelaySDPSourceInfo.h @@ -0,0 +1,55 @@ +/* + * + * @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: RelaySDPSourceInfo.h + + Contains: This object takes input SDP data, and uses it to support the SourceInfo + API. It looks for the x-qt-relay lines put in by some broadcasters, + and uses that information to construct OutputInfo objects. + + +*/ + +#ifndef __RELAY_SDP_SOURCE_INFO_H__ +#define __RELAY_SDP_SOURCE_INFO_H__ + +#include "StrPtrLen.h" +#include "SourceInfo.h" + +class RelaySDPSourceInfo : public SourceInfo +{ + public: + + // Reads in the SDP data from this file, and builds up the SourceInfo structures + RelaySDPSourceInfo(StrPtrLen* inSDPData) { Parse(inSDPData); } + virtual ~RelaySDPSourceInfo(); + + private: + + void Parse(StrPtrLen* inSDPData); +}; +#endif // __SDP_SOURCE_INFO_H__ + + diff --git a/APIModules/QTSSReflectorModule/RelaySession.cpp b/APIModules/QTSSReflectorModule/RelaySession.cpp new file mode 100644 index 0000000..d76851f --- /dev/null +++ b/APIModules/QTSSReflectorModule/RelaySession.cpp @@ -0,0 +1,269 @@ +/* + * + * @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: RelaySession.cpp + + Contains: Implementation of object defined in RelaySession.h. + + + +*/ + + +#include "RelaySession.h" +#include "QTSSModuleUtils.h" +#include "SocketUtils.h" +#include "../../revision.h" + +static StrPtrLen sUDPSourceStr("udp_source"); +static StrPtrLen sRTSPSourceStr("rtsp_source"); +static StrPtrLen sAnnouncedSourceStr("announced_source"); +static StrPtrLen sEmptyStr(""); + +static char* sRelaySessionObjectName = "relay_session"; +static char* sRelayNameName = "relay_name"; +static char* sSourceTypeName = "source_type"; +static char* sSourceIPAddrName = "source_ip_addr"; +static char* sSourceInIPAddrName = "source_in_ip_addr"; +static char* sSourceUDPPortsName = "source_udp_ports"; +static char* sSourceRTSPPortName = "source_rtsp_port"; +static char* sSourceURLName = "source_url"; +static char* sSourceUsernameName = "source_username"; +static char* sSourcePasswordName = "source_password"; +static char* sSourceTTLName = "source_ttl"; +static char* sRelayOutputObjectName = "relay_output"; + +QTSS_Object RelaySession::relayModuleAttributesObject; +QTSS_ObjectType RelaySession::qtssRelaySessionObjectType; + +QTSS_AttributeID RelaySession::sRelaySessionObjectID = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sRelayName = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourceType = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourceIPAddr = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourceInIPAddr = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourceUDPPorts = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourceRTSPPort = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourceURL = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourceUsername = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourcePassword = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sSourceTTL = qtssIllegalAttrID; +QTSS_AttributeID RelaySession::sRelayOutputObject = qtssIllegalAttrID; + +char RelaySession::sRelayUserAgent[20] = ""; + +void RelaySession::Register() +{ + qtssRelaySessionObjectType = 0; + + // create relay session object type + (void)QTSS_CreateObjectType(&qtssRelaySessionObjectType); + + // Add the static attributes to the qtssRelaySessionObjectType object + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sRelayNameName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sRelayNameName, &sRelayName); // relay name + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourceTypeName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourceTypeName, &sSourceType); // source type + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourceIPAddrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourceIPAddrName, &sSourceIPAddr); // source addr + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourceInIPAddrName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourceInIPAddrName, &sSourceInIPAddr); // interface addr + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourceUDPPortsName, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourceUDPPortsName, &sSourceUDPPorts); // udp ports + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourceRTSPPortName, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourceRTSPPortName, &sSourceRTSPPort); // rtsp port + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourceURLName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourceURLName, &sSourceURL); // url + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourceUsernameName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourceUsernameName, &sSourceUsername); // username + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourcePasswordName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourcePasswordName, &sSourcePassword); // password + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sSourceTTLName, NULL, qtssAttrDataTypeUInt16); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sSourceTTLName, &sSourceTTL); // ttl + + (void)QTSS_AddStaticAttribute(qtssRelaySessionObjectType, sRelayOutputObjectName, NULL, qtssAttrDataTypeQTSS_Object); + (void)QTSS_IDForAttr(qtssRelaySessionObjectType, sRelayOutputObjectName, &sRelayOutputObject); // relay output + + //char* strEnd = NULL; + char* relayStr = "QTSS_Relay/"; + //kVersionString is changed now -- it doesn't contain any spaces or the build number + //strEnd = strchr(kVersionString, ' '); + //Assert(strEnd != NULL); +#ifndef __Win32__ + //qtss_snprintf(sRelayUserAgent, ::strlen(relayStr) + (strEnd - kVersionString) + 1, "%s/%s", relayStr, kVersionString); + qtss_snprintf(sRelayUserAgent, ::strlen(relayStr) + ::strlen(kVersionString) + 1, "%s%s", relayStr, kVersionString); +#else + //_snprintf(sRelayUserAgent, ::strlen(relayStr) + (strEnd - kVersionString) + 1, "%s/%s", relayStr, kVersionString); + _snprintf(sRelayUserAgent, ::strlen(relayStr) + ::strlen(kVersionString) + 1, "%s%s", relayStr, kVersionString); +#endif +} + +void RelaySession::Initialize(QTSS_Object inRelayModuleAttributesObject) +{ + ReflectorSession::Initialize(); + + if (inRelayModuleAttributesObject != NULL) + { + relayModuleAttributesObject = inRelayModuleAttributesObject; + sRelaySessionObjectID = QTSSModuleUtils::CreateAttribute(inRelayModuleAttributesObject, sRelaySessionObjectName, qtssAttrDataTypeQTSS_Object, NULL, 0); + } +} + +QTSS_Error RelaySession::SetupRelaySession(SourceInfo* inInfo) +{ + QTSS_Error theErr = QTSS_NoErr; + theErr = this->SetupReflectorSession(inInfo, NULL); + + if (theErr != QTSS_NoErr) + return theErr; + + // create the reflector session object for this session + UInt32 outIndex = 0; + fRelaySessionObject = NULL; + + theErr = QTSS_LockObject(relayModuleAttributesObject); + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_CreateObjectValue (relayModuleAttributesObject , sRelaySessionObjectID, qtssRelaySessionObjectType, &outIndex, &fRelaySessionObject); + Assert(theErr == QTSS_NoErr); + + if (fRelaySessionObject == NULL) + return theErr; + + // set the values for all the static attributes in this session + + char* relayName = inInfo->Name(); // name of the relay + if (relayName != NULL) + theErr = QTSS_SetValue (fRelaySessionObject, sRelayName, 0, (void*)relayName, ::strlen(relayName)); + else + theErr = QTSS_SetValue (fRelaySessionObject, sRelayName, 0, (void*)sEmptyStr.Ptr, sEmptyStr.Len); + Assert(theErr == QTSS_NoErr); + + + StrPtrLen sourceStr; // type of source + + if (inInfo->IsRTSPSourceInfo()) + { + if (((RTSPSourceInfo*)inInfo)->IsAnnounce()) + sourceStr.Set(sAnnouncedSourceStr.Ptr, sAnnouncedSourceStr.Len); + else + sourceStr.Set(sRTSPSourceStr.Ptr, sRTSPSourceStr.Len); + } + else + sourceStr.Set(sUDPSourceStr.Ptr, sUDPSourceStr.Len); + + theErr = QTSS_SetValue (fRelaySessionObject, sSourceType, 0, (void*)sourceStr.Ptr, sourceStr.Len); + Assert(theErr == QTSS_NoErr); + + char theIPAddrBuf[20]; + StrPtrLen theIPAddr(theIPAddrBuf, 20); + + struct in_addr theSrcAddr; // source ip address + theSrcAddr.s_addr = htonl(inInfo->GetStreamInfo(0)->fSrcIPAddr); + SocketUtils::ConvertAddrToString(theSrcAddr, &theIPAddr); + + theErr = QTSS_SetValue (fRelaySessionObject, sSourceIPAddr, 0, (void*)theIPAddr.Ptr, theIPAddr.Len); + Assert(theErr == QTSS_NoErr); + + struct in_addr theDestAddr; // dest (of source) ip address + theDestAddr.s_addr = htonl(inInfo->GetStreamInfo(0)->fDestIPAddr); + SocketUtils::ConvertAddrToString(theDestAddr, &theIPAddr); + + theErr = QTSS_SetValue (fRelaySessionObject, sSourceInIPAddr, 0, (void*)theIPAddr.Ptr, theIPAddr.Len); + Assert(theErr == QTSS_NoErr); + + + for (UInt32 index = 0; index < (inInfo->GetNumStreams()); index++) // source udp ports + { + UInt16 udpPort = inInfo->GetStreamInfo(index)->fPort; + theErr = QTSS_SetValue (fRelaySessionObject, sSourceUDPPorts, index, &udpPort, sizeof(udpPort)); + Assert(theErr == QTSS_NoErr); + } + + if (inInfo->IsRTSPSourceInfo()) + { + RTSPSourceInfo* rtspInfo = (RTSPSourceInfo*)inInfo; + if (!rtspInfo->IsAnnounce()) + { + UInt16 rtspPort = (UInt16) rtspInfo->GetHostPort(); + char* username = rtspInfo->GetUsername(); + char* password = rtspInfo->GetPassword(); + theErr = QTSS_SetValue (fRelaySessionObject, sSourceRTSPPort, 0, &rtspPort, sizeof(rtspPort)); // source rtsp port + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValue (fRelaySessionObject, sSourceUsername, 0, username, sizeof(username)); // source username + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_SetValue (fRelaySessionObject, sSourcePassword, 0, password, sizeof(password)); // source password + Assert(theErr == QTSS_NoErr); + } + + char* url = rtspInfo->GetSourceURL(); + theErr = QTSS_SetValue (fRelaySessionObject, sSourceURL, 0, url, ::strlen(url)); + Assert(theErr == QTSS_NoErr); // source url + } + + UInt16 ttl = inInfo->GetStreamInfo(0)->fTimeToLive; + theErr = QTSS_SetValue (fRelaySessionObject, sSourceTTL, 0, &ttl, sizeof(ttl)); // source ttl + Assert(theErr == QTSS_NoErr); + + theErr = QTSS_UnlockObject(relayModuleAttributesObject); + Assert(theErr == QTSS_NoErr); + + return QTSS_NoErr; +} + +RelaySession::~RelaySession() +{ + QTSS_Object sessionObject; + UInt32 len = sizeof(QTSS_Object); + + for (int x = 0; QTSS_GetValue(relayModuleAttributesObject, sRelaySessionObjectID, x, &sessionObject, &len) == QTSS_NoErr; x++) + { + Assert(sessionObject != NULL); + Assert(len == sizeof(QTSS_Object)); + + if (sessionObject == fRelaySessionObject) + { + (void)QTSS_RemoveValue(relayModuleAttributesObject, sRelaySessionObjectID, x); + break; + } + } +} + + + + + + + diff --git a/APIModules/QTSSReflectorModule/RelaySession.h b/APIModules/QTSSReflectorModule/RelaySession.h new file mode 100644 index 0000000..3b7e092 --- /dev/null +++ b/APIModules/QTSSReflectorModule/RelaySession.h @@ -0,0 +1,89 @@ +/* + * + * @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: RelaySession.h + + Contains: Subclass of ReflectorSession. It has two static + attributes (QTSSRelayModule Attributes object + and the ReflectorSession attribute ID) + +*/ + +#include "QTSS.h" +#include "ReflectorSession.h" +#include "StrPtrLen.h" +#include "SourceInfo.h" +#include "RTSPSourceInfo.h" + +#ifndef _RELAY_SESSION_ +#define _RELAY_SESSION_ + +class RelaySession : public ReflectorSession +{ + public: + + // Call Register in the Relay Module's Register Role + static void Register(); + + // + // Initialize + // Call Initialize in the Relay Module's Initialize Role + static void Initialize(QTSS_Object inAttrObject); + + RelaySession(StrPtrLen* inSourceID, SourceInfo* inInfo = NULL):ReflectorSession(inSourceID, inInfo){}; + ~RelaySession(); + + QTSS_Error SetupRelaySession(SourceInfo* inInfo); + + QTSS_Object GetRelaySessionObject() { return fRelaySessionObject; } + static QTSS_AttributeID sRelayOutputObject; + + static char sRelayUserAgent[20]; + + private: + + QTSS_Object fRelaySessionObject; + + // gets set in the initialize method + static QTSS_Object relayModuleAttributesObject; + + static QTSS_ObjectType qtssRelaySessionObjectType; + + static QTSS_AttributeID sRelaySessionObjectID; + static QTSS_AttributeID sRelayName; + static QTSS_AttributeID sSourceType; + static QTSS_AttributeID sSourceIPAddr; + static QTSS_AttributeID sSourceInIPAddr; + static QTSS_AttributeID sSourceUDPPorts; + static QTSS_AttributeID sSourceRTSPPort; + static QTSS_AttributeID sSourceURL; + static QTSS_AttributeID sSourceUsername; + static QTSS_AttributeID sSourcePassword; + static QTSS_AttributeID sSourceTTL; + + +}; + +#endif diff --git a/APIModules/QTSSReflectorModule/SequenceNumberMap.cpp b/APIModules/QTSSReflectorModule/SequenceNumberMap.cpp new file mode 100644 index 0000000..04275f9 --- /dev/null +++ b/APIModules/QTSSReflectorModule/SequenceNumberMap.cpp @@ -0,0 +1,153 @@ +/* + * + * @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: SequenceNumberMap.cpp + + Contains: Implements object defined in SequenceNumberMap.h. + + + + +*/ + +#include +#include "MyAssert.h" +#include "OSMemory.h" + +#include "SequenceNumberMap.h" + +SequenceNumberMap::SequenceNumberMap(UInt32 inSlidingWindowSize) +: fSlidingWindow(NULL), + fWindowSize( (SInt32) inSlidingWindowSize), + fNegativeWindowSize( (SInt32) inSlidingWindowSize - (SInt32) (2 * inSlidingWindowSize)), + fHighestSeqIndex(0), + fHighestSeqNumber(0) +{ + Assert(fNegativeWindowSize < 0); + Assert(fWindowSize < 32768);//AddSequenceNumber makes this assumption + +} + +Bool16 SequenceNumberMap::AddSequenceNumber(UInt16 inSeqNumber) +{ + // Returns whether sequence number has already been added. + + //Check to see if object has been initialized + if (fSlidingWindow == NULL) + { + fSlidingWindow = NEW Bool16[fWindowSize + 1]; + ::memset(fSlidingWindow, 0, fWindowSize * sizeof(Bool16)); + fHighestSeqIndex = 0; + fHighestSeqNumber = inSeqNumber; + } + + // First check to see if this sequence number is so far below the highest sequence number + // we can't even put it in the sliding window. + + SInt16 theWindowOffset = inSeqNumber - fHighestSeqNumber; + + if (theWindowOffset < fNegativeWindowSize) + return false;//We don't know, but for safety, assume we haven't seen it. + + // If this seq # is higher thn the highest previous, set the highest to be this + // new sequence number, and zero out our sliding window as we go. + + while (theWindowOffset > 0) + { + fHighestSeqNumber++; + + fHighestSeqIndex++; + if (fHighestSeqIndex == fWindowSize) + fHighestSeqIndex = 0; + fSlidingWindow[fHighestSeqIndex] = false; + + theWindowOffset--; + } + + // Find the right entry in the sliding window for this sequence number, taking + // into account that we may need to wrap. + + SInt32 theWindowIndex = fHighestSeqIndex + theWindowOffset; + if (theWindowIndex < 0) + theWindowIndex += fWindowSize; + + Assert(theWindowIndex >= 0); + Assert(theWindowIndex < fWindowSize); + + // Turn this index on, return whether it was already turned on. + Bool16 alreadyAdded = fSlidingWindow[theWindowIndex]; + fSlidingWindow[theWindowIndex] = true; +#if SEQUENCENUMBERMAPTESTING + //if (alreadyAdded) + // qtss_printf("Found a duplicate seq num. Num = %d\n", inSeqNumber); +#endif + return alreadyAdded; +} + +#if SEQUENCENUMBERMAPTESTING +void SequenceNumberMap::Test() +{ + SequenceNumberMap theMap1; + Bool16 retval = theMap1.AddSequenceNumber(64674); + Assert(retval == false); + + retval = theMap1.AddSequenceNumber(64582); + Assert(retval == false); + + retval = theMap1.AddSequenceNumber(64777); + Assert(retval == false); + + retval = theMap1.AddSequenceNumber(64582); + Assert(retval == TRUE); + + retval = theMap1.AddSequenceNumber(64674); + Assert(retval == TRUE); + + retval = theMap1.AddSequenceNumber(1); + Assert(retval == FALSE); + + retval = theMap1.AddSequenceNumber(65500); + Assert(retval == FALSE); + + retval = theMap1.AddSequenceNumber(65500); + Assert(retval == FALSE); + + retval = theMap1.AddSequenceNumber(32768); + Assert(retval == FALSE); + + retval = theMap1.AddSequenceNumber(1024); + Assert(retval == FALSE); + + retval = theMap1.AddSequenceNumber(32757); + Assert(retval == FALSE); + + retval = theMap1.AddSequenceNumber(32799); + Assert(retval == FALSE); + + retval = theMap1.AddSequenceNumber(32768); + Assert(retval == FALSE); + +} +#endif diff --git a/APIModules/QTSSReflectorModule/SequenceNumberMap.h b/APIModules/QTSSReflectorModule/SequenceNumberMap.h new file mode 100644 index 0000000..0f206f0 --- /dev/null +++ b/APIModules/QTSSReflectorModule/SequenceNumberMap.h @@ -0,0 +1,74 @@ +/* + * + * @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: SequenceNumberMap.h + + Contains: Data structure for keeping track of duplicate sequence numbers. + Useful for removing duplicate packets from an RTP stream. + + + + +*/ + +#ifndef _SEQUENCE_NUMBER_MAP_H_ +#define _SEQUENCE_NUMBER_MAP_H_ + +#include "OSHeaders.h" + +#define SEQUENCENUMBERMAPTESTING 1 + +class SequenceNumberMap +{ + public: + + enum + { + kDefaultSlidingWindowSize = 256 + }; + + SequenceNumberMap(UInt32 inSlidingWindowSize = kDefaultSlidingWindowSize); + ~SequenceNumberMap() { delete [] fSlidingWindow; } + + // Returns whether this sequence number was already added or not. + Bool16 AddSequenceNumber(UInt16 inSeqNumber); + +#if SEQUENCENUMBERMAPTESTING + static void Test(); +#endif + + private: + + Bool16* fSlidingWindow; + + const SInt32 fWindowSize; + const SInt32 fNegativeWindowSize; + + UInt16 fHighestSeqIndex; + UInt16 fHighestSeqNumber; +}; + + +#endif diff --git a/APIModules/QTSSSpamDefenseModule.bproj/QTSSSpamDefenseModule.cpp b/APIModules/QTSSSpamDefenseModule.bproj/QTSSSpamDefenseModule.cpp new file mode 100644 index 0000000..47a1683 --- /dev/null +++ b/APIModules/QTSSSpamDefenseModule.bproj/QTSSSpamDefenseModule.cpp @@ -0,0 +1,284 @@ +/* + * + * @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: QTSSSpamDefanseModule.cpp + + Contains: Implementation of module described in .h file + + + +*/ + +#include "QTSSSpamDefenseModule.h" +#include "OSHashTable.h" +#include "OSMutex.h" +#include "QTSSModuleUtils.h" +#include "OSMemory.h" + +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_StreamRef sErrorLogStream = NULL; + +class IPAddrTableKey; + +class IPAddrTableElem +{ + public: + + IPAddrTableElem(UInt32 inIPAddr) : fIPAddr(inIPAddr), fRefCount(0), fNextHashEntry(NULL) {} + ~IPAddrTableElem() {} + + UInt32 GetRefCount() { return fRefCount; } + void IncrementRefCount() { fRefCount++; } + void DecrementRefCount() { fRefCount--; } + private: + + UInt32 fIPAddr;// this also serves as the hash value + UInt32 fRefCount; + + IPAddrTableElem* fNextHashEntry; + + friend class IPAddrTableKey; + friend class OSHashTable; +}; + + +class IPAddrTableKey +{ +public: + + //CONSTRUCTOR / DESTRUCTOR: + IPAddrTableKey(UInt32 inIPAddr) : fIPAddr(inIPAddr) {} + ~IPAddrTableKey() {} + + +private: + + //PRIVATE ACCESSORS: + SInt32 GetHashKey() { return fIPAddr; } + + //these functions are only used by the hash table itself. This constructor + //will break the "Set" functions. + IPAddrTableKey(IPAddrTableElem *elem) : fIPAddr(elem->fIPAddr) {} + + friend int operator ==(const IPAddrTableKey &key1, const IPAddrTableKey &key2) + { + return (key1.fIPAddr == key2.fIPAddr); + } + + //data: + UInt32 fIPAddr; + + friend class OSHashTable; +}; + +typedef OSHashTable IPAddrHashTable; + +// STATIC DATA +static IPAddrHashTable* sHashTable = NULL; +static OSMutex* sMutex; +static UInt32 sNumConnsPerIP = 0; +static UInt32 sDefaultNumConnsPerIP = 100; + +// ATTRIBUTES +static QTSS_AttributeID sIsFirstRequestAttr = qtssIllegalAttrID; +static QTSS_AttributeID sTooManyConnectionsErr = qtssIllegalAttrID; + +// FUNCTION PROTOTYPES +static QTSS_Error QTSSSpamDefenseModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error RereadPrefs(); +static QTSS_Error Authorize(QTSS_StandardRTSP_Params* inParams); +static QTSS_Error SessionClosing(QTSS_RTSPSession_Params* inParams); + +// FUNCTION IMPLEMENTATIONS + + +QTSS_Error QTSSSpamDefenseModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSSpamDefenseModuleDispatch); +} + + +QTSS_Error QTSSSpamDefenseModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RereadPrefs_Role: + return RereadPrefs(); + case QTSS_RTSPAuthorize_Role: + return Authorize(&inParams->rtspAuthParams); + case QTSS_RTSPSessionClosing_Role: + return SessionClosing(&inParams->rtspSessionClosingParams); + } + return QTSS_NoErr; +} + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // The spam defense module has one preference, the number of connections + // to allow per ip addr + static char* sIsFirstRequestName = "QTSSSpamDefenseModuleIsFirstRequest"; + + // Add text messages attributes + static char* sTooManyConnectionsName = "QTSSSpamDefenseModuleTooManyConnections"; + + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RereadPrefs_Role); + + (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role); + (void)QTSS_AddRole(QTSS_RTSPSessionClosing_Role); + + (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sIsFirstRequestName, NULL, qtssAttrDataTypeBool16); + (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sIsFirstRequestName, &sIsFirstRequestAttr); + + (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sTooManyConnectionsName, NULL, qtssAttrDataTypeCharArray); + (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sTooManyConnectionsName, &sTooManyConnectionsErr); + + // Tell the server our name! + static char* sModuleName = "QTSSSpamDefenseModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + sErrorLogStream = inParams->inErrorLogStream; + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + sMutex = NEW OSMutex(); + sHashTable = NEW IPAddrHashTable(277);//277 is prime, I think... + RereadPrefs(); + return QTSS_NoErr; +} + +QTSS_Error RereadPrefs() +{ + QTSSModuleUtils::GetAttribute(sPrefs, "num_conns_per_ip_addr", qtssAttrDataTypeUInt32, + &sNumConnsPerIP, &sDefaultNumConnsPerIP, sizeof(sNumConnsPerIP)); + return QTSS_NoErr; +} + +QTSS_Error Authorize(QTSS_StandardRTSP_Params* inParams) +{ + static Bool16 sTrue = true; + + Bool16* isFirstRequest = NULL; + UInt32* theIPAddr = NULL; + UInt32 theLen = 0; + + // Only do anything if this is the first request + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sIsFirstRequestAttr, 0, (void**)&isFirstRequest, &theLen); + if (isFirstRequest != NULL) + return QTSS_NoErr; + + // Get the IP address of this client. + (void)QTSS_GetValuePtr(inParams->inRTSPSession, qtssRTSPSesRemoteAddr, 0, (void**)&theIPAddr, &theLen); + if ((theIPAddr == NULL) || (theLen != sizeof(UInt32))) + { + return QTSS_NoErr; + } + + IPAddrTableKey theKey(*theIPAddr); + + // This must be atomic + OSMutexLocker locker(sMutex); + + // Check to see if this client currently has a connection open. + IPAddrTableElem* theElem = sHashTable->Map(&theKey); + if (theElem == NULL) + { + // Client doesn't have a connetion open currently. Create a map element, + // and add it into the map. + theElem = NEW IPAddrTableElem(*theIPAddr); + sHashTable->Add(theElem); + } + + // Check to see if this client has too many connections open. If it does, + // return an error, otherwise, allow the connection and increment the + // refcount. + if (theElem->GetRefCount() >= sNumConnsPerIP) { + QTSSModuleUtils::LogErrorStr(qtssMessageVerbosity, "Blocking connection from IP address"); + return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientForbidden, + sTooManyConnectionsErr); + } else + theElem->IncrementRefCount(); + + // Mark the request so we'll know subsequent ones aren't the first. + // Note that we only do this if we've successfully added this client to our map. + // That way, we only remove it in SessionClosing if we've added it. + (void)QTSS_SetValue(inParams->inRTSPSession, sIsFirstRequestAttr, 0, &sTrue, sizeof(sTrue)); + + return QTSS_NoErr; +} + +QTSS_Error SessionClosing(QTSS_RTSPSession_Params* inParams) +{ + UInt32* theIPAddr = NULL; + Bool16* isFirstRequest = NULL; + UInt32 theLen = 0; + + // Only remove this session from the map if it has been added in the first place + (void)QTSS_GetValuePtr(inParams->inRTSPSession, sIsFirstRequestAttr, 0, (void**)&isFirstRequest, &theLen); + if (isFirstRequest == NULL) + return QTSS_NoErr; + + // Get the IP address of this client. + (void)QTSS_GetValuePtr(inParams->inRTSPSession, qtssRTSPSesRemoteAddr, 0, (void**)&theIPAddr, &theLen); + if ((theIPAddr == NULL) || (theLen != sizeof(UInt32))) + { + return QTSS_NoErr; + } + + IPAddrTableKey theKey(*theIPAddr); + + // This must be atomic + OSMutexLocker locker(sMutex); + + // Check to see if this client currently has a connection open. + IPAddrTableElem* theElem = sHashTable->Map(&theKey); + if (theElem == NULL) + return QTSS_NoErr; //this may happen if there is another module denying connections + + // Decrement the refcount + if (theElem->GetRefCount() > 0) + theElem->DecrementRefCount(); + + // If the refcount is 0, remove this from the map, and delete it. + if (theElem->GetRefCount() == 0) + { + sHashTable->Remove(theElem); + delete theElem; + } + + return QTSS_NoErr; +} diff --git a/APIModules/QTSSSpamDefenseModule.bproj/QTSSSpamDefenseModule.h b/APIModules/QTSSSpamDefenseModule.bproj/QTSSSpamDefenseModule.h new file mode 100644 index 0000000..d6d09d3 --- /dev/null +++ b/APIModules/QTSSSpamDefenseModule.bproj/QTSSSpamDefenseModule.h @@ -0,0 +1,49 @@ +/* + * + * @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: QTSSSpamDefanseModule.h + + Contains: Protects the server against denial-of-service attacks by + only allowing X number of RTSP connections from a certain + IP address + + + + +*/ + + +#ifndef __QTSSSPAMDEFENSEMODULE_H__ +#define __QTSSSPAMDEFENSEMODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSSpamDefenseModule_Main(void* inPrivateArgs); +} + +#endif // __QTSSSPAMDEFENSEMODULE_H__ + diff --git a/APIModules/QTSSWebDebugModule/QTSSWebDebugModule.cpp b/APIModules/QTSSWebDebugModule/QTSSWebDebugModule.cpp new file mode 100644 index 0000000..37157fc --- /dev/null +++ b/APIModules/QTSSWebDebugModule/QTSSWebDebugModule.cpp @@ -0,0 +1,148 @@ +/* + * + * @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: QTSSWebDebugModule.cpp + + Contains: Implements web debug module + + +*/ + +#include "QTSSWebDebugModule.h" +#include "OS.h" +#include "OSMemory.h" +#include "StrPtrLen.h" +#include "ev.h" + +// STATIC DATA + +static QTSS_AttributeID sStateAttr = qtssIllegalAttrID; + +static StrPtrLen sRequestHeader("GET /debug HTTP"); + +#if MEMORY_DEBUGGING +static char* sResponseHeader = "HTTP/1.0 200 OK\r\nServer: TimeShare/1.0\r\nConnection: Close\r\nContent-Type: text/html\r\n\r\n"; +static char* sResponseEnd = ""; +#endif + +// FUNCTION PROTOTYPES + +QTSS_Error QTSSWebDebugModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Filter(QTSS_Filter_Params* inParams); + +QTSS_Error QTSSWebDebugModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSWebDebugModuleDispatch); +} + +QTSS_Error QTSSWebDebugModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_RTSPFilter_Role: + return Filter(&inParams->rtspFilterParams); + } + return QTSS_NoErr; +} + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_RTSPFilter_Role); + + // Register an attribute + static char* sStateName = "QTSSWebDebugModuleState"; + (void)QTSS_AddStaticAttribute(qtssRTSPRequestObjectType, sStateName, NULL, qtssAttrDataTypeUInt32); + (void)QTSS_IDForAttr(qtssRTSPRequestObjectType, sStateName, &sStateAttr); + + // Tell the server our name! + static char* sModuleName = "QTSSWebDebugModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + +QTSS_Error Filter(QTSS_Filter_Params* inParams) +{ + UInt32 theLen = 0; + char* theFullRequest = NULL; + (void)QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest, &theLen); + + if ((theFullRequest == NULL) || (theLen < sRequestHeader.Len)) + return QTSS_NoErr; + if (::memcmp(theFullRequest, sRequestHeader.Ptr, sRequestHeader.Len) != 0) + return QTSS_NoErr; + +#if MEMORY_DEBUGGING + UInt32* theStateVal = NULL; + (void)QTSS_GetValuePtr(inParams->inRTSPRequest, sStateAttr, 0, (void**)&theStateVal, &theLen); + //if ((theStateVal == NULL) || (theLen != sizeof(UInt32))) + //{ + Bool16 theFalse = false; + (void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespKeepAlive, 0, &theFalse, sizeof(theFalse)); + + // Begin writing the HTTP response. We don't need to worry about flow control + // because we're using the QTSS_RTSPRequestObject for the response, which does buffering + (void)QTSS_Write(inParams->inRTSPRequest, sResponseHeader, ::strlen(sResponseHeader), &theLen, 0); + + //QTSS_EventContextRef* theContext = NULL; + //(void)QTSS_GetValuePtr(inParams->inRTSPSession, qtssRTSPSesEventCntxt, 0, (void**)&theContext, &theLen); + //Assert(theContext != NULL); + //Assert(theLen == sizeof(QTSS_EventContextRef)); + + //(void)QTSS_RequestEvent(*theContext, EV_WR); + + // UInt32 theValue = 4; + // (void)QTSS_SetValue(inParams->inRTSPRequest, sStateAttr, 0, &theValue, sizeof(theValue)); + // return QTSS_NoErr; + //} + + //we must hold the tagQueue mutex for the duration of this exercise because + //we don't want any of the values we are reporting to change + OSMutexLocker locker(OSMemory::GetTagQueueMutex()); + + //write out header and total allocated memory + char buffer[1024]; + qtss_sprintf(buffer, "TimeShare Debug PageTotal dynamic memory allocated: %"_S32BITARG_"

List of objects:
", OSMemory::GetAllocatedMemory()); + (void)QTSS_Write(inParams->inRTSPRequest, buffer, ::strlen(buffer), &theLen, 0); + + //now report the list of tags: + for (OSQueueIter iter(OSMemory::GetTagQueue()); !iter.IsDone(); iter.Next()) + { + OSMemory::TagElem* elem = (OSMemory::TagElem*)iter.GetCurrent()->GetEnclosingObject(); + Assert(elem != NULL); + if (elem->numObjects > 0) + { + qtss_sprintf(buffer, "Object allocated at: %s, %d. Number of currently allocated objects: %"_S32BITARG_", Total size: %"_S32BITARG_"
", elem->fileName, elem->line, elem->numObjects, elem->totMemory); + (void)QTSS_Write(inParams->inRTSPRequest, buffer, ::strlen(buffer), &theLen, 0); + } + } + (void)QTSS_Write(inParams->inRTSPRequest, sResponseEnd, ::strlen(sResponseEnd), &theLen, 0); +#endif + return QTSS_NoErr; +} diff --git a/APIModules/QTSSWebDebugModule/QTSSWebDebugModule.h b/APIModules/QTSSWebDebugModule/QTSSWebDebugModule.h new file mode 100644 index 0000000..efd3530 --- /dev/null +++ b/APIModules/QTSSWebDebugModule/QTSSWebDebugModule.h @@ -0,0 +1,47 @@ +/* + * + * @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: QTSSWebDebugModule.h + + Contains: A module that uses the debugging information available in the server + to present a web page containing that information. Uses the Filter + module feature of the QTSS API. + + + + +*/ + +#ifndef __QTSSWEBDEBUGMODULE_H__ +#define __QTSSWEBDEBUGMODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSWebDebugModule_Main(void* inPrivateArgs); +} + +#endif //__QTSSWEBDEBUGMODULE_H__ diff --git a/APIModules/QTSSWebStatsModule/QTSSWebStatsModule.cpp b/APIModules/QTSSWebStatsModule/QTSSWebStatsModule.cpp new file mode 100644 index 0000000..4da7287 --- /dev/null +++ b/APIModules/QTSSWebStatsModule/QTSSWebStatsModule.cpp @@ -0,0 +1,991 @@ +/* + * + * @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: QTSSWebStatsModule.cpp + + Contains: Implements web stats module + + + + + +*/ + +#include + +#include /* for qtss_printf */ +#include /* for getloadavg & other useful stuff */ + +#include "QTSSWebStatsModule.h" +#include "OSArrayObjectDeleter.h" +#include "StringParser.h" +#include "StrPtrLen.h" +#include "QTSSModuleUtils.h" + +// STATIC DATA + +static char* sResponseHeader = "HTTP/1.0 200 OK\r\nServer: QTSS/3.0\r\nConnection: Close\r\nContent-Type: text/html\r\n\r\n"; + +static QTSS_ServerObject sServer = NULL; +static QTSS_ModulePrefsObject sAccessLogPrefs = NULL; +static QTSS_ModulePrefsObject sReflectorPrefs = NULL; +static QTSS_ModulePrefsObject sSvrControlPrefs = NULL; +static QTSS_ModulePrefsObject sPrefs = NULL; +static QTSS_PrefsObject sServerPrefs = NULL; + +static Bool16 sFalse = false; +static time_t sStartupTime = 0; + +static char* sDefaultURL = ""; +static StrPtrLen sDefaultURLStr; +static char* sDefaultURLPrefName = "web_stats_url"; + + +static QTSS_Error QTSSWebStatsModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); +static QTSS_Error FilterRequest(QTSS_Filter_Params* inParams); +static void SendStats(QTSS_StreamRef inStream, UInt32 refreshInterval, Bool16 displayHelp, StrPtrLen* fieldList); +static char* GetPrefAsString(QTSS_ModulePrefsObject inPrefsObject, char* inPrefName); + + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSWebStatsModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSWebStatsModuleDispatch); +} + + +QTSS_Error QTSSWebStatsModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParams->regParams); + case QTSS_Initialize_Role: + return Initialize(&inParams->initParams); + case QTSS_RTSPFilter_Role: + return FilterRequest(&inParams->rtspFilterParams); + } + return QTSS_NoErr; +} + + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + // Do role & attribute setup + (void)QTSS_AddRole(QTSS_Initialize_Role); + (void)QTSS_AddRole(QTSS_RTSPFilter_Role); + + // Tell the server our name! + static char* sModuleName = "QTSSWebStatsModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + return QTSS_NoErr; +} + + +QTSS_Error Initialize(QTSS_Initialize_Params* inParams) +{ + // Setup module utils + QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); + + sAccessLogPrefs = QTSSModuleUtils::GetModulePrefsObject(QTSSModuleUtils::GetModuleObjectByName("QTSSAccessLogModule")); + sReflectorPrefs = QTSSModuleUtils::GetModulePrefsObject(QTSSModuleUtils::GetModuleObjectByName("QTSSReflectorModule")); + sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule); + + // This module may not be present, so be careful... + QTSS_ModuleObject theSvrControlModule = QTSSModuleUtils::GetModuleObjectByName("QTSSSvrControlModule"); + if (theSvrControlModule != NULL) + sSvrControlPrefs = QTSSModuleUtils::GetModulePrefsObject(theSvrControlModule); + sServer = inParams->inServer; + sServerPrefs = inParams->inPrefs; + + sStartupTime = ::time(NULL); + + sDefaultURLStr.Delete(); + sDefaultURLStr.Set(QTSSModuleUtils::GetStringAttribute(sPrefs, sDefaultURLPrefName, sDefaultURL)); + + return QTSS_NoErr; +} + +QTSS_Error FilterRequest(QTSS_Filter_Params* inParams) +{ + UInt8 sParamStopMask[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0-9 //stop unless a '\t', ' ', or '&' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //10-19 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, //30-39 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 1 //250-255 + }; + + + //check to see if we should handle this request. Invokation is triggered + //by a "GET /" request + + QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest; + + StrPtrLen theFullRequest; + (void)QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len); + + StringParser fullRequest(&theFullRequest); + + StrPtrLen strPtr; + StrPtrLen paramName; + StrPtrLen fieldsList; + + fullRequest.ConsumeWord(&strPtr); + if ( strPtr.Equal(StrPtrLen("GET")) ) //it's a "Get" request + { + fullRequest.ConsumeWhitespace(); + if ( fullRequest.Expect('/') ) + { + UInt32 refreshInterval = 0; + Bool16 displayHelp = false; + + + OSCharArrayDeleter theWebStatsURL(GetPrefAsString(sPrefs, sDefaultURLPrefName)); + StrPtrLen theWebStatsURLPtr(theWebStatsURL.GetObject()); + + // If there isn't any web stats URL, we can just return at this point + if (theWebStatsURLPtr.Len == 0) + return QTSS_NoErr; + + fullRequest.ConsumeUntil(&strPtr, StringParser::sEOLWhitespaceQueryMask); + if ( strPtr.Len != 0 && strPtr.Equal(theWebStatsURLPtr) ) //it's a "stats" request + { + if ( fullRequest.Expect('?') ) + { + do { + fullRequest.ConsumeWord(¶mName); + + if( paramName.Len != 0) + { + + if ( paramName.Equal(StrPtrLen("refresh",strlen("refresh"))) ) + { + if (fullRequest.Expect('=')) + refreshInterval = fullRequest.ConsumeInteger(NULL); + } + else if ( paramName.Equal(StrPtrLen("help",strlen("help"))) ) + { + displayHelp = true; + } + else if ( paramName.Equal(StrPtrLen("fields",strlen("fields"))) ) + { + if (fullRequest.Expect('=')) + fullRequest.ConsumeUntil(&fieldsList, (UInt8*)sParamStopMask); + + } + } + } while ( paramName.Len != 0 && fullRequest.Expect('&') ); + } + + // Before sending a response, set keep alive to off for this connection + (void)QTSS_SetValue(theRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse)); + SendStats(inParams->inRTSPRequest, refreshInterval, displayHelp, (fieldsList.Len != 0) ? &fieldsList : NULL); + } + } + } + return QTSS_NoErr; +} + + +void SendStats(QTSS_StreamRef inStream, UInt32 refreshInterval, Bool16 displayHelp, StrPtrLen* fieldList) +{ + struct FieldIndex { + char* fieldName; + int fieldIndex; + }; + + const FieldIndex kFieldIndexes[] = { + {"title", 1}, + {"dnsname", 2}, + {"curtime", 3}, + {"", 4}, + {"serververs", 5}, + {"serverbornon", 6}, + {"serverstatus", 7}, + {"", 8}, + {"", 9}, + {"", 10}, + {"", 11}, + {"", 12}, + {"", 13}, + {"currtp", 14}, + {"currtsp", 15}, + {"currtsphttp", 16}, + {"curthru", 17}, + {"curpkts", 18}, + {"totbytes", 19}, + {"totconns", 20}, + {"", 21}, + {"connlimit", 22}, + {"thrulimit", 23}, + {"moviedir", 24}, + {"rtspip", 25}, + {"rtspport", 26}, + {"rtsptimeout", 27}, + {"rtptimeout", 28}, + {"secstobuffer", 29}, + {"", 30}, + {"accesslog", 31}, + {"accesslogdir",32}, + {"accesslogname", 33}, + {"accessrollsize", 34}, + {"accessrollinterval", 35}, + {"", 36}, + {"errorlog", 37}, + {"errorlogdir", 38}, + {"errorlogname", 39}, + {"errorrollsize", 40}, + {"errorrollinterval", 41}, + {"errorloglevel", 42}, + {"", 43}, + {"assertbreak", 44}, + {"autostart", 45}, + {"totbytesupdateinterval", 46}, + {"reflectordelay", 47}, + {"reflectorbucketsize", 48}, + {"historyinterval", 49}, + {"outoffiledesc", 50}, + {"numudpsockets", 51}, + {"apiversion", 52}, + {"numreliableudpbuffers", 53}, + {"reliableudpwastedbytes", 54}, + {"numtaskthreads", 55} + }; + const int kMaxFieldNum = 55; + static char* kEmptyStr = "?"; + char* thePrefStr = kEmptyStr; + + char buffer[1024]; + + (void)QTSS_Write(inStream, sResponseHeader, ::strlen(sResponseHeader), NULL, 0); + + if (refreshInterval > 0) + { + qtss_sprintf(buffer, "\n", refreshInterval); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + + //qtss_sprintf(buffer, "\n"); + //(void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + + char *theHTML = "\n"; + + (void)QTSS_Write(inStream, theHTML, ::strlen(theHTML), NULL, 0); + + if (displayHelp) + { + + #ifndef __MacOSX__ + static StrPtrLen sHelpLine1("

Streaming Server Statistics Help

\n"); + #else + static StrPtrLen sHelpLine1("

QuickTime Streaming Server Statistics Help

\n"); + #endif + + static StrPtrLen sHelpLine2("

Example:

\n"); + static StrPtrLen sHelpLine3("

http://server/statsURL?help&refresh=15&fields=curtime,cpuload

\n"); + + static StrPtrLen sHelpLine4("\"?\" means that there are options being attached to the stats request.
\n"); + static StrPtrLen sHelpLine5("\"&\" separates multiple stats options
\n
\n"); + + static StrPtrLen sHelpLine6("

The three possible parameters to stats are:

\n"); + static StrPtrLen sHelpLine7("

\"help\" -- shows the help information you're reading right now.

\n"); + static StrPtrLen sHelpLine8("

\"refresh=[n]\" -- tells the browser to automatically update the page every [n] seconds.

\n"); + static StrPtrLen sHelpLine9("

\"fields=[fieldList]\" -- show only the fields specified in comma delimited [fieldList]

\n"); + static StrPtrLen sHelpLine10("
The following fields are available for use with the \"fields\" option:

\n"); + + (void)QTSS_Write(inStream, sHelpLine1.Ptr, sHelpLine1.Len, NULL, 0); + (void)QTSS_Write(inStream, sHelpLine2.Ptr, sHelpLine2.Len, NULL, 0); + (void)QTSS_Write(inStream, sHelpLine3.Ptr, sHelpLine3.Len, NULL, 0); + + (void)QTSS_Write(inStream, sHelpLine4.Ptr, sHelpLine4.Len, NULL, 0); + (void)QTSS_Write(inStream, sHelpLine5.Ptr, sHelpLine5.Len, NULL, 0); + + (void)QTSS_Write(inStream, sHelpLine6.Ptr, sHelpLine6.Len, NULL, 0); + (void)QTSS_Write(inStream, sHelpLine7.Ptr, sHelpLine7.Len, NULL, 0); + (void)QTSS_Write(inStream, sHelpLine8.Ptr, sHelpLine8.Len, NULL, 0); + (void)QTSS_Write(inStream, sHelpLine9.Ptr, sHelpLine9.Len, NULL, 0); + (void)QTSS_Write(inStream, sHelpLine10.Ptr, sHelpLine10.Len, NULL, 0); + + for (short i = 0; i < kMaxFieldNum; i++) + { + qtss_sprintf(buffer, "
%s
\n", kFieldIndexes[i].fieldName); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + + static StrPtrLen sHelpLine11("


"); + (void)QTSS_Write(inStream, sHelpLine11.Ptr, sHelpLine11.Len, NULL, 0); + } + + StringParser fieldNamesParser(fieldList); + StrPtrLen fieldName; + int fieldNum = 0; + do + { + + + if (fieldList != NULL) + { + fieldNum = 0; + + fieldNamesParser.ConsumeWord(&fieldName); + + for (short i = 0; i < kMaxFieldNum; i++) + { + if ( fieldName.Equal(StrPtrLen(kFieldIndexes[i].fieldName, ::strlen(kFieldIndexes[i].fieldName))) ) + { + fieldNum = kFieldIndexes[i].fieldIndex; + break; + } + } + } + else + { + fieldNum++; + if ( fieldNum > kMaxFieldNum ) + fieldNum = 0; + } + + UInt32 theLen = 0; + + switch (fieldNum) + { + case 1: + { +#if __MacOSX__ + static StrPtrLen sStatsLine1("QuickTime Streaming Server Stats
\n"); + (void)QTSS_Write(inStream, sStatsLine1.Ptr, sStatsLine1.Len, NULL, 0); +#else + static StrPtrLen sStatsLine1("Streaming Server Stats
\n"); + (void)QTSS_Write(inStream, sStatsLine1.Ptr, sStatsLine1.Len, NULL, 0); +#endif + +#if __MacOSX__ + static StrPtrLen sStatsLine2("

QuickTime Streaming Server Statistics

\n"); + (void)QTSS_Write(inStream, sStatsLine2.Ptr, sStatsLine2.Len, NULL, 0); +#else + static StrPtrLen sStatsLine2("

Streaming Server Statistics

\n"); + (void)QTSS_Write(inStream, sStatsLine2.Ptr, sStatsLine2.Len, NULL, 0); +#endif + } + break; + + case 2: + { + StrPtrLen theDNS; + (void)QTSS_GetValuePtr(sServer, qtssSvrDefaultDNSName, 0, (void**)&theDNS.Ptr, &theDNS.Len); + + if ( theDNS.Ptr == NULL ) + { // no DNS, try for the IP address only. + (void)QTSS_GetValuePtr(sServer, qtssSvrDefaultIPAddr, 0, (void**)&theDNS.Ptr, &theDNS.Len); + + } + + if ( theDNS.Ptr != NULL ) + { + qtss_sprintf(buffer, "DNS Name (default): %s
\n", theDNS.Ptr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 3: + { + char uptimebuffer[1024]; + time_t curTime = ::time(NULL); + qtss_sprintf(uptimebuffer, "Current Time: %s
\n", qtss_ctime(&curTime, buffer, sizeof(buffer))); + (void)QTSS_Write(inStream, uptimebuffer, ::strlen(uptimebuffer), NULL, 0); + + time_t upTime = curTime - sStartupTime; + #define kDaySeconds (24 * 60 * 60) + #define kHourSeconds (60 * 60) + #define kMinuteSeconds 60 + + UInt32 upTimeDays = upTime / kDaySeconds; + UInt32 upTimeHours = (upTime % kDaySeconds) / kHourSeconds; + UInt32 upTimeMinutes = (upTime % kHourSeconds) / kMinuteSeconds; + UInt32 upTimeSeconds = (upTime % kMinuteSeconds); + qtss_snprintf(uptimebuffer,sizeof(uptimebuffer), "Up Time Total Seconds: %"_U32BITARG_"
\n", upTime); + uptimebuffer[sizeof(uptimebuffer) -1] = 0; + (void)QTSS_Write(inStream, uptimebuffer, ::strlen(uptimebuffer), NULL, 0); + + qtss_snprintf(uptimebuffer,sizeof(uptimebuffer), "Up Time: %"_U32BITARG_" days %"_U32BITARG_" hours %"_U32BITARG_" minutes %"_U32BITARG_" seconds
\n", upTimeDays, upTimeHours,upTimeMinutes, upTimeSeconds); + uptimebuffer[sizeof(uptimebuffer) -1] = 0; + (void)QTSS_Write(inStream, uptimebuffer, ::strlen(uptimebuffer), NULL, 0); + } + break; + + case 4: + { + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + case 5: + { + StrPtrLen theVersion; + (void)QTSS_GetValuePtr(sServer, qtssSvrRTSPServerHeader, 0, (void**)&theVersion.Ptr, &theVersion.Len); + Assert(theVersion.Ptr != NULL); + if (theVersion.Len > 7) //Skip the "Server:" text + theVersion.Ptr += 7; + qtss_sprintf(buffer, "Server Version: %s
\n", theVersion.Ptr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 6: + { + StrPtrLen theBuildDate; + (void)QTSS_GetValuePtr(sServer, qtssSvrServerBuildDate, 0, (void**)&theBuildDate.Ptr, &theBuildDate.Len); + Assert(theBuildDate.Ptr != NULL); + qtss_sprintf(buffer, "Server Build Date: %s
\n", theBuildDate.Ptr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 7: + { + char statusBuffer[1024]; + const char* states[] = { "Starting Up", + "Running", + "Refusing Connections", + "Fatal Error", + "Shutting Down" + }; + QTSS_ServerState theState = qtssRunningState; + theLen = sizeof(theState); + (void)QTSS_GetValue(sServer, qtssSvrState, 0, &theState, &theLen); + + if (theState == qtssRunningState) + { qtss_snprintf(statusBuffer, sizeof(statusBuffer), "Status: %s since %s
", states[theState], qtss_ctime(&sStartupTime,buffer,sizeof(buffer))); + } + else + qtss_snprintf(statusBuffer,sizeof(statusBuffer), "Status: %s
", states[theState]); + (void)QTSS_Write(inStream, statusBuffer, ::strlen(statusBuffer), NULL, 0); + } + break; + + case 8: + { + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + case 9: + { + + //NOOP + } + break; + + case 10: + { + + //NOOP + } + break; + + case 11: + { + + //NOOP + } + break; + + case 12: + { + /* + struct vm_statistics vmStats = {}; + if (vm_statistics (current_task (), &vmStats) != KERN_SUCCESS) + memset (&stats, '\0', sizeof (vmStats)) ; + */ + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + case 13: + { + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + //********************************** + + + + + case 14: + { + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrCurConn, 0, &thePrefStr); + qtss_sprintf(buffer, "Current RTP Connections: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 15: + { + (void)QTSS_GetValueAsString(sServer, qtssRTSPCurrentSessionCount, 0, &thePrefStr); + qtss_sprintf(buffer, "Current RTSP Connections: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 16: + { + (void)QTSS_GetValueAsString(sServer, qtssRTSPHTTPCurrentSessionCount, 0, &thePrefStr); + qtss_sprintf(buffer, "Current RTSP over HTTP Connections: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 17: + { + UInt32 curBandwidth = 0; + theLen = sizeof(curBandwidth); + (void)QTSS_GetValue(sServer, qtssRTPSvrCurBandwidth, 0, &curBandwidth, &theLen); + + qtss_sprintf(buffer, "Current Throughput: %"_U32BITARG_" kbits
\n", curBandwidth/1024); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 18: + { + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrCurPackets, 0, &thePrefStr); + qtss_sprintf(buffer, "Current Packets Per Second: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 19: + { + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrTotalBytes, 0, &thePrefStr); + qtss_sprintf(buffer, "Total Bytes Served: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 20: + { + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrTotalConn, 0, &thePrefStr); + qtss_sprintf(buffer, "Total Connections: %s
", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 21: + { + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + //************************************** + case 22: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsMaximumConnections, 0, &thePrefStr); + qtss_sprintf(buffer, "Maximum Connections: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 23: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsMaximumBandwidth, 0, &thePrefStr); + qtss_sprintf(buffer, "Maximum Throughput: %s Kbits
\n",thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 24: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsMovieFolder, 0, &thePrefStr); + qtss_sprintf(buffer, "Movie Folder Path: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 25: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsRTSPIPAddr, 0, &thePrefStr); + qtss_sprintf(buffer, "RTSP IP Address: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 26: + { + static StrPtrLen sRTSPPortsStart("RTSP Ports: "); + (void)QTSS_Write(inStream, sRTSPPortsStart.Ptr, sRTSPPortsStart.Len, NULL, 0); + + StrPtrLen thePort; + for (UInt32 theIndex = 0; true; theIndex++) + { + QTSS_Error theErr = QTSS_GetValuePtr(sServer, qtssSvrRTSPPorts, theIndex, (void**)&thePort.Ptr, &thePort.Len); + if (theErr != QTSS_NoErr) + break; + + Assert(thePort.Ptr != NULL); + char temp[20]; + qtss_sprintf(temp, "%u ", *(UInt16*)thePort.Ptr); + (void)QTSS_Write(inStream, temp, ::strlen(temp), NULL, 0); + } + + static StrPtrLen sRTSPPortsEnd("
\n"); + (void)QTSS_Write(inStream, sRTSPPortsEnd.Ptr, sRTSPPortsEnd.Len, NULL, 0); + } + break; + + case 27: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsRTSPTimeout, 0, &thePrefStr); + qtss_sprintf(buffer, "RTP Timeout: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 28: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsRTPTimeout, 0, &thePrefStr); + qtss_sprintf(buffer, "RTP Timeout: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 29: + { + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + case 30: + { + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + case 31: + { + if ( sAccessLogPrefs != NULL ) + { + thePrefStr = GetPrefAsString(sAccessLogPrefs, "request_logging"); + qtss_sprintf(buffer, "Access Logging: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 32: + { + if ( sAccessLogPrefs != NULL ) + { + thePrefStr = GetPrefAsString(sAccessLogPrefs, "request_logfile_dir"); + qtss_sprintf(buffer, "Access Log Directory: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 33: + { + if ( sAccessLogPrefs != NULL ) + { + thePrefStr = GetPrefAsString(sAccessLogPrefs, "request_logfile_name"); + qtss_sprintf(buffer, "Access Log Name: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 34: + { + if ( sAccessLogPrefs != NULL ) + { + thePrefStr = GetPrefAsString(sAccessLogPrefs, "request_logfile_size"); + qtss_sprintf(buffer, "Access Log Roll Size: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 35: + { + if ( sAccessLogPrefs != NULL ) + { + thePrefStr = GetPrefAsString(sAccessLogPrefs, "request_logfile_interval"); + qtss_sprintf(buffer, "Access Log Roll Interval (days): %s
", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 36: + { + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + case 37: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorLogEnabled, 0, &thePrefStr); + qtss_sprintf(buffer, "Error Logging: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 38: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorLogDir, 0, &thePrefStr); + qtss_sprintf(buffer, "Error Log Directory: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 39: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorLogName, 0, &thePrefStr); + qtss_sprintf(buffer, "Error Log Name: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 40: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsMaxErrorLogSize, 0, &thePrefStr); + qtss_sprintf(buffer, "Error Log Roll Size: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 41: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorRollInterval, 0, &thePrefStr); + qtss_sprintf(buffer, "Error Log Roll Interval (days): %s
\n",thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 42: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorLogVerbosity, 0, &thePrefStr); + qtss_sprintf(buffer, "Error Log Verbosity: %s
", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 43: + { + (void)QTSS_Write(inStream, "


", ::strlen("


"), NULL, 0); + } + break; + + case 44: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsBreakOnAssert, 0, &thePrefStr); + qtss_sprintf(buffer, "Break On Assert: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 45: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsAutoRestart, 0, &thePrefStr); + qtss_sprintf(buffer, "AutoStart: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 46: + { + (void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsTotalBytesUpdate, 0, &thePrefStr); + qtss_sprintf(buffer, "Total Bytes Update Interval: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 47: + { + if (sReflectorPrefs != NULL) + { + thePrefStr = GetPrefAsString(sReflectorPrefs, "reflector_delay"); + qtss_sprintf(buffer, "Reflector Delay Time: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 48: + { + if (sReflectorPrefs != NULL) + { + thePrefStr = GetPrefAsString(sReflectorPrefs, "reflector_bucket_size"); + qtss_sprintf(buffer, "Reflector Bucket Size: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 49: + { + if ( sSvrControlPrefs != NULL) + { + thePrefStr = GetPrefAsString(sSvrControlPrefs, "history_update_interval"); + qtss_sprintf(buffer, "History Update Interval: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + } + break; + + case 50: + { + Bool16 isOutOfDescriptors = false; + theLen = sizeof(isOutOfDescriptors); + (void)QTSS_GetValue(sServer, qtssSvrIsOutOfDescriptors, 0, &isOutOfDescriptors, &theLen); + + qtss_sprintf(buffer, "Out of file descriptors: %d
\n", isOutOfDescriptors); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 51: + { + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrNumUDPSockets, 0, &thePrefStr); + qtss_sprintf(buffer, "Number of UDP sockets: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 52: + { + UInt32 apiVersion = 0; + UInt32 size = sizeof(UInt32); + (void)QTSS_GetValue(sServer, qtssServerAPIVersion, 0, &apiVersion, &size); + qtss_sprintf(buffer, "API version: %d.%d
\n", (int)( (UInt32) (apiVersion & (UInt32) 0xFFFF0000L) >> 16), (int)(apiVersion &(UInt32) 0x0000FFFFL)); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 53: + { + UInt32 reliableUDPBuffers = 0; + UInt32 blahSize = sizeof(reliableUDPBuffers); + (void)QTSS_GetValue(sServer, qtssSvrNumReliableUDPBuffers, 0, &reliableUDPBuffers, &blahSize); + qtss_sprintf(buffer, "Num Reliable UDP Retransmit Buffers: %"_U32BITARG_"
\n", reliableUDPBuffers); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 54: + { + UInt32 wastedBufSpace = 0; + UInt32 blahSize2 = sizeof(wastedBufSpace); + (void)QTSS_GetValue(sServer, qtssSvrReliableUDPWastageInBytes, 0, &wastedBufSpace, &blahSize2); + qtss_sprintf(buffer, "Amount of buffer space being wasted in UDP Retrans buffers: %"_U32BITARG_"
\n", wastedBufSpace); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + case 55: + { + (void)QTSS_GetValueAsString(sServer, qtssSvrNumThreads, 0, &thePrefStr); + qtss_sprintf(buffer, "Number of Task Threads: %s
\n", thePrefStr); + (void)QTSS_Write(inStream, buffer, ::strlen(buffer), NULL, 0); + } + break; + + + + default: + break; + + + } //switch fieldNum + + + if (fieldList != NULL && !fieldNamesParser.Expect(',')) + fieldNum = 0; + + if (thePrefStr != kEmptyStr) + delete [] thePrefStr; + + thePrefStr = kEmptyStr; + + } while (fieldNum != 0); + + theHTML = "\n"; + (void)QTSS_Write(inStream, theHTML, ::strlen(theHTML), NULL, 0); +} + + + + + + +char* GetPrefAsString(QTSS_ModulePrefsObject inPrefsObject, char* inPrefName) +{ + static StrPtrLen sEmpty(""); + + // + // Get the attribute ID of this pref. + QTSS_AttributeID theID = qtssIllegalAttrID; + + if(inPrefsObject != NULL) + theID = QTSSModuleUtils::GetAttrID(inPrefsObject, inPrefName); + + char* theString = NULL; + + if(inPrefsObject != NULL) + (void)QTSS_GetValueAsString(inPrefsObject, theID, 0, &theString); + + if (theString == NULL) + theString = sEmpty.GetAsCString(); + + return theString; +} diff --git a/APIModules/QTSSWebStatsModule/QTSSWebStatsModule.h b/APIModules/QTSSWebStatsModule/QTSSWebStatsModule.h new file mode 100644 index 0000000..6d72179 --- /dev/null +++ b/APIModules/QTSSWebStatsModule/QTSSWebStatsModule.h @@ -0,0 +1,46 @@ +/* + * + * @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: QTSSWebStatsModule.h + + Contains: A module that uses the stats information available in the server + to present a web page containing that information. Uses the Filter + module feature of QTSS API. + + +*/ + +#ifndef __QTSSWEBSTATSMODULE_H__ +#define __QTSSWEBSTATSMODULE_H__ + +#include "QTSS.h" + +extern "C" +{ + EXPORT QTSS_Error QTSSWebStatsModule_Main(void* inPrivateArgs); +} + +#endif // __QTSSWEBSTATSMODULE_H__ + diff --git a/APIStubLib/QTSS.h b/APIStubLib/QTSS.h new file mode 100644 index 0000000..963fc0b --- /dev/null +++ b/APIStubLib/QTSS.h @@ -0,0 +1,2024 @@ +/* + * + * @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@ + * + */ + +#ifndef QTSS_H +#define QTSS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "OSHeaders.h" +#include "QTSSRTSPProtocol.h" + +#ifndef __Win32__ +#include +#endif + +#define QTSS_API_VERSION 0x00050000 +#define QTSS_MAX_MODULE_NAME_LENGTH 64 +#define QTSS_MAX_SESSION_ID_LENGTH 32 +#define QTSS_MAX_ATTRIBUTE_NAME_SIZE 64 + + +//******************************* +// ENUMERATED TYPES + +/**********************************/ +// Error Codes + +enum +{ + QTSS_NoErr = 0, + QTSS_RequestFailed = -1, + QTSS_Unimplemented = -2, + QTSS_RequestArrived = -3, + QTSS_OutOfState = -4, + QTSS_NotAModule = -5, + QTSS_WrongVersion = -6, + QTSS_IllegalService = -7, + QTSS_BadIndex = -8, + QTSS_ValueNotFound = -9, + QTSS_BadArgument = -10, + QTSS_ReadOnly = -11, + QTSS_NotPreemptiveSafe = -12, + QTSS_NotEnoughSpace = -13, + QTSS_WouldBlock = -14, + QTSS_NotConnected = -15, + QTSS_FileNotFound = -16, + QTSS_NoMoreData = -17, + QTSS_AttrDoesntExist = -18, + QTSS_AttrNameExists = -19, + QTSS_InstanceAttrsNotAllowed= -20 +}; +typedef SInt32 QTSS_Error; + +// QTSS_AddStreamFlags used in the QTSS_AddStream Callback function +enum +{ + qtssASFlagsNoFlags = 0x00000000, + qtssASFlagsAllowDestination = 0x00000001, + qtssASFlagsForceInterleave = 0x00000002, + qtssASFlagsDontUseSlowStart = 0x00000004, + qtssASFlagsForceUDPTransport = 0x00000008 +}; +typedef UInt32 QTSS_AddStreamFlags; + +// QTSS_PlayFlags used in the QTSS_Play Callback function. +enum +{ + qtssPlayFlagsSendRTCP = 0x00000010, // have the server generate RTCP Sender Reports + qtssPlayFlagsAppendServerInfo = 0x00000020 // have the server append the server info APP packet to your RTCP Sender Reports +}; +typedef UInt32 QTSS_PlayFlags; + +// Flags for QTSS_Write when writing to a QTSS_ClientSessionObject. +enum +{ + qtssWriteFlagsNoFlags = 0x00000000, + qtssWriteFlagsIsRTP = 0x00000001, + qtssWriteFlagsIsRTCP = 0x00000002, + qtssWriteFlagsWriteBurstBegin = 0x00000004, + qtssWriteFlagsBufferData = 0x00000008 +}; +typedef UInt32 QTSS_WriteFlags; + +// Flags for QTSS_SendStandardRTSPResponse +enum +{ + qtssPlayRespWriteTrackInfo = 0x00000001, + qtssSetupRespDontWriteSSRC = 0x00000002 +}; + + +// Flags for the qtssRTSPReqAction attribute in a QTSS_RTSPRequestObject. +enum +{ + qtssActionFlagsNoFlags = 0x00000000, + qtssActionFlagsRead = 0x00000001, + qtssActionFlagsWrite = 0x00000002, + qtssActionFlagsAdmin = 0x00000004, + qtssActionFlagsExtended = 0x40000000, + qtssActionQTSSExtended = 0x80000000, +}; +typedef UInt32 QTSS_ActionFlags; + +/**********************************/ +// RTP SESSION STATES +// +// Is this session playing, paused, or what? +enum +{ + qtssPausedState = 0, + qtssPlayingState = 1 +}; +typedef UInt32 QTSS_RTPSessionState; + +//*********************************/ +// CLIENT SESSION CLOSING REASON +// +// Why is this Client going away? +enum +{ + qtssCliSesCloseClientTeardown = 0, // QTSS_Teardown was called on this session + qtssCliSesCloseTimeout = 1, // Server is timing this session out + qtssCliSesCloseClientDisconnect = 2 // Client disconnected. +}; +typedef UInt32 QTSS_CliSesClosingReason; + +// CLIENT SESSION TEARDOWN REASON +// +// An attribute in the QTSS_ClientSessionObject +// +// When calling QTSS_Teardown, a module should specify the QTSS_CliSesTeardownReason in the QTSS_ClientSessionObject +// if the tear down was not a client request. +// +enum +{ + qtssCliSesTearDownClientRequest = 0, + qtssCliSesTearDownUnsupportedMedia = 1, + qtssCliSesTearDownServerShutdown = 2, + qtssCliSesTearDownServerInternalErr = 3, + qtssCliSesTearDownBroadcastEnded = 4 // A broadcast the client was watching ended + +}; +typedef UInt32 QTSS_CliSesTeardownReason; + +// Events +enum +{ + QTSS_ReadableEvent = 1, + QTSS_WriteableEvent = 2 +}; +typedef UInt32 QTSS_EventType; + +// Authentication schemes +enum +{ + qtssAuthNone = 0, + qtssAuthBasic = 1, + qtssAuthDigest = 2 +}; +typedef UInt32 QTSS_AuthScheme; + + +/**********************************/ +// RTSP SESSION TYPES +// +// Is this a normal RTSP session or an RTSP / HTTP session? +enum +{ + qtssRTSPSession = 0, + qtssRTSPHTTPSession = 1, + qtssRTSPHTTPInputSession= 2 //The input half of an RTSPHTTP session. These session types are usually very short lived. +}; +typedef UInt32 QTSS_RTSPSessionType; + +/**********************************/ +// +// What type of RTP transport is being used for the RTP stream? +enum +{ + qtssRTPTransportTypeUDP = 0, + qtssRTPTransportTypeReliableUDP = 1, + qtssRTPTransportTypeTCP = 2, + qtssRTPTransportType3GPPUDP = 3 +}; +typedef UInt32 QTSS_RTPTransportType; + +/**********************************/ +// +// What type of RTP network mode is being used for the RTP stream? +// unicast | multicast (mutually exclusive) +enum +{ + qtssRTPNetworkModeDefault = 0, // not declared + qtssRTPNetworkModeMulticast = 1, + qtssRTPNetworkModeUnicast = 2 +}; +typedef UInt32 QTSS_RTPNetworkMode; + + + +/**********************************/ +// +// The transport mode in a SETUP request +enum +{ + qtssRTPTransportModePlay = 0, + qtssRTPTransportModeRecord = 1 +}; +typedef UInt32 QTSS_RTPTransportMode; + +/**********************************/ +// PAYLOAD TYPES +// +// When a module adds an RTP stream to a client session, it must specify +// the stream's payload type. This is so that other modules can find out +// this information in a generalized fashion. Here are the currently +// defined payload types +enum +{ + qtssUnknownPayloadType = 0, + qtssVideoPayloadType = 1, + qtssAudioPayloadType = 2 +}; +typedef UInt32 QTSS_RTPPayloadType; + +/**********************************/ +// QTSS API OBJECT TYPES +enum +{ + qtssDynamicObjectType = FOUR_CHARS_TO_INT('d', 'y', 'm', 'c'), //dymc + qtssRTPStreamObjectType = FOUR_CHARS_TO_INT('r', 's', 't', 'o'), //rsto + qtssClientSessionObjectType = FOUR_CHARS_TO_INT('c', 's', 'e', 'o'), //cseo + qtssRTSPSessionObjectType = FOUR_CHARS_TO_INT('s', 's', 'e', 'o'), //sseo + qtssRTSPRequestObjectType = FOUR_CHARS_TO_INT('s', 'r', 'q', 'o'), //srqo + qtssRTSPHeaderObjectType = FOUR_CHARS_TO_INT('s', 'h', 'd', 'o'), //shdo + qtssServerObjectType = FOUR_CHARS_TO_INT('s', 'e', 'r', 'o'), //sero + qtssPrefsObjectType = FOUR_CHARS_TO_INT('p', 'r', 'f', 'o'), //prfo + qtssTextMessagesObjectType = FOUR_CHARS_TO_INT('t', 'x', 't', 'o'), //txto + qtssFileObjectType = FOUR_CHARS_TO_INT('f', 'i', 'l', 'e'), //file + qtssModuleObjectType = FOUR_CHARS_TO_INT('m', 'o', 'd', 'o'), //modo + qtssModulePrefsObjectType = FOUR_CHARS_TO_INT('m', 'o', 'd', 'p'), //modp + qtssAttrInfoObjectType = FOUR_CHARS_TO_INT('a', 't', 't', 'r'), //attr + qtssUserProfileObjectType = FOUR_CHARS_TO_INT('u', 's', 'p', 'o'), //uspo + qtssConnectedUserObjectType = FOUR_CHARS_TO_INT('c', 'u', 's', 'r'), //cusr + qtss3GPPStreamObjectType = FOUR_CHARS_TO_INT('3', 's', 't', 'r'), //3str + qtss3GPPClientSessionObjectType = FOUR_CHARS_TO_INT('3', 's', 'e', 's'), //3ses + qtss3GPPRTSPObjectType = FOUR_CHARS_TO_INT('3', 'r', 't', 's'), //3rts + qtss3GPPRequestObjectType = FOUR_CHARS_TO_INT('3', 'r', 'e', 'q') //3req + +}; +typedef UInt32 QTSS_ObjectType; + +/**********************************/ +// ERROR LOG VERBOSITIES +// +// This provides some information to the module on the priority or +// type of this error message. +// +// When modules write to the error log stream (see below), +// the verbosity is qtssMessageVerbosity. +enum +{ + qtssFatalVerbosity = 0, + qtssWarningVerbosity = 1, + qtssMessageVerbosity = 2, + qtssAssertVerbosity = 3, + qtssDebugVerbosity = 4, + + qtssIllegalVerbosity = 5 +}; +typedef UInt32 QTSS_ErrorVerbosity; + +enum +{ + qtssOpenFileNoFlags = 0, + qtssOpenFileAsync = 1, // File stream will be asynchronous (read may return QTSS_WouldBlock) + qtssOpenFileReadAhead = 2 // File stream will be used for a linear read through the file. +}; +typedef UInt32 QTSS_OpenFileFlags; + + +/**********************************/ +// SERVER STATES +// +// An attribute in the QTSS_ServerObject returns the server state +// as a QTSS_ServerState. Modules may also set the server state. +// +// Setting the server state to qtssFatalErrorState, or qtssShuttingDownState +// will cause the server to quit. +// +// Setting the state to qtssRefusingConnectionsState will cause the server +// to start refusing new connections. +enum +{ + qtssStartingUpState = 0, + qtssRunningState = 1, + qtssRefusingConnectionsState = 2, + qtssFatalErrorState = 3,//a fatal error has occurred, not shutting down yet + qtssShuttingDownState = 4, + qtssIdleState = 5 // Like refusing connections state, but will also kill any currently connected clients +}; +typedef UInt32 QTSS_ServerState; + +/**********************************/ +// ILLEGAL ATTRIBUTE ID +enum +{ + qtssIllegalAttrID = -1, + qtssIllegalServiceID = -1 +}; + +//*********************************/ +// QTSS DON'T CALL SENDPACKETS AGAIN +// If this time is specified as the next packet time when returning +// from QTSS_SendPackets_Role, the module won't get called again in +// that role until another QTSS_Play is issued +enum +{ + qtssDontCallSendPacketsAgain = -1 +}; + +// DATA TYPES +enum +{ + qtssAttrDataTypeUnknown = 0, + qtssAttrDataTypeCharArray = 1, + qtssAttrDataTypeBool16 = 2, + qtssAttrDataTypeSInt16 = 3, + qtssAttrDataTypeUInt16 = 4, + qtssAttrDataTypeSInt32 = 5, + qtssAttrDataTypeUInt32 = 6, + qtssAttrDataTypeSInt64 = 7, + qtssAttrDataTypeUInt64 = 8, + qtssAttrDataTypeQTSS_Object = 9, + qtssAttrDataTypeQTSS_StreamRef = 10, + qtssAttrDataTypeFloat32 = 11, + qtssAttrDataTypeFloat64 = 12, + qtssAttrDataTypeVoidPointer = 13, + qtssAttrDataTypeTimeVal = 14, + + qtssAttrDataTypeNumTypes = 15 +}; +typedef UInt32 QTSS_AttrDataType; + +enum +{ + qtssAttrModeRead = 1, + qtssAttrModeWrite = 2, + qtssAttrModePreempSafe = 4, + qtssAttrModeInstanceAttrAllowed = 8, + qtssAttrModeCacheable = 16, + qtssAttrModeDelete = 32 +}; +typedef UInt32 QTSS_AttrPermission; + + +enum +{ + qtssAttrRightNone = 0, + qtssAttrRightRead = 1 << 0, + qtssAttrRightWrite = 1 << 1, + qtssAttrRightAdmin = 1 << 2, + qtssAttrRightExtended = 1 << 30, // Set this flag in the qtssUserRights when defining a new right. The right is a string i.e. "myauthmodule.myright" store the string in the QTSS_UserProfileObject attribute qtssUserExtendedRights + qtssAttrRightQTSSExtended = 1 << 31 // This flag is reserved for future use by the server. Additional rights are stored in qtssUserQTSSExtendedRights. +}; +typedef UInt32 QTSS_AttrRights; // see QTSS_UserProfileObject + + +/**********************************/ +//BUILT IN SERVER ATTRIBUTES + +//The server maintains many attributes internally, and makes these available to plug-ins. +//Each value is a standard attribute, with a name and everything. Plug-ins may resolve the id's of +//these values by name if they'd like, but in the initialize role they will receive a struct of +//all the ids of all the internally maintained server parameters. This enumerated type block defines the indexes +//in that array for the id's. + +enum +{ + //QTSS_RTPStreamObject parameters. All of these are preemptive safe. + + qtssRTPStrTrackID = 0, //r/w //UInt32 //Unique ID identifying each stream. This will default to 0 unless set explicitly by a module. + qtssRTPStrSSRC = 1, //read //UInt32 //SSRC (Synchronization Source) generated by the server. Guarenteed to be unique amongst all streams in the session. + //This SSRC will be included in all RTCP Sender Reports generated by the server. See The RTP / RTCP RFC for more info on SSRCs. + qtssRTPStrPayloadName = 2, //r/w //char array //Payload name of the media on this stream. This will be empty unless set explicitly by a module. + qtssRTPStrPayloadType = 3, //r/w //QTSS_RTPPayloadType //Payload type of the media on this stream. This will default to qtssUnknownPayloadType unless set explicitly by a module. + qtssRTPStrFirstSeqNumber = 4, //r/w //SInt16 //Sequence number of the first RTP packet generated for this stream after the last PLAY request was issued. If known, this must be set by a module before calling QTSS_Play. It is used by the server to generate a proper RTSP PLAY response. + qtssRTPStrFirstTimestamp = 5, //r/w //SInt32 //RTP timestamp of the first RTP packet generated for this stream after the last PLAY request was issued. If known, this must be set by a module before calling QTSS_Play. It is used by the server to generate a proper RTSP PLAY response. + qtssRTPStrTimescale = 6, //r/w //SInt32 //Timescale for the track. If known, this must be set before calling QTSS_Play. + qtssRTPStrQualityLevel = 7, //r/w //UInt32 //Private + qtssRTPStrNumQualityLevels = 8, //r/w //UInt32 //Private + qtssRTPStrBufferDelayInSecs = 9, //r/w //Float32 //Size of the client's buffer. Server always sets this to 3 seconds, it is up to a module to determine what the buffer size is and set this attribute accordingly. + + // All of these parameters come out of the last RTCP packet received on this stream. + // If the corresponding field in the last RTCP packet was blank, the attribute value will be 0. + + qtssRTPStrFractionLostPackets = 10, //read //UInt32 // Fraction lost packets so far on this stream. + qtssRTPStrTotalLostPackets = 11, //read //UInt32 // Total lost packets so far on this stream. + qtssRTPStrJitter = 12, //read //UInt32 // Cumulative jitter on this stream. + qtssRTPStrRecvBitRate = 13, //read //UInt32 // Average bit rate received by the client in bits / sec. + qtssRTPStrAvgLateMilliseconds = 14, //read //UInt16 // Average msec packets received late. + qtssRTPStrPercentPacketsLost = 15, //read //UInt16 // Percent packets lost on this stream, as a fixed %. + qtssRTPStrAvgBufDelayInMsec = 16, //read //UInt16 // Average buffer delay in milliseconds + qtssRTPStrGettingBetter = 17, //read //UInt16 // Non-zero if the client is reporting that the stream is getting better. + qtssRTPStrGettingWorse = 18, //read //UInt16 // Non-zero if the client is reporting that the stream is getting worse. + qtssRTPStrNumEyes = 19, //read //UInt32 // Number of clients connected to this stream. + qtssRTPStrNumEyesActive = 20, //read //UInt32 // Number of clients playing this stream. + qtssRTPStrNumEyesPaused = 21, //read //UInt32 // Number of clients connected but currently paused. + qtssRTPStrTotPacketsRecv = 22, //read //UInt32 // Total packets received by the client + qtssRTPStrTotPacketsDropped = 23, //read //UInt16 // Total packets dropped by the client. + qtssRTPStrTotPacketsLost = 24, //read //UInt16 // Total packets lost. + qtssRTPStrClientBufFill = 25, //read //UInt16 // How much the client buffer is filled in 10ths of a second. + qtssRTPStrFrameRate = 26, //read //UInt16 // Current frame rate, in frames per second. + qtssRTPStrExpFrameRate = 27, //read //UInt16 // Expected frame rate, in frames per second. + qtssRTPStrAudioDryCount = 28, //read //UInt16 // Number of times the audio has run dry. + // Address & network related parameters + qtssRTPStrIsTCP = 29, //read //Bool16 //Is this RTP stream being sent over TCP? If false, it is being sent over UDP. + qtssRTPStrStreamRef = 30, //read //QTSS_StreamRef //A QTSS_StreamRef used for sending RTP or RTCP packets to the client. Use the QTSS_WriteFlags to specify whether each packet is an RTP or RTCP packet. + qtssRTPStrTransportType = 31, //read //QTSS_RTPTransportType // What kind of transport is being used? + qtssRTPStrStalePacketsDropped = 32, //read //UInt32 // Number of packets dropped by QTSS_Write because they were too old. + qtssRTPStrCurrentAckTimeout = 33, //read //UInt32 // Current ack timeout being advertised to the client in msec (part of reliable udp). + + qtssRTPStrCurPacketsLostInRTCPInterval = 34, // read //UInt32 // An RTCP delta count of lost packets equal to qtssRTPStrPercentPacketsLost + qtssRTPStrPacketCountInRTCPInterval = 35, // read //UInt32 // An RTCP delta count of packets + qtssRTPStrSvrRTPPort = 36, //read //UInt16 // Port the server is sending RTP packets from for this stream + qtssRTPStrClientRTPPort = 37, //read //UInt16 // Port the server is sending RTP packets to for this stream + qtssRTPStrNetworkMode = 38, //read //QTSS_RTPNetworkMode // unicast or multicast + qtssRTPStr3gppObject = 39, //read //QTSS_3GPPStreamObject // QTSS_ObjectType qtss3GPPStreamObjectType 3gpp data for the stream object + qtssRTPStrThinningDisabled = 40, //read //Bool16 //Stream thinning is disabled on this stream. + qtssRTPStrNumParams = 41 + +}; +typedef UInt32 QTSS_RTPStreamAttributes; + +enum +{ + //All text names are identical to the enumerated type names + qtss3GPPStreamEnabled = 0, + qtss3GPPStreamRateAdaptBufferBytes = 1, + qtss3GPPStreamRateAdaptTimeMilli = 2, + qtss3GPPStreamNumParams = 3 +}; +typedef UInt32 QTSS_RTPStream3GPPAttributes; //QTSS_3GPPStreamObject + + +enum +{ + //QTSS_ClientSessionObject parameters. All of these are preemptive safe + + qtssCliSesStreamObjects = 0, //read //QTSS_RTPStreamObject//Iterated attribute. All the QTSS_RTPStreamRefs belonging to this session. + qtssCliSesCreateTimeInMsec = 1, //read //QTSS_TimeVal //Time in milliseconds the session was created. + qtssCliSesFirstPlayTimeInMsec = 2, //read //QTSS_TimeVal //Time in milliseconds the first QTSS_Play call was issued. + qtssCliSesPlayTimeInMsec = 3, //read //QTSS_TimeVal //Time in milliseconds the most recent play was issued. + qtssCliSesAdjustedPlayTimeInMsec= 4, //read //QTSS_TimeVal //Private - do not use + qtssCliSesRTPBytesSent = 5, //read //UInt32 //Number of RTP bytes sent so far on this session. + qtssCliSesRTPPacketsSent = 6, //read //UInt32 //Number of RTP packets sent so far on this session. + qtssCliSesState = 7, //read //QTSS_RTPSessionState // State of this session: is it paused or playing currently? + qtssCliSesPresentationURL = 8, //read //char array //Presentation URL for this session. This URL is the "base" URL for the session. RTSP requests to this URL are assumed to affect all streams on the session. + qtssCliSesFirstUserAgent = 9, //read //char array //Private + qtssCliSesMovieDurationInSecs = 10, //r/w //Float64 //Duration of the movie for this session in seconds. This will default to 0 unless set by a module. + qtssCliSesMovieSizeInBytes = 11, //r/w //UInt64 //Movie size in bytes. This will default to 0 unless explictly set by a module + qtssCliSesMovieAverageBitRate = 12, //r/w //UInt32 //average bits per second based on total RTP bits/movie duration. This will default to 0 unless explictly set by a module. + qtssCliSesLastRTSPSession = 13, //read //QTSS_RTSPSessionObject //Private + qtssCliSesFullURL = 14, //read //char array //full Presentation URL for this session. Same as qtssCliSesPresentationURL, but includes rtsp://domain.com prefix + qtssCliSesHostName = 15, //read //char array //requestes host name for s session. Just the "domain.com" portion from qtssCliSesFullURL above + + qtssCliRTSPSessRemoteAddrStr = 16, //read //char array //IP address addr of client, in dotted-decimal format. + qtssCliRTSPSessLocalDNS = 17, //read //char array //DNS name of local IP address for this RTSP connection. + qtssCliRTSPSessLocalAddrStr = 18, //read //char array //Ditto, in dotted-decimal format. + + qtssCliRTSPSesUserName = 19, //read //char array // from the most recent (last) request. + qtssCliRTSPSesUserPassword = 20, //read //char array // from the most recent (last) request. + qtssCliRTSPSesURLRealm = 21, //read //char array // from the most recent (last) request. + + qtssCliRTSPReqRealStatusCode = 22, //read //UInt32 //Same as qtssRTSPReqRTSPReqRealStatusCode, the status from the most recent (last) request. + qtssCliTeardownReason = 23, //r/w //QTSS_CliSesTeardownReason // Must be set by a module that calls QTSS_Teardown if it is not a client requested disconnect. + + qtssCliSesReqQueryString = 24, //read //char array //Query string from the request that creates this client session + + qtssCliRTSPReqRespMsg = 25, //read //char array // from the most recent (last) request. Error message sent back to client if response was an error. + + qtssCliSesCurrentBitRate = 26, //read //UInt32 //Current bit rate of all the streams on this session. This is not an average. In bits per second. + qtssCliSesPacketLossPercent = 27, //read //Float32 //Current percent loss as a fraction. .5 = 50%. This is not an average. + qtssCliSesTimeConnectedInMsec = 28, //read //SInt64 //Time in milliseconds that this client has been connected. + qtssCliSesCounterID = 29, //read //UInt32 //A unique, non-repeating ID for this session. + qtssCliSesRTSPSessionID = 30, //read //char array//The RTSP session ID that refers to this client session + qtssCliSesFramesSkipped = 31, //r/w //UInt32 //Modules can set this to be the number of frames skipped for this client + qtssCliSesTimeoutMsec = 32, //r/w //UInt32 // client session timeout in milliseconds refreshed by RefreshTimeout API call or any rtcp or rtp packet on the session. + qtssCliSesOverBufferEnabled = 33, //read //Bool16 // client overbuffers using dynamic rate streams + qtssCliSesRTCPPacketsRecv = 34, //read //UInt32 //Number of RTCP packets received so far on this session. + qtssCliSesRTCPBytesRecv = 35, //read //UInt32 //Number of RTCP bytes received so far on this session. + qtssCliSesStartedThinning = 36, //read //Bool16 // At least one of the streams in the session is thinned + qtssCliSes3GPPObject = 37, //read //QTSS_3GPPClientSessionObject //QTSS_ObjectType qtss3GPPClientSessionObjectType + qtssCliSessLastRTSPBandwidth = 38, //read //UInt32 // The last RTSP Bandwidth header value received from the client. + qtssCliSessIs3GPPSession = 39, //read //Bool16 // Client is using 3gpp RTSP headers + qtssCliSesNumParams = 40 + +}; +typedef UInt32 QTSS_ClientSessionAttributes; + +//QTSS_3GPPClientSessionObject //class RTPSession3GPP +enum +{ + //All text names are identical to the enumerated type names + qtss3GPPCliSesEnabled = 0, //read //Bool16 //initialized to preference setting + qtss3GPPCliSesLinkCharGuaranteedBitRate = 1, //read //UInt32 //The Received Link Characteristic rate. default = 0 + qtss3GPPCliSesLinkCharMaxBitRate = 2, //read //UInt32 //The Received Link Characteristic max. default = 0 + qtss3GPPCliSesLinkCharMaxTransferDelayMilliSec = 3, //read //UInt32 //The Received Link Characteristic transfer delay. default = 0 + qtss3GPPCliSesLinkCharURL = 4, //read //char array //The Received Link Characteristic URL. + + qtss3GPPCliSesNumParams = 5 +}; +typedef UInt32 QTSS_ClientSession3GPPAttributes; + +enum +{ + //QTSS_RTSPSessionObject parameters + + //Valid in any role that receives a QTSS_RTSPSessionObject + qtssRTSPSesID = 0, //read //UInt32 //This is a unique ID for each session since the server started up. + qtssRTSPSesLocalAddr = 1, //read //UInt32 //Local IP address for this RTSP connection + qtssRTSPSesLocalAddrStr = 2, //read //char array //Ditto, in dotted-decimal format. + qtssRTSPSesLocalDNS = 3, //read //char array //DNS name of local IP address for this RTSP connection. + qtssRTSPSesRemoteAddr = 4, //read //UInt32 //IP address of client. + qtssRTSPSesRemoteAddrStr= 5, //read //char array //IP address addr of client, in dotted-decimal format. + qtssRTSPSesEventCntxt = 6, //read //QTSS_EventContextRef //An event context for the RTSP connection to the client. This should primarily be used to wait for EV_WR events if flow-controlled when responding to a client. + qtssRTSPSesType = 7, //read //QTSS_RTSPSession //Is this a normal RTSP session, or is it a HTTP tunnelled RTSP session? + qtssRTSPSesStreamRef = 8, //read //QTSS_RTSPSessionStream // A QTSS_StreamRef used for sending data to the RTSP client. + + qtssRTSPSesLastUserName = 9,//read //char array // Private + qtssRTSPSesLastUserPassword = 10,//read //char array // Private + qtssRTSPSesLastURLRealm = 11,//read //char array // Private + + qtssRTSPSesLocalPort = 12, //read //UInt16 // This is the local port for the connection + qtssRTSPSesRemotePort = 13, //read //UInt16 // This is the client port for the connection + + qtssRTSPSes3GPPObject = 14, //read //QTSS_3GPPRTSPSessionObject //QTSS_ObjectType qtss3GPPRTSPObjectType 3gpp data and state info + + qtssRTSPSesLastDigestChallenge = 15,//read //char array // Private + qtssRTSPSesNumParams = 16 +}; +typedef UInt32 QTSS_RTSPSessionAttributes; + +//QTSS_3GPPRTSPSessionObject //class RTSPSession3GPP +enum +{ + //All text names are identical to the enumerated type names + qtss3GPPRTSPSesEnabled = 0, + qtss3GPPRTSPSessNumParams = 1 +}; +typedef UInt32 QTSS_3GPPRTSPSessionAttributes; + + +enum +{ + //All text names are identical to the enumerated type names + + //QTSS_RTSPRequestObject parameters. All of these are pre-emptive safe parameters + + //Available in every role that receives the QTSS_RTSPRequestObject + + qtssRTSPReqFullRequest = 0, //read //char array //The full request sent by the client + + //Available in every method that receives the QTSS_RTSPRequestObject except for the QTSS_FilterMethod + + qtssRTSPReqMethodStr = 1, //read //char array //RTSP Method of this request. + qtssRTSPReqFilePath = 2, //r/w //char array //Not pre-emptive safe!! //URI for this request, converted to a local file system path. + qtssRTSPReqURI = 3, //read //char array //URI for this request + qtssRTSPReqFilePathTrunc = 4, //read //char array //Not pre-emptive safe!! //Same as qtssRTSPReqFilePath, without the last element of the path + qtssRTSPReqFileName = 5, //read //char array //Not pre-emptive safe!! //Everything after the last path separator in the file system path + qtssRTSPReqFileDigit = 6, //read //char array //Not pre-emptive safe!! //If the URI ends with one or more digits, this points to those. + qtssRTSPReqAbsoluteURL = 7, //read //char array //The full URL, starting from "rtsp://" + qtssRTSPReqTruncAbsoluteURL = 8, //read //char array //Absolute URL without last element of path + qtssRTSPReqMethod = 9, //read //QTSS_RTSPMethod //Method as QTSS_RTSPMethod + qtssRTSPReqStatusCode = 10, //r/w //QTSS_RTSPStatusCode //The current status code for the request as QTSS_RTSPStatusCode. By default, it is always qtssSuccessOK. If a module sets this attribute, and calls QTSS_SendRTSPHeaders, the status code of the header generated by the server will reflect this value. + qtssRTSPReqStartTime = 11, //read //Float64 //Start time specified in Range: header of PLAY request. + qtssRTSPReqStopTime = 12, //read //Float64 //Stop time specified in Range: header of PLAY request. + qtssRTSPReqRespKeepAlive = 13, //r/w //Bool16 //Will (should) the server keep the connection alive. Set this to false if the connection should be terminated after completion of this request. + qtssRTSPReqRootDir = 14, //r/w //char array //Not pre-emptive safe!! //Root directory to use for this request. The default value for this parameter is the server's media folder path. Modules may set this attribute from the QTSS_RTSPRoute_Role. + qtssRTSPReqRealStatusCode = 15, //read //UInt32 //Same as qtssRTSPReqStatusCode, but translated from QTSS_RTSPStatusCode into an actual RTSP status code. + qtssRTSPReqStreamRef = 16, //read //QTSS_RTSPRequestStream //A QTSS_StreamRef for sending data to the RTSP client. This stream ref, unlike the one provided as an attribute in the QTSS_RTSPSessionObject, will never return EWOULDBLOCK in response to a QTSS_Write or a QTSS_WriteV call. + + qtssRTSPReqUserName = 17, //read //char array//decoded Authentication information when provided by the RTSP request. See RTSPSessLastUserName. + qtssRTSPReqUserPassword = 18, //read //char array //decoded Authentication information when provided by the RTSP request. See RTSPSessLastUserPassword. + qtssRTSPReqUserAllowed = 19, //r/w //Bool16 //Default is server pref based, set to false if request is denied. Missing or bad movie files should allow the server to handle the situation and return true. + qtssRTSPReqURLRealm = 20, //r/w //char array //The authorization entity for the client to display "Please enter password for -realm- at server name. The default realm is "Streaming Server". + qtssRTSPReqLocalPath = 21, //read //char array //Not pre-emptive safe!! //The full local path to the file. This Attribute is first set after the Routing Role has run and before any other role is called. + + qtssRTSPReqIfModSinceDate = 22, //read //QTSS_TimeVal // If the RTSP request contains an If-Modified-Since header, this is the if-modified date, converted to a QTSS_TimeVal + + + qtssRTSPReqQueryString = 23, //read //char array // query stting (CGI parameters) passed to the server in the request URL, does not include the '?' separator + + qtssRTSPReqRespMsg = 24, //r/w //char array // A module sending an RTSP error to the client should set this to be a text message describing why the error occurred. This description is useful to add to log files. Once the RTSP response has been sent, this attribute contains the response message. + qtssRTSPReqContentLen = 25, //read //UInt32 // Content length of incoming RTSP request body + qtssRTSPReqSpeed = 26, //read //Float32 // Value of Speed header, converted to a Float32. + qtssRTSPReqLateTolerance = 27, //read //Float32 // Value of the late-tolerance field of the x-RTP-Options header, or -1 if not present. + + qtssRTSPReqTransportType = 28, //read //QTSS_RTPTransportType // What kind of transport? + qtssRTSPReqTransportMode = 29, //read //QTSS_RTPTransportMode // A setup request from the client. * maybe should just be an enum or the text of the mode value? + qtssRTSPReqSetUpServerPort = 30, //r/w //UInt16 // the ServerPort to respond to a client SETUP request with. + + qtssRTSPReqAction = 31, //r/w //QTSS_ActionFlags //Set by a module in the QTSS_RTSPSetAction_Role - for now, the server will set it as the role hasn't been added yet + qtssRTSPReqUserProfile = 32, //r/w //QTSS_UserProfileObject //Object's username is filled in by the server and its password and group memberships filled in by the authentication module. + qtssRTSPReqPrebufferMaxTime = 33, //read //Float32 //The maxtime field of the x-Prebuffer RTSP header + qtssRTSPReqAuthScheme = 34, //read //QTSS_AuthScheme + + qtssRTSPReqSkipAuthorization = 35, //r/w //Bool16 // Set by a module that wants the particular request to be + // allowed by all authorization modules + qtssRTSPReqNetworkMode = 36, //read //QTSS_RTPNetworkMode // unicast or multicast + qtssRTSPReqDynamicRateState = 37, //read //SInt32 // -1 not in request, 0 off, 1 on + qtssRTSPReq3GPPRequestObject = 38, //read //QTSS_3GPPRequestObject //QTSS_ObjectType qtss3GPPRequestObject + qtssRTSPReqBandwidthBits = 39, //read //UInt32 // Value of the Bandwdith header. Default is 0. + qtssRTSPReqUserFound = 40, //r/w //Bool16 //Default is false, set to true if the user is found in the authenticate role and the module wants to take ownership of authenticating the user. + qtssRTSPReqAuthHandled = 41, //r/w //Bool16 //Default is false, set to true in the authorize role to take ownerhsip of authorizing the request. + qtssRTSPReqDigestChallenge = 42, //read //char array //Challenge used by the server for Digest authentication + qtssRTSPReqDigestResponse = 43, //read //char array //Digest response used by the server for Digest authentication + qtssRTSPReqNumParams = 44 + +}; +typedef UInt32 QTSS_RTSPRequestAttributes; + +enum +{ + //All text names are identical to the enumerated type names + qtss3GPPRequestEnabled = 0, //r/w //Bool16 + qtss3GPPRequestRateAdaptationStreamData = 1, //read //char array //the rate adaptation url and parameters per stream + qtss3GPPRequestNumParams = 2 +}; +typedef UInt32 QTSS_RTSPRequest3GPPAttributes; + + +enum +{ + //QTSS_ServerObject parameters + + // These parameters ARE pre-emptive safe. + + qtssServerAPIVersion = 0, //read //UInt32 //The API version supported by this server (format 0xMMMMmmmm, where M=major version, m=minor version) + qtssSvrDefaultDNSName = 1, //read //char array //The "default" DNS name of the server + qtssSvrDefaultIPAddr = 2, //read //UInt32 //The "default" IP address of the server + qtssSvrServerName = 3, //read //char array //Name of the server + qtssSvrServerVersion = 4, //read //char array //Version of the server + qtssSvrServerBuildDate = 5, //read //char array //When was the server built? + qtssSvrRTSPPorts = 6, //read // NOT PREEMPTIVE SAFE!//UInt16 //Indexed parameter: all the ports the server is listening on + qtssSvrRTSPServerHeader = 7, //read //char array //Server: header that the server uses to respond to RTSP clients + + // These parameters are NOT pre-emptive safe, they cannot be accessed + // via. QTSS_GetValuePtr. Some exceptions noted below + + qtssSvrState = 8, //r/w //QTSS_ServerState //The current state of the server. If a module sets the server state, the server will respond in the appropriate fashion. Setting to qtssRefusingConnectionsState causes the server to refuse connections, setting to qtssFatalErrorState or qtssShuttingDownState causes the server to quit. + qtssSvrIsOutOfDescriptors = 9, //read //Bool16 //true if the server has run out of file descriptors, false otherwise + qtssRTSPCurrentSessionCount = 10, //read //UInt32 //Current number of connected clients over standard RTSP + qtssRTSPHTTPCurrentSessionCount = 11, //read //UInt32 //Current number of connected clients over RTSP / HTTP + + qtssRTPSvrNumUDPSockets = 12, //read //UInt32 //Number of UDP sockets currently being used by the server + qtssRTPSvrCurConn = 13, //read //UInt32 //Number of clients currently connected to the server + qtssRTPSvrTotalConn = 14, //read //UInt32 //Total number of clients since startup + qtssRTPSvrCurBandwidth = 15, //read //UInt32 //Current bandwidth being output by the server in bits per second + qtssRTPSvrTotalBytes = 16, //read //UInt64 //Total number of bytes served since startup + qtssRTPSvrAvgBandwidth = 17, //read //UInt32 //Average bandwidth being output by the server in bits per second + qtssRTPSvrCurPackets = 18, //read //UInt32 //Current packets per second being output by the server + qtssRTPSvrTotalPackets = 19, //read //UInt64 //Total number of bytes served since startup + + qtssSvrHandledMethods = 20, //r/w //QTSS_RTSPMethod //The methods that the server supports. Modules should append the methods they support to this attribute in their QTSS_Initialize_Role. + qtssSvrModuleObjects = 21, //read // this IS PREMPTIVE SAFE! //QTSS_ModuleObject // A module object representing each module + qtssSvrStartupTime = 22, //read //QTSS_TimeVal //Time the server started up + qtssSvrGMTOffsetInHrs = 23, //read //SInt32 //Server time zone (offset from GMT in hours) + qtssSvrDefaultIPAddrStr = 24, //read //char array //The "default" IP address of the server as a string + + qtssSvrPreferences = 25, //read //QTSS_PrefsObject // An object representing each the server's preferences + qtssSvrMessages = 26, //read //QTSS_Object // An object containing the server's error messages. + qtssSvrClientSessions = 27, //read //QTSS_Object // An object containing all client sessions stored as indexed QTSS_ClientSessionObject(s). + qtssSvrCurrentTimeMilliseconds = 28, //read //QTSS_TimeVal //Server's current time in milliseconds. Retrieving this attribute is equivalent to calling QTSS_Milliseconds + qtssSvrCPULoadPercent = 29, //read //Float32 //Current % CPU being used by the server + + qtssSvrNumReliableUDPBuffers = 30, //read //UInt32 //Number of buffers currently allocated for UDP retransmits + qtssSvrReliableUDPWastageInBytes= 31, //read //UInt32 //Amount of data in the reliable UDP buffers being wasted + qtssSvrConnectedUsers = 32, //r/w //QTSS_Object //List of connected user sessions (updated by modules for their sessions) + + qtssMP3SvrCurConn = 33, //r/w //UInt32 //Number of MP3 client sessions connected + qtssMP3SvrTotalConn = 34, //r/w //UInt32 //Total number of MP3 clients since startup + qtssMP3SvrCurBandwidth = 35, //r/w //UInt32 //Current MP3 bandwidth being output by the server in bits per second + qtssMP3SvrTotalBytes = 36, //r/w //UInt64 //Total number of MP3 bytes served since startup + qtssMP3SvrAvgBandwidth = 37, //r/w //UInt32 //Average MP3 bandwidth being output by the server in bits per second + + qtssSvrServerBuild = 38, //read //char array //build of the server + qtssSvrServerPlatform = 39, //read //char array //Platform (OS) of the server + qtssSvrRTSPServerComment = 40, //read //char array //RTSP comment for the server header + qtssSvrNumThinned = 41, //read //SInt32 //Number of thinned sessions + qtssSvrNumThreads = 42, //read //UInt32 //Number of task threads // see also qtssPrefsRunNumThreads + qtssSvrNumParams = 43 +}; +typedef UInt32 QTSS_ServerAttributes; + +enum +{ + //QTSS_PrefsObject parameters + + // Valid in all methods. None of these are pre-emptive safe, so the version + // of QTSS_GetAttribute that copies data must be used. + + // All of these parameters are read-write. + + qtssPrefsRTSPTimeout = 0, //"rtsp_timeout" //UInt32 //RTSP timeout in seconds sent to the client. + qtssPrefsRealRTSPTimeout = 1, //"real_rtsp_timeout" //UInt32 //Amount of time in seconds the server will wait before disconnecting idle RTSP clients. 0 means no timeout + qtssPrefsRTPTimeout = 2, //"rtp_timeout" //UInt32 //Amount of time in seconds the server will wait before disconnecting idle RTP clients. 0 means no timeout + qtssPrefsMaximumConnections = 3, //"maximum_connections" //SInt32 //Maximum # of concurrent RTP connections allowed by the server. -1 means unlimited. + qtssPrefsMaximumBandwidth = 4, //"maximum_bandwidth" //SInt32 //Maximum amt of bandwidth the server is allowed to serve in K bits. -1 means unlimited. + qtssPrefsMovieFolder = 5, //"movie_folder" //char array //Path to the root movie folder + qtssPrefsRTSPIPAddr = 6, //"bind_ip_addr" //char array //IP address the server should accept RTSP connections on. 0.0.0.0 means all addresses on the machine. + qtssPrefsBreakOnAssert = 7, //"break_on_assert" //Bool16 //If true, the server will break in the debugger when an assert fails. + qtssPrefsAutoRestart = 8, //"auto_restart" //Bool16 //If true, the server will automatically restart itself if it crashes. + qtssPrefsTotalBytesUpdate = 9, //"total_bytes_update" //UInt32 //Interval in seconds between updates of the server's total bytes and current bandwidth statistics + qtssPrefsAvgBandwidthUpdate = 10, //"average_bandwidth_update" //UInt32 //Interval in seconds between computations of the server's average bandwidth + qtssPrefsSafePlayDuration = 11, //"safe_play_duration" //UInt32 //Hard to explain... see streamingserver.conf + qtssPrefsModuleFolder = 12, //"module_folder" //char array //Path to the module folder + + // There is a compiled-in error log module that loads before all the other modules + // (so it can log errors from the get-go). It uses these prefs. + + qtssPrefsErrorLogName = 13, //"error_logfile_name" //char array //Name of error log file + qtssPrefsErrorLogDir = 14, //"error_logfile_dir" //char array //Path to error log file directory + qtssPrefsErrorRollInterval = 15, //"error_logfile_interval" //UInt32 //Interval in days between error logfile rolls + qtssPrefsMaxErrorLogSize = 16, //"error_logfile_size" //UInt32 //Max size in bytes of the error log + qtssPrefsErrorLogVerbosity = 17, //"error_logfile_verbosity" //UInt32 //Max verbosity level of messages the error logger will log + qtssPrefsScreenLogging = 18, //"screen_logging" //Bool16 //Should the error logger echo messages to the screen? + qtssPrefsErrorLogEnabled = 19, //"error_logging" //Bool16 //Is error logging enabled? + + qtssPrefsDropVideoAllPacketsDelayInMsec = 20, //"drop_all_video_delay" //SInt32 // Don't send video packets later than this + qtssPrefsStartThinningDelayInMsec = 21, //"start_thinning_delay" //SInt32 // lateness at which we might start thinning + qtssPrefsLargeWindowSizeInK = 22, //"large_window_size" // UInt32 //default size that will be used for high bitrate movies + qtssPrefsWindowSizeThreshold = 23, //"window_size_threshold" // UInt32 //bitrate at which we switch to larger window size + + qtssPrefsMinTCPBufferSizeInBytes = 24, //"min_tcp_buffer_size" //UInt32 // When streaming over TCP, this is the minimum size the TCP socket send buffer can be set to + qtssPrefsMaxTCPBufferSizeInBytes = 25, //"max_tcp_buffer_size" //UInt32 // When streaming over TCP, this is the maximum size the TCP socket send buffer can be set to + qtssPrefsTCPSecondsToBuffer = 26, //"tcp_seconds_to_buffer" //Float32 // When streaming over TCP, the size of the TCP send buffer is scaled based on the bitrate of the movie. It will fit all the data that gets sent in this amount of time. + + qtssPrefsDoReportHTTPConnectionAddress = 27, //"do_report_http_connection_ip_address" //Bool16 // when behind a round robin DNS, the client needs to be told the specific ip address of the maching handling its request. this pref tells the server to repot its IP address in the reply to the HTTP GET request when tunneling RTSP through HTTP + + qtssPrefsDefaultAuthorizationRealm = 28, // "default_authorization_realm" //char array // + + qtssPrefsRunUserName = 29, //"run_user_name" //char array //Run under this user's account + qtssPrefsRunGroupName = 30, //"run_group_name" //char array //Run under this group's account + + qtssPrefsSrcAddrInTransport = 31, //"append_source_addr_in_transport" // Bool16 //If true, the server will append the src address to the Transport header responses + qtssPrefsRTSPPorts = 32, //"rtsp_ports" // UInt16 + + qtssPrefsMaxRetransDelayInMsec = 33, //"max_retransmit_delay" // UInt32 //maximum interval between when a retransmit is supposed to be sent and when it actually gets sent. Lower values means smoother flow but slower server performance + qtssPrefsSmallWindowSizeInK = 34, //"small_window_size" // UInt32 //default size that will be used for low bitrate movies + qtssPrefsAckLoggingEnabled = 35, //"ack_logging_enabled" // Bool16 //Debugging only: turns on detailed logging of UDP acks / retransmits + qtssPrefsRTCPPollIntervalInMsec = 36, //"rtcp_poll_interval" // UInt32 //interval (in Msec) between poll for RTCP packets + qtssPrefsRTCPSockRcvBufSizeInK = 37, //"rtcp_rcv_buf_size" // UInt32 //Size of the receive socket buffer for udp sockets used to receive rtcp packets + qtssPrefsSendInterval = 38, //"send_interval" // UInt32 // + qtssPrefsThickAllTheWayDelayInMsec = 39, //"thick_all_the_way_delay" // UInt32 // + qtssPrefsAltTransportIPAddr = 40, //"alt_transport_src_ipaddr"// char //If empty, the server uses its own IP addr in the source= param of the transport header. Otherwise, it uses this addr. + qtssPrefsMaxAdvanceSendTimeInSec = 41, //"max_send_ahead_time" // UInt32 //This is the farthest in advance the server will send a packet to a client that supports overbuffering. + qtssPrefsReliableUDPSlowStart = 42, //"reliable_udp_slow_start" // Bool16 //Is reliable UDP slow start enabled? + qtssPrefsAutoDeleteSDPFiles = 43, //"auto_delete_sdp_files" // Bool16 //SDP files in the Movies directory tree are deleted after a Broadcaster's RTSP controlled SDP session ends. + qtssPrefsAuthenticationScheme = 44, //"authentication_scheme" // char //Set this to be the authentication scheme you want the server to use. "basic", "digest", and "none" are the currently supported values + qtssPrefsDeleteSDPFilesInterval = 45, //"sdp_file_delete_interval_seconds" //UInt32 //Feature rem + qtssPrefsAutoStart = 46, //"auto_start" //Bool16 //If true, streaming server likes to be started at system startup + qtssPrefsReliableUDP = 47, //"reliable_udp" //Bool16 //If true, uses reliable udp transport if requested by the client + qtssPrefsReliableUDPDirs = 48, //"reliable_udp_dirs" //CharArray + qtssPrefsReliableUDPPrintfs = 49, //"reliable_udp_printfs" //Bool16 //If enabled, server prints out interesting statistics for the reliable UDP clients + + qtssPrefsDropAllPacketsDelayInMsec = 50, //"drop_all_packets_delay" // SInt32 // don't send any packets later than this + qtssPrefsThinAllTheWayDelayInMsec = 51, //"thin_all_the_way_delay" // SInt32 // thin to key frames + qtssPrefsAlwaysThinDelayInMsec = 52, //"always_thin_delay" // SInt32 // we always start to thin at this point + qtssPrefsStartThickingDelayInMsec = 53, //"start_thicking_delay" // SInt32 // maybe start thicking at this point + qtssPrefsQualityCheckIntervalInMsec = 54, //"quality_check_interval" // UInt32 // adjust thinnning params this often + qtssPrefsEnableRTSPErrorMessage = 55, //"RTSP_error_message" //Bool16 // Appends a content body string error message for reported RTSP errors. + qtssPrefsEnableRTSPDebugPrintfs = 56, //"RTSP_debug_printfs" //Boo1l6 // printfs incoming RTSPRequests and Outgoing RTSP responses. + + qtssPrefsEnableMonitorStatsFile = 57, //"enable_monitor_stats_file" //Bool16 //write server stats to the monitor file + qtssPrefsMonitorStatsFileIntervalSec = 58, //"monitor_stats_file_interval_seconds" // private + qtssPrefsMonitorStatsFileName = 59, //"monitor_stats_file_name" // private + + qtssPrefsEnablePacketHeaderPrintfs = 60, // "enable_packet_header_printfs" //Bool16 // RTP and RTCP printfs of outgoing packets. + qtssPrefsPacketHeaderPrintfOptions = 61, // "packet_header_printf_options" //char //set of printfs to print. Form is [text option] [;] default is "rtp;rr;sr;". This means rtp packets, rtcp sender reports, and rtcp receiver reports. + qtssPrefsOverbufferRate = 62, // "overbuffer_rate" //Float32 + qtssPrefsMediumWindowSizeInK = 63, // "medium_window_size" // UInt32 //default size that will be used for medium bitrate movies + qtssPrefsWindowSizeMaxThreshold = 64, //"window_size_threshold" // UInt32 //bitrate at which we switch from medium to large window size + qtssPrefsEnableRTSPServerInfo = 65, //"RTSP_server_info" //Boo1l6 // Adds server info to the RTSP responses. + qtssPrefsRunNumThreads = 66, //"run_num_threads" //UInt32 // if value is non-zero, will create that many task threads; otherwise a thread will be created for each processor + qtssPrefsPidFile = 67, //"pid_file" //Char Array //path to pid file + qtssPrefsCloseLogsOnWrite = 68, // "force_logs_close_on_write" //Bool16 // force log files to close after each write. + qtssPrefsDisableThinning = 69, // "disable_thinning" //Bool16 // Usually used for performance testing. Turn off stream thinning from packet loss or stream lateness. + qtssPrefsPlayersReqRTPHeader = 70, // "player_requires_rtp_header_info" //Char array //name of player to match against the player's user agent header + qtssPrefsPlayersReqBandAdjust = 71, // "player_requires_bandwidth_adjustment //Char array //name of player to match against the player's user agent header + qtssPrefsPlayersReqNoPauseTimeAdjust = 72, // "player_requires_no_pause_time_adjustment //Char array //name of player to match against the player's user agent header + qtssPrefsEnable3gppProtocol = 73, // "enable_3gpp_protocol //Bool16 //enable or disable 3gpp release 6 protocol support featues + qtssPrefsEnable3gppProtocolRateAdapt = 74, // "enable_3gpp_protocol_rate_adaptation //Bool16 //enable or disable 3gpp release 6 rate adaptation featues + qtssPrefs3gppRateAdaptReportFrequency = 75, // "3gpp_protocol_rate_adaptation_report_frequency //UInt16 //requested rate adaptation rtcp report frequency + qtssPrefsDefaultStreamQuality = 76, // "default_stream_quality //UInt16 //0 is all day and best quality. Higher values are worse maximum depends on the media and the media module + qtssPrefsPlayersReqRTPStartTimeAdjust = 77, // "player_requires_rtp_start_time_adjust" //Char Array //name of players to match against the player's user agent header + qtssPrefsEnable3gppDebugPrintfs = 78, // "enable_3gpp_debug_printfs" //Boo1l6 // 3gpp rate adaptation state and debugging printfs. + qtssPrefsEnableUDPMonitor = 79, // "enable_udp_monitor_stream" //Boo1l6 // reflect all udp streams to the monitor ports, use an sdp to view + qtssPrefsUDPMonitorAudioPort = 80, // "udp_monitor_video_port" //UInt16 // localhost destination port of reflected stream + qtssPrefsUDPMonitorVideoPort = 81, // "udp_monitor_audio_port" //UInt16 // localhost destination port of reflected stream + qtssPrefsUDPMonitorDestIPAddr = 82, // "udp_monitor_dest_ip" //char array //IP address the server should send RTP monitor reflected streams. + qtssPrefsUDPMonitorSourceIPAddr = 83, // "udp_monitor_src_ip" //char array //client IP address the server monitor should reflect. *.*.*.* means all client addresses. + qtssPrefsEnableAllowGuestDefault = 84, // "enable_allow_guest_authorize_default" //Boo1l6 // server hint to access modules to allow guest access as the default (can be overriden in a qtaccess file or other means) + qtssPrefsNumRTSPThreads = 85, // "run_num_rtsp_threads" //UInt32 // if value is non-zero, the server will create that many task threads; otherwise a single thread will be created. + qtssPrefsPlayersReqDisable3gppRateAdapt = 86, // "player_requires_disable_3gpp_rate_adapt" //Char array //name of players to match against the player's user agent header + qtssPrefsPlayersReq3GPPTargetTime = 87, // "player_requires_3gpp_target_time" //Char array //name of player to set the target time for + qtssPrefs3GPPTargetTime = 88, // "3gpp_target_time_milliseconds" //UInt32 // milliseconds set as the target time. + qtssPrefsPlayersReqDisableThinning = 89, // "player_requires_disable_thinning" //Char array //name of player to set the target time for + + qtssPrefsNumParams = 90 +}; + +typedef UInt32 QTSS_PrefsAttributes; + +enum +{ + //QTSS_TextMessagesObject parameters + + // All of these parameters are read-only, char*'s, and preemptive-safe. + + qtssMsgNoMessage = 0, //"NoMessage" + qtssMsgNoURLInRequest = 1, + qtssMsgBadRTSPMethod = 2, + qtssMsgNoRTSPVersion = 3, + qtssMsgNoRTSPInURL = 4, + qtssMsgURLTooLong = 5, + qtssMsgURLInBadFormat = 6, + qtssMsgNoColonAfterHeader = 7, + qtssMsgNoEOLAfterHeader = 8, + qtssMsgRequestTooLong = 9, + qtssMsgNoModuleFolder = 10, + qtssMsgCouldntListen = 11, + qtssMsgInitFailed = 12, + qtssMsgNotConfiguredForIP = 13, + qtssMsgDefaultRTSPAddrUnavail = 14, + qtssMsgBadModule = 15, + qtssMsgRegFailed = 16, + qtssMsgRefusingConnections = 17, + qtssMsgTooManyClients = 18, + qtssMsgTooMuchThruput = 19, + qtssMsgNoSessionID = 20, + qtssMsgFileNameTooLong = 21, + qtssMsgNoClientPortInTransport = 22, + qtssMsgRTPPortMustBeEven = 23, + qtssMsgRTCPPortMustBeOneBigger = 24, + qtssMsgOutOfPorts = 25, + qtssMsgNoModuleForRequest = 26, + qtssMsgAltDestNotAllowed = 27, + qtssMsgCantSetupMulticast = 28, + qtssListenPortInUse = 29, + qtssListenPortAccessDenied = 30, + qtssListenPortError = 31, + qtssMsgBadBase64 = 32, + qtssMsgSomePortsFailed = 33, + qtssMsgNoPortsSucceeded = 34, + qtssMsgCannotCreatePidFile = 35, + qtssMsgCannotSetRunUser = 36, + qtssMsgCannotSetRunGroup = 37, + qtssMsgNoSesIDOnDescribe = 38, + qtssServerPrefMissing = 39, + qtssServerPrefWrongType = 40, + qtssMsgCantWriteFile = 41, + qtssMsgSockBufSizesTooLarge = 42, + qtssMsgBadFormat = 43, + qtssMsgNumParams = 44 + +}; +typedef UInt32 QTSS_TextMessagesAttributes; + +enum +{ + //QTSS_FileObject parameters + + // All of these parameters are preemptive-safe. + + qtssFlObjStream = 0, // read // QTSS_FileStream. Stream ref for this file object + qtssFlObjFileSysModuleName = 1, // read // char array. Name of the file system module handling this file object + qtssFlObjLength = 2, // r/w // UInt64. Length of the file + qtssFlObjPosition = 3, // read // UInt64. Current position of the file pointer in the file. + qtssFlObjModDate = 4, // r/w // QTSS_TimeVal. Date & time of last modification + + qtssFlObjNumParams = 5 +}; +typedef UInt32 QTSS_FileObjectAttributes; + +enum +{ + //QTSS_ModuleObject parameters + + qtssModName = 0, //read //preemptive-safe //char array //Module name. + qtssModDesc = 1, //r/w //not preemptive-safe //char array //Text description of what the module does + qtssModVersion = 2, //r/w //not preemptive-safe //UInt32 //Version of the module. UInt32 format should be 0xMM.m.v.bbbb M=major version m=minor version v=very minor version b=build # + qtssModRoles = 3, //read //preemptive-safe //QTSS_Role //List of all the roles this module has registered for. + qtssModPrefs = 4, //read //preemptive-safe //QTSS_ModulePrefsObject //An object containing as attributes the preferences for this module + qtssModAttributes = 5, //read //preemptive-safe //QTSS_Object + + qtssModNumParams = 6 +}; +typedef UInt32 QTSS_ModuleObjectAttributes; + +enum +{ + //QTSS_AttrInfoObject parameters + + // All of these parameters are preemptive-safe. + + qtssAttrName = 0, //read //char array //Attribute name + qtssAttrID = 1, //read //QTSS_AttributeID //Attribute ID + qtssAttrDataType = 2, //read //QTSS_AttrDataType //Data type + qtssAttrPermissions = 3, //read //QTSS_AttrPermission //Permissions + + qtssAttrInfoNumParams = 4 +}; +typedef UInt32 QTSS_AttrInfoObjectAttributes; + +enum +{ + //QTSS_UserProfileObject parameters + + // All of these parameters are preemptive-safe. + + qtssUserName = 0, //read //char array + qtssUserPassword = 1, //r/w //char array + qtssUserGroups = 2, //r/w //char array - multi-valued attribute, all values should be C strings padded with \0s to // make them all of the same length + qtssUserRealm = 3, //r/w //char array - the authentication realm for username + qtssUserRights = 4, //r/w //QTSS_AttrRights - rights granted this user + qtssUserExtendedRights = 5, //r/w //qtssAttrDataTypeCharArray - a list of strings with extended rights granted to the user. + qtssUserQTSSExtendedRights = 6, //r/w //qtssAttrDataTypeCharArray - a private list of strings with extended rights granted to the user and reserved by QTSS/Apple. + qtssUserNumParams = 7, +}; +typedef UInt32 QTSS_UserProfileObjectAttributes; + +enum +{ + //QTSS_ConnectedUserObject parameters + + //All of these are preemptive safe + + qtssConnectionType = 0, //read //char array // type of user connection (e.g. "RTP reflected" or "MP3") + qtssConnectionCreateTimeInMsec = 1, //read //QTSS_TimeVal //Time in milliseconds the session was created. + qtssConnectionTimeConnectedInMsec = 2, //read //QTSS_TimeVal //Time in milliseconds the session was created. + qtssConnectionBytesSent = 3, //read //UInt32 //Number of RTP bytes sent so far on this session. + qtssConnectionMountPoint = 4, //read //char array //Presentation URL for this session. This URL is the "base" URL for the session. RTSP requests to this URL are assumed to affect all streams on the session. + qtssConnectionHostName = 5, //read //char array //host name for this request + + qtssConnectionSessRemoteAddrStr = 6, //read //char array //IP address addr of client, in dotted-decimal format. + qtssConnectionSessLocalAddrStr = 7, //read //char array //Ditto, in dotted-decimal format. + + qtssConnectionCurrentBitRate = 8, //read //UInt32 //Current bit rate of all the streams on this session. This is not an average. In bits per second. + qtssConnectionPacketLossPercent = 9, //read //Float32 //Current percent loss as a fraction. .5 = 50%. This is not an average. + + qtssConnectionTimeStorage = 10, //read //QTSS_TimeVal //Internal, use qtssConnectionTimeConnectedInMsec above + + qtssConnectionNumParams = 11 +}; +typedef UInt32 QTSS_ConnectedUserObjectAttributes; + + +/********************************************************************/ +// QTSS API ROLES +// +// Each role represents a unique situation in which a module may be +// invoked. Modules must specify which roles they want to be invoked for. + +enum +{ + //Global + QTSS_Register_Role = FOUR_CHARS_TO_INT('r', 'e', 'g', ' '), //reg //All modules get this once at startup + QTSS_Initialize_Role = FOUR_CHARS_TO_INT('i', 'n', 'i', 't'), //init //Gets called once, later on in the startup process + QTSS_Shutdown_Role = FOUR_CHARS_TO_INT('s', 'h', 'u', 't'), //shut //Gets called once at shutdown + + QTSS_ErrorLog_Role = FOUR_CHARS_TO_INT('e', 'l', 'o', 'g'), //elog //This gets called when the server wants to log an error. + QTSS_RereadPrefs_Role = FOUR_CHARS_TO_INT('p', 'r', 'e', 'f'), //pref //This gets called when the server rereads preferences. + QTSS_StateChange_Role = FOUR_CHARS_TO_INT('s', 't', 'a', 't'), //stat //This gets called whenever the server changes state. + + QTSS_Interval_Role = FOUR_CHARS_TO_INT('t', 'i', 'm', 'r'), //timr //This gets called whenever the module's interval timer times out calls. + + //RTSP-specific + QTSS_RTSPFilter_Role = FOUR_CHARS_TO_INT('f', 'i', 'l', 't'), //filt //Filter all RTSP requests before the server parses them + QTSS_RTSPRoute_Role = FOUR_CHARS_TO_INT('r', 'o', 'u', 't'), //rout //Route all RTSP requests to the correct root folder. + QTSS_RTSPAuthenticate_Role = FOUR_CHARS_TO_INT('a', 't', 'h', 'n'), //athn //Authenticate the RTSP request username. + QTSS_RTSPAuthorize_Role = FOUR_CHARS_TO_INT('a', 'u', 't', 'h'), //auth //Authorize RTSP requests to proceed + QTSS_RTSPPreProcessor_Role = FOUR_CHARS_TO_INT('p', 'r', 'e', 'p'), //prep //Pre-process all RTSP requests before the server responds. + //Modules may opt to "steal" the request and return a client response. + QTSS_RTSPRequest_Role = FOUR_CHARS_TO_INT('r', 'e', 'q', 'u'), //requ //Process an RTSP request & send client response + QTSS_RTSPPostProcessor_Role = FOUR_CHARS_TO_INT('p', 'o', 's', 't'), //post //Post-process all RTSP requests + QTSS_RTSPSessionClosing_Role = FOUR_CHARS_TO_INT('s', 'e', 's', 'c'), //sesc //RTSP session is going away + + QTSS_RTSPIncomingData_Role = FOUR_CHARS_TO_INT('i', 'c', 'm', 'd'), //icmd //Incoming interleaved RTP data on this RTSP connection + + //RTP-specific + QTSS_RTPSendPackets_Role = FOUR_CHARS_TO_INT('s', 'e', 'n', 'd'), //send //Send RTP packets to the client + QTSS_ClientSessionClosing_Role = FOUR_CHARS_TO_INT('d', 'e', 's', 's'), //dess //Client session is going away + + //RTCP-specific + QTSS_RTCPProcess_Role = FOUR_CHARS_TO_INT('r', 't', 'c', 'p'), //rtcp //Process all RTCP packets sent to the server + + //File system roles + QTSS_OpenFilePreProcess_Role = FOUR_CHARS_TO_INT('o', 'p', 'p', 'r'), //oppr + QTSS_OpenFile_Role = FOUR_CHARS_TO_INT('o', 'p', 'f', 'l'), //opfl + QTSS_AdviseFile_Role = FOUR_CHARS_TO_INT('a', 'd', 'f', 'l'), //adfl + QTSS_ReadFile_Role = FOUR_CHARS_TO_INT('r', 'd', 'f', 'l'), //rdfl + QTSS_CloseFile_Role = FOUR_CHARS_TO_INT('c', 'l', 'f', 'l'), //clfl + QTSS_RequestEventFile_Role = FOUR_CHARS_TO_INT('r', 'e', 'f', 'l'), //refl + +}; +typedef UInt32 QTSS_Role; + + +//***********************************************/ +// TYPEDEFS + +typedef void* QTSS_StreamRef; +typedef void* QTSS_Object; +typedef void* QTSS_ServiceFunctionArgsPtr; +typedef SInt32 QTSS_AttributeID; +typedef SInt32 QTSS_ServiceID; +typedef SInt64 QTSS_TimeVal; + +typedef QTSS_Object QTSS_RTPStreamObject; +typedef QTSS_Object QTSS_RTSPSessionObject; +typedef QTSS_Object QTSS_RTSPRequestObject; +typedef QTSS_Object QTSS_RTSPHeaderObject; +typedef QTSS_Object QTSS_ClientSessionObject; +typedef QTSS_Object QTSS_ServerObject; +typedef QTSS_Object QTSS_PrefsObject; +typedef QTSS_Object QTSS_TextMessagesObject; +typedef QTSS_Object QTSS_FileObject; +typedef QTSS_Object QTSS_ModuleObject; +typedef QTSS_Object QTSS_ModulePrefsObject; +typedef QTSS_Object QTSS_AttrInfoObject; +typedef QTSS_Object QTSS_UserProfileObject; +typedef QTSS_Object QTSS_ConnectedUserObject; + +typedef QTSS_Object QTSS_3GPPStreamObject; +typedef QTSS_Object QTSS_3GPPClientSessionObject; +typedef QTSS_Object QTSS_3GPPRTSPSessionObject; +typedef QTSS_Object QTSS_3GPPRequestObject; + +typedef QTSS_StreamRef QTSS_ErrorLogStream; +typedef QTSS_StreamRef QTSS_FileStream; +typedef QTSS_StreamRef QTSS_RTSPSessionStream; +typedef QTSS_StreamRef QTSS_RTSPRequestStream; +typedef QTSS_StreamRef QTSS_RTPStreamStream; +typedef QTSS_StreamRef QTSS_SocketStream; + +typedef QTSS_RTSPStatusCode QTSS_SessionStatusCode; + +//***********************************************/ +// ROLE PARAMETER BLOCKS +// +// Each role has a unique set of parameters that get passed +// to the module. + +typedef struct +{ + char outModuleName[QTSS_MAX_MODULE_NAME_LENGTH]; +} QTSS_Register_Params; + +typedef struct +{ + QTSS_ServerObject inServer; // Global dictionaries + QTSS_PrefsObject inPrefs; + QTSS_TextMessagesObject inMessages; + QTSS_ErrorLogStream inErrorLogStream; // Writing to this stream causes modules to + // be invoked in the QTSS_ErrorLog_Role + QTSS_ModuleObject inModule; +} QTSS_Initialize_Params; + +typedef struct +{ + QTSS_ErrorVerbosity inVerbosity; + char* inBuffer; + +} QTSS_ErrorLog_Params; + +typedef struct +{ + QTSS_ServerState inNewState; +} QTSS_StateChange_Params; + +typedef struct +{ + QTSS_RTSPSessionObject inRTSPSession; + QTSS_RTSPRequestObject inRTSPRequest; + QTSS_RTSPHeaderObject inRTSPHeaders; + QTSS_ClientSessionObject inClientSession; + +} QTSS_StandardRTSP_Params; + +typedef struct +{ + QTSS_RTSPSessionObject inRTSPSession; + QTSS_RTSPRequestObject inRTSPRequest; + char** outNewRequest; + +} QTSS_Filter_Params; + +typedef struct +{ + QTSS_RTSPRequestObject inRTSPRequest; +} QTSS_RTSPAuth_Params; + +typedef struct +{ + QTSS_RTSPSessionObject inRTSPSession; + QTSS_ClientSessionObject inClientSession; + char* inPacketData; + UInt32 inPacketLen; + +} QTSS_IncomingData_Params; + +typedef struct +{ + QTSS_RTSPSessionObject inRTSPSession; +} QTSS_RTSPSession_Params; + +typedef struct +{ + QTSS_ClientSessionObject inClientSession; + QTSS_TimeVal inCurrentTime; + QTSS_TimeVal outNextPacketTime; +} QTSS_RTPSendPackets_Params; + +typedef struct +{ + QTSS_ClientSessionObject inClientSession; + QTSS_CliSesClosingReason inReason; +} QTSS_ClientSessionClosing_Params; + +typedef struct +{ + QTSS_ClientSessionObject inClientSession; + QTSS_RTPStreamObject inRTPStream; + void* inRTCPPacketData; + UInt32 inRTCPPacketDataLen; +} QTSS_RTCPProcess_Params; + +typedef struct +{ + char* inPath; + QTSS_OpenFileFlags inFlags; + QTSS_Object inFileObject; +} QTSS_OpenFile_Params; + +typedef struct +{ + QTSS_Object inFileObject; + UInt64 inPosition; + UInt32 inSize; +} QTSS_AdviseFile_Params; + +typedef struct +{ + QTSS_Object inFileObject; + UInt64 inFilePosition; + void* ioBuffer; + UInt32 inBufLen; + UInt32* outLenRead; +} QTSS_ReadFile_Params; + +typedef struct +{ + QTSS_Object inFileObject; +} QTSS_CloseFile_Params; + +typedef struct +{ + QTSS_Object inFileObject; + QTSS_EventType inEventMask; +} QTSS_RequestEventFile_Params; + +typedef union +{ + QTSS_Register_Params regParams; + QTSS_Initialize_Params initParams; + QTSS_ErrorLog_Params errorParams; + QTSS_StateChange_Params stateChangeParams; + + QTSS_Filter_Params rtspFilterParams; + QTSS_IncomingData_Params rtspIncomingDataParams; + QTSS_StandardRTSP_Params rtspRouteParams; + QTSS_RTSPAuth_Params rtspAthnParams; + QTSS_StandardRTSP_Params rtspAuthParams; + QTSS_StandardRTSP_Params rtspPreProcessorParams; + QTSS_StandardRTSP_Params rtspRequestParams; + QTSS_StandardRTSP_Params rtspPostProcessorParams; + QTSS_RTSPSession_Params rtspSessionClosingParams; + + QTSS_RTPSendPackets_Params rtpSendPacketsParams; + QTSS_ClientSessionClosing_Params clientSessionClosingParams; + QTSS_RTCPProcess_Params rtcpProcessParams; + + QTSS_OpenFile_Params openFilePreProcessParams; + QTSS_OpenFile_Params openFileParams; + QTSS_AdviseFile_Params adviseFileParams; + QTSS_ReadFile_Params readFileParams; + QTSS_CloseFile_Params closeFileParams; + QTSS_RequestEventFile_Params reqEventFileParams; + +} QTSS_RoleParams, *QTSS_RoleParamPtr; + +typedef struct +{ + void* packetData; + QTSS_TimeVal packetTransmitTime; + QTSS_TimeVal suggestedWakeupTime; +} QTSS_PacketStruct; + + +/********************************************************************/ +// ENTRYPOINTS & FUNCTION TYPEDEFS + +// MAIN ENTRYPOINT FOR MODULES +// +// Every QTSS API must implement two functions: a main entrypoint, and a dispatch +// function. The main entrypoint gets called by the server at startup to do some +// initialization. Your main entrypoint must follow the convention established below +// +// QTSS_Error mymodule_main(void* inPrivateArgs) +// { +// return _stublibrary_main(inPrivateArgs, MyDispatchFunction); +// } +// +// + +typedef QTSS_Error (*QTSS_MainEntryPointPtr)(void* inPrivateArgs); +typedef QTSS_Error (*QTSS_DispatchFuncPtr)(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock); + +// STUB LIBRARY MAIN +QTSS_Error _stublibrary_main(void* inPrivateArgs, QTSS_DispatchFuncPtr inDispatchFunc); + +/********************************************************************/ +// QTSS_New +// QTSS_Delete +// +// These should be used for all dynamic memory allocation done from +// within modules. The memoryIdentifier is used for debugging: +// the server can track this memory to make memory leak debugging easier. +void* QTSS_New(FourCharCode inMemoryIdentifier, UInt32 inSize); +void QTSS_Delete(void* inMemory); + +/********************************************************************/ +// QTSS_Milliseconds +// +// The server maintains a millisecond timer internally. The current +// value of that timer can be obtained from this function. This value +// is not valid between server executions. +// +// All millisecond values used in QTSS API use this timer, unless otherwise noted +QTSS_TimeVal QTSS_Milliseconds(); + + +/********************************************************************/ +// QTSS_MilliSecsTo1970Secs +// +// Convert milliseconds from the QTSS_Milliseconds call to +// second's since 1970 +// +time_t QTSS_MilliSecsTo1970Secs(QTSS_TimeVal inQTSS_MilliSeconds); + +/********************************************************************/ +// QTSS_AddRole +// +// Only available from QTSS_Initialize role. Call this for all the roles you +// would like your module to operate on. +// +// Returns: QTSS_NoErr +// QTSS_OutOfState: If this function isn't being called from the Register role +// QTSS_RequestFailed: If module is registering for the QTSS_RTSPRequest_Role +// and there already is such a module. +// QTSS_BadArgument: Registering for a nonexistent role. +QTSS_Error QTSS_AddRole(QTSS_Role inRole); + + +/*****************************************/ +// ATTRIBUTE / OBJECT CALLBACKS +// + + +/********************************************************************/ +// QTSS_LockObject +// +// Grabs the mutex for this object so that accesses to the objects attributes +// from other threads will block. Note that objects created through QTSS_CreateObjectValue +// will share a mutex with the parent object. +// +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: bad object +QTSS_Error QTSS_LockObject(QTSS_Object inObject); + +/********************************************************************/ +// QTSS_UnlockObject +// +// Releases the mutex for this object. +// +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: bad object +QTSS_Error QTSS_UnlockObject(QTSS_Object inObject); + +/********************************************************************/ +// QTSS_CreateObjectType +// +// Creates a new object type. Attributes can be added to this object type and then it can +// be passed into QTSS_CreateObjectValue. +// +// This may only be called from the QTSS_Register role. +// +// Returns: QTSS_NoErr +// QTSS_RequestFailed: Too many object types already exist. +QTSS_Error QTSS_CreateObjectType(QTSS_ObjectType* outType); + +/********************************************************************/ +// QTSS_AddStaticAttribute +// +// Adds a new static attribute to a predefined object type. All added attributes implicitly have +// qtssAttrModeRead, qtssAttrModeWrite, and qtssAttrModePreempSafe permissions. "inUnused" should +// always be NULL. Specify the data type and name of the attribute. +// +// This may only be called from the QTSS_Register role. +// +// Returns: QTSS_NoErr +// QTSS_OutOfState: If this function isn't being called from the Register role +// QTSS_BadArgument: Adding an attribute to a nonexistent object type, attribute +// name too long, or NULL arguments. +// QTSS_AttrNameExists: The name must be unique. +QTSS_Error QTSS_AddStaticAttribute( QTSS_ObjectType inObjectType, char* inAttrName, + void* inUnused, QTSS_AttrDataType inAttrDataType); + +/********************************************************************/ +// QTSS_AddInstanceAttribute +// +// Adds a new instance attribute to a predefined object type. All added attributes implicitly have +// qtssAttrModeRead, qtssAttrModeWrite, and qtssAttrModePreempSafe permissions. "inUnused" should +// always be NULL. Specify the data type and name of the attribute. +// +// This may be called at any time. +// +// Returns: QTSS_NoErr +// QTSS_OutOfState: If this function isn't being called from the Register role +// QTSS_BadArgument: Adding an attribute to a nonexistent object type, attribute +// name too long, or NULL arguments. +// QTSS_AttrNameExists: The name must be unique. +QTSS_Error QTSS_AddInstanceAttribute( QTSS_Object inObject, char* inAttrName, + void* inUnused, QTSS_AttrDataType inAttrDataType); + +/********************************************************************/ +// QTSS_RemoveInstanceAttribute +// +// Removes an existing instance attribute. This may be called at any time +// +// Returns: QTSS_NoErr +// QTSS_OutOfState: If this function isn't being called from the Register role +// QTSS_BadArgument: Bad object type. +// QTSS_AttrDoesntExist: Bad attribute ID +QTSS_Error QTSS_RemoveInstanceAttribute(QTSS_Object inObject, QTSS_AttributeID inID); + +/********************************************************************/ +// Getting attribute information +// +// The following callbacks allow modules to discover at runtime what +// attributes exist in which objects and object types, and discover +// all attribute meta-data + +/********************************************************************/ +// QTSS_IDForAttr +// +// Given an attribute name, this returns its accompanying attribute ID. +// The ID can in turn be used to retrieve the attribute value from +// a object. This callback applies only to static attributes +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_IDForAttr(QTSS_ObjectType inObjectType, const char* inAttributeName, + QTSS_AttributeID* outID); + +/********************************************************************/ +// QTSS_GetAttrInfoByID +// +// Searches for an attribute with the specified ID in the specified object. +// If found, this function returns a QTSS_AttrInfoObject describing the attribute. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument +// QTSS_AttrDoesntExist +QTSS_Error QTSS_GetAttrInfoByID(QTSS_Object inObject, QTSS_AttributeID inAttrID, + QTSS_AttrInfoObject* outAttrInfoObject); + +/********************************************************************/ +// QTSS_GetAttrInfoByName +// +// Searches for an attribute with the specified name in the specified object. +// If found, this function returns a QTSS_AttrInfoObject describing the attribute. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument +// QTSS_AttrDoesntExist +QTSS_Error QTSS_GetAttrInfoByName(QTSS_Object inObject, char* inAttrName, + QTSS_AttrInfoObject* outAttrInfoObject); + +/********************************************************************/ +// QTSS_GetAttrInfoByIndex +// +// Allows caller to iterate over all the attributes in the specified object. +// Returns a QTSS_AttrInfoObject for the attribute with the given index (0.. num attributes). +// +// Returns: QTSS_NoErr +// QTSS_BadArgument +// QTSS_AttrDoesntExist +QTSS_Error QTSS_GetAttrInfoByIndex(QTSS_Object inObject, UInt32 inIndex, + QTSS_AttrInfoObject* outAttrInfoObject); + +/********************************************************************/ +// QTSS_GetNumAttributes +// +// Returns the number of attributes in the specified object. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// +QTSS_Error QTSS_GetNumAttributes (QTSS_Object inObject, UInt32* outNumAttributes); + +/********************************************************************/ +// QTSS_GetValuePtr +// +// NOT TO BE USED WITH NON-PREEMPTIVE-SAFE attributes (or provide your own locking +// using QTSS_LockObject). +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_NotPreemptiveSafe: Attempt to get a non-preemptive safe attribute +// QTSS_BadIndex: Attempt to get non-existent index. +QTSS_Error QTSS_GetValuePtr (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex, + void** outBuffer, UInt32* outLen); + +/********************************************************************/ +// QTSS_GetValue +// +// Copies the data into provided buffer. If QTSS_NotEnoughSpace is returned, outLen is still set. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_NotEnoughSpace: Value is too big for buffer provided. +// QTSS_BadIndex: Attempt to get non-existent index. +QTSS_Error QTSS_GetValue (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex, + void* ioBuffer, UInt32* ioLen); + +/********************************************************************/ +// QTSS_GetValueAsString +// +// Returns the specified attribute converted to a C-string. This call allocates +// memory for the string which should be disposed of using QTSS_Delete. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_BadIndex: Attempt to get non-existent index. +QTSS_Error QTSS_GetValueAsString (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex, + char** outString); + + +/********************************************************************/ +// QTSS_TypeStringToType +// QTSS_TypeToTypeString +// +// Returns a text name for the specified QTSS_AttrDataType, or vice-versa +// +// Returns: QTSS_NoErr +// QTSS_BadArgument +QTSS_Error QTSS_TypeStringToType(const char* inTypeString, QTSS_AttrDataType* outType); +QTSS_Error QTSS_TypeToTypeString(const QTSS_AttrDataType inType, char** outTypeString); + + +/********************************************************************/ +// QTSS_StringToValue +// +// Given a C-string and a QTSS_AttrDataType, this function converts the C-string +// to the specified type and puts the result in ioBuffer. ioBuffer must be allocated +// by the caller and must be big enough to contain the converted value. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_NotEnoughSpace: Value is too big for buffer provided. +// +// QTSS_ValueToString +// +// Given a buffer containing a value of the specified type, this function converts +// the value to a C-string. This string is allocated internally and must be disposed of +// using QTSS_Delete +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_StringToValue(const char* inValueAsString, const QTSS_AttrDataType inType, void* ioBuffer, UInt32* ioBufSize); +QTSS_Error QTSS_ValueToString(const void* inValue, const UInt32 inValueLen, const QTSS_AttrDataType inType, char** outString); + +/********************************************************************/ +// QTSS_SetValue +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_ReadOnly: Attribute is read only. +// QTSS_BadIndex: Attempt to set non-0 index of attribute with a param retrieval function. +// +QTSS_Error QTSS_SetValue (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex, const void* inBuffer, UInt32 inLen); + +/********************************************************************/ +// QTSS_SetValuePtr +// +// This allows you to have an attribute that simply reflects the value of a variable in your module. +// If the update to this variable is not atomic, you should protect updates using QTSS_LockObject. +// This can't be used with indexed attributes. Make sure the inBuffer provided exists as long as this +// attribute exists. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_ReadOnly: Attribute is read only. +// +QTSS_Error QTSS_SetValuePtr (QTSS_Object inObject, QTSS_AttributeID inID, const void* inBuffer, UInt32 inLen); + +/********************************************************************/ +// QTSS_CreateObjectValue +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_ReadOnly: Attribute is read only. +// +QTSS_Error QTSS_CreateObjectValue (QTSS_Object inObject, QTSS_AttributeID inID, QTSS_ObjectType inType, UInt32* outIndex, QTSS_Object* outCreatedObject); + +/********************************************************************/ +// QTSS_GetNumValues +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// +QTSS_Error QTSS_GetNumValues (QTSS_Object inObject, QTSS_AttributeID inID, UInt32* outNumValues); + +/********************************************************************/ +// QTSS_RemoveValue +// +// This function removes the value with the specified index. If there +// are any values following this index, they will be reordered. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_ReadOnly: Attribute is read only. +// QTSS_BadIndex: Attempt to set non-0 index of attribute with a param retrieval function. +// +QTSS_Error QTSS_RemoveValue (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex); + +/*****************************************/ +// STREAM CALLBACKS +// +// The QTSS API provides QTSS_StreamRefs as a generalized stream abstraction. Mostly, +// QTSS_StreamRefs are used for communicating with the client. For instance, +// in the QTSS_RTSPRequest_Role, modules receive a QTSS_StreamRef which can be +// used for reading RTSP data from the client, and sending RTSP response data to the client. +// +// Additionally, QTSS_StreamRefs are generalized enough to be used in many other situations. +// For instance, modules receive a QTSS_StreamRef for the error log. When modules want +// to report errors, they can use these same routines, passing in the error log StreamRef. + +/********************************************************************/ +// QTSS_Write +// +// Writes data to a stream. +// +// Returns: QTSS_NoErr +// QTSS_WouldBlock: The stream cannot accept any data at this time. +// QTSS_NotConnected: The stream receiver is no longer connected. +// QTSS_BadArgument: NULL argument. +QTSS_Error QTSS_Write(QTSS_StreamRef inRef, const void* inBuffer, UInt32 inLen, UInt32* outLenWritten, QTSS_WriteFlags inFlags); + +/********************************************************************/ +// QTSS_WriteV +// +// Works similar to the POSIX WriteV, and takes a POSIX iovec. +// THE FIRST ENTRY OF THE IOVEC MUST BE BLANK!!! +// +// Returns: QTSS_NoErr +// QTSS_WouldBlock: The stream cannot accept any data at this time. +// QTSS_NotConnected: The stream receiver is no longer connected. +// QTSS_BadArgument: NULL argument. +QTSS_Error QTSS_WriteV(QTSS_StreamRef inRef, iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, UInt32* outLenWritten); + +/********************************************************************/ +// QTSS_Flush +// +// Some QTSS_StreamRefs (QTSS_RequestRef, for example) buffers data before sending it +// out. Calling this forces the stream to write the data immediately. +// +// Returns: QTSS_NoErr +// QTSS_WouldBlock: Stream cannot be completely flushed at this time. +// QTSS_NotConnected: The stream receiver is no longer connected. +// QTSS_BadArgument: NULL argument. +QTSS_Error QTSS_Flush(QTSS_StreamRef inRef); + +/********************************************************************/ +// QTSS_Read +// +// Reads data out of the stream +// +// Arguments inRef: The stream to read from. +// ioBuffer: A buffer to place the read data +// inBufLen: The length of ioBuffer. +// outLengthRead: If function returns QTSS_NoErr, on output this will be set to the +// amount of data actually read. +// +// Returns: QTSS_NoErr +// QTSS_WouldBlock +// QTSS_RequestFailed +// QTSS_BadArgument +QTSS_Error QTSS_Read(QTSS_StreamRef inRef, void* ioBuffer, UInt32 inBufLen, UInt32* outLengthRead); + +/********************************************************************/ +// QTSS_Seek +// +// Sets the current stream position to inNewPosition +// +// Arguments inRef: The stream to read from. +// inNewPosition: Offset from the start of the stream. +// +// Returns: QTSS_NoErr +// QTSS_RequestFailed +// QTSS_BadArgument +QTSS_Error QTSS_Seek(QTSS_StreamRef inRef, UInt64 inNewPosition); + +/********************************************************************/ +// QTSS_Advise +// +// Lets the stream know that the specified section of the stream will be read soon. +// +// Arguments inRef: The stream to advise. +// inPosition: Offset from the start of the stream of the advise region. +// inAdviseSize: Size of the advise region. +// +// Returns: QTSS_NoErr +// QTSS_RequestFailed +// QTSS_BadArgument +QTSS_Error QTSS_Advise(QTSS_StreamRef inRef, UInt64 inPosition, UInt32 inAdviseSize); + + +/*****************************************/ +// SERVICES +// +// Oftentimes modules have functionality that they want accessable from other +// modules. An example of this might be a logging module that allows other +// modules to write messages to the log. +// +// Modules can use the following callbacks to register and invoke "services". +// Adding & finding services works much like adding & finding attributes in +// an object. A service has a name. In order to invoke a service, the calling +// module must know the name of the service and resolve that name into an ID. +// +// Each service has a parameter block format that is specific to that service. +// Modules that are exporting services should carefully document the services they +// export, and modules calling services should take care to fail gracefully +// if the service isn't present or returns an error. + +typedef QTSS_Error (*QTSS_ServiceFunctionPtr)(QTSS_ServiceFunctionArgsPtr); + +/********************************************************************/ +// QTSS_AddService +// +// This function registers a service with the specified name, and +// associates it with the specified function pointer. +// QTSS_AddService may only be called from the QTSS_Register role +// +// Returns: QTSS_NoErr +// QTSS_OutOfState: If this function isn't being called from the Register role +// QTSS_BadArgument: Service name too long, or NULL arguments. +QTSS_Error QTSS_AddService(const char* inServiceName, QTSS_ServiceFunctionPtr inFunctionPtr); + + +/********************************************************************/ +// QTSS_IDForService +// +// Much like QTSS_IDForAttr, this resolves a service name into its +// corresponding QTSS_ServiceID. The QTSS_ServiceID can then be used to +// invoke the service. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_IDForService(const char* inTag, QTSS_ServiceID* outID); + +/********************************************************************/ +// QTSS_DoService +// +// Invokes the service. Return value from this function comes from the service +// function itself, unless the QTSS_IllegalService errorcode is returned, +// which is returned when the QTSS_ServiceID is bad. +QTSS_Error QTSS_DoService(QTSS_ServiceID inID, QTSS_ServiceFunctionArgsPtr inArgs); + +/********************************************************************/ +// BUILT-IN SERVICES +// +// The server registers some built-in services when it starts up. +// Here are macros for their names & descriptions of what they do + +// Rereads the preferences, also causes the QTSS_RereadPrefs_Role to be invoked +#define QTSS_REREAD_PREFS_SERVICE "RereadPreferences" + + + +/*****************************************/ +// RTSP HEADER CALLBACKS +// +// As a convience to modules that want to send RTSP responses, the server +// has internal utilities for formatting a proper RTSP response. When a module +// calls QTSS_SendRTSPHeaders, the server sends a proper RTSP status line, using +// the request's current status code, and also sends the proper CSeq header, +// session ID header, and connection header. +// +// Any other headers can be appended by calling QTSS_AppendRTSPHeader. They will be +// sent along with everything else when QTSS_SendRTSPHeaders is called. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_SendRTSPHeaders(QTSS_RTSPRequestObject inRef); +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_AppendRTSPHeader(QTSS_RTSPRequestObject inRef, QTSS_RTSPHeader inHeader, const char* inValue, UInt32 inValueLen); + + +/*****************************************/ +// QTSS_SendStandardRTSPResponse +// +// This function is also provided as an optional convienence to modules who are sending +// "typical" RTSP responses to clients. The function uses the QTSS_RTSPRequestObject and +// the QTSS_Object as inputs, where the object may either be a QTSS_ClientSessionObject +// or a QTSS_RTPStreamObject, depending on the method. The response is written to the +// stream provided. +// +// Below is a description of what is returned for each method this function supports: +// +// DESCRIBE: +// +// Writes status line, CSeq, SessionID, Connection headers as determined by the request. +// Writes a Content-Base header with the Content-Base being the URL provided. +// Writes a Content-Type header of "application/sdp" +// QTSS_Object must be a QTSS_ClientSessionObject. +// +// SETUP: +// +// Writes status line, CSeq, SessionID, Connection headers as determined by the request. +// Writes a Transport header with the client & server ports (if connection is over UDP). +// QTSS_Object must be a QTSS_RTPStreamObject. +// +// PLAY: +// +// Writes status line, CSeq, SessionID, Connection headers as determined by the request. +// QTSS_Object must be a QTSS_ClientSessionObject. +// +// Specify whether you want the server to append the seq#, timestamp, & ssrc info to +// the RTP-Info header via. the qtssPlayRespWriteTrackInfo flag. +// +// PAUSE: +// +// Writes status line, CSeq, SessionID, Connection headers as determined by the request. +// QTSS_Object must be a QTSS_ClientSessionObject. +// +// TEARDOWN: +// +// Writes status line, CSeq, SessionID, Connection headers as determined by the request. +// QTSS_Object must be a QTSS_ClientSessionObject. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_SendStandardRTSPResponse(QTSS_RTSPRequestObject inRTSPRequest, QTSS_Object inRTPInfo, UInt32 inFlags); + + +/*****************************************/ +// CLIENT SESSION CALLBACKS +// +// QTSS API Modules have the option of generating and sending RTP packets. Only +// one module currently can generate packets for a particular session. In order +// to do this, call QTSS_AddRTPStream. This must be done in response to a RTSP +// request, and typically is done in response to a SETUP request from the client. +// +// After one or more streams have been added to the session, the module that "owns" +// the packet sending for that session can call QTSS_Play to start the streams playing. +// After calling QTSS_Play, the module will get invoked in the QTSS_SendPackets_Role. +// Calling QTSS_Pause stops playing. +// +// The "owning" module may call QTSS_Teardown at any time. Doing this closes the +// session and will cause the QTSS_SessionClosing_Role to be invoked for this session. +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_RequestFailed: QTSS_RTPStreamObject couldn't be created. +QTSS_Error QTSS_AddRTPStream(QTSS_ClientSessionObject inClientSession, QTSS_RTSPRequestObject inRTSPRequest, QTSS_RTPStreamObject* outStream, QTSS_AddStreamFlags inFlags); +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_RequestFailed: No streams added to this session. +QTSS_Error QTSS_Play(QTSS_ClientSessionObject inClientSession, QTSS_RTSPRequestObject inRTSPRequest, QTSS_PlayFlags inPlayFlags); +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_Pause(QTSS_ClientSessionObject inClientSession); +// +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_Teardown(QTSS_ClientSessionObject inClientSession); + +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +QTSS_Error QTSS_RefreshTimeOut(QTSS_ClientSessionObject inClientSession); + +/*****************************************/ +// FILE SYSTEM CALLBACKS +// +// All modules that interact with the local file system should use these APIs instead +// of the direct operating system calls. +// +// This is for two reasons: 1) to ensure portability of your module across different +// platforms such as Win32 and different versions of the UNIX operating system. +// +// 2) To ensure your module will work properly if there is a 3rd party file system +// or database that contains media files. + +/********************************************************************/ +// QTSS_OpenFileObject +// +// Arguments inPath: a NULL-terminated C-string containing a full path to the file to open. +// inPath must be in the local (operating system) file system path style. +// inFlags: desired flags. +// outFileObject: If function returns QTSS_NoErr, on output this will be a QTSS_Object +// for the file. +// +// Returns: QTSS_NoErr +// QTSS_FileNotFound +// QTSS_RequestFailed +// QTSS_BadArgument +QTSS_Error QTSS_OpenFileObject(char* inPath, QTSS_OpenFileFlags inFlags, QTSS_Object* outFileObject); + +/********************************************************************/ +// QTSS_CloseFileObject +// +// Closes the file object. +// +// Arguments: inFileObject: the file to close +// +// Returns: QTSS_NoErr +// QTSS_BadArgument +QTSS_Error QTSS_CloseFileObject(QTSS_Object inFileObject); + + +/*****************************************/ +// SOCKET CALLBACKS +// +// It is not necessary for a module that internally uses network I/O to go through +// the QTSS API for their networking APIs. However, it is highly recommended +// to use nonblocking network I/O from a module. With nonblocking network I/O, it +// is very important to be able to receive socket events. +// +// To facilitate this, QTSS API provides the following two callbacks to link external +// sockets into the QTSS API streams framework. +// +// Once a module has created a QTSS stream out of its socket, it is possible to use the +// QTSS_RequestEvent callback to receive events on the socket. + + +/********************************************************************/ +// QTSS_CreateStreamFromSocket +// +// Creates a socket stream. +// +// Arguments: inFileDesc: the socket +// +// Returns: QTSS_NoErr +QTSS_Error QTSS_CreateStreamFromSocket(int inFileDesc, QTSS_SocketStream* outStream); + + +/********************************************************************/ +// QTSS_DestroySocketStream +// +// Creates a socket stream. +// +// Arguments: inFileDesc: the socket +// +// Returns: QTSS_NoErr +QTSS_Error QTSS_DestroySocketStream(QTSS_SocketStream inStream); + + +/*****************************************/ +// ASYNC I/O CALLBACKS +// +// QTSS modules must be kind in how they use the CPU. The server doesn't +// prevent a poorly implemented QTSS module from hogging the processing +// capability of the server, at the expense of other modules and other clients. +// +// It is therefore imperitive that a module use non-blocking, or async, I/O. +// If a module were to block, say, waiting to read file data off disk, this stall +// would affect the entire server. +// +// This problem is resolved in QTSS API in a number of ways. +// +// Firstly, all QTSS_StreamRefs provided to modules are non-blocking, or async. +// Modules should be prepared to receive EWOULDBLOCK errors in response to +// QTSS_Read, QTSS_Write, & QTSS_WriteV calls, with certain noted exceptions +// in the case of responding to RTSP requests. +// +// Modules that open their own file descriptors for network or file I/O can +// create separate threads for handling I/O. In this case, these descriptors +// can remain blocking, as long as they always block on the private module threads. +// +// In most cases, however, creating a separate thread for I/O is not viable for the +// kind of work the module would like to do. For instance, a module may wish +// to respond to a RTSP DESCRIBE request, but can't immediately because constructing +// the response would require I/O that would block. +// +// The problem is once the module returns from the QTSS_RTSPProcess_Role, the +// server will mistakenly consider the request handled, and move on. It won't +// know that the module has more work to do before it finishes processing the DESCRIBE. +// +// In this case, the module needs to tell the server to delay processing of the +// DESCRIBE request until the file descriptor's blocking condition is lifted. +// The module can do this by using the provided "event" callback routines. + +// Returns: QTSS_NoErr +// QTSS_BadArgument: Bad argument +// QTSS_OutOfState: if this callback is made from a role that doesn't allow async I/O events +// QTSS_RequestFailed: Not currently possible to request an event. + +QTSS_Error QTSS_RequestEvent(QTSS_StreamRef inStream, QTSS_EventType inEventMask); +QTSS_Error QTSS_SignalStream(QTSS_StreamRef inStream, QTSS_EventType inEventMask); + +QTSS_Error QTSS_SetIdleTimer(SInt64 inIdleMsec); +QTSS_Error QTSS_SetIntervalRoleTimer(SInt64 inIdleMsec); + +QTSS_Error QTSS_RequestGlobalLock(); +Bool16 QTSS_IsGlobalLocked(); +QTSS_Error QTSS_GlobalUnLock(); + + +/*****************************************/ +// AUTHENTICATE and AUTHORIZE CALLBACKS +// +// All modules that want Authentication outside of the +// QTSS_RTSPAuthenticate_Role must use the QTSS_Authenticate callback +// and must pass in the request object +// All modules that want Authorization outside of the +// QTSS_RTSPAuthorize_Role should use the QTSS_Authorize callback +// and must pass in the request object +/********************************************************************/ + +// QTSS_Authenticate +// +// Arguments inputs: inAuthUserName: the username that is to be authenticated +// inAuthResourceLocalPath:the resource that is to be authorized access +// inAuthMoviesDir: the movies directory (reqd. for finding the access file) +// inAuthRequestAction: the action that is performed for the resource +// inAuthScheme: the authentication scheme (the password retrieved will be based on it) +// ioAuthRequestObject: the request object +// The object is filled with the attributes passed in +// Returns: QTSS_NoErr +// QTSS_BadArgument if any of the input arguments are null +QTSS_Error QTSS_Authenticate( const char* inAuthUserName, + const char* inAuthResourceLocalPath, + const char* inAuthMoviesDir, + QTSS_ActionFlags inAuthRequestAction, + QTSS_AuthScheme inAuthScheme, + QTSS_RTSPRequestObject ioAuthRequestObject); + +// QTSS_Authorize +// +// Arguments inputs: inAuthRequestObject: the request object +// +// outputs: outAuthRealm: the authentication realm +// outAuthUserAllowed: true if user is allowed, and false otherwise +// +// Returns: QTSS_NoErr +// QTSS_BadArgument +QTSS_Error QTSS_Authorize(QTSS_RTSPRequestObject inAuthRequestObject, char** outAuthRealm, Bool16* outAuthUserAllowed); + + +void QTSS_LockStdLib(); +void QTSS_UnlockStdLib(); + +#ifdef QTSS_OLDROUTINENAMES + +// +// Legacy routines + +// +// QTSS_AddAttribute has been replaced by QTSS_AddStaticAttribute +QTSS_Error QTSS_AddAttribute(QTSS_ObjectType inObjectType, const char* inAttributeName, + void* inUnused); + +#endif + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/APIStubLib/QTSSRTSPProtocol.h b/APIStubLib/QTSSRTSPProtocol.h new file mode 100644 index 0000000..929478e --- /dev/null +++ b/APIStubLib/QTSSRTSPProtocol.h @@ -0,0 +1,215 @@ +/* + * + * @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: QTSSRTSPProtocol.h + + Contains: Constant & Enum definitions for RTSP protocol type parts + of QTSS API. + + +*/ + +#ifndef QTSS_RTSPPROTOCOL_H +#define QTSS_RTSPPROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "OSHeaders.h" + +enum +{ + qtssDescribeMethod = 0, + qtssSetupMethod = 1, + qtssTeardownMethod = 2, + qtssNumVIPMethods = 3, + + qtssPlayMethod = 3, + qtssPauseMethod = 4, + qtssOptionsMethod = 5, + qtssAnnounceMethod = 6, + qtssGetParameterMethod = 7, + qtssSetParameterMethod = 8, + qtssRedirectMethod = 9, + qtssRecordMethod = 10, + + qtssNumMethods = 11, + qtssIllegalMethod = 11 + +}; +typedef UInt32 QTSS_RTSPMethod; + + +enum +{ + //These are the common request headers (optimized) + qtssAcceptHeader = 0, + qtssCSeqHeader = 1, + qtssUserAgentHeader = 2, + qtssTransportHeader = 3, + qtssSessionHeader = 4, + qtssRangeHeader = 5, + qtssNumVIPHeaders = 6, + + //Other request headers + qtssAcceptEncodingHeader = 6, + qtssAcceptLanguageHeader = 7, + qtssAuthorizationHeader = 8, + qtssBandwidthHeader = 9, + qtssBlockSizeHeader = 10, + qtssCacheControlHeader = 11, + qtssConferenceHeader = 12, + qtssConnectionHeader = 13, + qtssContentBaseHeader = 14, + qtssContentEncodingHeader = 15, + qtssContentLanguageHeader = 16, + qtssContentLengthHeader = 17, + qtssContentLocationHeader = 18, + qtssContentTypeHeader = 19, + qtssDateHeader = 20, + qtssExpiresHeader = 21, + qtssFromHeader = 22, + qtssHostHeader = 23, + qtssIfMatchHeader = 24, + qtssIfModifiedSinceHeader = 25, + qtssLastModifiedHeader = 26, + qtssLocationHeader = 27, + qtssProxyAuthenticateHeader = 28, + qtssProxyRequireHeader = 29, + qtssRefererHeader = 30, + qtssRetryAfterHeader = 31, + qtssRequireHeader = 32, + qtssRTPInfoHeader = 33, + qtssScaleHeader = 34, + qtssSpeedHeader = 35, + qtssTimestampHeader = 36, + qtssVaryHeader = 37, + qtssViaHeader = 38, + qtssNumRequestHeaders = 39, + + //Additional response headers + qtssAllowHeader = 39, + qtssPublicHeader = 40, + qtssServerHeader = 41, + qtssUnsupportedHeader = 42, + qtssWWWAuthenticateHeader = 43, + qtssSameAsLastHeader = 44, + + //Newly added headers + qtssExtensionHeaders = 45, + + qtssXRetransmitHeader = 45, + qtssXAcceptRetransmitHeader = 46, + qtssXRTPMetaInfoHeader = 47, + qtssXTransportOptionsHeader = 48, + qtssXPacketRangeHeader = 49, + qtssXPreBufferHeader = 50, + qtssXDynamicRateHeader = 51, + qtssXAcceptDynamicRateHeader= 52, + + // QT Player random data request + qtssXRandomDataSizeHeader = 53, + + // 3gpp release 6 + qtss3GPPLinkCharHeader = 54, + qtss3GPPAdaptationHeader = 55, + qtss3GPPQOEFeedback = 56, + qtss3GPPQOEMetrics = 57, + + // 3gpp annex g + qtssXPreDecBufSizeHeader = 58, + qtssXInitPredecBufPeriodHeader = 59, + qtssXInitPostDecBufPeriodHeader = 60, + qtss3GPPVideoPostDecBufSizeHeader = 61, + + + qtssNumHeaders = 62, + qtssIllegalHeader = 62 + +}; +typedef UInt32 QTSS_RTSPHeader; + + +enum +{ + qtssContinue = 0, //100 + qtssSuccessOK = 1, //200 + qtssSuccessCreated = 2, //201 + qtssSuccessAccepted = 3, //202 + qtssSuccessNoContent = 4, //203 + qtssSuccessPartialContent = 5, //204 + qtssSuccessLowOnStorage = 6, //250 + qtssMultipleChoices = 7, //300 + qtssRedirectPermMoved = 8, //301 + qtssRedirectTempMoved = 9, //302 + qtssRedirectSeeOther = 10, //303 + qtssRedirectNotModified = 11, //304 + qtssUseProxy = 12, //305 + qtssClientBadRequest = 13, //400 + qtssClientUnAuthorized = 14, //401 + qtssPaymentRequired = 15, //402 + qtssClientForbidden = 16, //403 + qtssClientNotFound = 17, //404 + qtssClientMethodNotAllowed = 18, //405 + qtssNotAcceptable = 19, //406 + qtssProxyAuthenticationRequired = 20, //407 + qtssRequestTimeout = 21, //408 + qtssClientConflict = 22, //409 + qtssGone = 23, //410 + qtssLengthRequired = 24, //411 + qtssPreconditionFailed = 25, //412 + qtssRequestEntityTooLarge = 26, //413 + qtssRequestURITooLarge = 27, //414 + qtssUnsupportedMediaType = 28, //415 + qtssClientParameterNotUnderstood = 29, //451 + qtssClientConferenceNotFound = 30, //452 + qtssClientNotEnoughBandwidth = 31, //453 + qtssClientSessionNotFound = 32, //454 + qtssClientMethodNotValidInState = 33, //455 + qtssClientHeaderFieldNotValid = 34, //456 + qtssClientInvalidRange = 35, //457 + qtssClientReadOnlyParameter = 36, //458 + qtssClientAggregateOptionNotAllowed = 37, //459 + qtssClientAggregateOptionAllowed = 38, //460 + qtssClientUnsupportedTransport = 39, //461 + qtssClientDestinationUnreachable = 40, //462 + qtssServerInternal = 41, //500 + qtssServerNotImplemented = 42, //501 + qtssServerBadGateway = 43, //502 + qtssServerUnavailable = 44, //503 + qtssServerGatewayTimeout = 45, //505 + qtssRTSPVersionNotSupported = 46, //504 + qtssServerOptionNotSupported = 47, //551 + qtssNumStatusCodes = 48 + +}; +typedef UInt32 QTSS_RTSPStatusCode; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/APIStubLib/QTSS_Private.cpp b/APIStubLib/QTSS_Private.cpp new file mode 100644 index 0000000..7161d7c --- /dev/null +++ b/APIStubLib/QTSS_Private.cpp @@ -0,0 +1,404 @@ +/* + * + * @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: QTSS_Private.c + + Contains: Code for stub library and stub callback functions. + + + +*/ + +#include +#include "SafeStdLib.h" +#ifndef __Win32__ +#include +#include +#endif + +#include "QTSS.h" +#include "QTSS_Private.h" + +static QTSS_CallbacksPtr sCallbacks = NULL; +static QTSS_StreamRef sErrorLogStream = NULL; + +QTSS_Error _stublibrary_main(void* inPrivateArgs, QTSS_DispatchFuncPtr inDispatchFunc) +{ + QTSS_PrivateArgsPtr theArgs = (QTSS_PrivateArgsPtr)inPrivateArgs; + + // Setup + + sCallbacks = theArgs->inCallbacks; + sErrorLogStream = theArgs->inErrorLogStream; + + // Send requested information back to the server + + theArgs->outStubLibraryVersion = QTSS_API_VERSION; + theArgs->outDispatchFunction = inDispatchFunc; + + return QTSS_NoErr; +} + +// STUB FUNCTION DEFINITIONS + +void* QTSS_New(FourCharCode inMemoryIdentifier, UInt32 inSize) +{ + return (void *) ((QTSS_CallbackPtrProcPtr) sCallbacks->addr [kNewCallback]) (inMemoryIdentifier, inSize); +} + +void QTSS_Delete(void* inMemory) +{ + (sCallbacks->addr [kDeleteCallback]) (inMemory); +} + +SInt64 QTSS_Milliseconds(void) +{ + SInt64 outMilliseconds = 0; + (sCallbacks->addr [kMillisecondsCallback]) (&outMilliseconds); + return outMilliseconds; +} + +time_t QTSS_MilliSecsTo1970Secs(SInt64 inQTSS_MilliSeconds) +{ + time_t outSeconds = 0; + (sCallbacks->addr [kConvertToUnixTimeCallback]) (&inQTSS_MilliSeconds, &outSeconds); + return outSeconds; +} + +// STARTUP ROUTINES + +QTSS_Error QTSS_AddRole(QTSS_Role inRole) +{ + return (sCallbacks->addr [kAddRoleCallback]) (inRole); +} + +// DICTIONARY ROUTINES + +QTSS_Error QTSS_CreateObjectType(QTSS_ObjectType* outType) +{ + return (sCallbacks->addr [kCreateObjectTypeCallback]) (outType); +} + +QTSS_Error QTSS_AddAttribute(QTSS_ObjectType inType, const char* inTag, void* inUnused) +{ + return (sCallbacks->addr [kAddAttributeCallback]) (inType, inTag, inUnused); +} + +QTSS_Error QTSS_AddStaticAttribute(QTSS_ObjectType inObjectType, char* inAttrName, void* inUnused, QTSS_AttrDataType inAttrDataType) +{ + return (sCallbacks->addr [kAddStaticAttributeCallback]) (inObjectType, inAttrName, inUnused, inAttrDataType); +} + +QTSS_Error QTSS_AddInstanceAttribute(QTSS_Object inObject, char* inAttrName, void* inUnused, QTSS_AttrDataType inAttrDataType) +{ + return (sCallbacks->addr [kAddInstanceAttributeCallback]) (inObject, inAttrName, inUnused, inAttrDataType); +} + +QTSS_Error QTSS_RemoveInstanceAttribute(QTSS_Object inObject, QTSS_AttributeID inID) +{ + return (sCallbacks->addr [kRemoveInstanceAttributeCallback]) (inObject, inID); +} + +QTSS_Error QTSS_IDForAttr(QTSS_ObjectType inType, const char* inTag, QTSS_AttributeID* outID) +{ + return (sCallbacks->addr [kIDForTagCallback]) (inType, inTag, outID); +} + +QTSS_Error QTSS_GetAttrInfoByIndex(QTSS_Object inObject, UInt32 inIndex, QTSS_Object* outAttrInfoObject) +{ + return (sCallbacks->addr [kGetAttrInfoByIndexCallback]) (inObject, inIndex, outAttrInfoObject); +} + +QTSS_Error QTSS_GetAttrInfoByID(QTSS_Object inObject, QTSS_AttributeID inAttrID, QTSS_Object* outAttrInfoObject) +{ + return (sCallbacks->addr [kGetAttrInfoByIDCallback]) (inObject, inAttrID, outAttrInfoObject); +} + +QTSS_Error QTSS_GetAttrInfoByName(QTSS_Object inObject, char* inAttrName, QTSS_Object* outAttrInfoObject) +{ + return (sCallbacks->addr [kGetAttrInfoByNameCallback]) (inObject, inAttrName, outAttrInfoObject); +} + +QTSS_Error QTSS_GetValuePtr (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, void** outBuffer, UInt32* outLen) +{ + return (sCallbacks->addr [kGetAttributePtrByIDCallback]) (inDictionary, inID, inIndex, outBuffer, outLen); +} + +QTSS_Error QTSS_GetValue (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, void* ioBuffer, UInt32* ioLen) +{ + return (sCallbacks->addr [kGetAttributeByIDCallback]) (inDictionary, inID, inIndex, ioBuffer, ioLen); +} + +QTSS_Error QTSS_GetValueAsString (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex, char** outString) +{ + return (sCallbacks->addr [kGetValueAsStringCallback]) (inObject, inID, inIndex, outString); +} + +QTSS_Error QTSS_TypeStringToType(const char* inTypeString, QTSS_AttrDataType* outType) +{ + return (sCallbacks->addr [kTypeStringToTypeCallback]) (inTypeString, outType); +} + +QTSS_Error QTSS_TypeToTypeString(const QTSS_AttrDataType inType, char** outTypeString) +{ + return (sCallbacks->addr [kTypeToTypeStringCallback]) (inType, outTypeString); +} + +QTSS_Error QTSS_StringToValue(const char* inValueAsString, const QTSS_AttrDataType inType, void* ioBuffer, UInt32* ioBufSize) +{ + return (sCallbacks->addr [kStringToValueCallback]) (inValueAsString, inType, ioBuffer, ioBufSize); +} + +QTSS_Error QTSS_ValueToString(const void* inValue, const UInt32 inValueLen, const QTSS_AttrDataType inType, char** outString) +{ + return (sCallbacks->addr [kValueToStringCallback]) (inValue, inValueLen, inType, outString); +} + +QTSS_Error QTSS_SetValue (QTSS_Object inDictionary, QTSS_AttributeID inID,UInt32 inIndex, const void* inBuffer, UInt32 inLen) +{ + return (sCallbacks->addr [kSetAttributeByIDCallback]) (inDictionary, inID, inIndex, inBuffer, inLen); +} + +QTSS_Error QTSS_SetValuePtr (QTSS_Object inDictionary, QTSS_AttributeID inID, const void* inBuffer, UInt32 inLen) +{ + return (sCallbacks->addr [kSetAttributePtrCallback]) (inDictionary, inID, inBuffer, inLen); +} + +QTSS_Error QTSS_CreateObjectValue (QTSS_Object inDictionary, QTSS_AttributeID inID, QTSS_ObjectType inType, UInt32* outIndex, QTSS_Object* outCreatedObject) +{ + return (sCallbacks->addr [kCreateObjectValueCallback]) (inDictionary, inID, inType, outIndex, outCreatedObject); +} + +QTSS_Error QTSS_GetNumValues (QTSS_Object inObject, QTSS_AttributeID inID, UInt32* outNumValues) +{ + return (sCallbacks->addr [kGetNumValuesCallback]) (inObject, inID, outNumValues); +} + +QTSS_Error QTSS_GetNumAttributes (QTSS_Object inObject, UInt32* outNumValues) +{ + return (sCallbacks->addr [kGetNumAttributesCallback]) (inObject, outNumValues); +} + +QTSS_Error QTSS_RemoveValue (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex) +{ + return (sCallbacks->addr [kRemoveValueCallback]) (inObject, inID, inIndex); +} + +// STREAM ROUTINES + +QTSS_Error QTSS_Write(QTSS_StreamRef inStream, const void* inBuffer, UInt32 inLen, UInt32* outLenWritten, UInt32 inFlags) +{ + return (sCallbacks->addr [kWriteCallback]) (inStream, inBuffer, inLen, outLenWritten, inFlags); +} + +QTSS_Error QTSS_WriteV(QTSS_StreamRef inStream, iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, UInt32* outLenWritten) +{ + return (sCallbacks->addr [kWriteVCallback]) (inStream, inVec, inNumVectors, inTotalLength, outLenWritten); +} + +QTSS_Error QTSS_Flush(QTSS_StreamRef inStream) +{ + return (sCallbacks->addr [kFlushCallback]) (inStream); +} + +QTSS_Error QTSS_Read(QTSS_StreamRef inRef, void* ioBuffer, UInt32 inBufLen, UInt32* outLengthRead) +{ + return (sCallbacks->addr [kReadCallback]) (inRef, ioBuffer, inBufLen, outLengthRead); +} + +QTSS_Error QTSS_Seek(QTSS_StreamRef inRef, UInt64 inNewPosition) +{ + return (sCallbacks->addr [kSeekCallback]) (inRef, inNewPosition); +} + +QTSS_Error QTSS_Advise(QTSS_StreamRef inRef, UInt64 inPosition, UInt32 inAdviseSize) +{ + return (sCallbacks->addr [kAdviseCallback]) (inRef, inPosition, inAdviseSize); +} + + + +// SERVICE ROUTINES + +QTSS_Error QTSS_AddService(const char* inServiceName, QTSS_ServiceFunctionPtr inFunctionPtr) +{ + return (sCallbacks->addr [kAddServiceCallback]) (inServiceName, inFunctionPtr); +} + +QTSS_Error QTSS_IDForService(const char* inTag, QTSS_ServiceID* outID) +{ + return (sCallbacks->addr [kIDForServiceCallback]) (inTag, outID); +} + +QTSS_Error QTSS_DoService(QTSS_ServiceID inID, QTSS_ServiceFunctionArgsPtr inArgs) +{ + return (sCallbacks->addr [kDoServiceCallback]) (inID, inArgs); +} + +// RTSP ROUTINES + +QTSS_Error QTSS_SendRTSPHeaders(QTSS_RTSPRequestObject inRef) +{ + return (sCallbacks->addr [kSendRTSPHeadersCallback]) (inRef); +} + +QTSS_Error QTSS_AppendRTSPHeader(QTSS_RTSPRequestObject inRef, QTSS_RTSPHeader inHeader, const char* inValue, UInt32 inValueLen) +{ + return (sCallbacks->addr [kAppendRTSPHeadersCallback]) (inRef, inHeader, inValue, inValueLen); +} + +QTSS_Error QTSS_SendStandardRTSPResponse(QTSS_RTSPRequestObject inRTSPRequest, QTSS_Object inRTPInfo, UInt32 inFlags) +{ + return (sCallbacks->addr [kSendStandardRTSPCallback]) (inRTSPRequest, inRTPInfo, inFlags); +} + +// RTP ROUTINES + +QTSS_Error QTSS_AddRTPStream(QTSS_ClientSessionObject inClientSession, QTSS_RTSPRequestObject inRTSPRequest, QTSS_RTPStreamObject* outStream, QTSS_AddStreamFlags inFlags) +{ + return (sCallbacks->addr [kAddRTPStreamCallback]) (inClientSession, inRTSPRequest, outStream, inFlags); +} + +QTSS_Error QTSS_Play(QTSS_ClientSessionObject inClientSession, QTSS_RTSPRequestObject inRTSPRequest, QTSS_PlayFlags inPlayFlags) +{ + return (sCallbacks->addr [kPlayCallback]) (inClientSession, inRTSPRequest, inPlayFlags); +} + +QTSS_Error QTSS_Pause(QTSS_ClientSessionObject inClientSession) +{ + return (sCallbacks->addr [kPauseCallback]) (inClientSession); +} + +QTSS_Error QTSS_Teardown(QTSS_ClientSessionObject inClientSession) +{ + return (sCallbacks->addr [kTeardownCallback]) (inClientSession); +} + +QTSS_Error QTSS_RefreshTimeOut(QTSS_ClientSessionObject inClientSession) +{ + return (sCallbacks->addr [kRefreshTimeOutCallback]) (inClientSession); +} + + +// FILE SYSTEM ROUTINES + +QTSS_Error QTSS_OpenFileObject(char* inPath, QTSS_OpenFileFlags inFlags, QTSS_Object* outFileObject) +{ + return (sCallbacks->addr [kOpenFileObjectCallback]) (inPath, inFlags, outFileObject); +} + +QTSS_Error QTSS_CloseFileObject(QTSS_Object inFileObject) +{ + return (sCallbacks->addr [kCloseFileObjectCallback]) (inFileObject); +} + +// SOCKET ROUTINES + +QTSS_Error QTSS_CreateStreamFromSocket(int inFileDesc, QTSS_StreamRef* outStream) +{ + return (sCallbacks->addr [kCreateSocketStreamCallback]) (inFileDesc, outStream); +} + +QTSS_Error QTSS_DestroySocketStream(QTSS_StreamRef inStream) +{ + return (sCallbacks->addr [kDestroySocketStreamCallback]) (inStream); + +} + +// ASYNC I/O STREAM ROUTINES + +QTSS_Error QTSS_RequestEvent(QTSS_StreamRef inStream, QTSS_EventType inEventMask) +{ + return (sCallbacks->addr [kRequestEventCallback]) (inStream, inEventMask); +} + +QTSS_Error QTSS_SignalStream(QTSS_StreamRef inStream) +{ + return (sCallbacks->addr [kSignalStreamCallback]) (inStream); +} + +QTSS_Error QTSS_SetIdleTimer(SInt64 inIdleMsec) +{ + return (sCallbacks->addr [kSetIdleTimerCallback]) (inIdleMsec); +} + +QTSS_Error QTSS_SetIntervalRoleTimer(SInt64 inIdleMsec) +{ + return (sCallbacks->addr [kSetIntervalRoleTimerCallback]) (inIdleMsec); +} + +QTSS_Error QTSS_RequestGlobalLock() +{ + return (sCallbacks->addr [kRequestGlobalLockCallback]) (); +} + +// SYNCH GLOBAL MULTIPLE READERS/SINGLE WRITER ROUTINES + +Bool16 QTSS_IsGlobalLocked() +{ + return (Bool16) (sCallbacks->addr [kIsGlobalLockedCallback]) (); +} + +QTSS_Error QTSS_GlobalUnLock() +{ + return (sCallbacks->addr [kUnlockGlobalLock]) (); +} + +QTSS_Error QTSS_LockObject(QTSS_Object inObject) +{ + return (sCallbacks->addr [kLockObjectCallback]) (inObject); +} + +QTSS_Error QTSS_UnlockObject(QTSS_Object inObject) +{ + return (sCallbacks->addr [kUnlockObjectCallback]) (inObject); +} + +// AUTHENTICATION AND AUTHORIZATION ROUTINE +QTSS_Error QTSS_Authenticate( const char* inAuthUserName, + const char* inAuthResourceLocalPath, + const char* inAuthMoviesDir, + QTSS_ActionFlags inAuthRequestAction, + QTSS_AuthScheme inAuthScheme, + QTSS_RTSPRequestObject ioAuthRequestObject) +{ + return (sCallbacks->addr [kAuthenticateCallback]) (inAuthUserName, inAuthResourceLocalPath, inAuthMoviesDir, inAuthRequestAction, inAuthScheme, ioAuthRequestObject); +} + +QTSS_Error QTSS_Authorize(QTSS_RTSPRequestObject inAuthRequestObject, char** outAuthRealm, Bool16* outAuthUserAllowed) +{ + return (sCallbacks->addr [kAuthorizeCallback]) (inAuthRequestObject, outAuthRealm, outAuthUserAllowed); +} + +void QTSS_LockStdLib() +{ + (sCallbacks->addr [kLockStdLibCallback]) (); +} + +void QTSS_UnlockStdLib() +{ + (sCallbacks->addr [kUnlockStdLibCallback]) (); +} + diff --git a/APIStubLib/QTSS_Private.h b/APIStubLib/QTSS_Private.h new file mode 100644 index 0000000..2385406 --- /dev/null +++ b/APIStubLib/QTSS_Private.h @@ -0,0 +1,158 @@ +/* + * + * @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: QTSS_Private.h + + Contains: Implementation-specific structures and typedefs used by the + implementation of QTSS API in the Darwin Streaming Server + + + +*/ + + +#ifndef QTSS_PRIVATE_H +#define QTSS_PRIVATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "OSHeaders.h" +#include "QTSS.h" + +class QTSSModule; +class Task; + +typedef QTSS_Error (*QTSS_CallbackProcPtr)(...); +typedef void* (*QTSS_CallbackPtrProcPtr)(...); + +enum +{ + // Indexes for each callback routine. Addresses of the callback routines get + // placed in an array. + // IMPORTANT: When adding new callbacks, add only to the end of the list and increment the + // kLastCallback value. Inserting or changing the index order will break dynamic modules + // built with another release. + + kNewCallback = 0, + kDeleteCallback = 1, + kMillisecondsCallback = 2, + kConvertToUnixTimeCallback = 3, + kAddRoleCallback = 4, + kAddAttributeCallback = 5, + kIDForTagCallback = 6, + kGetAttributePtrByIDCallback = 7, + kGetAttributeByIDCallback = 8, + kSetAttributeByIDCallback = 9, + kWriteCallback = 10, + kWriteVCallback = 11, + kFlushCallback = 12, + kAddServiceCallback = 13, + kIDForServiceCallback = 14, + kDoServiceCallback = 15, + kSendRTSPHeadersCallback = 16, + kAppendRTSPHeadersCallback = 17, + kSendStandardRTSPCallback = 18, + kAddRTPStreamCallback = 19, + kPlayCallback = 20, + kPauseCallback = 21, + kTeardownCallback = 22, + kRequestEventCallback = 23, + kSetIdleTimerCallback = 24, + kOpenFileObjectCallback = 25, + kCloseFileObjectCallback = 26, + kReadCallback = 27, + kSeekCallback = 28, + kAdviseCallback = 29, + kGetNumValuesCallback = 30, + kGetNumAttributesCallback = 31, + kSignalStreamCallback = 32, + kCreateSocketStreamCallback = 33, + kDestroySocketStreamCallback = 34, + kAddStaticAttributeCallback = 35, + kAddInstanceAttributeCallback = 36, + kRemoveInstanceAttributeCallback= 37, + kGetAttrInfoByIndexCallback = 38, + kGetAttrInfoByNameCallback = 39, + kGetAttrInfoByIDCallback = 40, + kGetValueAsStringCallback = 41, + kTypeToTypeStringCallback = 42, + kTypeStringToTypeCallback = 43, + kStringToValueCallback = 44, + kValueToStringCallback = 45, + kRemoveValueCallback = 46, + kRequestGlobalLockCallback = 47, + kIsGlobalLockedCallback = 48, + kUnlockGlobalLock = 49, + kAuthenticateCallback = 50, + kAuthorizeCallback = 51, + kRefreshTimeOutCallback = 52, + kCreateObjectValueCallback = 53, + kCreateObjectTypeCallback = 54, + kLockObjectCallback = 55, + kUnlockObjectCallback = 56, + kSetAttributePtrCallback = 57, + kSetIntervalRoleTimerCallback = 58, + kLockStdLibCallback = 59, + kUnlockStdLibCallback = 60, + kLastCallback = 61 +}; + +typedef struct { + // Callback function pointer array + QTSS_CallbackProcPtr addr [kLastCallback]; +} QTSS_Callbacks, *QTSS_CallbacksPtr; + +typedef struct +{ + UInt32 inServerAPIVersion; + QTSS_CallbacksPtr inCallbacks; + QTSS_StreamRef inErrorLogStream; + UInt32 outStubLibraryVersion; + QTSS_DispatchFuncPtr outDispatchFunction; + +} QTSS_PrivateArgs, *QTSS_PrivateArgsPtr; + +typedef struct +{ + QTSSModule* curModule; // this structure is setup in each thread + QTSS_Role curRole; // before invoking a module in a role. Sometimes + Task* curTask; // this info. helps callback implementation + Bool16 eventRequested; + Bool16 globalLockRequested; // request event with global lock. + Bool16 isGlobalLocked; + SInt64 idleTime; // If a module has requested idle time. + +} QTSS_ModuleState, *QTSS_ModuleStatePtr; + +QTSS_StreamRef GetErrorLogStream(); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/APPLE_LICENSE b/APPLE_LICENSE new file mode 100644 index 0000000..fe81a60 --- /dev/null +++ b/APPLE_LICENSE @@ -0,0 +1,367 @@ +APPLE PUBLIC SOURCE LICENSE +Version 2.0 - August 6, 2003 + +Please read this License carefully before downloading this software. +By downloading or using this software, you are agreeing to be bound by +the terms of this License. If you do not or cannot agree to the terms +of this License, please do not download or use the software. + +1. General; Definitions. This License applies to any program or other +work which Apple Computer, Inc. ("Apple") makes publicly available and +which contains a notice placed by Apple identifying such program or +work as "Original Code" and stating that it is subject to the terms of +this Apple Public Source License version 2.0 ("License"). As used in +this License: + +1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is +the grantor of rights, (i) claims of patents that are now or hereafter +acquired, owned by or assigned to Apple and (ii) that cover subject +matter contained in the Original Code, but only to the extent +necessary to use, reproduce and/or distribute the Original Code +without infringement; and (b) in the case where You are the grantor of +rights, (i) claims of patents that are now or hereafter acquired, +owned by or assigned to You and (ii) that cover subject matter in Your +Modifications, taken alone or in combination with Original Code. + +1.2 "Contributor" means any person or entity that creates or +contributes to the creation of Modifications. + +1.3 "Covered Code" means the Original Code, Modifications, the +combination of Original Code and any Modifications, and/or any +respective portions thereof. + +1.4 "Externally Deploy" means: (a) to sublicense, distribute or +otherwise make Covered Code available, directly or indirectly, to +anyone other than You; and/or (b) to use Covered Code, alone or as +part of a Larger Work, in any way to provide a service, including but +not limited to delivery of content, through electronic communication +with a client other than You. + +1.5 "Larger Work" means a work which combines Covered Code or portions +thereof with code not governed by the terms of this License. + +1.6 "Modifications" mean any addition to, deletion from, and/or change +to, the substance and/or structure of the Original Code, any previous +Modifications, the combination of Original Code and any previous +Modifications, and/or any respective portions thereof. When code is +released as a series of files, a Modification is: (a) any addition to +or deletion from the contents of a file containing Covered Code; +and/or (b) any new file or other representation of computer program +statements that contains any part of Covered Code. + +1.7 "Original Code" means (a) the Source Code of a program or other +work as originally made available by Apple under this License, +including the Source Code of any updates or upgrades to such programs +or works made available by Apple under this License, and that has been +expressly identified by Apple as such in the header file(s) of such +work; and (b) the object code compiled from such Source Code and +originally made available by Apple under this License. + +1.8 "Source Code" means the human readable form of a program or other +work that is suitable for making modifications to it, including all +modules it contains, plus any associated interface definition files, +scripts used to control compilation and installation of an executable +(object code). + +1.9 "You" or "Your" means an individual or a legal entity exercising +rights under this License. For legal entities, "You" or "Your" +includes any entity which controls, is controlled by, or is under +common control with, You, where "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of fifty percent +(50%) or more of the outstanding shares or beneficial ownership of +such entity. + +2. Permitted Uses; Conditions & Restrictions. Subject to the terms +and conditions of this License, Apple hereby grants You, effective on +the date You accept this License and download the Original Code, a +world-wide, royalty-free, non-exclusive license, to the extent of +Apple's Applicable Patent Rights and copyrights covering the Original +Code, to do the following: + +2.1 Unmodified Code. You may use, reproduce, display, perform, +internally distribute within Your organization, and Externally Deploy +verbatim, unmodified copies of the Original Code, for commercial or +non-commercial purposes, provided that in each instance: + +(a) You must retain and reproduce in all copies of Original Code the +copyright and other proprietary notices and disclaimers of Apple as +they appear in the Original Code, and keep intact all notices in the +Original Code that refer to this License; and + +(b) You must include a copy of this License with every copy of Source +Code of Covered Code and documentation You distribute or Externally +Deploy, and You may not offer or impose any terms on such Source Code +that alter or restrict this License or the recipients' rights +hereunder, except as permitted under Section 6. + +2.2 Modified Code. You may modify Covered Code and use, reproduce, +display, perform, internally distribute within Your organization, and +Externally Deploy Your Modifications and Covered Code, for commercial +or non-commercial purposes, provided that in each instance You also +meet all of these conditions: + +(a) You must satisfy all the conditions of Section 2.1 with respect to +the Source Code of the Covered Code; + +(b) You must duplicate, to the extent it does not already exist, the +notice in Exhibit A in each file of the Source Code of all Your +Modifications, and cause the modified files to carry prominent notices +stating that You changed the files and the date of any change; and + +(c) If You Externally Deploy Your Modifications, You must make +Source Code of all Your Externally Deployed Modifications either +available to those to whom You have Externally Deployed Your +Modifications, or publicly available. Source Code of Your Externally +Deployed Modifications must be released under the terms set forth in +this License, including the license grants set forth in Section 3 +below, for as long as you Externally Deploy the Covered Code or twelve +(12) months from the date of initial External Deployment, whichever is +longer. You should preferably distribute the Source Code of Your +Externally Deployed Modifications electronically (e.g. download from a +web site). + +2.3 Distribution of Executable Versions. In addition, if You +Externally Deploy Covered Code (Original Code and/or Modifications) in +object code, executable form only, You must include a prominent +notice, in the code itself as well as in related documentation, +stating that Source Code of the Covered Code is available under the +terms of this License with information on how and where to obtain such +Source Code. + +2.4 Third Party Rights. You expressly acknowledge and agree that +although Apple and each Contributor grants the licenses to their +respective portions of the Covered Code set forth herein, no +assurances are provided by Apple or any Contributor that the Covered +Code does not infringe the patent or other intellectual property +rights of any other entity. Apple and each Contributor disclaim any +liability to You for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a +condition to exercising the rights and licenses granted hereunder, You +hereby assume sole responsibility to secure any other intellectual +property rights needed, if any. For example, if a third party patent +license is required to allow You to distribute the Covered Code, it is +Your responsibility to acquire that license before distributing the +Covered Code. + +3. Your Grants. In consideration of, and as a condition to, the +licenses granted to You under this License, You hereby grant to any +person or entity receiving or distributing Covered Code under this +License a non-exclusive, royalty-free, perpetual, irrevocable license, +under Your Applicable Patent Rights and other intellectual property +rights (other than patent) owned or controlled by You, to use, +reproduce, display, perform, modify, sublicense, distribute and +Externally Deploy Your Modifications of the same scope and extent as +Apple's licenses under Sections 2.1 and 2.2 above. + +4. Larger Works. You may create a Larger Work by combining Covered +Code with other code not governed by the terms of this License and +distribute the Larger Work as a single product. In each such instance, +You must make sure the requirements of this License are fulfilled for +the Covered Code or any portion thereof. + +5. Limitations on Patent License. Except as expressly stated in +Section 2, no other patent rights, express or implied, are granted by +Apple herein. Modifications and/or Larger Works may require additional +patent licenses from Apple which Apple may grant in its sole +discretion. + +6. Additional Terms. You may choose to offer, and to charge a fee for, +warranty, support, indemnity or liability obligations and/or other +rights consistent with the scope of the license granted herein +("Additional Terms") to one or more recipients of Covered Code. +However, You may do so only on Your own behalf and as Your sole +responsibility, and not on behalf of Apple or any Contributor. You +must obtain the recipient's agreement that any such Additional Terms +are offered by You alone, and You hereby agree to indemnify, defend +and hold Apple and every Contributor harmless for any liability +incurred by or claims asserted against Apple or such Contributor by +reason of any such Additional Terms. + +7. Versions of the License. Apple may publish revised and/or new +versions of this License from time to time. Each version will be given +a distinguishing version number. Once Original Code has been published +under a particular version of this License, You may continue to use it +under the terms of that version. You may also choose to use such +Original Code under the terms of any subsequent version of this +License published by Apple. No one other than Apple has the right to +modify the terms applicable to Covered Code created under this +License. + +8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in +part pre-release, untested, or not fully tested works. The Covered +Code may contain errors that could cause failures or loss of data, and +may be incomplete or contain inaccuracies. You expressly acknowledge +and agree that use of the Covered Code, or any portion thereof, is at +Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND +WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND +APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE +PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM +ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF +MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR +PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD +PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST +INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE +FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS, +THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR +ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO +ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE +AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY. +You acknowledge that the Covered Code is not intended for use in the +operation of nuclear facilities, aircraft navigation, communication +systems, or air traffic control machines in which case the failure of +the Covered Code could lead to death, personal injury, or severe +physical or environmental damage. + +9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO +EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL, +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING +TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR +ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY, +TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF +APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY +REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF +INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY +TO YOU. In no event shall Apple's total liability to You for all +damages (other than as may be required by applicable law) under this +License exceed the amount of fifty dollars ($50.00). + +10. Trademarks. This License does not grant any rights to use the +trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS", +"QuickTime", "QuickTime Streaming Server" or any other trademarks, +service marks, logos or trade names belonging to Apple (collectively +"Apple Marks") or to any trademark, service mark, logo or trade name +belonging to any Contributor. You agree not to use any Apple Marks in +or as part of the name of products derived from the Original Code or +to endorse or promote products derived from the Original Code other +than as expressly permitted by and in strict compliance at all times +with Apple's third party trademark usage guidelines which are posted +at http://www.apple.com/legal/guidelinesfor3rdparties.html. + +11. Ownership. Subject to the licenses granted under this License, +each Contributor retains all rights, title and interest in and to any +Modifications made by such Contributor. Apple retains all rights, +title and interest in and to the Original Code and any Modifications +made by or on behalf of Apple ("Apple Modifications"), and such Apple +Modifications will not be automatically subject to this License. Apple +may, at its sole discretion, choose to license such Apple +Modifications under this License, or on different terms from those +contained in this License or may choose not to license them at all. + +12. Termination. + +12.1 Termination. This License and the rights granted hereunder will +terminate: + +(a) automatically without notice from Apple if You fail to comply with +any term(s) of this License and fail to cure such breach within 30 +days of becoming aware of such breach; + +(b) immediately in the event of the circumstances described in Section +13.5(b); or + +(c) automatically without notice from Apple if You, at any time during +the term of this License, commence an action for patent infringement +against Apple; provided that Apple did not first commence +an action for patent infringement against You in that instance. + +12.2 Effect of Termination. Upon termination, You agree to immediately +stop any further use, reproduction, modification, sublicensing and +distribution of the Covered Code. All sublicenses to the Covered Code +which have been properly granted prior to termination shall survive +any termination of this License. Provisions which, by their nature, +should remain in effect beyond the termination of this License shall +survive, including but not limited to Sections 3, 5, 8, 9, 10, 11, +12.2 and 13. No party will be liable to any other for compensation, +indemnity or damages of any sort solely as a result of terminating +this License in accordance with its terms, and termination of this +License will be without prejudice to any other right or remedy of +any party. + +13. Miscellaneous. + +13.1 Government End Users. The Covered Code is a "commercial item" as +defined in FAR 2.101. Government software and technical data rights in +the Covered Code include only those rights customarily provided to the +public as defined in this License. This customary commercial license +in technical data and software is provided in accordance with FAR +12.211 (Technical Data) and 12.212 (Computer Software) and, for +Department of Defense purchases, DFAR 252.227-7015 (Technical Data -- +Commercial Items) and 227.7202-3 (Rights in Commercial Computer +Software or Computer Software Documentation). Accordingly, all U.S. +Government End Users acquire Covered Code with only those rights set +forth herein. + +13.2 Relationship of Parties. This License will not be construed as +creating an agency, partnership, joint venture or any other form of +legal association between or among You, Apple or any Contributor, and +You will not represent to the contrary, whether expressly, by +implication, appearance or otherwise. + +13.3 Independent Development. Nothing in this License will impair +Apple's right to acquire, license, develop, have others develop for +it, market and/or distribute technology or products that perform the +same or similar functions as, or otherwise compete with, +Modifications, Larger Works, technology or products that You may +develop, produce, market or distribute. + +13.4 Waiver; Construction. Failure by Apple or any Contributor to +enforce any provision of this License will not be deemed a waiver of +future enforcement of that or any other provision. Any law or +regulation which provides that the language of a contract shall be +construed against the drafter will not apply to this License. + +13.5 Severability. (a) If for any reason a court of competent +jurisdiction finds any provision of this License, or portion thereof, +to be unenforceable, that provision of the License will be enforced to +the maximum extent permissible so as to effect the economic benefits +and intent of the parties, and the remainder of this License will +continue in full force and effect. (b) Notwithstanding the foregoing, +if applicable law prohibits or restricts You from fully and/or +specifically complying with Sections 2 and/or 3 or prevents the +enforceability of either of those Sections, this License will +immediately terminate and You must immediately discontinue any use of +the Covered Code and destroy all copies of it that are in your +possession or control. + +13.6 Dispute Resolution. Any litigation or other dispute resolution +between You and Apple relating to this License shall take place in the +Northern District of California, and You and Apple hereby consent to +the personal jurisdiction of, and venue in, the state and federal +courts within that District with respect to this License. The +application of the United Nations Convention on Contracts for the +International Sale of Goods is expressly excluded. + +13.7 Entire Agreement; Governing Law. This License constitutes the +entire agreement between the parties with respect to the subject +matter hereof. This License shall be governed by the laws of the +United States and the State of California, except that body of +California law concerning conflicts of law. + +Where You are located in the province of Quebec, Canada, the following +clause applies: The parties hereby confirm that they have requested +that this License and all related documents be drafted in English. Les +parties ont exige que le present contrat et tous les documents +connexes soient rediges en anglais. + +EXHIBIT A. + +"Portions Copyright (c) 1999-2003 Apple Computer, 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." diff --git a/AtomicLib/README b/AtomicLib/README new file mode 100644 index 0000000..7dd7c39 --- /dev/null +++ b/AtomicLib/README @@ -0,0 +1,139 @@ +/* --- Version 4.0 --- 12-Feb-1999 --- */ +/* + * Include the following routines : + * atomic_or() + * scaledtimestamp() + * + * Updated the copyright. + */ + +/* + * unsigned int atomic_or(unsigned int *area, unsigned int mask) + * + * Atomically or the mask into *area. + * Returns the old value. + */ +/* + * long long scaledtimestamp(double scale) + * + * Read the PPC timebase. Convert the time base value based on + * scale. + * + * Caveat: scale can not be 0, NaN, Inf. It's upto the caller + * to validate scale before calling this. + */ + +/* --- Version 3.0 --- 23-Oct-1998 --- */ +/* + * Made the headers c++ friendly + * Build a static library with dynamic code generation. + * If you make a copy of the libatomic.a do not forget to run "ranlib". + */ + +/* --- Version 2.0 --- 23-Oct-1998 --- */ +/* + * Added routines described in timestamp.h + * Test program for these is in hmi.c + */ + +/* --- Version 1.0 --- 12-Oct-1998 --- */ +/* + * void spin_lock_init(spin_lock_t) + * + * Initialize a spin lock. + * These locks should be cache aligned and a multiple of cache size. + */ + +/* + * void spin_lock_unlock(spin_lock_t) + * + * Unconditionally release lock. + */ + +/* + * unsigned int spin_lock_lock(spin_lock_t) + * + * Try to acquire spin-lock. Return success (1). + */ + +/* + * unsigned int spin_lock_bit(spin_lock_t, unsigned int bits) + * + * Try to acquire spin-lock. The second parameter is the bit mask to + * test and set. multiple bits may be set. + * Return success (1). + */ + +/* + * unsigned int spin_unlock_bit(spin_lock_t, unsigned int bits) + * + * Release bit based spin-lock. The second parameter is the bit mask to + * clear. Multiple bits may be cleared. + */ + +/* + * unsigned int spin_lock_try(spin_lock_t) + * + * Try to acquire spin-lock. Return success (1) or failure (0). + */ + +/* + * unsigned int spin_lock_held(spin_lock_t) + * + * Return 1 if lock is held + * N.B. Racy, of course. + */ + +/* + * unsigned int compare_and_store(unsigned int oval, + * unsigned int nval, unsigned int *area) + * + * Compare oval to area if equal, store nval, and return true + * else return false and no store + * This is an atomic operation + */ + +/* + * unsigned int atomic_add(unsigned int *area, int val) + * + * Atomically add the second parameter to the first. + * Returns the result. + */ + +/* + * unsigned int atomic_sub(unsigned int *area, int val) + * + * Atomically subtract the second parameter from the first. + * Returns the result. + */ + +/* + * void queue_atomic(unsigned int * anchor, + * unsigned int * elem, unsigned int disp) + * + * Atomically inserts the element at the head of the list + * anchor is the pointer to the first element + * element is the pointer to the element to insert + * disp is the displacement into the element to the chain pointer + */ + +/* + * void queue_atomic_list(unsigned int * anchor, + * unsigned int * first, unsigned int * last, + * unsigned int disp) + * + * Atomically inserts the list of elements at the head of the list + * anchor is the pointer to the first element + * first is the pointer to the first element to insert + * last is the pointer to the last element to insert + * disp is the displacement into the element to the chain pointer + */ + +/* + * unsigned int *dequeue_atomic(unsigned int *anchor, unsigned int disp) + * + * Atomically removes the first element in a list and returns it. + * anchor is the pointer to the first element + * disp is the displacement into the element to the chain pointer + * Returns element if found, 0 if empty. + */ diff --git a/AtomicLib/atomic.h b/AtomicLib/atomic.h new file mode 100644 index 0000000..42a9bc1 --- /dev/null +++ b/AtomicLib/atomic.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* + * + * History: + * 11-Feb-1999 Umesh Vaishampayan (umeshv@apple.com) + * Added atomic_or(). + * + * 26-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Made the header c++ friendly. + * + * 12-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Changed simple_ to spin_ so as to coexist with cthreads till + * the merge to the system framework. + * + * 8-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Created from the kernel code to be in a dynamic shared library. + * Kernel code created by: Bill Angell (angell@apple.com) + */ + +#ifndef _ATOMIC_H_ +#define _ATOMIC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Locking routines */ + +struct spin_lock { /* sizeof cache line */ + unsigned int lock_data; + unsigned int pad[7]; +}; + +typedef struct spin_lock *spin_lock_t; + +extern void spin_lock_init(spin_lock_t); + +extern void spin_lock_unlock(spin_lock_t); + +extern unsigned int spin_lock_lock(spin_lock_t); + +extern unsigned int spin_lock_bit(spin_lock_t, unsigned int bits); + +extern unsigned int spin_unlock_bit(spin_lock_t, unsigned int bits); + +extern unsigned int spin_lock_try(spin_lock_t); + +extern unsigned int spin_lock_held(spin_lock_t); + +/* Other atomic routines */ + +extern unsigned int compare_and_store(unsigned int oval, + unsigned int nval, unsigned int *area); + +extern unsigned int atomic_add(unsigned int *area, int val); + +extern unsigned int atomic_or(unsigned int *area, unsigned int mask); + +extern unsigned int atomic_sub(unsigned int *area, int val); + +extern void queue_atomic(unsigned int *anchor, + unsigned int *elem, unsigned int disp); + +extern void queue_atomic_list(unsigned int *anchor, + unsigned int *first, unsigned int *last, + unsigned int disp); + +extern unsigned int *dequeue_atomic(unsigned int *anchor, unsigned int disp); + +#ifdef __cplusplus +} +#endif + +#endif /* _ATOMIC_H_ */ diff --git a/AtomicLib/atomic_subr.s b/AtomicLib/atomic_subr.s new file mode 100644 index 0000000..241d16c --- /dev/null +++ b/AtomicLib/atomic_subr.s @@ -0,0 +1,304 @@ +/* + * + * @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: atomic_subr.s + * + * History: + * 11-Feb-1999 Umesh Vaishampayan (umeshv@apple.com) + * Added atomic_or(). + * + * 12-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Changed simple_ to spin_ so as to coexist with cthreads till + * the merge to the system framework. + * + * 8-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Created from the kernel code to be in a dynamic shared library. + * Kernel code created by: Bill Angell (angell@apple.com) + */ + +#include + +/* + * void spin_lock_init(spin_lock_t) + * + * Initialize a spin lock. + * These locks should be cache aligned and a multiple of cache size. + */ +LEAF(_spin_lock_init) + li r0, 0 /* set lock to free == 0 */ + stw r0, 0(r3) /* Initialize the lock */ + blr + +/* + * void spin_lock_unlock(spin_lock_t) + * + * Unconditionally release lock. + */ +LEAF(_spin_lock_unlock) + sync /* Flush writes done under lock */ + li r0, 0 /* set lock to free */ + stw r0, 0(r3) + blr + +/* + * unsigned int spin_lock_lock(spin_lock_t) + * + * Try to acquire spin-lock. Return success (1). + */ +LEAF(_spin_lock_lock) + mr r5,r3 /* Get the address of the lock */ + +Lcktry: + lwarx r6,0,r5 /* Grab the lock value */ + li r3,1 /* Set the return value */ + mr. r6,r6 /* Is it locked? */ + bne- Lcksniff /* Yeah, wait for it to clear... */ + stwcx. r3,0,r5 /* Try to sieze that darn lock */ + beq+ Lckgot /* We got it, yahoo... */ + b Lcktry /* Try again if the store failed... */ + +Lcksniff: + lwz r3,0(r5) /* Get that lock in here */ + mr. r3,r3 /* Is it free yet? */ + beq+ Lcktry /* Yeah, try for it again... */ + b Lcksniff /* keep trying... */ + +Lckgot: + isync /* Make sure we don't use a */ + /* speculativily loaded value */ + blr + +/* + * unsigned int spin_lock_bit(spin_lock_t, unsigned int bits) + * + * Try to acquire spin-lock. The second parameter is the bit mask to + * test and set. multiple bits may be set. + * Return success (1). + */ +LEAF(_spin_lock_bit) +Lbittry: + lwarx r6,0,r3 /* Grab the lock value */ + and. r0,r6,r4 /* See if any of the lock bits are on */ + or r6,r6,r4 /* Turn on the lock bits */ + bne- Lbitsniff /* Yeah, wait for it to clear... */ + stwcx. r6,0,r3 /* Try to sieze that there durn lock */ + beq+ Lbitgot /* We got it, yahoo... */ + b Lbittry /* Try again if the store failed... */ + +Lbitsniff: + lwz r6,0(r3) /* Get that lock in here */ + and. r0,r6,r4 /* See if any of the lock bits are on */ + beq+ Lbittry /* Yeah, try for it again... */ + b Lbitsniff /* keep trying... */ + +Lbitgot: + li r3,1 /* Set good return code */ + isync /* Make sure we don't use a */ + /* speculativily loaded value */ + blr + + +/* + * unsigned int spin_unlock_bit(spin_lock_t, unsigned int bits) + * + * Release bit based spin-lock. The second parameter is the bit mask to + * clear. Multiple bits may be cleared. + */ +LEAF(_spin_unlock_bit) +Lubittry: + lwarx r0,0,r3 /* Grab the lock value */ + andc r0,r0,r4 /* Clear the lock bits */ + stwcx. r0,0,r3 /* Try to clear that there durn lock */ + bne- Lubittry /* Try again, couldn't save it... */ + blr /* Leave... */ + +/* + * unsigned int spin_lock_try(spin_lock_t) + * + * Try to acquire spin-lock. Return success (1) or failure (0). + */ +LEAF(_spin_lock_try) + li r4, 1 /* value to be stored... 1==taken */ + +L_lock_try_loop: + lwarx r5, 0,r3 /* Ld from addr of arg and reserve */ + cmpwi r5, 0 /* TEST... */ + bne- L_lock_try_failed /* branch if taken. Predict free */ + + stwcx. r4, 0,r3 /* And SET (if still reserved) */ + bne- L_lock_try_loop /* If set failed, loop back */ + + isync + li r3,1 /* Set that the lock was free */ + blr + +L_lock_try_failed: + li r3,0 /* FAILURE - lock was taken */ + blr + +/* + * unsigned int spin_lock_held(spin_lock_t) + * + * Return 1 if lock is held + * N.B. Racy, of course. + */ +LEAF(_spin_lock_held) + isync /* Make sure we don't use a */ + /* speculativily fetched lock */ + lwz r3, 0(r3) /* Return value of lock */ + blr + +/* + * unsigned int compare_and_store(unsigned int oval, + * unsigned int nval, unsigned int *area) + * + * Compare oval to area if equal, store nval, and return true + * else return false and no store + * This is an atomic operation + */ +LEAF(_compare_and_store) + mr r6,r3 /* Save the old value */ + +Lcstry: + lwarx r9,0,r5 /* Grab the area value */ + li r3,1 /* Assume it works */ + cmplw cr0,r9,r6 /* Does it match the old value? */ + bne- Lcsfail /* No, it must have changed... */ + stwcx. r4,0,r5 /* Try to save the new value */ + bne- Lcstry /* Didn't get it, try again... */ + isync /* Just hold up prefetch */ + blr /* Return... */ + +Lcsfail: + li r3,0 /* Set failure */ + blr + +/* + * unsigned int atomic_add(unsigned int *area, int val) + * + * Atomically add the second parameter to the first. + * Returns the result. + */ +LEAF(_atomic_add) + mr r6,r3 /* Save the area */ + +Laddtry: + lwarx r3,0,r6 /* Grab the area value */ + add r3,r3,r4 /* Add the value */ + stwcx. r3,0,r6 /* Try to save the new value */ + bne- Laddtry /* Didn't get it, try again... */ + blr /* Return... */ + + +/* + * unsigned int atomic_or(unsigned int *area, unsigned int mask) + * + * Atomically or the mask into *area. + * Returns the old value. + */ +LEAF(_atomic_or) + mr r6,r3 /* Save the area */ + +Lortry: + lwarx r3,0,r6 /* Grab the area value */ + or r5,r3,r4 /* or the mask */ + stwcx. r5,0,r6 /* Try to save the new value */ + bne- Lortry /* Didn't get it, try again... */ + blr /* Return the old value... */ + + +/* + * unsigned int atomic_sub(unsigned int *area, int val) + * + * Atomically subtract the second parameter from the first. + * Returns the result. + */ +LEAF(_atomic_sub) + mr r6,r3 /* Save the area */ + +Lsubtry: + lwarx r3,0,r6 /* Grab the area value */ + sub r3,r3,r4 /* Subtract the value */ + stwcx. r3,0,r6 /* Try to save the new value */ + bne- Lsubtry /* Didn't get it, try again... */ + blr /* Return... */ + +/* + * void queue_atomic(unsigned int * anchor, + * unsigned int * elem, unsigned int disp) + * + * Atomically inserts the element at the head of the list + * anchor is the pointer to the first element + * element is the pointer to the element to insert + * disp is the displacement into the element to the chain pointer + */ +LEAF(_queue_atomic) + mr r7,r4 /* Make end point the same as start */ + mr r8,r5 /* Copy the displacement also */ + b Lqueue_comm /* Join common code... */ + +/* + * void queue_atomic_list(unsigned int * anchor, + * unsigned int * first, unsigned int * last, + * unsigned int disp) + * + * Atomically inserts the list of elements at the head of the list + * anchor is the pointer to the first element + * first is the pointer to the first element to insert + * last is the pointer to the last element to insert + * disp is the displacement into the element to the chain pointer + */ +LEAF(_queue_atomic_list) + mr r7,r5 /* Make end point the same as start */ + mr r8,r6 /* Copy the displacement also */ + +Lqueue_comm: + lwarx r9,0,r3 /* Pick up the anchor */ + stwx r9,r8,r7 /* Chain that to the end of the new stuff */ + stwcx. r4,0,r3 /* Try to chain into the front */ + bne- Lqueue_comm /* Didn't make it, try again... */ + blr /* Return... */ + +/* + * unsigned int *dequeue_atomic(unsigned int *anchor, unsigned int disp) + * + * Atomically removes the first element in a list and returns it. + * anchor is the pointer to the first element + * disp is the displacement into the element to the chain pointer + * Returns element if found, 0 if empty. + */ +LEAF(_dequeue_atomic) + mr r5,r3 /* Save the anchor */ + +Ldequeue_comm: + lwarx r3,0,r5 /* Pick up the anchor */ + mr. r3,r3 /* Is the list empty? */ + beqlr- /* Leave it list empty... */ + lwzx r9,r4,r3 /* Get the next in line */ + stwcx. r9,0,r5 /* Try to chain into the front */ + bne- Ldequeue_comm /* Didn't make it, try again... */ + blr /* Return... */ + diff --git a/AtomicLib/hmi.c b/AtomicLib/hmi.c new file mode 100644 index 0000000..72551ea --- /dev/null +++ b/AtomicLib/hmi.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* + * to test use of timestamp() + */ + + /* cc hmi.c -o hmi libatomic.a */ + +#include +#include "timestamp.h" + +void +main(int argc, char **argv) +{ + int i, j; + SInt64 ts1, ts2; + SInt64 ts3, ts4; + struct timescale tsc; + double scale; + + if (argc <= 1) { + qtss_fprintf(stderr, "Usage: %s loop-count\n", argv[0]); + exit(1); + } + + j = atoi(argv[1]); + + ts1 = timestamp(); /* START */ + + /* Loop for the given loop-count */ + for (i=0; i < j; i++) { + ; + } + + ts2 = timestamp(); /* END */ + + qtss_printf("ts1 = %qd, ts2 = %qd\n", ts1, ts2); + + utimescale(&tsc); + scale = (double)tsc.tsc_numerator / (double)tsc.tsc_denominator; + + ts1 = (SInt64)((double)ts1 * scale); + ts2 = (SInt64)((double)ts2 * scale); + + qtss_printf("ts1 = %qd, ts2 = %qd, micro seconds = %qd\n", + ts1, ts2, (ts2 - ts1)); + + /* Use the scaledtimestamp() now */ + + ts3 = scaledtimestamp(scale); /* START */ + + /* Loop for the given loop-count */ + for (i=0; i < j; i++) { + ; + } + + ts4 = scaledtimestamp(scale); /* END */ + + qtss_printf("ts3 = %qd, ts4 = %qd, micro seconds = %qd\n", + ts3, ts4, (ts4 - ts3)); + +} diff --git a/AtomicLib/timescale.c b/AtomicLib/timescale.c new file mode 100644 index 0000000..841b89a --- /dev/null +++ b/AtomicLib/timescale.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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: timescale.c + * + * History: + * 23-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Created. + */ + +#include "timestamp.h" +#include +#include "SafeStdLib.h" +void +utimescale(struct timescale *tscp) +{ +// stop not supported + char *death = 0; + *death = 0; + exit (-1); +#if 0 // old code + unsigned int theNanosecondNumerator = 0; + unsigned int theNanosecondDenominator = 0; + + MKGetTimeBaseInfo(NULL, &theNanosecondNumerator, &theNanosecondDenominator, NULL, NULL); + tscp->tsc_numerator = theNanosecondNumerator / 1000; /* PPC magic number */ + tscp->tsc_denominator = theNanosecondDenominator; + return; +#endif + +} + diff --git a/AtomicLib/timestamp.h b/AtomicLib/timestamp.h new file mode 100644 index 0000000..f1884d1 --- /dev/null +++ b/AtomicLib/timestamp.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* + * + * History: + * 08-Feb-1999 Umesh Vaishampayan (umeshv@apple.com) + * Added scaledtimestamp(). + * + * 26-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Made the header c++ friendly. + * + * 23-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Created. + */ + +#ifndef _TIMESTAMP_H_ +#define _TIMESTAMP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "OSHeaders.h" + +/* Get a 64 bit timestamp */ +extern SInt64 timestamp(void); + +struct timescale { + SInt64 tsc_numerator; + SInt64 tsc_denominator; +}; + +/* + * Get numerator and denominator to convert value returned + * by timestamp() to microseconds + */ +extern void utimescale(struct timescale *tscp); + +extern SInt64 scaledtimestamp(double scale); + +#ifdef __cplusplus +} +#endif + +#endif /* _TIMESTAMP_H_ */ diff --git a/AtomicLib/timestamp.s b/AtomicLib/timestamp.s new file mode 100644 index 0000000..dc4e28f --- /dev/null +++ b/AtomicLib/timestamp.s @@ -0,0 +1,133 @@ +/* + * + * @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: timestamp.s + * + * History: + * 12-Feb-1999 Umesh Vaishampayan (umeshv@apple.com) + * Integrated scaledtimestamp() written by + * Joe Sokol (sokol1@apple.com) + * + * 23-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Created. + */ + +#include + +/* + * long long timestamp(void) + * + * Read the PPC timebase. + */ +LEAF(_timestamp) +Lagain: + mftbu r3 + mftb r4 + mftbu r6 + cmpw r6, r3 + bne- Lagain + blr + + +/* + * long long scaledtimestamp(double scale) + * + * Read the PPC timebase. Convert the time base value based on + * scale. + * + * Caveat: scale can not be 0, NaN, Inf. It's upto the caller + * to validate scale before calling this. + */ + +LEAF(_scaledtimestamp) +Lagain1: + mftbu r3 + mftb r4 + mftbu r6 + cmpw r6,r3 + bne- Lagain1 + +; r3 and r4 have the time base. +; convert the long long value to double +L_LLtoD: + cntlzw r0,r3 + cmplwi cr0,r0,31 + bc 12,1,L2 + subfic r10,r0,63 + subfic r11,r10,52 + subfic r0,r11,32 + srw r0,r4,r0 + slw r9,r3,r11 + or r3,r9,r0 + b L6 +L2: + cntlzw r0,r4 + subfic r10,r0,31 + subfic r11,r10,52 + cmplwi cr0,r11,31 + bc 4,1,L4 + addi r0,r11,-32 + slw r3,r4,r0 + li r4,0 + b L3 +L4: + subfic r0,r11,32 + srw r3,r4,r0 +L6: + slw r4,r4,r11 +L3: + addi r0,r10,1023 + slwi r0,r0,20 + rlwimi r3,r0,0,0,11 + stw r3,-8(r1) + stw r4,-4(r1) + lfd f0,-8(r1) ; load the double representation of time base + + fmul f0,f0,f1 ; f1 has scale to convert timestamp + +; convert the double to long long +L_DtoLL: + stfd f0,-8(r1) + lwz r3,-8(r1) + lwz r4,-4(r1) + srwi r0,r3,20 + rlwinm r9,r3,0,12,31 + subfic r8,r0,1075 + cmplwi cr0,r8,31 + oris r3,r9,0x10 + bc 4,1,L8 + addi r0,r8,-32 + srw r4,r3,r0 + li r3,0 + blr +L8: + subfic r0,r8,32 + slw r0,r3,r0 + srw r9,r4,r8 + or r4,r9,r0 + srw r3,r3,r8 + blr + diff --git a/CommonUtilitiesLib/ConfParser.cpp b/CommonUtilitiesLib/ConfParser.cpp new file mode 100644 index 0000000..8f88c0f --- /dev/null +++ b/CommonUtilitiesLib/ConfParser.cpp @@ -0,0 +1,251 @@ +/* + * + * @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@ + * + */ + + +#include "ConfParser.h" +#include "OSMemory.h" + +#include "MyAssert.h" +#include "OSHeaders.h" + +#include + +#include "GetWord.h" +#include "Trim.h" + + +#include +#include + + +static Bool16 SampleConfigSetter( const char* paramName, const char* paramValue[], void* userData ); +static void DisplayConfigErr( const char*fname, int lineCount, const char*lineBuff, const char *errMessage ); + + + +void TestParseConfigFile() +{ + ParseConfigFile( false, "qtss.conf", SampleConfigSetter, NULL ); + +} + +static Bool16 SampleConfigSetter( const char* paramName, const char* paramValue[], void* /*userData*/ ) +{ + qtss_printf( "param: %s", paramName ); + + int x = 0; + + while ( paramValue[x] ) + { + qtss_printf( " value(%"_S32BITARG_"): %s ", (SInt32)x, (char *) paramValue[x] ); + x++; + } + + qtss_printf( "\n" ); + + return false; +} + + +static void DisplayConfigErr( const char*fname, int lineCount, const char*lineBuff, const char *errMessage ) +{ + + qtss_printf( "- Configuration file error:\n" ); + + + if ( lineCount ) + qtss_printf( " file: %s, line# %i\n", fname, lineCount ); + else + qtss_printf( " file: %s\n", fname ); + + if ( lineBuff ) + qtss_printf( " text: %s", lineBuff ); // lineBuff already includes a \n + + if ( errMessage ) + qtss_printf( " reason: %s\n", errMessage ); // lineBuff already includes a \n +} + + +int ParseConfigFile( + Bool16 allowNullValues + , const char* fname + , Bool16 (*ConfigSetter)( const char* paramName, const char* paramValue[], void* userData ) + , void* userData ) +{ + int error = -1; + FILE *configFile; + int lineCount = 0; + + Assert( fname ); + Assert( ConfigSetter ); + + + if (!fname) return error; + if (!ConfigSetter) return error; + + + configFile = fopen( fname, "r" ); + +// Assert( configFile ); + + if ( configFile ) + { + SInt32 lineBuffSize = kConfParserMaxLineSize; + SInt32 wordBuffSize = kConfParserMaxParamSize; + + + char lineBuff[kConfParserMaxLineSize]; + char wordBuff[kConfParserMaxParamSize]; + + char *next; + + // debug assistance -- CW debugger won't display large char arrays as strings + //char* l = lineBuff; + //char* w = wordBuff; + + + do + { + next = lineBuff; + + // get a line ( fgets adds \n+ 0x00 ) + + if ( fgets( lineBuff, lineBuffSize, configFile ) == NULL ) + break; + + lineCount++; + error = 0; // allow empty lines at beginning of file. + + // trim the leading whitespace + next = TrimLeft( lineBuff ); + + if (*next) + { + + if ( *next == '#' ) + { + // it's a comment + // prabably do nothing in release version? + + // qtss_printf( "comment: %s" , &lineBuff[1] ); + + error = 0; + + } + else + { char* param; + + // grab the param name, quoted or not + if ( *next == '"' ) + next = GetQuotedWord( wordBuff, next, wordBuffSize ); + else + next = GetWord( wordBuff, next, wordBuffSize ); + + Assert( *wordBuff ); + + param = NEW char[strlen( wordBuff ) + 1 ]; + + Assert( param ); + + if ( param ) + { + const char* values[kConfParserMaxParamValues+1]; + int maxValues = 0; + + strcpy( param, wordBuff ); + + + values[maxValues] = NULL; + + while ( maxValues < kConfParserMaxParamValues && *next ) + { + // ace + next = TrimLeft( next ); + + if (*next) + { + if ( *next == '"' ) + next = GetQuotedWord( wordBuff, next, wordBuffSize ); + else + next = GetWord( wordBuff, next, wordBuffSize ); + + char* value = NEW char[strlen( wordBuff ) + 1 ]; + + Assert( value ); + + if ( value ) + { + strcpy( value, wordBuff ); + + values[maxValues++] = value; + values[maxValues] = 0; + } + + } + + } + + if ( (maxValues > 0 || allowNullValues) && !(*ConfigSetter)( param, values, userData ) ) + error = 0; + else + { error = -1; + if ( maxValues > 0 ) + DisplayConfigErr( fname, lineCount, lineBuff, "Parameter could not be set." ); + else + DisplayConfigErr( fname, lineCount, lineBuff, "No value to set." ); + } + + delete [] param; + + maxValues = 0; + + while ( values[maxValues] ) + { char** tempValues = (char**)values; // Need to do this to delete a const + delete [] tempValues[maxValues]; + maxValues++; + } + + + } + + } + + + } + + + + } while ( feof( configFile ) == 0 && error == 0 ); + + (void)fclose( configFile ); + } +// else +// { +// qtss_printf("Couldn't open config file at: %s\n", fname); +// } + + return error; + +} diff --git a/CommonUtilitiesLib/ConfParser.h b/CommonUtilitiesLib/ConfParser.h new file mode 100644 index 0000000..d3dc0cf --- /dev/null +++ b/CommonUtilitiesLib/ConfParser.h @@ -0,0 +1,49 @@ +#ifndef __ConfParser__ +#define __ConfParser__ + +/* + * + * @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@ + * + */ + + + +#include "OSHeaders.h" + +// the maximum size + 1 of a parameter +#define kConfParserMaxParamSize 512 + + +// the maximum size + 1 of single line in the file +#define kConfParserMaxLineSize 1024 + +// the maximum number of values per config parameter +#define kConfParserMaxParamValues 10 + + +void TestParseConfigFile(); + +int ParseConfigFile( Bool16 allowNullValues, const char* fname \ + , Bool16 (*ConfigSetter)( const char* paramName, const char* paramValue[], void * userData ) \ + , void* userData ); +#endif diff --git a/CommonUtilitiesLib/DateTranslator.cpp b/CommonUtilitiesLib/DateTranslator.cpp new file mode 100644 index 0000000..1002311 --- /dev/null +++ b/CommonUtilitiesLib/DateTranslator.cpp @@ -0,0 +1,159 @@ +/* + * + * @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: DateTranslator.h + + Contains: Efficient routines & data structures for converting from + RFC 1123 compliant date strings to local file system dates & vice versa. + + +*/ + +#include "DateTranslator.h" + +#include + +#include "OSHeaders.h" +#include "OS.h" + +#include "StringParser.h" +#include "StrPtrLen.h" + +// If you assign values of 0 - 25 for all the letters, and sum up the values of +// the letters in each month, you get a table that looks like this. For instance, +// "Jul" = 9 + 20 + 11 = 40. The value of July in a C tm struct is 6, so position +// 40 = 6 in this array. + +const UInt32 kMonthHashTable[] = +{ + 12, 12, 12, 12, 12, 12, 12, 12, 12, 11, // 0 - 9 + 1, 12, 12, 12, 12, 12, 12, 12, 12, 12, // 10 - 19 + 12, 12, 0, 12, 12, 12, 7, 12, 12, 2, // 20 - 29 + 12, 12, 3, 12, 12, 9, 4, 8, 12, 12, // 30 - 39 + 6, 12, 5, 12, 12, 12, 12, 12, 10, 12 // 40 - 49 +}; +const UInt32 kMonthHashTableSize = 49; + + +SInt64 DateTranslator::ParseDate(StrPtrLen* inDateString) +{ + //SEE RFC 1123 for details on the date string format + //ex: Mon, 04 Nov 1996 21:42:17 GMT + + // Parse the date buffer, filling out a tm struct + struct tm theDateStruct; + ::memset(&theDateStruct, 0, sizeof(theDateStruct)); + + // All RFC 1123 dates are the same length. + if (inDateString->Len != DateBuffer::kDateBufferLen) + return 0; + + StringParser theDateParser(inDateString); + + // the day of the week is redundant... we can skip it! + theDateParser.ConsumeLength(NULL, 5); + + // We are at the date now. + theDateStruct.tm_mday = theDateParser.ConsumeInteger(NULL); + theDateParser.ConsumeWhitespace(); + + // We are at the month now. Use our hand-crafted perfect hash table + // to get the right value to place in the tm struct + if (theDateParser.GetDataRemaining() < 4) + return 0; + + UInt32 theIndex = ConvertCharToMonthTableIndex(theDateParser.GetCurrentPosition()[0]) + + ConvertCharToMonthTableIndex(theDateParser.GetCurrentPosition()[1]) + + ConvertCharToMonthTableIndex(theDateParser.GetCurrentPosition()[2]); + + if (theIndex > kMonthHashTableSize) + return 0; + + theDateStruct.tm_mon = kMonthHashTable[theIndex]; + + // If the month is illegal, return an error + if (theDateStruct.tm_mon >= 12) + return 0; + + // Skip over the date + theDateParser.ConsumeLength(NULL, 4); + + // Grab the year (years since 1900 is what the tm struct wants) + theDateStruct.tm_year = theDateParser.ConsumeInteger(NULL) - 1900; + theDateParser.ConsumeWhitespace(); + + // Now just grab hour, minute, second + theDateStruct.tm_hour = theDateParser.ConsumeInteger(NULL); + theDateStruct.tm_hour += OS::GetGMTOffset(); + + theDateParser.ConsumeLength(NULL, 1); //skip over ':' + + theDateStruct.tm_min = theDateParser.ConsumeInteger(NULL); + theDateParser.ConsumeLength(NULL, 1); //skip over ':' + + theDateStruct.tm_sec = theDateParser.ConsumeInteger(NULL); + + // Ok, we've filled out the tm struct completely, now convert it to a time_t + time_t theTime = ::mktime(&theDateStruct); + return (SInt64)theTime * 1000; // convert to a time value in our timebase. +} + +void DateTranslator::UpdateDateBuffer(DateBuffer* inDateBuffer, const SInt64& inDate, time_t gmtoffset) +{ + if (inDateBuffer == NULL) + return; + + struct tm* gmt = NULL; + struct tm timeResult; + + if (inDate == 0) + { + time_t calendarTime = ::time(NULL) + gmtoffset; + gmt = ::qtss_gmtime(&calendarTime, &timeResult); + } + else + { + time_t convertedTime = (time_t)(inDate / (SInt64)1000) + gmtoffset ; // Convert from msec to sec + gmt = ::qtss_gmtime(&convertedTime, &timeResult); + } + + Assert(gmt != NULL); //is it safe to assert this? + size_t size = 0; + if (0 == gmtoffset) + size = qtss_strftime( inDateBuffer->fDateBuffer, sizeof(inDateBuffer->fDateBuffer), + "%a, %d %b %Y %H:%M:%S GMT", gmt); + + Assert(size == DateBuffer::kDateBufferLen); +} + +void DateBuffer::InexactUpdate() +{ + SInt64 theCurTime = OS::Milliseconds(); + if ((fLastDateUpdate == 0) || ((fLastDateUpdate + kUpdateInterval) < theCurTime)) + { + fLastDateUpdate = theCurTime; + this->Update(0); + } +} diff --git a/CommonUtilitiesLib/DateTranslator.h b/CommonUtilitiesLib/DateTranslator.h new file mode 100644 index 0000000..6c2373e --- /dev/null +++ b/CommonUtilitiesLib/DateTranslator.h @@ -0,0 +1,113 @@ +/* + * + * @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: DateTranslator.h + + Contains: Efficient routines & data structures for converting from + RFC 1123 compliant date strings to local file system dates & vice versa. + + + +*/ + +#ifndef _DATE_TRANSLATOR_H_ +#define _DATE_TRANSLATOR_H_ + +#include +#include +#include "StrPtrLen.h" + +class DateBuffer; + +class DateTranslator +{ + public: + + // this updates the DateBuffer to be the current date / time. + // If you wish to set the DateBuffer to a particular date, pass in that date. + // Dates should be in the OS.h compliant format + static void UpdateDateBuffer(DateBuffer* inDateBuffer, const SInt64& inDate, time_t gmtoffset = 0); + + //Given an HTTP/1.1 compliant date string (in one of the three 1.1 formats) + //this returns an OS.h compliant date/time value. + static SInt64 ParseDate(StrPtrLen* inDateString); + + private: + + static UInt32 ConvertCharToMonthTableIndex(int inChar) + { + return (UInt32)(toupper(inChar) - 'A'); // Convert to a value between 0 - 25 + } +}; + +class DateBuffer +{ +public: + + // This class provides no protection against being accessed from multiple threads + // simultaneously. Update & InexactUpdate rewrite the date buffer, so care should + // be taken to protect against simultaneous access. + + DateBuffer() : fLastDateUpdate(0) { fDateBuffer[0] = 0; } + ~DateBuffer() {} + + //SEE RFC 1123 for details on the date string format + //ex: Mon, 04 Nov 1996 21:42:17 GMT + + //RFC 1123 date strings are always of this length + enum + { + kDateBufferLen = 29 + }; + + // Updates this date buffer to reflect the current time. + // If a date is provided, this updates the DateBuffer to be that date. + void Update(const SInt64& inDate) { DateTranslator::UpdateDateBuffer(this, inDate); } + + // Updates this date buffer to reflect the current time, with a certain degree + // of inexactitude (the range of error is defined by the kUpdateInterval value) + void InexactUpdate(); + + //returns a NULL terminated C-string always of kHTTPDateLen length. + char *GetDateBuffer() { return fDateBuffer; } + +private: + + enum + { + kUpdateInterval = 60000 // Update every minute + }; + + //+1 for terminator +1 for padding + char fDateBuffer[kDateBufferLen + 2]; + SInt64 fLastDateUpdate; + + friend class DateTranslator; +}; + + +#endif + + diff --git a/CommonUtilitiesLib/DssStopwatch.h b/CommonUtilitiesLib/DssStopwatch.h new file mode 100644 index 0000000..8a52717 --- /dev/null +++ b/CommonUtilitiesLib/DssStopwatch.h @@ -0,0 +1,106 @@ +/* + * + * @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@ + * + */ +#ifndef __DSS_STOPWATCH__ +#define __DSS_STOPWATCH__ +//#include "DssStopwatch.h" + +#include "OS.h" + + +class DssEggtimer { + public: + enum { kDurationNeverExpire = -1 }; + + DssEggtimer( SInt64 inMilliseconds ) { fTimerDuration = inMilliseconds; Reset(); } + + void OneShotSetTo( SInt64 inMilliseconds ) + { + // set the egg timer to this time for one cycle. + // there after Reset will use fTimerDuration + fExpirationMilliseconds = OS::Milliseconds() + inMilliseconds; + } + void Reset() + { + //if ( fTimerDuration != (SInt64)kDurationNeverExpire ) + fExpirationMilliseconds = OS::Milliseconds() + fTimerDuration; + } + + void ResetTo(SInt64 inMilliseconds) + { + fTimerDuration = inMilliseconds; + this->Reset(); + } + + Bool16 Expired() + { + //if (fTimerDuration == (SInt64)kDurationNeverExpire ) + // return false; + + return fExpirationMilliseconds <= OS::Milliseconds(); + } + SInt64 MaxDuration() { return fTimerDuration; } + + private: + SInt64 fTimerDuration; + SInt64 fExpirationMilliseconds; + +}; + +class DssMillisecondStopwatch { + + public: + DssMillisecondStopwatch() : + fIsStarted(false) + , fTimerDuration(-1) + {} + ; + void Start() { fStartedAt = OS::Milliseconds(); fIsStarted = true; } + void Stop() { fTimerDuration = OS::Milliseconds() - fStartedAt; } + + SInt64 Duration() { return fTimerDuration; } + + private: + Bool16 fIsStarted; + SInt64 fTimerDuration; + SInt64 fStartedAt; +}; + +class DssDurationTimer { + + public: + DssDurationTimer() { fStartedAtMsec = OS::Milliseconds(); } + void Reset() { fStartedAtMsec = OS::Milliseconds(); } + void ResetToDuration( SInt64 inDurationInMsec ) { fStartedAtMsec = OS::Milliseconds() - inDurationInMsec; } + SInt64 DurationInMilliseconds() { return OS::Milliseconds() - fStartedAtMsec; } + SInt64 DurationInSeconds() { return (OS::Milliseconds() - fStartedAtMsec) / (SInt64)1000; } + + + private: + SInt64 fStartedAtMsec; +}; + + +#endif + diff --git a/CommonUtilitiesLib/EventContext.cpp b/CommonUtilitiesLib/EventContext.cpp new file mode 100644 index 0000000..7cec317 --- /dev/null +++ b/CommonUtilitiesLib/EventContext.cpp @@ -0,0 +1,283 @@ +/* + * + * @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: EventContext.cpp + + Contains: Impelments object in .h file + + + +*/ + +#include "EventContext.h" +#include "OSThread.h" +#include "atomic.h" + +#include +#include + +#ifndef __Win32__ +#include +#endif + +#if MACOSXEVENTQUEUE +#include "tempcalls.h" //includes MacOS X prototypes of event queue functions +#endif + +#define EVENT_CONTEXT_DEBUG 0 + +#if EVENT_CONTEXT_DEBUG +#include "OS.h" +#endif + +#ifdef __Win32__ +unsigned int EventContext::sUniqueID = WM_USER; // See commentary in RequestEvent +#else +unsigned int EventContext::sUniqueID = 1; +#endif + +EventContext::EventContext(int inFileDesc, EventThread* inThread) +: fFileDesc(inFileDesc), + fUniqueID(0), + fUniqueIDStr((char*)&fUniqueID, sizeof(fUniqueID)), + fEventThread(inThread), + fWatchEventCalled(false), + fAutoCleanup(true) +{} + + +void EventContext::InitNonBlocking(int inFileDesc) +{ + fFileDesc = inFileDesc; + +#ifdef __Win32__ + u_long one = 1; + int err = ::ioctlsocket(fFileDesc, FIONBIO, &one); +#else + int flag = ::fcntl(fFileDesc, F_GETFL, 0); + int err = ::fcntl(fFileDesc, F_SETFL, flag | O_NONBLOCK); +#endif + AssertV(err == 0, OSThread::GetErrno()); +} + +void EventContext::Cleanup() +{ + int err = 0; + + if (fFileDesc != kInvalidFileDesc) + { + //if this object is registered in the table, unregister it now + if (fUniqueID > 0) + { + fEventThread->fRefTable.UnRegister(&fRef); + +#if !MACOSXEVENTQUEUE + select_removeevent(fFileDesc);//The eventqueue / select shim requires this +#ifdef __Win32__ + err = ::closesocket(fFileDesc); +#endif + +#else + //On Linux (possibly other UNIX implementations) you MUST NOT close the fd before + //removing the fd from the select mask, and having the select function wake up + //to register this fact. If you close the fd first, bad things may happen, like + //the socket not getting unbound from the port & IP addr. + // + //So, what we do is have the select thread itself call close. This is triggered + //by calling removeevent. + err = ::close(fFileDesc); +#endif + } + else +#ifdef __Win32__ + err = ::closesocket(fFileDesc); +#else + err = ::close(fFileDesc); +#endif + } + + fFileDesc = kInvalidFileDesc; + fUniqueID = 0; + + AssertV(err == 0, OSThread::GetErrno());//we don't really care if there was an error, but it's nice to know +} + + +void EventContext::SnarfEventContext( EventContext &fromContext ) +{ + //+ show that we called watchevent + // copy the unique id + // set our fUniqueIDStr to the unique id + // copy the eventreq + // find the old event object + // show us as the object in the fRefTable + // we take the OSRef from the old context, point it at our context + // + //TODO - this whole operation causes a race condition for Event posting + // way up the chain we need to disable event posting + // or copy the posted events afer this op completes + + fromContext.fFileDesc = kInvalidFileDesc; + + fWatchEventCalled = fromContext.fWatchEventCalled; + fUniqueID = fromContext.fUniqueID; + fUniqueIDStr.Set((char*)&fUniqueID, sizeof(fUniqueID)), + + ::memcpy( &fEventReq, &fromContext.fEventReq, sizeof( struct eventreq ) ); + + fRef.Set( fUniqueIDStr, this ); + fEventThread->fRefTable.Swap(&fRef); + fEventThread->fRefTable.UnRegister(&fromContext.fRef); +} + +void EventContext::RequestEvent(int theMask) +{ +#if DEBUG + fModwatched = true; +#endif + + // + // The first time this function gets called, we're supposed to + // call watchevent. Each subsequent time, call modwatch. That's + // the way the MacOS X event queue works. + + if (fWatchEventCalled) + { + fEventReq.er_eventbits = theMask; +#if MACOSXEVENTQUEUE + if (modwatch(&fEventReq, theMask) != 0) +#else + if (select_modwatch(&fEventReq, theMask) != 0) +#endif + AssertV(false, OSThread::GetErrno()); + } + else + { + //allocate a Unique ID for this socket, and add it to the ref table + +#ifdef __Win32__ + // + // Kind of a hack. On Win32, the way that we pass around the unique ID is + // by making it the message ID of our Win32 message (see win32ev.cpp). + // Messages must be >= WM_USER. Hence this code to restrict the numberspace + // of our UniqueIDs. + if (!compare_and_store(8192, WM_USER, &sUniqueID)) // Fix 2466667: message IDs above a + fUniqueID = (PointerSizedInt)atomic_add(&sUniqueID, 1); // level are ignored, so wrap at 8192 + else + fUniqueID = (PointerSizedInt)WM_USER; +#else + if (!compare_and_store(10000000, 1, &sUniqueID)) + fUniqueID = (PointerSizedInt)atomic_add(&sUniqueID, 1); + else + fUniqueID = 1; +#endif + + fRef.Set(fUniqueIDStr, this); + fEventThread->fRefTable.Register(&fRef); + + //fill out the eventreq data structure + ::memset( &fEventReq, '\0', sizeof(fEventReq)); + fEventReq.er_type = EV_FD; + fEventReq.er_handle = fFileDesc; + fEventReq.er_eventbits = theMask; + fEventReq.er_data = (void*)fUniqueID; + + fWatchEventCalled = true; +#if MACOSXEVENTQUEUE + if (watchevent(&fEventReq, theMask) != 0) +#else + if (select_watchevent(&fEventReq, theMask) != 0) +#endif + //this should never fail, but if it does, cleanup. + AssertV(false, OSThread::GetErrno()); + + } +} + +void EventThread::Entry() +{ + struct eventreq theCurrentEvent; + ::memset( &theCurrentEvent, '\0', sizeof(theCurrentEvent) ); + + while (true) + { + int theErrno = EINTR; + while (theErrno == EINTR) + { +#if MACOSXEVENTQUEUE + int theReturnValue = waitevent(&theCurrentEvent, NULL); +#else + int theReturnValue = select_waitevent(&theCurrentEvent, NULL); +#endif + //Sort of a hack. In the POSIX version of the server, waitevent can return + //an actual POSIX errorcode. + if (theReturnValue >= 0) + theErrno = theReturnValue; + else + theErrno = OSThread::GetErrno(); + } + + AssertV(theErrno == 0, theErrno); + + //ok, there's data waiting on this socket. Send a wakeup. + if (theCurrentEvent.er_data != NULL) + { + //The cookie in this event is an ObjectID. Resolve that objectID into + //a pointer. + StrPtrLen idStr((char*)&theCurrentEvent.er_data, sizeof(theCurrentEvent.er_data)); + OSRef* ref = fRefTable.Resolve(&idStr); + if (ref != NULL) + { + EventContext* theContext = (EventContext*)ref->GetObject(); +#if DEBUG + theContext->fModwatched = false; +#endif + theContext->ProcessEvent(theCurrentEvent.er_eventbits); + fRefTable.Release(ref); + + + } + } + +#if EVENT_CONTEXT_DEBUG + SInt64 yieldStart = OS::Milliseconds(); +#endif + + this->ThreadYield(); + +#if EVENT_CONTEXT_DEBUG + SInt64 yieldDur = OS::Milliseconds() - yieldStart; + static SInt64 numZeroYields; + + if ( yieldDur > 1 ) + { + qtss_printf( "EventThread time in OSTHread::Yield %i, numZeroYields %i\n", (SInt32)yieldDur, (SInt32)numZeroYields ); + numZeroYields = 0; + } + else + numZeroYields++; +#endif + } +} diff --git a/CommonUtilitiesLib/EventContext.h b/CommonUtilitiesLib/EventContext.h new file mode 100644 index 0000000..6fa8b83 --- /dev/null +++ b/CommonUtilitiesLib/EventContext.h @@ -0,0 +1,180 @@ +/* + * + * @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: EventContext.h + + Contains: An event context provides the intelligence to take an event + generated from a UNIX file descriptor (usually EV_RE or EV_WR) + and signal a Task. + + + + +*/ + +#ifndef __EVENT_CONTEXT_H__ +#define __EVENT_CONTEXT_H__ + +#include "OSThread.h" +#include "Task.h" +#include "OSRef.h" + +#if MACOSXEVENTQUEUE + #ifdef AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER + #include + #else + #include "ev.h" + #endif +#else + #include "ev.h" +#endif + + +//enable to trace event context execution and the task associated with the context +#define EVENTCONTEXT_DEBUG 0 + +class EventThread; + +class EventContext +{ + public: + + // + // Constructor. Pass in the EventThread you would like to receive + // events for this context, and the fd that this context applies to + EventContext(int inFileDesc, EventThread* inThread); + virtual ~EventContext() { if (fAutoCleanup) this->Cleanup(); } + + // + // InitNonBlocking + // + // Sets inFileDesc to be non-blocking. Once this is called, the + // EventContext object "owns" the file descriptor, and will close it + // when Cleanup is called. This is necessary because of some weird + // select() behavior. DON'T CALL CLOSE ON THE FD ONCE THIS IS CALLED!!!! + void InitNonBlocking(int inFileDesc); + + // + // Cleanup. Will be called by the destructor, but can be called earlier + void Cleanup(); + + // + // Arms this EventContext. Pass in the events you would like to receive + void RequestEvent(int theMask = EV_RE); + + + // + // Provide the task you would like to be notified + void SetTask(Task* inTask) + { + fTask = inTask; + if (EVENTCONTEXT_DEBUG) + { + if (fTask== NULL) + qtss_printf("EventContext::SetTask context=%p task= NULL\n", (void *) this); + else + qtss_printf("EventContext::SetTask context=%p task= %p name=%s\n",(void *) this,(void *) fTask, fTask->fTaskName); + } + } + + // when the HTTP Proxy tunnels takes over a TCPSocket, we need to maintain this context too + void SnarfEventContext( EventContext &fromContext ); + + // Don't cleanup this socket automatically + void DontAutoCleanup() { fAutoCleanup = false; } + + // Direct access to the FD is not recommended, but is needed for modules + // that want to use the Socket classes and need to request events on the fd. + int GetSocketFD() { return fFileDesc; } + + enum + { + kInvalidFileDesc = -1 //int + }; + + protected: + + // + // ProcessEvent + // + // When an event occurs on this file descriptor, this function + // will get called. Default behavior is to Signal the associated + // task, but that behavior may be altered / overridden. + // + // Currently, we always generate a Task::kReadEvent + virtual void ProcessEvent(int /*eventBits*/) + { + if (EVENTCONTEXT_DEBUG) + { + if (fTask== NULL) + qtss_printf("EventContext::ProcessEvent context=%p task=NULL\n",(void *) this); + else + qtss_printf("EventContext::ProcessEvent context=%p task=%p TaskName=%s\n",(void *)this,(void *) fTask, fTask->fTaskName); + } + + if (fTask != NULL) + fTask->Signal(Task::kReadEvent); + } + + int fFileDesc; + + private: + + struct eventreq fEventReq; + + OSRef fRef; + PointerSizedInt fUniqueID; + StrPtrLen fUniqueIDStr; + EventThread* fEventThread; + Bool16 fWatchEventCalled; + int fEventBits; + Bool16 fAutoCleanup; + + Task* fTask; +#if DEBUG + Bool16 fModwatched; +#endif + + static unsigned int sUniqueID; + + friend class EventThread; +}; + +class EventThread : public OSThread +{ + public: + + EventThread() : OSThread() {} + virtual ~EventThread() {} + + private: + + virtual void Entry(); + OSRefTable fRefTable; + + friend class EventContext; +}; + +#endif //__EVENT_CONTEXT_H__ diff --git a/CommonUtilitiesLib/FastCopyMacros.h b/CommonUtilitiesLib/FastCopyMacros.h new file mode 100644 index 0000000..4dd8ef2 --- /dev/null +++ b/CommonUtilitiesLib/FastCopyMacros.h @@ -0,0 +1,40 @@ +/* + * + * @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@ + * + */ +#ifndef __FastCopyMacros__ +#define __FastCopyMacros__ + +#define COPY_BYTE( dest, src ) ( *((char*)(dest)) = *((char*)(src)) ) +#define COPY_WORD( dest, src ) ( *((SInt16*)(dest)) = *((SInt16*)(src)) ) +#define COPY_LONG_WORD( dest, src ) ( *((SInt32*)(dest)) = *((SInt32*)(src)) ) +#define COPY_LONG_LONG_WORD( dest, src ) ( *((SInt64*)(dest)) = *((SInt64**)(src)) ) + +#define MOVE_BYTE( dest, src ) ( dest = *((char*)(src)) ) +#define MOVE_WORD( dest, src ) ( dest = *((SInt16*)(src)) ) +#define MOVE_LONG_WORD( dest, src ) ( dest = *((SInt32*)(src)) ) +#define MOVE_LONG_LONG_WORD( dest, src ) ( dest = *((SInt64**)(src)) ) + + +#endif + diff --git a/CommonUtilitiesLib/GetWord.c b/CommonUtilitiesLib/GetWord.c new file mode 100644 index 0000000..0fb53f1 --- /dev/null +++ b/CommonUtilitiesLib/GetWord.c @@ -0,0 +1,102 @@ +/* + * + * @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@ + * + */ + +#include "GetWord.h" + +char* GetWord( char* toWordPtr, char* fromStrPtr, SInt32 limit ) +{ + // get a word from a string + // copy result into toWordPtr, return one past end of the + // word, limit is max for toWordPtr + // fromStrPtr + + // trim any leading white space + while ( (unsigned char)*fromStrPtr <= 0x20 && *fromStrPtr ) + fromStrPtr++; + + // copy until we find more white space + while ( limit && (unsigned char)*fromStrPtr > 0x20 && *fromStrPtr ) + { + *toWordPtr++ = *fromStrPtr++; + limit--; + } + + *toWordPtr = 0x00; + + return (char *) fromStrPtr; +} + +char * GetQuotedWord( char* toWordPtr, char* fromStrPtr, SInt32 limit ) +{ + // get a quote encoded word from a string + // make include white space + int lastWasQuote = 0; + + // trim any leading white space + while ( ( (unsigned char)*fromStrPtr <= 0x20 ) && *fromStrPtr ) + fromStrPtr++; + + + if ( (unsigned char)*fromStrPtr == '"' ) + { // must lead with quote sign after white space + fromStrPtr++; + + + + // copy until we find the last single quote + while ( limit && *fromStrPtr ) + { + if ( (unsigned char)*fromStrPtr == '"' ) + { + if ( lastWasQuote ) + { + *toWordPtr++ = '"'; + lastWasQuote = 0; + limit--; + } + else + lastWasQuote = 1; + } + else + { + if ( !lastWasQuote ) + { *toWordPtr++ = *fromStrPtr; + limit--; + } + else // we're done, hit a quote by itself + break; + + } + + // consume the char we read + fromStrPtr++; + + } + } + + *toWordPtr = 0x00; + + return (char *) fromStrPtr; +} diff --git a/CommonUtilitiesLib/GetWord.h b/CommonUtilitiesLib/GetWord.h new file mode 100644 index 0000000..45dae24 --- /dev/null +++ b/CommonUtilitiesLib/GetWord.h @@ -0,0 +1,43 @@ +/* + * + * @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@ + * + */ + +#ifndef __getword__ +#define __getword__ + + #ifdef __cplusplus + extern "C" { + #endif + + #include "OSHeaders.h" + + char* GetWord( char* toWordPtr, char* fromStrPtr, SInt32 limit ); + char* GetQuotedWord( char* toWordPtr, char* fromStrPtr, SInt32 limit ); + + #ifdef __cplusplus + } + #endif + + +#endif diff --git a/CommonUtilitiesLib/IdleTask.cpp b/CommonUtilitiesLib/IdleTask.cpp new file mode 100644 index 0000000..3614040 --- /dev/null +++ b/CommonUtilitiesLib/IdleTask.cpp @@ -0,0 +1,124 @@ +/* + * + * @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: IdleTask.cpp + + Contains: IdleTasks are identical to normal tasks (see task.h) with one exception: + + You can schedule them for timeouts. If you call SetIdleTimer + on one, after the time has elapsed the task object will receive an + OS_IDLE event. + + + +*/ + +#include "IdleTask.h" +#include "OSMemory.h" +#include "OS.h" + +//IDLETASKTHREAD IMPLEMENTATION: +IdleTaskThread* IdleTask::sIdleThread = NULL; + +void IdleTaskThread::SetIdleTimer(IdleTask *activeObj, SInt64 msec) +{ + //note: OSHeap doesn't support a random remove, so this function + //won't change the timeout value if there is already one set + if (activeObj->fIdleElem.IsMemberOfAnyHeap()) + return; + activeObj->fIdleElem.SetValue(OS::Milliseconds() + msec); + + { + OSMutexLocker locker(&fHeapMutex); + fIdleHeap.Insert(&activeObj->fIdleElem); + } + fHeapCond.Signal(); +} + +void IdleTaskThread::CancelTimeout(IdleTask* idleObj) +{ + Assert(idleObj != NULL); + OSMutexLocker locker(&fHeapMutex); + fIdleHeap.Remove(&idleObj->fIdleElem); +} + +void +IdleTaskThread::Entry() +{ + OSMutexLocker locker(&fHeapMutex); + + while (true) + { + //if there are no events to process, block. + if (fIdleHeap.CurrentHeapSize() == 0) + fHeapCond.Wait(&fHeapMutex); + SInt64 msec = OS::Milliseconds(); + + //pop elements out of the heap as long as their timeout time has arrived + while ((fIdleHeap.CurrentHeapSize() > 0) && (fIdleHeap.PeekMin()->GetValue() <= msec)) + { + IdleTask* elem = (IdleTask*)fIdleHeap.ExtractMin()->GetEnclosingObject(); + Assert(elem != NULL); + elem->Signal(Task::kIdleEvent); + } + + //we are done sending idle events. If there is a lowest tick count, then + //we need to sleep until that time. + if (fIdleHeap.CurrentHeapSize() > 0) + { + SInt64 timeoutTime = fIdleHeap.PeekMin()->GetValue(); + //because sleep takes a 32 bit number + timeoutTime -= msec; + Assert(timeoutTime > 0); + UInt32 smallTime = (UInt32)timeoutTime; + fHeapCond.Wait(&fHeapMutex, smallTime); + } + } +} + +void IdleTask::Initialize() +{ + if (sIdleThread == NULL) + { + sIdleThread = NEW IdleTaskThread(); + sIdleThread->Start(); + } +} + +IdleTask::~IdleTask() +{ + //clean up stuff used by idle thread routines + Assert(sIdleThread != NULL); + + OSMutexLocker locker(&sIdleThread->fHeapMutex); + + //Check to see if there is a pending timeout. If so, get this object + //out of the heap + if (fIdleElem.IsMemberOfAnyHeap()) + sIdleThread->CancelTimeout(this); +} + + + diff --git a/CommonUtilitiesLib/IdleTask.h b/CommonUtilitiesLib/IdleTask.h new file mode 100644 index 0000000..5465f34 --- /dev/null +++ b/CommonUtilitiesLib/IdleTask.h @@ -0,0 +1,108 @@ +/* + * + * @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: IdleTask.h + + Contains: IdleTasks are identical to normal tasks (see task.h) with one exception: + + You can schedule them for timeouts. If you call SetIdleTimer + on one, after the time has elapsed the task object will receive an + OS_IDLE event. + + + +*/ + + +#ifndef _IDLETASK_H_ +#define _IDLETASK_H_ + +#include "Task.h" + +#include "OSThread.h" +#include "OSHeap.h" +#include "OSMutex.h" +#include "OSCond.h" + +class IdleTask; + +//merely a private implementation detail of IdleTask +class IdleTaskThread : private OSThread +{ +private: + + IdleTaskThread() : OSThread(), fHeapMutex() {} + virtual ~IdleTaskThread() { Assert(fIdleHeap.CurrentHeapSize() == 0); } + + void SetIdleTimer(IdleTask *idleObj, SInt64 msec); + void CancelTimeout(IdleTask *idleObj); + + virtual void Entry(); + OSHeap fIdleHeap; + OSMutex fHeapMutex; + OSCond fHeapCond; + friend class IdleTask; +}; + + +class IdleTask : public Task +{ + +public: + + //Call Initialize before using this class + static void Initialize(); + + IdleTask() : Task(), fIdleElem() { this->SetTaskName("IdleTask"); fIdleElem.SetEnclosingObject(this); } + + //This object does a "best effort" of making sure a timeout isn't + //pending for an object being deleted. In other words, if there is + //a timeout pending, and the destructor is called, things will get cleaned + //up. But callers must ensure that SetIdleTimer isn't called at the same + //time as the destructor, or all hell will break loose. + virtual ~IdleTask(); + + //SetIdleTimer: + //This object will receive an OS_IDLE event in the following number of milliseconds. + //Only one timeout can be outstanding, if there is already a timeout scheduled, this + //does nothing. + void SetIdleTimer(SInt64 msec) { sIdleThread->SetIdleTimer(this, msec); } + + //CancelTimeout + //If there is a pending timeout for this object, this function cancels it. + //If there is no pending timeout, this function does nothing. + //Currently not supported because OSHeap doesn't support random remove + void CancelTimeout() { sIdleThread->CancelTimeout(this); } + +private: + + OSHeapElem fIdleElem; + + //there is only one idle thread shared by all idle tasks. + static IdleTaskThread* sIdleThread; + + friend class IdleTaskThread; +}; +#endif diff --git a/CommonUtilitiesLib/MakeDir.c b/CommonUtilitiesLib/MakeDir.c new file mode 100644 index 0000000..819831c --- /dev/null +++ b/CommonUtilitiesLib/MakeDir.c @@ -0,0 +1,110 @@ + +/* + * + * @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@ + * + */ + + +#include "MakeDir.h" + +#include "PathDelimiter.h" + + +#if (! __MACOS__) + #include + #include + #include + #ifndef __solaris__ + #include + #endif + #include +#else + #include "BogusDefs.h" +#endif + +#include +#include +#include "SafeStdLib.h" + + +int MakeDir(const char* inPath, int mode) +{ + struct stat theStatBuffer; + if (stat(inPath, &theStatBuffer) == -1) + { + //this directory doesn't exist, so let's try to create it + if (mkdir(inPath, mode) == -1) + return -1; //€- (QTSS_ErrorCode)OSThread::GetErrno(); + } + else if (!S_ISDIR(theStatBuffer.st_mode)) + return -1; //€- QTSS_FileExists;//there is a file at this point in the path! + + //directory exists + return 0; //€- QTSS_NoErr; +} + +int RecursiveMakeDir(const char* inPath, int mode) +{ + //PL_ASSERT(inPath != NULL); + char pathCopy[256]; + char* thePathTraverser = pathCopy; + int theErr; + char oldChar; + + + if ( strlen( inPath ) > 255 ) + return -1; + + //iterate through the path, replacing kPathDelimiterChar with '\0' as we go + + strcpy( pathCopy, inPath ); + + //skip over the first / in the path. + if (*thePathTraverser == kPathDelimiterChar ) + thePathTraverser++; + + while (*thePathTraverser != '\0') + { + if (*thePathTraverser == kPathDelimiterChar) + { + //we've found a filename divider. Now that we have a complete + //filename, see if this partial path exists. + + //make the partial path into a C string + // mkdir does not like a trailing '/' + oldChar = *thePathTraverser; + *thePathTraverser = '\0'; + theErr = MakeDir(pathCopy, mode); + //there is a directory here. Just continue in our traversal + *thePathTraverser = oldChar; + + if (theErr) + return theErr; + } + + thePathTraverser++; + } + + //need to create the last directory in the path + return MakeDir(inPath, mode); +} diff --git a/CommonUtilitiesLib/MakeDir.h b/CommonUtilitiesLib/MakeDir.h new file mode 100644 index 0000000..16713e2 --- /dev/null +++ b/CommonUtilitiesLib/MakeDir.h @@ -0,0 +1,59 @@ +/* + * + * @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@ + * + */ + +#ifndef __makedir__ +#define __makedir__ + +#if (! __MACOS__) + #include + #include + #include + #ifndef __solaris__ || __hpux__ + #include + #endif + #include +#else + #include "BogusDefs.h" +#endif + +#ifndef S_IRWXU + #define S_IRWXU 0 +#endif + + + #ifdef __cplusplus + extern "C" { + #endif + + + int MakeDir( const char* path, int mode ); + int RecursiveMakeDir( const char*inPath, int mode); + + #ifdef __cplusplus + } + #endif + + +#endif diff --git a/CommonUtilitiesLib/MyAssert.cpp b/CommonUtilitiesLib/MyAssert.cpp new file mode 100644 index 0000000..2b98644 --- /dev/null +++ b/CommonUtilitiesLib/MyAssert.cpp @@ -0,0 +1,50 @@ +/* + * + * @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@ + * + */ + +#include "MyAssert.h" +#include "OSHeaders.h" +#include "SafeStdLib.h" + +static AssertLogger* sLogger = NULL; + +void SetAssertLogger(AssertLogger* theLogger) +{ + sLogger = theLogger; +} + +void MyAssert(char *inMessage) +{ + if (sLogger != NULL) + sLogger->LogAssert(inMessage); + else + { + qtss_printf("%s\n", inMessage); +#if __Win32__ + DebugBreak(); +#else + (*(SInt32*)0) = 0; +#endif + } +} diff --git a/CommonUtilitiesLib/MyAssert.h b/CommonUtilitiesLib/MyAssert.h new file mode 100644 index 0000000..6588f40 --- /dev/null +++ b/CommonUtilitiesLib/MyAssert.h @@ -0,0 +1,94 @@ +/* + * + * @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@ + * + */ + +#ifndef _MYASSERT_H_ +#define _MYASSERT_H_ + +#include +#include "SafeStdLib.h" + +#ifdef __cplusplus +class AssertLogger +{ + public: + // An interface so the MyAssert function can write a message + virtual void LogAssert(char* inMessage) = 0; + virtual ~AssertLogger(){}; +}; + +// If a logger is provided, asserts will be logged. Otherwise, asserts will cause a bus error +void SetAssertLogger(AssertLogger* theLogger); +#endif + +#if ASSERT + void MyAssert(char *s); + + #define kAssertBuffSize 256 + + #define Assert(condition) { \ + \ + if (!(condition)) \ + { \ + char s[kAssertBuffSize]; \ + s[kAssertBuffSize -1] = 0; \ + qtss_snprintf (s,kAssertBuffSize -1, "_Assert: %s, %d",__FILE__, __LINE__ ); \ + MyAssert(s); \ + } } + + + #define AssertV(condition,errNo) { \ + if (!(condition)) \ + { \ + char s[kAssertBuffSize]; \ + s[kAssertBuffSize -1] = 0; \ + qtss_snprintf( s,kAssertBuffSize -1, "_AssertV: %s, %d (%d)",__FILE__, __LINE__, errNo ); \ + MyAssert(s); \ + } } + + + #define Warn(condition) { \ + if (!(condition)) \ + qtss_printf( "_Warn: %s, %d\n",__FILE__, __LINE__ ); } + + #define WarnV(condition,msg) { \ + if (!(condition)) \ + qtss_printf ("_WarnV: %s, %d (%s)\n",__FILE__, __LINE__, msg ); } + + #define WarnVE(condition,msg,err) { \ + if (!(condition)) \ + { char buffer[kAssertBuffSize]; \ + buffer[kAssertBuffSize -1] = 0; \ + qtss_printf ("_WarnV: %s, %d (%s, %s [err=%d])\n",__FILE__, __LINE__, msg, qtss_strerror(err,buffer,sizeof(buffer) -1), err ); \ + } } + +#else + + #define Assert(condition) ((void) 0) + #define AssertV(condition,errNo) ((void) 0) + #define Warn(condition) ((void) 0) + #define WarnV(condition,msg) ((void) 0) + +#endif +#endif //_MY_ASSERT_H_ diff --git a/CommonUtilitiesLib/OS.cpp b/CommonUtilitiesLib/OS.cpp new file mode 100644 index 0000000..6db5045 --- /dev/null +++ b/CommonUtilitiesLib/OS.cpp @@ -0,0 +1,470 @@ +/* + * + * @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: OS.cpp + + Contains: OS utility functions + + + + +*/ + +#include +#include "SafeStdLib.h" +#include + +#include +#include +#include + +#include + +#ifndef __Win32__ +#include +#endif + +#ifdef __sgi__ +#include +#endif + +#include "OS.h" +#include "OSThread.h" +#include "MyAssert.h" +#include "OSFileSource.h" + +#if __MacOSX__ + +#ifndef __COREFOUNDATION__ +#include +//extern "C" { void Microseconds (UnsignedWide *microTickCount); } +#endif + +#endif + + +#if (__FreeBSD__ || __MacOSX__) + #include +#endif + +#if (__solaris__ || __linux__ || __linuxppc__) + #include "StringParser.h" +#endif + +#if __sgi__ + #include +#endif + + +double OS::sDivisor = 0; +double OS::sMicroDivisor = 0; +SInt64 OS::sMsecSince1970 = 0; +SInt64 OS::sMsecSince1900 = 0; +SInt64 OS::sInitialMsec = 0; +SInt64 OS::sWrapTime = 0; +SInt64 OS::sCompareWrap = 0; +SInt64 OS::sLastTimeMilli = 0; +OSMutex OS::sStdLibOSMutex; + +#if DEBUG || __Win32__ +#include "OSMutex.h" +#include "OSMemory.h" +static OSMutex* sLastMillisMutex = NULL; +#endif + +void OS::Initialize() +{ + Assert (sInitialMsec == 0); // do only once + if (sInitialMsec != 0) return; + ::tzset(); + + //setup t0 value for msec since 1900 + + //t.tv_sec is number of seconds since Jan 1, 1970. Convert to seconds since 1900 + SInt64 the1900Sec = (SInt64) (24 * 60 * 60) * (SInt64) ((70 * 365) + 17) ; + sMsecSince1900 = the1900Sec * 1000; + + sWrapTime = (SInt64) 0x00000001 << 32; + sCompareWrap = (SInt64) 0xffffffff << 32; + sLastTimeMilli = 0; + + sInitialMsec = OS::Milliseconds(); //Milliseconds uses sInitialMsec so this assignment is valid only once. + + sMsecSince1970 = ::time(NULL); // POSIX time always returns seconds since 1970 + sMsecSince1970 *= 1000; // Convert to msec + + +#if DEBUG || __Win32__ + sLastMillisMutex = NEW OSMutex(); +#endif +} + +SInt64 OS::Milliseconds() +{ +/* +#if __MacOSX__ + +#if DEBUG + OSMutexLocker locker(sLastMillisMutex); +#endif + + UnsignedWide theMicros; + ::Microseconds(&theMicros); + SInt64 scalarMicros = theMicros.hi; + scalarMicros <<= 32; + scalarMicros += theMicros.lo; + scalarMicros = ((scalarMicros / 1000) - sInitialMsec) + sMsecSince1970; // convert to msec + +#if DEBUG + static SInt64 sLastMillis = 0; + //Assert(scalarMicros >= sLastMillis); // currently this fails on dual processor machines + sLastMillis = scalarMicros; +#endif + return scalarMicros; +*/ +#if __Win32__ + OSMutexLocker locker(sLastMillisMutex); + // curTimeMilli = timeGetTime() + ((sLastTimeMilli/ 2^32) * 2^32) + // using binary & to reduce it to one operation from two + // sCompareWrap and sWrapTime are constants that are never changed + // sLastTimeMilli is updated with the curTimeMilli after each call to this function + SInt64 curTimeMilli = (UInt32) ::timeGetTime() + (sLastTimeMilli & sCompareWrap); + if((curTimeMilli - sLastTimeMilli) < 0) + { + curTimeMilli += sWrapTime; + } + sLastTimeMilli = curTimeMilli; + + // For debugging purposes + //SInt64 tempCurMsec = (curTimeMilli - sInitialMsec) + sMsecSince1970; + //SInt32 tempCurSec = tempCurMsec / 1000; + //char buffer[kTimeStrSize]; + //qtss_printf("OS::MilliSeconds current time = %s\n", qtss_ctime(&tempCurSec, buffer, sizeof(buffer))); + + return (curTimeMilli - sInitialMsec) + sMsecSince1970; // convert to application time +#else + struct timeval t; + int theErr = ::gettimeofday(&t, NULL); + Assert(theErr == 0); + + SInt64 curTime; + curTime = t.tv_sec; + curTime *= 1000; // sec -> msec + curTime += t.tv_usec / 1000; // usec -> msec + + return (curTime - sInitialMsec) + sMsecSince1970; +#endif + +} + +SInt64 OS::Microseconds() +{ +/* +#if __MacOSX__ + UnsignedWide theMicros; + ::Microseconds(&theMicros); + SInt64 theMillis = theMicros.hi; + theMillis <<= 32; + theMillis += theMicros.lo; + return theMillis; +*/ +#if __Win32__ + SInt64 curTime = (SInt64) ::timeGetTime(); // system time in milliseconds + curTime -= sInitialMsec; // convert to application time + curTime *= 1000; // convert to microseconds + return curTime; +#else + struct timeval t; + int theErr = ::gettimeofday(&t, NULL); + Assert(theErr == 0); + + SInt64 curTime; + curTime = t.tv_sec; + curTime *= 1000000; // sec -> usec + curTime += t.tv_usec; + + return curTime - (sInitialMsec * 1000); +#endif +} + +SInt32 OS::GetGMTOffset() +{ +#ifdef __Win32__ + TIME_ZONE_INFORMATION tzInfo; + DWORD theErr = ::GetTimeZoneInformation(&tzInfo); + if (theErr == TIME_ZONE_ID_INVALID) + return 0; + + return ((tzInfo.Bias / 60) * -1); +#else + + time_t clock; + struct tm *tmptr= localtime(&clock); + if (tmptr == NULL) + return 0; + + return tmptr->tm_gmtoff / 3600;//convert seconds to hours before or after GMT +#endif +} + + +SInt64 OS::HostToNetworkSInt64(SInt64 hostOrdered) +{ +#if BIGENDIAN + return hostOrdered; +#else + return (SInt64) ( (UInt64) (hostOrdered << 56) | (UInt64) (((UInt64) 0x00ff0000 << 32) & (hostOrdered << 40)) + | (UInt64) ( ((UInt64) 0x0000ff00 << 32) & (hostOrdered << 24)) | (UInt64) (((UInt64) 0x000000ff << 32) & (hostOrdered << 8)) + | (UInt64) ( ((UInt64) 0x00ff0000 << 8) & (hostOrdered >> 8)) | (UInt64) ((UInt64) 0x00ff0000 & (hostOrdered >> 24)) + | (UInt64) ( (UInt64) 0x0000ff00 & (hostOrdered >> 40)) | (UInt64) ((UInt64) 0x00ff & (hostOrdered >> 56)) ); +#endif +} + +SInt64 OS::NetworkToHostSInt64(SInt64 networkOrdered) +{ +#if BIGENDIAN + return networkOrdered; +#else + return (SInt64) ( (UInt64) (networkOrdered << 56) | (UInt64) (((UInt64) 0x00ff0000 << 32) & (networkOrdered << 40)) + | (UInt64) ( ((UInt64) 0x0000ff00 << 32) & (networkOrdered << 24)) | (UInt64) (((UInt64) 0x000000ff << 32) & (networkOrdered << 8)) + | (UInt64) ( ((UInt64) 0x00ff0000 << 8) & (networkOrdered >> 8)) | (UInt64) ((UInt64) 0x00ff0000 & (networkOrdered >> 24)) + | (UInt64) ( (UInt64) 0x0000ff00 & (networkOrdered >> 40)) | (UInt64) ((UInt64) 0x00ff & (networkOrdered >> 56)) ); +#endif +} + + +OS_Error OS::MakeDir(char *inPath) +{ + struct stat theStatBuffer; + if (::stat(inPath, &theStatBuffer) == -1) + { + //this directory doesn't exist, so let's try to create it +#ifdef __Win32__ + if (::mkdir(inPath) == -1) +#else + if (::mkdir(inPath, S_IRWXU) == -1) +#endif + return (OS_Error)OSThread::GetErrno(); + } +#ifdef __Win32__ + else if (!(theStatBuffer.st_mode & _S_IFDIR)) // MSVC++ doesn't define the S_ISDIR macro + return EEXIST; // there is a file at this point in the path! +#else + else if (!S_ISDIR(theStatBuffer.st_mode)) + return EEXIST;//there is a file at this point in the path! +#endif + + //directory exists + return OS_NoErr; +} + +OS_Error OS::RecursiveMakeDir(char *inPath) +{ + Assert(inPath != NULL); + + //iterate through the path, replacing '/' with '\0' as we go + char *thePathTraverser = inPath; + + //skip over the first / in the path. + if (*thePathTraverser == kPathDelimiterChar) + thePathTraverser++; + + while (*thePathTraverser != '\0') + { + if (*thePathTraverser == kPathDelimiterChar) + { + //we've found a filename divider. Now that we have a complete + //filename, see if this partial path exists. + + //make the partial path into a C string + *thePathTraverser = '\0'; + OS_Error theErr = MakeDir(inPath); + //there is a directory here. Just continue in our traversal + *thePathTraverser = kPathDelimiterChar; + + if (theErr != OS_NoErr) + return theErr; + } + thePathTraverser++; + } + + //need to create the last directory in the path + return MakeDir(inPath); +} + +Bool16 OS::ThreadSafe() +{ + +#if (__MacOSX__) // check for version 7 or greater for thread safe stdlib + char releaseStr[32] = ""; + size_t strLen = sizeof(releaseStr); + int mib[2]; + mib[0] = CTL_KERN; + mib[1] = KERN_OSRELEASE; + + UInt32 majorVers = 0; + int err = sysctl(mib,2, releaseStr, &strLen, NULL,0); + if (err == 0) + { + StrPtrLen rStr(releaseStr,strLen); + char* endMajor = rStr.FindString("."); + if (endMajor != NULL) // truncate to an int value. + *endMajor = 0; + + if (::strlen(releaseStr) > 0) //convert to an int + ::sscanf(releaseStr, "%"_U32BITARG_"", &majorVers); + } + if (majorVers < 7) // less than OS X Panther 10.3 + return false; // force 1 worker thread because < 10.3 means std c lib is not thread safe. + +#endif + + return true; + +} + + +UInt32 OS::GetNumProcessors() +{ +#if (__Win32__) + SYSTEM_INFO theSystemInfo; + ::GetSystemInfo(&theSystemInfo); + + return (UInt32)theSystemInfo.dwNumberOfProcessors; +#endif + +#if (__MacOSX__ || __FreeBSD__) + int numCPUs = 1; + size_t len = sizeof(numCPUs); + int mib[2]; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + (void) ::sysctl(mib,2,&numCPUs,&len,NULL,0); + if (numCPUs < 1) + numCPUs = 1; + return (UInt32) numCPUs; +#endif + +#if(__linux__ || __linuxppc__) + + char cpuBuffer[8192] = ""; + StrPtrLen cpuInfoBuf(cpuBuffer, sizeof(cpuBuffer)); + FILE *cpuFile = ::fopen( "/proc/cpuinfo", "r" ); + if (cpuFile) + { cpuInfoBuf.Len = ::fread(cpuInfoBuf.Ptr, sizeof(char), cpuInfoBuf.Len, cpuFile); + ::fclose(cpuFile); + } + + StringParser cpuInfoFileParser(&cpuInfoBuf); + StrPtrLen line; + StrPtrLen word; + UInt32 numCPUs = 0; + + while( cpuInfoFileParser.GetDataRemaining() != 0 ) + { + cpuInfoFileParser.GetThruEOL(&line); // Read each line + StringParser lineParser(&line); + lineParser.ConsumeWhitespace(); //skip over leading whitespace + + if (lineParser.GetDataRemaining() == 0) // must be an empty line + continue; + + lineParser.ConsumeUntilWhitespace(&word); + + if ( word.Equal("processor") ) // found a processor as first word in line + { numCPUs ++; + } + } + + if (numCPUs == 0) + numCPUs = 1; + + return numCPUs; +#endif + +#if(__solaris__) +{ + UInt32 numCPUs = 0; + char linebuff[512] = ""; + StrPtrLen line(linebuff, sizeof(linebuff)); + StrPtrLen word; + + FILE *p = ::popen("uname -X","r"); + while((::fgets(linebuff, sizeof(linebuff -1), p)) > 0) + { + StringParser lineParser(&line); + lineParser.ConsumeWhitespace(); //skip over leading whitespace + + if (lineParser.GetDataRemaining() == 0) // must be an empty line + continue; + + lineParser.ConsumeUntilWhitespace(&word); + + if ( word.Equal("NumCPU")) // found a tag as first word in line + { + lineParser.GetThru(NULL,'='); + lineParser.ConsumeWhitespace(); //skip over leading whitespace + lineParser.ConsumeUntilWhitespace(&word); //read the number of cpus + if (word.Len > 0) + ::sscanf(word.Ptr, "%"_U32BITARG_"", &numCPUs); + + break; + } + } + if (numCPUs == 0) + numCPUs = 1; + + ::pclose(p); + + return numCPUs; +} +#endif + +#if(__sgi__) + UInt32 numCPUs = 0; + + numCPUs = sysconf(_SC_NPROC_ONLN); + + return numCPUs; +#endif + + + return 1; +} + + +//CISCO provided fix for integer + fractional fixed64. +SInt64 OS::TimeMilli_To_Fixed64Secs(SInt64 inMilliseconds) +{ + SInt64 result = inMilliseconds / 1000; // The result is in lower bits. + result <<= 32; // shift it to higher 32 bits + // Take the remainder (rem = inMilliseconds%1000) and multiply by + // 2**32, divide by 1000, effectively this gives (rem/1000) as a + // binary fraction. + double p = ldexp((double)(inMilliseconds%1000), +32) / 1000.; + UInt32 frac = (UInt32)p; + result |= frac; + return result; +} diff --git a/CommonUtilitiesLib/OS.h b/CommonUtilitiesLib/OS.h new file mode 100644 index 0000000..0fa77e9 --- /dev/null +++ b/CommonUtilitiesLib/OS.h @@ -0,0 +1,144 @@ +/* + * + * @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: OS.h + + Contains: OS utility functions. Memory allocation, time, etc. + + + +*/ + +#ifndef _OS_H_ +#define _OS_H_ + + +#include "OSHeaders.h" +#include "OSMutex.h" +#include + +class OS +{ + public: + + //call this before calling anything else + static void Initialize(); + + static SInt32 Min(SInt32 a, SInt32 b) { if (a < b) return a; return b; } + + // + // Milliseconds always returns milliseconds since Jan 1, 1970 GMT. + // This basically makes it the same as a POSIX time_t value, except + // in msec, not seconds. To convert to a time_t, divide by 1000. + static SInt64 Milliseconds(); + + static SInt64 Microseconds(); + + // Some processors (MIPS, Sparc) cannot handle non word aligned memory + // accesses. So, we need to provide functions to safely get at non-word + // aligned memory. + static inline UInt32 GetUInt32FromMemory(UInt32* inP); + + //because the OS doesn't seem to have these functions + static SInt64 HostToNetworkSInt64(SInt64 hostOrdered); + static SInt64 NetworkToHostSInt64(SInt64 networkOrdered); + + static SInt64 TimeMilli_To_Fixed64Secs(SInt64 inMilliseconds); //new CISCO provided implementation + //disable: calculates integer value only { return (SInt64) ( (Float64) inMilliseconds / 1000) * ((SInt64) 1 << 32 ) ; } + static SInt64 Fixed64Secs_To_TimeMilli(SInt64 inFixed64Secs) + { UInt64 value = (UInt64) inFixed64Secs; return (value >> 32) * 1000 + (((value % ((UInt64) 1 << 32)) * 1000) >> 32); } + + //This converts the local time (from OS::Milliseconds) to NTP time. + static SInt64 TimeMilli_To_1900Fixed64Secs(SInt64 inMilliseconds) + { return TimeMilli_To_Fixed64Secs(sMsecSince1900) + TimeMilli_To_Fixed64Secs(inMilliseconds); } + + static SInt64 TimeMilli_To_UnixTimeMilli(SInt64 inMilliseconds) + { return inMilliseconds; } + + static time_t TimeMilli_To_UnixTimeSecs(SInt64 inMilliseconds) + { return (time_t) ( (SInt64) TimeMilli_To_UnixTimeMilli(inMilliseconds) / (SInt64) 1000); } + + static time_t UnixTime_Secs(void) // Seconds since 1970 + { return TimeMilli_To_UnixTimeSecs(Milliseconds()); } + + static time_t Time1900Fixed64Secs_To_UnixTimeSecs(SInt64 in1900Fixed64Secs) + { return (time_t)( (SInt64) ((SInt64) ( in1900Fixed64Secs - TimeMilli_To_Fixed64Secs(sMsecSince1900) ) / ((SInt64) 1 << 32) ) ); } + + static SInt64 Time1900Fixed64Secs_To_TimeMilli(SInt64 in1900Fixed64Secs) + { return ( (SInt64) ( (Float64) ((SInt64) in1900Fixed64Secs - (SInt64) TimeMilli_To_Fixed64Secs(sMsecSince1900) ) / (Float64) ((SInt64) 1 << 32) ) * 1000) ; } + + // Returns the offset in hours between local time and GMT (or UTC) time. + static SInt32 GetGMTOffset(); + + //Both these functions return QTSS_NoErr, QTSS_FileExists, or POSIX errorcode + //Makes whatever directories in this path that don't exist yet + static OS_Error RecursiveMakeDir(char *inPath); + //Makes the directory at the end of this path + static OS_Error MakeDir(char *inPath); + + // Discovery of how many processors are on this machine + static UInt32 GetNumProcessors(); + + // CPU Load + static Float32 GetCurrentCPULoadPercent(); + + // Mutex for StdLib calls + static OSMutex* GetStdLibMutex() { return &sStdLibOSMutex; } + + static SInt64 InitialMSec() { return sInitialMsec; } + static Float32 StartTimeMilli_Float() { return (Float32) ( (Float64) ( (SInt64) OS::Milliseconds() - (SInt64) OS::InitialMSec()) / (Float64) 1000.0 ); } + static SInt64 StartTimeMilli_Int() { return (OS::Milliseconds() - OS::InitialMSec()); } + + static Bool16 ThreadSafe(); + + private: + + static double sDivisor; + static double sMicroDivisor; + static SInt64 sMsecSince1900; + static SInt64 sMsecSince1970; + static SInt64 sInitialMsec; + static SInt32 sMemoryErr; + static void SetDivisor(); + static SInt64 sWrapTime; + static SInt64 sCompareWrap; + static SInt64 sLastTimeMilli; + static OSMutex sStdLibOSMutex; +}; + +inline UInt32 OS::GetUInt32FromMemory(UInt32* inP) +{ +#if ALLOW_NON_WORD_ALIGN_ACCESS + return *inP; +#else + char* tempPtr = (char*)inP; + UInt32 temp = 0; + ::memcpy(&temp, tempPtr, sizeof(UInt32)); + return temp; +#endif +} + + +#endif diff --git a/CommonUtilitiesLib/OSArrayObjectDeleter.h b/CommonUtilitiesLib/OSArrayObjectDeleter.h new file mode 100644 index 0000000..d75df30 --- /dev/null +++ b/CommonUtilitiesLib/OSArrayObjectDeleter.h @@ -0,0 +1,88 @@ +/* + * + * @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: OSArrayObjectDeleter.h + + Contains: Auto object for deleting arrays. + + + +*/ + +#ifndef __OS_ARRAY_OBJECT_DELETER_H__ +#define __OS_ARRAY_OBJECT_DELETER_H__ + +#include "MyAssert.h" + +template +class OSArrayObjectDeleter +{ + public: + OSArrayObjectDeleter() : fT(NULL) {} + OSArrayObjectDeleter(T* victim) : fT(victim) {} + ~OSArrayObjectDeleter() { delete [] fT; } + + void ClearObject() { fT = NULL; } + + void SetObject(T* victim) + { + Assert(fT == NULL); + fT = victim; + } + T* GetObject() { return fT; } + + operator T*() { return fT; } + + private: + + T* fT; +}; + + +template +class OSPtrDeleter +{ + public: + OSPtrDeleter() : fT(NULL) {} + OSPtrDeleter(T* victim) : fT(victim) {} + ~OSPtrDeleter() { delete fT; } + + void ClearObject() { fT = NULL; } + + void SetObject(T* victim) + { Assert(fT == NULL); + fT = victim; + } + + private: + + T* fT; +}; + + +typedef OSArrayObjectDeleter OSCharPointerArrayDeleter; +typedef OSArrayObjectDeleter OSCharArrayDeleter; + +#endif //__OS_OBJECT_ARRAY_DELETER_H__ diff --git a/CommonUtilitiesLib/OSBufferPool.cpp b/CommonUtilitiesLib/OSBufferPool.cpp new file mode 100644 index 0000000..177dd69 --- /dev/null +++ b/CommonUtilitiesLib/OSBufferPool.cpp @@ -0,0 +1,60 @@ +/* + * + * @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: OSBufferPool.cpp + + Contains: Fast access to fixed size buffers. + + Written By: Denis Serenyi + +*/ + +#include "OSBufferPool.h" +#include "OSMemory.h" + +void* OSBufferPool::Get() +{ + OSMutexLocker locker(&fMutex); + if (fQueue.GetLength() == 0) + { + fTotNumBuffers++; + char* theNewBuf = NEW char[fBufSize + sizeof(OSQueueElem)]; + + // + // We need to construct a Queue Element, but we don't actually need + // to use it in this function, so to avoid a compiler warning just + // don't assign the result to anything. + (void)new (theNewBuf) OSQueueElem(theNewBuf + sizeof(OSQueueElem)); + + return theNewBuf + sizeof(OSQueueElem); + } + return fQueue.DeQueue()->GetEnclosingObject(); +} + +void OSBufferPool::Put(void* inBuffer) +{ + OSMutexLocker locker(&fMutex); + fQueue.EnQueue((OSQueueElem*)((char*)inBuffer - sizeof(OSQueueElem))); +} diff --git a/CommonUtilitiesLib/OSBufferPool.h b/CommonUtilitiesLib/OSBufferPool.h new file mode 100644 index 0000000..feebe44 --- /dev/null +++ b/CommonUtilitiesLib/OSBufferPool.h @@ -0,0 +1,76 @@ +/* + * + * @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: OSBufferPool.h + + Contains: Fast access to fixed size buffers. + + Written By: Denis Serenyi + +*/ + +#ifndef __OS_BUFFER_POOL_H__ +#define __OS_BUFFER_POOL_H__ + +#include "OSQueue.h" +#include "OSMutex.h" + +class OSBufferPool +{ + public: + + OSBufferPool(UInt32 inBufferSize) : fBufSize(inBufferSize), fTotNumBuffers(0) {} + + // + // This object currently *does not* clean up for itself when + // you destruct it! + ~OSBufferPool() {} + + // + // ACCESSORS + UInt32 GetTotalNumBuffers() { return fTotNumBuffers; } + UInt32 GetNumAvailableBuffers() { return fQueue.GetLength(); } + + // + // All these functions are thread-safe + + // + // Gets a buffer out of the pool. This buffer must be replaced + // by calling Put when you are done with it. + void* Get(); + + // + // Returns a buffer retreived by Get back to the pool. + void Put(void* inBuffer); + + private: + + OSMutex fMutex; + OSQueue fQueue; + UInt32 fBufSize; + UInt32 fTotNumBuffers; +}; + +#endif //__OS_BUFFER_POOL_H__ diff --git a/CommonUtilitiesLib/OSCodeFragment.cpp b/CommonUtilitiesLib/OSCodeFragment.cpp new file mode 100644 index 0000000..c831113 --- /dev/null +++ b/CommonUtilitiesLib/OSCodeFragment.cpp @@ -0,0 +1,153 @@ +/* + * + * @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: OSCodeFragment.cpp + + Contains: Implementation of object defined in OSCodeFragment.h + + +*/ + +#include +#include "SafeStdLib.h" +#include +#include "MyAssert.h" + +#if __Win32__ + // Win32 includes here +#elif __MacOSX__ + #include + #include +#else +#include +#endif + +#include "OSCodeFragment.h" + +void OSCodeFragment::Initialize() +{ +// does nothing...should do any CFM initialization here +} + +OSCodeFragment::OSCodeFragment(const char* inPath) +: fFragmentP(NULL) +{ +#if defined(HPUX) || defined(HPUX10) + shl_t handle; + fFragmentP = shl_load(inPath, BIND_IMMEDIATE|BIND_VERBOSE|BIND_NOSTART, 0L); +#elif defined(OSF1) ||\ + (defined(__FreeBSD_version) && (__FreeBSD_version >= 220000)) + fFragmentP = dlopen((char *)inPath, RTLD_NOW | RTLD_GLOBAL); +#elif defined(__FreeBSD__) + fFragmentP = dlopen(inPath, RTLD_NOW); +#elif defined(__sgi__) + fFragmentP = dlopen(inPath, RTLD_NOW); // not sure this should be either RTLD_NOW or RTLD_LAZY +#elif defined(__Win32__) + fFragmentP = ::LoadLibrary(inPath); +#elif defined(__MacOSX__) + CFStringRef theString = CFStringCreateWithCString( kCFAllocatorDefault, inPath, kCFStringEncodingASCII); + + // + // In MacOSX, our "fragments" are CF bundles, which are really + // directories, so our paths are paths to a directory + CFURLRef bundleURL = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, + theString, + kCFURLPOSIXPathStyle, + true); + + // + // I figure CF is safe about having NULL passed + // into its functions (if fBundle failed to get created). + // So, I won't worry about error checking myself + fFragmentP = CFBundleCreate( kCFAllocatorDefault, bundleURL ); + Boolean success = false; + if (fFragmentP != NULL) + success = CFBundleLoadExecutable( fFragmentP ); + if (!success && fFragmentP != NULL) + { + CFRelease( fFragmentP ); + fFragmentP = NULL; + } + + CFRelease(bundleURL); + CFRelease(theString); + +#else + fFragmentP = dlopen(inPath, RTLD_NOW | RTLD_GLOBAL); + //fprintf (stderr, "%s\n", dlerror()); + +#endif +} + +OSCodeFragment::~OSCodeFragment() +{ + if (fFragmentP == NULL) + return; + +#if defined(HPUX) || defined(HPUX10) + shl_unload((shl_t)fFragmentP); +#elif defined(__Win32__) + BOOL theErr = ::FreeLibrary(fFragmentP); + Assert(theErr); +#elif defined(__MacOSX__) + CFBundleUnloadExecutable( fFragmentP ); + CFRelease( fFragmentP ); +#else + dlclose(fFragmentP); +#endif +} + +void* OSCodeFragment::GetSymbol(const char* inSymbolName) +{ + if (fFragmentP == NULL) + return NULL; + +#if defined(HPUX) || defined(HPUX10) + void *symaddr = NULL; + int status; + + errno = 0; + status = shl_findsym((shl_t *)&fFragmentP, symname, TYPE_PROCEDURE, &symaddr); + if (status == -1 && errno == 0) /* try TYPE_DATA instead */ + status = shl_findsym((shl_t *)&fFragmentP, inSymbolName, TYPE_DATA, &symaddr); + return (status == -1 ? NULL : symaddr); +#elif defined(DLSYM_NEEDS_UNDERSCORE) + char *symbol = (char*)malloc(sizeof(char)*(strlen(inSymbolName)+2)); + void *retval; + qtss_sprintf(symbol, "_%s", inSymbolName); + retval = dlsym(fFragmentP, symbol); + free(symbol); + return retval; +#elif defined(__Win32__) + return ::GetProcAddress(fFragmentP, inSymbolName); +#elif defined(__MacOSX__) + CFStringRef theString = CFStringCreateWithCString( kCFAllocatorDefault, inSymbolName, kCFStringEncodingASCII); + void* theSymbol = (void*)CFBundleGetFunctionPointerForName( fFragmentP, theString ); + CFRelease(theString); + return theSymbol; +#else + return dlsym(fFragmentP, inSymbolName); +#endif +} diff --git a/CommonUtilitiesLib/OSCodeFragment.h b/CommonUtilitiesLib/OSCodeFragment.h new file mode 100644 index 0000000..bf0c001 --- /dev/null +++ b/CommonUtilitiesLib/OSCodeFragment.h @@ -0,0 +1,68 @@ +/* + * + * @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: OSDynamicLoader.h + + Contains: OS abstraction for loading code fragments. + + + +*/ + +#ifndef _OS_CODEFRAGMENT_H_ +#define _OS_CODEFRAGMENT_H_ + +#include +#include "SafeStdLib.h" +#include "OSHeaders.h" + +#ifdef __MacOSX__ +#include +#endif + +class OSCodeFragment +{ + public: + + static void Initialize(); + + OSCodeFragment(const char* inPath); + ~OSCodeFragment(); + + Bool16 IsValid() { return (fFragmentP != NULL); } + void* GetSymbol(const char* inSymbolName); + + private: + +#ifdef __Win32__ + HMODULE fFragmentP; +#elif __MacOSX__ + CFBundleRef fFragmentP; +#else + void* fFragmentP; +#endif +}; + +#endif//_OS_CODEFRAGMENT_H_ diff --git a/CommonUtilitiesLib/OSCond.cpp b/CommonUtilitiesLib/OSCond.cpp new file mode 100644 index 0000000..673a72e --- /dev/null +++ b/CommonUtilitiesLib/OSCond.cpp @@ -0,0 +1,119 @@ +/* + * + * @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: OSCond.cpp + + Contains: Implementation of OSCond class + + + +*/ + +#include "OSCond.h" +#include "OSMutex.h" +#include "OSThread.h" +#include "MyAssert.h" + +#if __PTHREADS_MUTEXES__ +#include +#endif + + +OSCond::OSCond() +{ +#ifdef __Win32__ + fCondition = ::CreateEvent(NULL, FALSE, FALSE, NULL); + Assert(fCondition != NULL); +#elif __PTHREADS_MUTEXES__ + #if __MacOSX__ + int ret = pthread_cond_init(&fCondition, NULL); + Assert(ret == 0); + #else + pthread_condattr_t cond_attr; + pthread_condattr_init(&cond_attr); + int ret = pthread_cond_init(&fCondition, &cond_attr); + Assert(ret == 0); + #endif +#else + fCondition = mycondition_alloc(); + Assert(fCondition != NULL); +#endif +} + +OSCond::~OSCond() +{ +#ifdef __Win32__ + BOOL theErr = ::CloseHandle(fCondition); + Assert(theErr == TRUE); +#elif __PTHREADS_MUTEXES__ + pthread_cond_destroy(&fCondition); +#else + Assert(fCondition != NULL); + mycondition_free(fCondition); +#endif +} + +#if __PTHREADS_MUTEXES__ +void OSCond::TimedWait(OSMutex* inMutex, SInt32 inTimeoutInMilSecs) +{ + struct timespec ts; + struct timeval tv; + struct timezone tz; + int sec, usec; + + //These platforms do refcounting manually, and wait will release the mutex, + // so we need to update the counts here + + inMutex->fHolderCount--; + inMutex->fHolder = 0; + + + if (inTimeoutInMilSecs == 0) + (void)pthread_cond_wait(&fCondition, &inMutex->fMutex); + else + { + gettimeofday(&tv, &tz); + sec = inTimeoutInMilSecs / 1000; + inTimeoutInMilSecs = inTimeoutInMilSecs - (sec * 1000); + Assert(inTimeoutInMilSecs < 1000); + usec = inTimeoutInMilSecs * 1000; + Assert(tv.tv_usec < 1000000); + ts.tv_sec = tv.tv_sec + sec; + ts.tv_nsec = (tv.tv_usec + usec) * 1000; + Assert(ts.tv_nsec < 2000000000); + if(ts.tv_nsec > 999999999) + { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + (void)pthread_cond_timedwait(&fCondition, &inMutex->fMutex, &ts); + } + + + inMutex->fHolderCount++; + inMutex->fHolder = pthread_self(); + +} +#endif diff --git a/CommonUtilitiesLib/OSCond.h b/CommonUtilitiesLib/OSCond.h new file mode 100644 index 0000000..c72ce5a --- /dev/null +++ b/CommonUtilitiesLib/OSCond.h @@ -0,0 +1,129 @@ +/* + * + * @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: OSCond.h + + Contains: A simple condition variable abstraction + + + + +*/ + +#ifndef _OSCOND_H_ +#define _OSCOND_H_ + +#ifndef __Win32__ + #if __PTHREADS_MUTEXES__ + #include + #else + #include "mycondition.h" + #endif +#endif + +#include "OSMutex.h" +#include "MyAssert.h" + +class OSCond +{ + public: + + OSCond(); + ~OSCond(); + + inline void Signal(); + inline void Wait(OSMutex* inMutex, SInt32 inTimeoutInMilSecs = 0); + inline void Broadcast(); + + private: + +#ifdef __Win32__ + HANDLE fCondition; + UInt32 fWaitCount; +#elif __PTHREADS_MUTEXES__ + pthread_cond_t fCondition; + void TimedWait(OSMutex* inMutex, SInt32 inTimeoutInMilSecs); +#else + mycondition_t fCondition; +#endif +}; + +inline void OSCond::Wait(OSMutex* inMutex, SInt32 inTimeoutInMilSecs) +{ +#ifdef __Win32__ + DWORD theTimeout = INFINITE; + if (inTimeoutInMilSecs > 0) + theTimeout = inTimeoutInMilSecs; + inMutex->Unlock(); + fWaitCount++; + DWORD theErr = ::WaitForSingleObject(fCondition, theTimeout); + fWaitCount--; + Assert((theErr == WAIT_OBJECT_0) || (theErr == WAIT_TIMEOUT)); + inMutex->Lock(); +#elif __PTHREADS_MUTEXES__ + this->TimedWait(inMutex, inTimeoutInMilSecs); +#else + Assert(fCondition != NULL); + mycondition_wait(fCondition, inMutex->fMutex, inTimeoutInMilSecs); +#endif +} + +inline void OSCond::Signal() +{ +#ifdef __Win32__ + BOOL theErr = ::SetEvent(fCondition); + Assert(theErr == TRUE); +#elif __PTHREADS_MUTEXES__ + pthread_cond_signal(&fCondition); +#else + Assert(fCondition != NULL); + mycondition_signal(fCondition); +#endif +} + +inline void OSCond::Broadcast() +{ +#ifdef __Win32__ + // + // There doesn't seem like any more elegant way to + // implement Broadcast using events in Win32. + // This will work, it may generate spurious wakeups, + // but condition variables are allowed to generate + // spurious wakeups + UInt32 waitCount = fWaitCount; + for (UInt32 x = 0; x < waitCount; x++) + { + BOOL theErr = ::SetEvent(fCondition); + Assert(theErr == TRUE); + } +#elif __PTHREADS_MUTEXES__ + pthread_cond_broadcast(&fCondition); +#else + Assert(fCondition != NULL); + mycondition_broadcast(fCondition); +#endif +} + +#endif //_OSCOND_H_ diff --git a/CommonUtilitiesLib/OSFileSource.cpp b/CommonUtilitiesLib/OSFileSource.cpp new file mode 100644 index 0000000..b16d5ba --- /dev/null +++ b/CommonUtilitiesLib/OSFileSource.cpp @@ -0,0 +1,633 @@ +/* + * + * @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: osfile.cpp + + Contains: simple file abstraction + + + + +*/ + +#include +#include + +#include +#include +#include +#include + +#ifndef __Win32__ +#include +#endif + +#include "OSFileSource.h" +#include "OSMemory.h" +#include "OSThread.h" +#include "OS.h" +#include "OSQueue.h" +#include "OSHeaders.h" + +#define FILE_SOURCE_DEBUG 0 +#define FILE_SOURCE_BUFFTEST 0 +#define TEST_TIME 0 + +#if TEST_TIME +static SInt64 startTime = 0; +static SInt64 durationTime = 0; +static SInt32 sReadCount = 0; +static SInt32 sByteCount = 0; +static Bool16 sMovie = false; + +#endif + + +#if READ_LOG +extern UInt32 xTrackID; +void OSFileSource::SetLog(const char *inPath) +{ + fFilePath[0] =0; + ::strcpy(fFilePath,inPath); + + if (fFile != -1 && fFileLog == NULL) + { + ::strcat(fFilePath,inPath); + ::strcat(fFilePath,".readlog"); + fFileLog = ::fopen(fFilePath,"w+"); + if (fFileLog && IsValid()) + { qtss_fprintf(fFileLog, "%s","QTFILE_READ_LOG\n"); + qtss_fprintf(fFileLog, "size: %qu\n",GetLength()); + qtss_printf("OSFileSource::SetLog=%s\n",fFilePath); + + } + ::fclose(fFileLog); + } +} +#else +void OSFileSource::SetLog(const char *inPath) +{ + +#if FILE_SOURCE_DEBUG + qtss_printf("OSFileSource::SetLog=%s\n",inPath); +#endif + +} +#endif + + + +FileBlockBuffer::~FileBlockBuffer(void) +{ + if (fDataBuffer != NULL) + { + Assert (fDataBuffer[fBufferSize] == 0); + +#if FILE_SOURCE_DEBUG + ::memset( (char *)fDataBuffer,0, fBufferSize); + qtss_printf("FileBlockBuffer::~FileBlockBuffer delete %"_U32BITARG_" this=%"_U32BITARG_"\n",fDataBuffer, this); +#endif + delete fDataBuffer; + fDataBuffer = NULL; + fArrayIndex = -1; + } + else + Assert(false); +} + +void FileBlockBuffer::AllocateBuffer(UInt32 buffSize) +{ + fBufferSize = buffSize; + fDataBuffer = NEW char[buffSize + 1]; + fDataBuffer[buffSize] = 0; + +#if FILE_SOURCE_DEBUG + this->CleanBuffer(); + qtss_printf("FileBlockBuffer::FileBlockBuffer allocate buff ptr =%"_U32BITARG_" len=%"_U32BITARG_" this=%"_U32BITARG_"\n",fDataBuffer,buffSize,this); +#endif + +} + +void FileBlockBuffer::TestBuffer(void) +{ + +#if FILE_SOURCE_BUFFTEST + if (fDataBuffer != NULL) + Assert (fDataBuffer[fBufferSize] == 0); +#endif + +} + +void FileBlockPool::MarkUsed(FileBlockBuffer* inBuffPtr) +{ + if (NULL == inBuffPtr) + return; + + if (fQueue.GetTail() != inBuffPtr->GetQElem()) // Least Recently Used tail is last accessed + { + fQueue.Remove(inBuffPtr->GetQElem()); + fQueue.EnQueue(inBuffPtr->GetQElem()); // put on tail + } +} + +FileBlockBuffer *FileBlockPool::GetBufferElement(UInt32 bufferSizeBytes) +{ + FileBlockBuffer* theNewBuf = NULL; + if ( fNumCurrentBuffers < fMaxBuffers) + { +#if FILE_SOURCE_DEBUG + qtss_printf("FileBlockPool::GetBufferElement NEW element fNumCurrentBuffers=%"_U32BITARG_" fMaxBuffers=%"_U32BITARG_" fBufferUnitSizeBytes=%"_U32BITARG_" bufferSizeBytes=%"_U32BITARG_"\n",fNumCurrentBuffers,fMaxBuffers,fBufferUnitSizeBytes,bufferSizeBytes); +#endif + theNewBuf = NEW FileBlockBuffer(); + theNewBuf->AllocateBuffer(bufferSizeBytes); + fNumCurrentBuffers++; + theNewBuf->fQElem.SetEnclosingObject(theNewBuf); + fQueue.EnQueue(theNewBuf->GetQElem()); // put on tail + Assert(theNewBuf != NULL); + return theNewBuf; + } + + OSQueueElem *theElem = fQueue.DeQueue(); // get head + + Assert(theElem != NULL); + + if (theElem == NULL) + return NULL; + + theNewBuf = (FileBlockBuffer*) theElem->GetEnclosingObject(); + Assert(theNewBuf != NULL); + //qtss_printf("FileBlockPool::GetBufferElement reuse buffer theNewBuf=%"_U32BITARG_" fDataBuffer=%"_U32BITARG_" fArrayIndex=%"_S32BITARG_"\n",theNewBuf,theNewBuf->fDataBuffer,theNewBuf->fArrayIndex); + + return theNewBuf; + +} + +void FileBlockPool::DeleteBlockPool(void) +{ + + FileBlockBuffer *buffer = NULL; + OSQueueElem* theElem = fQueue.DeQueue(); + while (theElem != NULL) + { buffer = (FileBlockBuffer *) theElem->GetEnclosingObject(); + delete buffer; + theElem = fQueue.DeQueue(); + } + + fMaxBuffers = 1; + fNumCurrentBuffers = 0; + fBufferUnitSizeBytes = kBufferUnitSize; +} + +FileBlockPool::~FileBlockPool(void) +{ + + this->DeleteBlockPool(); +} + + +void FileMap::AllocateBufferMap(UInt32 inUnitSizeInK, UInt32 inNumBuffSizeUnits, UInt32 inBufferIncCount, UInt32 inMaxBitRateBuffSizeInBlocks, UInt64 fileLen, UInt32 inBitRate) +{ + + if (fFileMapArray != NULL && fNumBuffSizeUnits == inNumBuffSizeUnits && inBufferIncCount == fBlockPool.GetMaxBuffers()) + return; + + if( inUnitSizeInK < 1 ) + inUnitSizeInK = 1; + + fBlockPool.SetBufferUnitSize(inUnitSizeInK); + + if (inBitRate == 0) // just use the maximum possible size + inBitRate = inMaxBitRateBuffSizeInBlocks * fBlockPool.GetBufferUnitSizeBytes(); + + if (inNumBuffSizeUnits == 0) // calculate the buffer size ourselves + { + inNumBuffSizeUnits = inBitRate / fBlockPool.GetBufferUnitSizeBytes(); + + if( inNumBuffSizeUnits > inMaxBitRateBuffSizeInBlocks) // max is 8 * buffUnit Size (32k) = 256K + { inNumBuffSizeUnits = inMaxBitRateBuffSizeInBlocks; + } + } //else the inNumBuffSizeUnits is explicitly defined so just use that value + + if( inNumBuffSizeUnits < 1 ) + inNumBuffSizeUnits = 1; + + this->DeleteMap(); + fBlockPool.DeleteBlockPool(); + + fNumBuffSizeUnits = inNumBuffSizeUnits; + fDataBufferSize = fBlockPool.GetBufferUnitSizeBytes() * inNumBuffSizeUnits; + + fBlockPool.SetMaxBuffers(inBufferIncCount); + fBlockPool.SetBuffIncValue(inBufferIncCount); + + fMapArraySize = (fileLen / fDataBufferSize) + 1; + fFileMapArray = NEW FileBlockBuffer *[ (SInt32) (fMapArraySize + 1) ]; + + this->Clean(); // required because fFileMapArray's array is used to store buffer pointers. +#if FILE_SOURCE_DEBUG + qtss_printf("FileMap::AllocateBufferMap shared buffers fFileMapArray=%"_U32BITARG_" fDataBufferSize= %"_U32BITARG_" fMapArraySize=%"_U32BITARG_" fileLen=%qu \n",fFileMapArray, fDataBufferSize, fMapArraySize,fileLen); +#endif + +} + +void FileMap::DeleteOldBuffs() +{ + while (fBlockPool.GetNumCurrentBuffers() > fBlockPool.GetMaxBuffers()) // delete any old buffers + { + FileBlockBuffer *theElem = fBlockPool.GetBufferElement(fDataBufferSize); + fFileMapArray[theElem->fArrayIndex] = NULL; + delete theElem; + fBlockPool.DecCurBuffers(); + } +} + +char *FileMap::GetBuffer(SInt64 buffIndex, Bool16 *outFillBuff) +{ + Assert(outFillBuff != NULL); + *outFillBuff = true; // we are re-using or just created a buff + + this->DeleteOldBuffs(); + Assert(buffIndex < (SInt32) fMapArraySize); + + FileBlockBuffer *theElem = fFileMapArray[buffIndex]; + if ( NULL == theElem) + { + #if FILE_SOURCE_DEBUG + qtss_printf("FileMap::GetBuffer call fBlockPool.GetBufferElement(); buffIndex=%"_S32BITARG_"\n",buffIndex); + #endif + + theElem = fBlockPool.GetBufferElement(fDataBufferSize); + Assert(theElem); + } + + fBlockPool.MarkUsed(theElem); // must happen here after getting a pre-allocated or used buffer. + + if (theElem->fArrayIndex == buffIndex) // found a pre-allocated and filled buffer + { + #if FILE_SOURCE_DEBUG + //qtss_printf("FileMap::GetBuffer pre-allocated buff buffIndex=%"_S32BITARG_"\n",buffIndex); + #endif + + *outFillBuff = false; + return theElem->fDataBuffer; + } + + if (theElem->fArrayIndex >= 0) + { + fFileMapArray[theElem->fArrayIndex] = NULL; // reset the old map location + } + fFileMapArray[buffIndex] = theElem; // a new buffer + theElem->fArrayIndex = buffIndex; // record the index + +#if FILE_SOURCE_DEBUG + theElem->CleanBuffer(); +#endif + + return theElem->fDataBuffer; + +} + + + +void FileMap::Clean(void) +{ + if (fFileMapArray != NULL) + ::memset( (char *)fFileMapArray,0, (SInt32) (sizeof(FileBlockBuffer *) * fMapArraySize) ); +} + +void FileMap::DeleteMap(void) +{ + if (NULL == fFileMapArray) + return; + +#if FILE_SOURCE_DEBUG + qtss_printf("FileMap::DeleteMap fFileMapArray=%"_U32BITARG_" fMapArraySize=%"_S32BITARG_" \n",fFileMapArray, fMapArraySize); + this->Clean(); +#endif + + delete fFileMapArray; + fFileMapArray = NULL; + +} + + +void OSFileSource::Set(const char *inPath) +{ + Close(); + +#if __Win32__ + fFile = open(inPath, O_RDONLY | O_BINARY); +#elif __linux__ + fFile = open(inPath, O_RDONLY | O_LARGEFILE); +#else + fFile = open(inPath, O_RDONLY); +#endif + + if (fFile != -1) + { + struct stat buf; + ::memset(&buf,sizeof(buf),0); + if (::fstat(fFile, &buf) >= 0) + { + fLength = buf.st_size; + fModDate = buf.st_mtime; + if (fModDate < 0) + fModDate = 0; +#ifdef __Win32__ + fIsDir = buf.st_mode & _S_IFDIR; +#else + fIsDir = S_ISDIR(buf.st_mode); +#endif + this->SetLog(inPath); + } + else + this->Close(); + } +} + + + +void OSFileSource::Advise(UInt64 , UInt32 ) +{ +// does nothing on platforms other than MacOSXServer +} + + +OS_Error OSFileSource::FillBuffer(char* ioBuffer, char *buffStart, SInt32 buffIndex) +{ + UInt32 buffSize = fFileMap.GetMaxBufSize(); + UInt64 startPos = (UInt64) buffIndex * (UInt64) buffSize; + UInt32 readLen = 0; + + OS_Error theErr = this->ReadFromPos(startPos, buffStart, buffSize, &readLen); + + fFileMap.SetIndexBuffFillSize(buffIndex, readLen); + fFileMap.TestBuffer(buffIndex); + + return theErr; +} + +#if FILE_SOURCE_BUFFTEST +static SInt32 sBuffCount = 1; +#endif + +OS_Error OSFileSource::Read(UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32* outRcvLen) +{ + + if ( ( !fFileMap.Initialized() ) + || ( !fCacheEnabled ) + || ( fFileMap.GetBuffIndex(inPosition+inLength) > fFileMap.GetMaxBuffIndex() ) + ) + return this->ReadFromPos(inPosition, inBuffer, inLength, outRcvLen); + + return this->ReadFromCache(inPosition, inBuffer, inLength, outRcvLen); +} + + +OS_Error OSFileSource::ReadFromCache(UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32* outRcvLen) +{ + OSMutexLocker locker(&fMutex); + + if (!fFileMap.Initialized() || !fCacheEnabled) + { Assert(0); + } + + Assert(outRcvLen != NULL); + *outRcvLen = 0; + + if (inPosition >= fLength) // eof + return OS_NoErr; + + SInt64 buffIndex = fFileMap.GetBuffIndex(inPosition); + SInt64 buffSize = 0; + SInt64 maxBuffSize = fFileMap.GetMaxBufSize(); + SInt64 endIndex = fFileMap.GetBuffIndex(inPosition+inLength); + SInt64 maxIndex = fFileMap.GetMaxBuffIndex(); + SInt64 buffPos = inPosition - fFileMap.GetBuffOffset(buffIndex); + SInt64 buffOffsetLen = 0; + char *buffStart = NULL; + SInt64 buffCopyLen = inLength; + SInt64 bytesToCopy = inLength; + char *buffOut = (char*)inBuffer; + Bool16 fillBuff = true; + char *buffOffset = NULL; + +#if FILE_SOURCE_BUFFTEST + char testBuff[inLength + 1]; + buffOut = (char*)testBuff; + sBuffCount ++; + ::memset(inBuffer,0,inLength); + ::memset(testBuff,0,inLength); +#endif + + if (buffIndex > endIndex || endIndex > maxIndex) + { +#if FILE_SOURCE_DEBUG + + qtss_printf("OSFileSource::ReadFromCache bad index: buffIndex=%"_S32BITARG_" endIndex=%"_S32BITARG_" maxIndex=%"_S32BITARG_"\n",buffIndex,endIndex,maxIndex); + qtss_printf("OSFileSource::ReadFromCache inPosition =%qu buffSize = %"_U32BITARG_" index=%"_S32BITARG_"\n",inPosition, fFileMap.GetMaxBufSize(),buffIndex); +#endif + Assert(0); + } + + while (buffIndex <= endIndex && buffIndex <= maxIndex) + { +#if FILE_SOURCE_DEBUG + qtss_printf("OSFileSource::ReadFromCache inPosition =%qu buffSize = %"_U32BITARG_" index=%"_S32BITARG_"\n",inPosition, fFileMap.GetMaxBufSize(),buffIndex); +#endif + + buffStart = fFileMap.GetBuffer(buffIndex, &fillBuff); + Assert(buffStart != NULL); + + if (fillBuff) + { + OS_Error theErr = this->FillBuffer( (char *) inBuffer, (char *) buffStart, (SInt32) buffIndex); + if (theErr != OS_NoErr) + return theErr; + + } + + + buffSize = fFileMap.GetBuffSize(buffIndex); + buffOffset = &buffStart[buffPos]; + + if ( (buffPos == 0) && + (bytesToCopy <= maxBuffSize) && + (buffSize < bytesToCopy) + ) // that's all there is in the file + { + + #if FILE_SOURCE_DEBUG + qtss_printf("OSFileSource::ReadFromCache end of file reached buffIndex=%"_U32BITARG_" buffSize = %"_S32BITARG_" bytesToCopy=%"_U32BITARG_"\n",buffIndex, buffSize,bytesToCopy); + #endif + Assert(buffSize <= (SInt64) kUInt32_Max); + ::memcpy(buffOut,buffOffset,(UInt32) buffSize); + *outRcvLen += (UInt32) buffSize; + break; + } + + buffOffsetLen = buffSize - buffPos; + if (buffCopyLen >= buffOffsetLen) + buffCopyLen = buffOffsetLen; + + Assert(buffCopyLen <= buffSize); + + ::memcpy(buffOut,buffOffset, (UInt32) buffCopyLen); + buffOut += buffCopyLen; + *outRcvLen += (UInt32) buffCopyLen; + bytesToCopy -= buffCopyLen; + Assert(bytesToCopy >= 0); + + buffCopyLen = bytesToCopy; + buffPos = 0; + buffIndex ++; + + } + +#if FILE_SOURCE_DEBUG + //qtss_printf("OSFileSource::ReadFromCache inLength= %"_U32BITARG_" *outRcvLen=%"_U32BITARG_"\n",inLength, *outRcvLen); +#endif + +#if FILE_SOURCE_BUFFTEST + { UInt32 outLen = 0; + OS_Error theErr = this->ReadFromPos(inPosition, inBuffer, inLength, &outLen); + + Assert(*outRcvLen == outLen); + if (*outRcvLen != outLen) + qtss_printf("OSFileSource::ReadFromCache *outRcvLen != outLen *outRcvLen=%"_U32BITARG_" outLen=%"_U32BITARG_"\n",*outRcvLen,outLen); + + for (int i = 0; i < inLength; i++) + { if ( ((char*)inBuffer)[i] != testBuff[i]) + { qtss_printf("OSFileSource::ReadFromCache byte pos %d of %"_U32BITARG_" failed len=%"_U32BITARG_" inPosition=%qu sBuffCount=%"_S32BITARG_"\n",i,inLength,outLen,inPosition,sBuffCount); + break; + } + } + } +#endif + + return OS_NoErr; +} + +OS_Error OSFileSource::ReadFromDisk(void* inBuffer, UInt32 inLength, UInt32* outRcvLen) +{ + #if FILE_SOURCE_BUFFTEST + qtss_printf("OSFileSource::Read inLength=%"_U32BITARG_" fFile=%d\n",inLength,fFile); + #endif + +#if __Win32__ + if (_lseeki64(fFile, fPosition, SEEK_SET) == -1) + return OSThread::GetErrno(); +#else + if (lseek(fFile, fPosition, SEEK_SET) == -1) + return OSThread::GetErrno(); +#endif + + + int rcvLen = ::read(fFile, (char*)inBuffer, inLength); + if (rcvLen == -1) + return OSThread::GetErrno(); + + if (outRcvLen != NULL) + *outRcvLen = rcvLen; + + fPosition += rcvLen; + fReadPos = fPosition; + + return OS_NoErr; +} + +OS_Error OSFileSource::ReadFromPos(UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32* outRcvLen) +{ +#if TEST_TIME + { + startTime = OS::Milliseconds(); + sReadCount++; + if (outRcvLen) + *outRcvLen = 0; + qtss_printf("OSFileSource::Read sReadCount = %"_S32BITARG_" totalbytes=%"_S32BITARG_" readsize=%"_U32BITARG_"\n",sReadCount,sByteCount,inLength); + } +#endif + + this->Seek(inPosition); + OS_Error err = this->ReadFromDisk(inBuffer,inLength,outRcvLen); + +#if READ_LOG + if (fFileLog) + { fFileLog = ::fopen(fFilePath,"a"); + if (fFileLog) + { qtss_fprintf(fFileLog, "read: %qu %"_U32BITARG_" %"_U32BITARG_"\n",inPosition, *outRcvLen, xTrackID); + ::fclose(fFileLog); + } + } + +#endif +#if TEST_TIME + { + durationTime += OS::Milliseconds() - startTime; + sByteCount += *outRcvLen; + } +#endif + + return err; +} + +void OSFileSource::SetTrackID(UInt32 trackID) +{ +#if READ_LOG + fTrackID = trackID; +// qtss_printf("OSFileSource::SetTrackID = %"_U32BITARG_" this=%"_U32BITARG_"\n",fTrackID,(UInt32) this); +#endif +} + + +void OSFileSource::Close() +{ + if ((fFile != -1) && (fShouldClose)) + { ::close(fFile); + + #if READ_LOG + if ( 0 && fFileLog != NULL ) + { ::fclose(fFileLog); + fFileLog = NULL; + fFilePath[0] =0; + } + #endif + } + + fFile = -1; + fModDate = 0; + fLength = 0; + fPosition = 0; + fReadPos = 0; + +#if TEST_TIME + if (fShouldClose) + { sMovie = 0; +// qtss_printf("OSFileSource::Close sReadCount = %"_S32BITARG_" totalbytes=%"_S32BITARG_"\n",sReadCount,sByteCount); +// qtss_printf("OSFileSource::Close durationTime = %qd\n",durationTime); + } +#endif + +} diff --git a/CommonUtilitiesLib/OSFileSource.h b/CommonUtilitiesLib/OSFileSource.h new file mode 100644 index 0000000..d29d59b --- /dev/null +++ b/CommonUtilitiesLib/OSFileSource.h @@ -0,0 +1,234 @@ +/* + * + * @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: osfilesource.h + + Contains: simple file abstraction. This file abstraction is ONLY to be + used for files intended for serving + + +*/ + +#ifndef __OSFILE_H_ +#define __OSFILE_H_ + +#include +#include + +#include "OSHeaders.h" +#include "StrPtrLen.h" +#include "OSQueue.h" + +#define READ_LOG 0 + +class FileBlockBuffer +{ + + public: + FileBlockBuffer(): fArrayIndex(-1),fBufferSize(0),fBufferFillSize(0),fDataBuffer(NULL),fDummy(0){} + ~FileBlockBuffer(void); + void AllocateBuffer(UInt32 buffSize); + void TestBuffer(void); + void CleanBuffer() { ::memset(fDataBuffer,0, fBufferSize); } + void SetFillSize(UInt32 fillSize) {fBufferFillSize = fillSize;} + UInt32 GetFillSize(void) { return fBufferFillSize;} + OSQueueElem *GetQElem() { return &fQElem; } + SInt64 fArrayIndex; + UInt32 fBufferSize; + UInt32 fBufferFillSize; + char *fDataBuffer; + OSQueueElem fQElem; + UInt32 fDummy; +}; + + + +class FileBlockPool +{ + enum { + kDataBufferUnitSizeExp = 15,// base 2 exponent + kBufferUnitSize = (1 << kDataBufferUnitSizeExp ) // 32Kbytes + }; + + public: + FileBlockPool(void) : fMaxBuffers(1), fNumCurrentBuffers(0), fBufferUnitSizeBytes(kBufferUnitSize){} + ~FileBlockPool(void); + + void SetMaxBuffers(UInt32 maxBuffers) { if (maxBuffers > 0) fMaxBuffers = maxBuffers; } + + void SetBuffIncValue(UInt32 bufferInc) { if (bufferInc > 0) fBufferInc = bufferInc;} + void IncMaxBuffers(void) { fMaxBuffers += fBufferInc; } + void DecMaxBuffers(void) { if (fMaxBuffers > fBufferInc) fMaxBuffers-= fBufferInc; } + void DecCurBuffers(void) { if (fNumCurrentBuffers > 0) fNumCurrentBuffers--; } + + void SetBufferUnitSize (UInt32 inUnitSizeInK) { fBufferUnitSizeBytes = inUnitSizeInK * 1024; } + UInt32 GetBufferUnitSizeBytes() { return fBufferUnitSizeBytes; } + UInt32 GetMaxBuffers(void) { return fMaxBuffers; } + UInt32 GetIncBuffers() { return fBufferInc; } + UInt32 GetNumCurrentBuffers(void) { return fNumCurrentBuffers; } + void DeleteBlockPool(); + FileBlockBuffer* GetBufferElement(UInt32 bufferSizeBytes); + void MarkUsed(FileBlockBuffer* inBuffPtr); + + private: + OSQueue fQueue; + UInt32 fMaxBuffers; + UInt32 fNumCurrentBuffers; + UInt32 fBufferInc; + UInt32 fBufferUnitSizeBytes; + UInt32 fBufferDataSizeBytes; + +}; + +class FileMap +{ + + public: + FileMap(void):fFileMapArray(NULL),fDataBufferSize(0),fMapArraySize(0),fNumBuffSizeUnits(0) {} + ~FileMap(void) {fFileMapArray = NULL;} + void AllocateBufferMap(UInt32 inUnitSizeInK, UInt32 inNumBuffSizeUnits, UInt32 inBufferIncCount, UInt32 inMaxBitRateBuffSizeInBlocks, UInt64 fileLen, UInt32 inBitRate); + char* GetBuffer(SInt64 bufIndex, Bool16 *outIsEmptyBuff); + void TestBuffer(SInt32 bufIndex) {Assert (bufIndex >= 0); fFileMapArray[bufIndex]->TestBuffer();}; + void SetIndexBuffFillSize(SInt32 bufIndex, UInt32 fillSize) { Assert (bufIndex >= 0); fFileMapArray[bufIndex]->SetFillSize(fillSize);} + UInt32 GetMaxBufSize(void) {return fDataBufferSize;} + UInt32 GetBuffSize(SInt64 bufIndex) { Assert (bufIndex >= 0); return fFileMapArray[bufIndex]->GetFillSize(); } + UInt32 GetIncBuffers(void) { return fBlockPool.GetIncBuffers(); } + void IncMaxBuffers() {fBlockPool.IncMaxBuffers(); } + void DecMaxBuffers() {fBlockPool.DecMaxBuffers(); } + Bool16 Initialized() { return fFileMapArray == NULL ? false : true; } + void Clean(void); + void DeleteMap(void); + void DeleteOldBuffs(void); + SInt64 GetBuffIndex(UInt64 inPosition) { return inPosition / this->GetMaxBufSize(); } + SInt64 GetMaxBuffIndex() { Assert(fMapArraySize > 0); return fMapArraySize -1; } + UInt64 GetBuffOffset(SInt64 bufIndex) { return (UInt64) (bufIndex * this->GetMaxBufSize() ); } + FileBlockPool fBlockPool; + + FileBlockBuffer** fFileMapArray; + + private: + + UInt32 fDataBufferSize; + SInt64 fMapArraySize; + UInt32 fNumBuffSizeUnits; + +}; + +class OSFileSource +{ + public: + + OSFileSource() : fFile(-1), fLength(0), fPosition(0), fReadPos(0), fShouldClose(true), fIsDir(false), fCacheEnabled(false) + { + + #if READ_LOG + fFileLog = NULL; + fTrackID = 0; + fFilePath[0]=0; + #endif + + } + + OSFileSource(const char *inPath) : fFile(-1), fLength(0), fPosition(0), fReadPos(0), fShouldClose(true), fIsDir(false),fCacheEnabled(false) + { + Set(inPath); + + #if READ_LOG + fFileLog = NULL; + fTrackID = 0; + fFilePath[0]=0; + #endif + + } + + ~OSFileSource() { Close(); fFileMap.DeleteMap();} + + //Sets this object to reference this file + void Set(const char *inPath); + + // Call this if you don't want Close or the destructor to close the fd + void DontCloseFD() { fShouldClose = false; } + + //Advise: this advises the OS that we are going to be reading soon from the + //following position in the file + void Advise(UInt64 advisePos, UInt32 adviseAmt); + + OS_Error Read(void* inBuffer, UInt32 inLength, UInt32* outRcvLen = NULL) + { return ReadFromDisk(inBuffer, inLength, outRcvLen); + } + + OS_Error Read(UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32* outRcvLen = NULL); + OS_Error ReadFromDisk(void* inBuffer, UInt32 inLength, UInt32* outRcvLen = NULL); + OS_Error ReadFromCache(UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32* outRcvLen = NULL); + OS_Error ReadFromPos(UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32* outRcvLen = NULL); + void EnableFileCache(Bool16 enabled) {OSMutexLocker locker(&fMutex); fCacheEnabled = enabled; } + Bool16 GetCacheEnabled() { return fCacheEnabled; } + void AllocateFileCache(UInt32 inUnitSizeInK = 32, UInt32 bufferSizeUnits = 0, UInt32 incBuffers = 1, UInt32 inMaxBitRateBuffSizeInBlocks = 8, UInt32 inBitRate = 32768) + { fFileMap.AllocateBufferMap(inUnitSizeInK, bufferSizeUnits,incBuffers, inMaxBitRateBuffSizeInBlocks, fLength, inBitRate); + } + void IncMaxBuffers() {OSMutexLocker locker(&fMutex); fFileMap.IncMaxBuffers(); } + void DecMaxBuffers() {OSMutexLocker locker(&fMutex); fFileMap.DecMaxBuffers(); } + + OS_Error FillBuffer(char* ioBuffer, char *buffStart, SInt32 bufIndex); + + void Close(); + time_t GetModDate() { return fModDate; } + UInt64 GetLength() { return fLength; } + UInt64 GetCurOffset() { return fPosition; } + void Seek(SInt64 newPosition) { fPosition = newPosition; } + Bool16 IsValid() { return fFile != -1; } + Bool16 IsDir() { return fIsDir; } + + // For async I/O purposes + int GetFD() { return fFile; } + void SetTrackID(UInt32 trackID); + // So that close won't do anything + void ResetFD() { fFile=-1; } + + void SetLog(const char *inPath); + + private: + + int fFile; + UInt64 fLength; + UInt64 fPosition; + UInt64 fReadPos; + Bool16 fShouldClose; + Bool16 fIsDir; + time_t fModDate; + + + OSMutex fMutex; + FileMap fFileMap; + Bool16 fCacheEnabled; +#if READ_LOG + FILE* fFileLog; + char fFilePath[1024]; + UInt32 fTrackID; +#endif + +}; + +#endif //__OSFILE_H_ diff --git a/CommonUtilitiesLib/OSHashTable.h b/CommonUtilitiesLib/OSHashTable.h new file mode 100644 index 0000000..57e8341 --- /dev/null +++ b/CommonUtilitiesLib/OSHashTable.h @@ -0,0 +1,168 @@ +/* + * + * @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: OSHashTable.h + + Contains: Defines a template class for hash tables. + + + + +*/ + +#ifndef _OSHASHTABLE_H_ +#define _OSHASHTABLE_H_ + +#include "MyAssert.h" +#include "OSHeaders.h" + +/* +T must have a fNextHashEntry field, and key(T) must returns the key of type K. +K must have a method GetHashKey() that returns an UInt32 bit hash value. +Will the hash table can contain duplicate keys, the Map function will return only the first one. +*/ + +template +class OSHashTable { +public: + OSHashTable( UInt32 size ) + { + fHashTable = new ( T*[size] ); + Assert( fHashTable ); + memset( fHashTable, 0, sizeof(T*) * size ); + fSize = size; + // Determine whether the hash size is a power of 2 + // if not set the mask to zero, otherwise we can + // use the mask which is faster for ComputeIndex + fMask = fSize - 1; + if ((fMask & fSize) != 0) + fMask = 0; + fNumEntries = 0; + } + ~OSHashTable() + { + delete [] fHashTable; + } + void Add( T* entry ) { + Assert( entry->fNextHashEntry == NULL ); + K key( entry ); + UInt32 theIndex = ComputeIndex( key.GetHashKey() ); + entry->fNextHashEntry = fHashTable[ theIndex ]; + fHashTable[ theIndex ] = entry; + fNumEntries++; + } + void Remove( T* entry ) + { + K key( entry ); + UInt32 theIndex = ComputeIndex( key.GetHashKey() ); + T* elem = fHashTable[ theIndex ]; + T* last = NULL; + while (elem && elem != entry) { + last = elem; + elem = elem->fNextHashEntry; + } + + if ( elem ) // sometimes remove is called 2x ( swap, then un register ) + { + Assert(elem); + if (last) + last->fNextHashEntry = elem->fNextHashEntry; + else + fHashTable[ theIndex ] = elem->fNextHashEntry; + elem->fNextHashEntry = NULL; + fNumEntries--; + } + } + T* Map( K* key ) + { + UInt32 theIndex = ComputeIndex( key->GetHashKey() ); + T* elem = fHashTable[ theIndex ]; + while (elem) { + K elemKey( elem ); + if (elemKey == *key) + break; + elem = elem->fNextHashEntry; + } + return elem; + } + UInt64 GetNumEntries() { return fNumEntries; } + + UInt32 GetTableSize() { return fSize; } + T* GetTableEntry( int i ) { return fHashTable[i]; } + +private: + T** fHashTable; + UInt32 fSize; + UInt32 fMask; + UInt64 fNumEntries; + + UInt32 ComputeIndex( UInt32 hashKey ) + { + if (fMask) + return( hashKey & fMask ); + else + return( hashKey % fSize ); + } +}; + +template +class OSHashTableIter { +public: + OSHashTableIter( OSHashTable* table ) + { + fHashTable = table; + First(); + } + void First() + { + for (fIndex = 0; fIndex < fHashTable->GetTableSize(); fIndex++) { + fCurrent = fHashTable->GetTableEntry( fIndex ); + if (fCurrent) + break; + } + } + void Next() + { + fCurrent = fCurrent->fNextHashEntry; + if (!fCurrent) { + for (fIndex = fIndex + 1; fIndex < fHashTable->GetTableSize(); fIndex++) { + fCurrent = fHashTable->GetTableEntry( fIndex ); + if (fCurrent) + break; + } + } + } + Bool16 IsDone() + { + return( fCurrent == NULL ); + } + T* GetCurrent() { return fCurrent; } + +private: + OSHashTable* fHashTable; + T* fCurrent; + UInt32 fIndex; +}; +#endif //_OSHASHTABLE_H_ diff --git a/CommonUtilitiesLib/OSHeaders.c b/CommonUtilitiesLib/OSHeaders.c new file mode 100644 index 0000000..3a39a2e --- /dev/null +++ b/CommonUtilitiesLib/OSHeaders.c @@ -0,0 +1,35 @@ +#include "OSHeaders.h" + +#if __linux__ + // OpenBSD strlcpy implementation to allow usage of it while on Linux. + /* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ + size_t + strlcpy(char *dst, const char *src, size_t siz) + { + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ + } +#endif \ No newline at end of file diff --git a/CommonUtilitiesLib/OSHeaders.h b/CommonUtilitiesLib/OSHeaders.h new file mode 100644 index 0000000..24b2b21 --- /dev/null +++ b/CommonUtilitiesLib/OSHeaders.h @@ -0,0 +1,562 @@ +/* + * + * @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@ + * + */ + +#ifndef OSHeaders_H +#define OSHeaders_H +#include + + + +#ifndef TRUE + #define TRUE 1 +#endif + +#ifndef FALSE + #define FALSE 0 +#endif + + + +/* Platform-specific components */ +#if __MacOSX__ + + /* Defines */ + #define _64BITARG_ "ll" + #define _S64BITARG_ "lld" + #define _U64BITARG_ "llu" + +#if __LP64__ + #define _S32BITARG_ "d" + #define _U32BITARG_ "u" + #define _SPOINTERSIZEARG_ _S64BITARG_ + #define _UPOINTERSIZEARG_ _U64BITARG_ +#else + #define _S32BITARG_ "ld" + #define _U32BITARG_ "lu" + #define _SPOINTERSIZEARG_ _S32BITARG_ + #define _UPOINTERSIZEARG_ _U32BITARG_ +#endif + + /* paths */ + #define kEOLString "\n" + #define kPathDelimiterString "/" + #define kPathDelimiterChar '/' + #define kPartialPathBeginsWithDelimiter 0 + + /* Includes */ + #include + + /* Constants */ + #define QT_TIME_TO_LOCAL_TIME (-2082844800) + #define QT_PATH_SEPARATOR '/' + + +#include "/System/Library/Frameworks/CoreServices.framework/Headers/../Frameworks/CarbonCore.framework/Headers/MacTypes.h" + +#define kSInt16_Max (SInt16) SHRT_MAX +#define kUInt16_Max (UInt16) USHRT_MAX + +#define kSInt32_Max (SInt32) LONG_MAX +#define kUInt32_Max (UInt32) ULONG_MAX + +#define kSInt64_Max (SInt64) LONG_LONG_MAX +#define kUInt64_Max (UInt64) ULONG_LONG_MAX + +#if 0 // old defs we are now using MacTypes.h + /* Typedefs */ + typedef unsigned char UInt8; + typedef signed char SInt8; + typedef unsigned short UInt16; + typedef signed short SInt16; + typedef unsigned int UInt32; + typedef signed int SInt32; + typedef signed long long SInt64; + typedef unsigned long long UInt64; + typedef float Float32; + typedef double Float64; + typedef UInt32 FourCharCode; + typedef FourCharCode OSType; +#endif + + typedef signed long PointerSizedInt; + typedef unsigned long PointerSizedUInt; + typedef UInt16 Bool16; + typedef UInt8 Bool8; + + + #ifdef FOUR_CHARS_TO_INT + #error Conflicting Macro "FOUR_CHARS_TO_INT" + #endif + + #define FOUR_CHARS_TO_INT( c1, c2, c3, c4 ) ( c1 << 24 | c2 << 16 | c3 << 8 | c4 ) + + #ifdef TW0_CHARS_TO_INT + #error Conflicting Macro "TW0_CHARS_TO_INT" + #endif + + #define TW0_CHARS_TO_INT( c1, c2 ) ( c1 << 8 | c2 ) + + +#elif __linux__ || __linuxppc__ || __FreeBSD__ + + /* Defines */ + #define _64BITARG_ "q" + #define _S64BITARG_ "lld" + #define _U64BITARG_ "llu" + +#if __LP64__ + #define _S32BITARG_ "d" + #define _U32BITARG_ "u" +#else + #define _S32BITARG_ "ld" + #define _U32BITARG_ "lu" +#endif + #define _SPOINTERSIZEARG_ _S64BITARG_ + #define _UPOINTERSIZEARG_ _U64BITARG_ + + /* paths */ + #define kEOLString "\n" + #define kPathDelimiterString "/" + #define kPathDelimiterChar '/' + #define kPartialPathBeginsWithDelimiter 0 + + /* Includes */ + #include + + /* Constants */ + #define QT_TIME_TO_LOCAL_TIME (-2082844800) + #define QT_PATH_SEPARATOR '/' + + /* Typedefs */ + typedef signed long PointerSizedInt; + typedef unsigned long PointerSizedUInt; + typedef unsigned char UInt8; + typedef signed char SInt8; + typedef unsigned short UInt16; + typedef signed short SInt16; + typedef unsigned int UInt32; + typedef signed int SInt32; + typedef signed long SInt64; + typedef unsigned long UInt64; + typedef float Float32; + typedef double Float64; + typedef UInt16 Bool16; + typedef UInt8 Bool8; + + /* Define max values of our custom typedefs */ + #define kSInt16_Max (SInt16) SHRT_MAX + #define kUInt16_Max (UInt16) USHRT_MAX + + #define kSInt32_Max (SInt32) INT_MAX + #define kUInt32_Max (UInt32) UINT_MAX + + #define kSInt64_Max (SInt64) LONG_MAX + #define kUInt64_Max (UInt64) ULONG_MAX + + + typedef unsigned int FourCharCode; + typedef FourCharCode OSType; + + #ifdef FOUR_CHARS_TO_INT + #error Conflicting Macro "FOUR_CHARS_TO_INT" + #endif + + #define FOUR_CHARS_TO_INT( c1, c2, c3, c4 ) ( c1 << 24 | c2 << 16 | c3 << 8 | c4 ) + + #ifdef TW0_CHARS_TO_INT + #error Conflicting Macro "TW0_CHARS_TO_INT" + #endif + + #define TW0_CHARS_TO_INT( c1, c2 ) ( c1 << 8 | c2 ) + +#if __linux__ + size_t strlcpy(char *dst, const char *src, size_t siz); +#endif + +#elif __Win32__ + + /* Defines */ + #define _64BITARG_ "I64" + #define _S64BITARG_ "I64d" + #define _U64BITARG_ "I64u" +#if __LP64__ + #define _S32BITARG_ "d" + #define _U32BITARG_ "u" +#else + #define _S32BITARG_ "ld" + #define _U32BITARG_ "lu" +#endif + + /* paths */ + #define kEOLString "\r\n" + #define kPathDelimiterString "\\" + #define kPathDelimiterChar '\\' + #define kPartialPathBeginsWithDelimiter 0 + + #define crypt(buf, salt) ((char*)buf) + + /* Includes */ + #include + #include + #include + #include + #include + #include + #include + #include + + + #define R_OK 0 + #define W_OK 1 + + // POSIX errorcodes + #define ENOTCONN 1002 + #define EADDRINUSE 1004 + #define EINPROGRESS 1007 + #define ENOBUFS 1008 + #define EADDRNOTAVAIL 1009 + + // Winsock does not use iovecs + struct iovec { + u_long iov_len; // this is not the POSIX definition, it is rather defined to be + char FAR* iov_base; // equivalent to a WSABUF for easy integration into Win32 + }; + + /* Constants */ + #define QT_TIME_TO_LOCAL_TIME (-2082844800) + #define QT_PATH_SEPARATOR '/' + + /* Typedefs */ + typedef signed long PointerSizedInt; + typedef unsigned long PointerSizedUInt; + typedef unsigned char UInt8; + typedef signed char SInt8; + typedef unsigned short UInt16; + typedef signed short SInt16; + typedef unsigned long UInt32; + typedef signed long SInt32; + typedef LONGLONG SInt64; + typedef ULONGLONG UInt64; + typedef float Float32; + typedef double Float64; + typedef UInt16 Bool16; + typedef UInt8 Bool8; + + typedef unsigned long FourCharCode; + typedef FourCharCode OSType; + + #ifdef FOUR_CHARS_TO_INT + #error Conflicting Macro "FOUR_CHARS_TO_INT" + #endif + + #define FOUR_CHARS_TO_INT( c1, c2, c3, c4 ) ( c1 << 24 | c2 << 16 | c3 << 8 | c4 ) + + #ifdef TW0_CHARS_TO_INT + #error Conflicting Macro "TW0_CHARS_TO_INT" + #endif + + #define TW0_CHARS_TO_INT( c1, c2 ) ( c1 << 8 | c2 ) + + #define kSInt16_Max USHRT_MAX + #define kUInt16_Max USHRT_MAX + + #define kSInt32_Max LONG_MAX + #define kUInt32_Max ULONG_MAX + + #undef kSInt64_Max + #define kSInt64_Max 9223372036854775807i64 + + #undef kUInt64_Max + #define kUInt64_Max (kSInt64_Max * 2ULL + 1) + +#elif __sgi__ + /* Defines */ + #define _64BITARG_ "ll" + #define _S64BITARG_ "lld" + #define _U64BITARG_ "llu" +#if __LP64__ + #define _S32BITARG_ "d" + #define _U32BITARG_ "u" +#else + #define _S32BITARG_ "ld" + #define _U32BITARG_ "lu" +#endif + + /* paths */ + #define kPathDelimiterString "/" + #define kPathDelimiterChar '/' + #define kPartialPathBeginsWithDelimiter 0 + #define kEOLString "\n" + + /* Includes */ + #include + #include + #include + + /* Constants */ + #define QT_TIME_TO_LOCAL_TIME (-2082844800) + #define QT_PATH_SEPARATOR '/' + + /* Typedefs */ + typedef unsigned char boolean; + #define true 1 + #define false 0 + + typedef signed long PointerSizedInt; + typedef unsigned long PointerSizedUInt; + typedef unsigned char UInt8; + typedef signed char SInt8; + typedef unsigned short UInt16; + typedef signed short SInt16; + typedef unsigned long UInt32; + typedef signed long SInt32; + typedef signed long long SInt64; + typedef unsigned long long UInt64; + typedef float Float32; + typedef double Float64; + + typedef UInt16 Bool16; + + typedef unsigned long FourCharCode; + typedef FourCharCode OSType; + + /* Nulled-out new() for use without memory debugging */ + /* #define NEW(t,c,v) new c v + #define NEW_ARRAY(t,c,n) new c[n] */ + + #define thread_t pthread_t + #define cthread_errno() errno + + #ifdef FOUR_CHARS_TO_INT + #error Conflicting Macro "FOUR_CHARS_TO_INT" + #endif + + #define FOUR_CHARS_TO_INT( c1, c2, c3, c4 ) ( c1 << 24 | c2 << 16 | c3 << 8 | c4 ) + + #ifdef TW0_CHARS_TO_INT + #error Conflicting Macro "TW0_CHARS_TO_INT" + #endif + + #define TW0_CHARS_TO_INT( c1, c2 ) ( c1 << 8 | c2 ) + +#elif defined(sun) // && defined(sparc) + + /* Defines */ + #define _64BITARG_ "ll" + #define _S64BITARG_ "Ild" + #define _U64BITARG_ "llu" +#if __LP64__ + #define _S32BITARG_ "d" + #define _U32BITARG_ "u" +#else + #define _S32BITARG_ "ld" + #define _U32BITARG_ "lu" +#endif + + /* paths */ + #define kPathDelimiterString "/" + #define kPathDelimiterChar '/' + #define kPartialPathBeginsWithDelimiter 0 + #define kEOLString "\n" + + /* Includes */ + #include + #include + + /* Constants */ + #define QT_TIME_TO_LOCAL_TIME (-2082844800) + #define QT_PATH_SEPARATOR '/' + + /* Typedefs */ + //typedef unsigned char Bool16; + //#define true 1 + //#define false 0 + + typedef signed long PointerSizedInt; + typedef unsigned long PointerSizedUInt; + typedef unsigned char UInt8; + typedef signed char SInt8; + typedef unsigned short UInt16; + typedef signed short SInt16; + typedef unsigned long UInt32; + typedef signed long SInt32; + typedef signed long long SInt64; + typedef unsigned long long UInt64; + typedef float Float32; + typedef double Float64; + typedef UInt16 Bool16; + typedef UInt8 Bool8; + + typedef unsigned long FourCharCode; + typedef FourCharCode OSType; + + #ifdef FOUR_CHARS_TO_INT + #error Conflicting Macro "FOUR_CHARS_TO_INT" + #endif + + #define FOUR_CHARS_TO_INT( c1, c2, c3, c4 ) ( c1 << 24 | c2 << 16 | c3 << 8 | c4 ) + + #ifdef TW0_CHARS_TO_INT + #error Conflicting Macro "TW0_CHARS_TO_INT" + #endif + + #define TW0_CHARS_TO_INT( c1, c2 ) ( c1 << 8 | c2 ) + +#elif defined(__hpux__) + + /* Defines */ + #define _64BITARG_ "ll" + #define _S64BITARG_ "Ild" + #define _U64BITARG_ "llu" +#if __LP64__ + #define _S32BITARG_ "d" + #define _U32BITARG_ "u" +#else + #define _S32BITARG_ "ld" + #define _U32BITARG_ "lu" +#endif + + /* paths */ + #define kPathDelimiterString "/" + #define kPathDelimiterChar '/' + #define kPartialPathBeginsWithDelimiter 0 + #define kEOLString "\n" + + /* Includes */ + #include + #include + + /* Constants */ + #define QT_TIME_TO_LOCAL_TIME (-2082844800) + #define QT_PATH_SEPARATOR '/' + + /* Typedefs */ + //typedef unsigned char Bool16; + //#define true 1 + //#define false 0 + + typedef signed long PointerSizedInt; + typedef unsigned long PointerSizedUInt; + typedef unsigned char UInt8; + typedef signed char SInt8; + typedef unsigned short UInt16; + typedef signed short SInt16; + typedef unsigned long UInt32; + typedef signed long SInt32; + typedef signed long long SInt64; + typedef unsigned long long UInt64; + typedef float Float32; + typedef double Float64; + typedef UInt16 Bool16; + typedef UInt8 Bool8; + + typedef unsigned long FourCharCode; + typedef FourCharCode OSType; + + #ifdef FOUR_CHARS_TO_INT + #error Conflicting Macro "FOUR_CHARS_TO_INT" + #endif + + #define FOUR_CHARS_TO_INT( c1, c2, c3, c4 ) ( c1 << 24 | c2 << 16 | c3 << 8 | c4 ) + + #ifdef TW0_CHARS_TO_INT + #error Conflicting Macro "TW0_CHARS_TO_INT" + #endif + + #define TW0_CHARS_TO_INT( c1, c2 ) ( c1 << 8 | c2 ) + +#elif defined(__osf__) + + /* Defines */ + #define _64BITARG_ "l" + #define _S64BITARG_ "ld" + #define _U64BITARG_ "lu" +#if __LP64__ + #define _S32BITARG_ "ld" + #define _U32BITARG_ "lu" +#else + #define _S32BITARG_ "d" + #define _U32BITARG_ "u" +#endif + + /* paths */ + #define kEOLString "\n" + #define kPathDelimiterString "/" + #define kPathDelimiterChar '/' + #define kPartialPathBeginsWithDelimiter 0 + + /* Includes */ + #include + #include + + /* Constants */ + #define QT_TIME_TO_LOCAL_TIME (-2082844800) + #define QT_PATH_SEPARATOR '/' + + /* Typedefs */ + typedef unsigned long PointerSizedInt; + typedef unsigned char UInt8; + typedef signed char SInt8; + typedef unsigned short UInt16; + typedef signed short SInt16; + typedef unsigned int UInt32; + typedef signed int SInt32; + typedef signed long SInt64; + typedef unsigned long UInt64; + typedef float Float32; + typedef double Float64; + typedef UInt16 Bool16; + typedef UInt8 Bool8; + + typedef unsigned int FourCharCode; + typedef FourCharCode OSType; + + #ifdef FOUR_CHARS_TO_INT + #error Conflicting Macro "FOUR_CHARS_TO_INT" + #endif + + #define FOUR_CHARS_TO_INT( c1, c2, c3, c4 ) ( c1 << 24 | c2 << 16 | c3 << 8 | c4 ) + + #ifdef TW0_CHARS_TO_INT + #error Conflicting Macro "TW0_CHARS_TO_INT" + #endif + + #define TW0_CHARS_TO_INT( c1, c2 ) ( c1 << 8 | c2 ) + + +#endif + +typedef SInt32 OS_Error; + +enum +{ + OS_NoErr = (OS_Error) 0, + OS_BadURLFormat = (OS_Error) -100, + OS_NotEnoughSpace = (OS_Error) -101 +}; + +#include "../PlatformHeader.h" + +#endif /* OSHeaders_H */ diff --git a/CommonUtilitiesLib/OSHeap.cpp b/CommonUtilitiesLib/OSHeap.cpp new file mode 100644 index 0000000..7da51bf --- /dev/null +++ b/CommonUtilitiesLib/OSHeap.cpp @@ -0,0 +1,421 @@ +/* + * + * @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: OSHeap.cpp + + Contains: Implements a heap + + + +*/ + +#include + +#include "OSHeap.h" +#include "OSMemory.h" + +OSHeap::OSHeap(UInt32 inStartSize) +: fFreeIndex(1) +{ + if (inStartSize < 2) + fArraySize = 2; + else + fArraySize = inStartSize; + + fHeap = NEW OSHeapElem*[fArraySize]; +} + +void OSHeap::Insert(OSHeapElem* inElem) +{ + Assert(inElem != NULL); + + if ((fHeap == NULL) || (fFreeIndex == fArraySize)) + { + fArraySize *= 2; + OSHeapElem** tempArray = NEW OSHeapElem*[fArraySize]; + if ((fHeap != NULL) && (fFreeIndex > 1)) + memcpy(tempArray, fHeap, sizeof(OSHeapElem*) * fFreeIndex); + + delete [] fHeap; + fHeap = tempArray; + } + + Assert(fHeap != NULL); + Assert(inElem->fCurrentHeap == NULL); + Assert(fArraySize > fFreeIndex); + +#if _OSHEAP_TESTING_ + SanityCheck(1); +#endif + + //insert the element into the last leaf of the tree + fHeap[fFreeIndex] = inElem; + + //bubble the new element up to its proper place in the heap + + //start at the last leaf of the tree + UInt32 swapPos = fFreeIndex; + while (swapPos > 1) + { + //move up the chain until we get to the root, bubbling this new element + //to its proper place in the tree + UInt32 nextSwapPos = swapPos >> 1; + + //if this child is greater than it's parent, we need to do the old + //switcheroo + if (fHeap[swapPos]->fValue < fHeap[nextSwapPos]->fValue) + { + OSHeapElem* temp = fHeap[swapPos]; + fHeap[swapPos] = fHeap[nextSwapPos]; + fHeap[nextSwapPos] = temp; + swapPos = nextSwapPos; + } + else + //if not, we are done! + break; + } + inElem->fCurrentHeap = this; + fFreeIndex++; +} + + +OSHeapElem* OSHeap::Extract(UInt32 inIndex) +{ + if ((fHeap == NULL) || (fFreeIndex <= inIndex)) + return NULL; + +#if _OSHEAP_TESTING_ + SanityCheck(1); +#endif + + //store a reference to the element we want to extract + OSHeapElem* victim = fHeap[inIndex]; + Assert(victim->fCurrentHeap == this); + victim->fCurrentHeap = NULL; + + //but now we need to preserve this heuristic. We do this by taking + //the last leaf, putting it at the empty position, then heapifying that chain + fHeap[inIndex] = fHeap[fFreeIndex - 1]; + fFreeIndex--; + + //The following is an implementation of the Heapify algorithm (CLR 7.1 pp 143) + //The gist is that this new item at the top of the heap needs to be bubbled down + //until it is bigger than its two children, therefore maintaining the heap property. + + UInt32 parent = inIndex; + while (parent < fFreeIndex) + { + //which is bigger? parent or left child? + UInt32 greatest = parent; + UInt32 leftChild = parent * 2; + if ((leftChild < fFreeIndex) && (fHeap[leftChild]->fValue < fHeap[parent]->fValue)) + greatest = leftChild; + + //which is bigger? the biggest so far or the right child? + UInt32 rightChild = (parent * 2) + 1; + if ((rightChild < fFreeIndex) && (fHeap[rightChild]->fValue < fHeap[greatest]->fValue)) + greatest = rightChild; + + //if the parent is in fact bigger than its two children, we have bubbled + //this element down far enough + if (greatest == parent) + break; + + //parent is not bigger than at least one of its two children, so swap the parent + //with the largest item. + OSHeapElem* temp = fHeap[parent]; + fHeap[parent] = fHeap[greatest]; + fHeap[greatest] = temp; + + //now heapify the remaining chain + parent = greatest; + } + + return victim; +} + +OSHeapElem* OSHeap::Remove(OSHeapElem* elem) +{ + if ((fHeap == NULL) || (fFreeIndex == 1)) + return NULL; + +#if _OSHEAP_TESTING_ + SanityCheck(1); +#endif + + //first attempt to locate this element in the heap + UInt32 theIndex = 1; + for ( ; theIndex < fFreeIndex; theIndex++) + if (elem == fHeap[theIndex]) + break; + + //either we've found it, or this is a bogus element + if (theIndex == fFreeIndex) + return NULL; + + return Extract(theIndex); +} + + +#if _OSHEAP_TESTING_ + +void OSHeap::SanityCheck(UInt32 root) +{ + //make sure root is greater than both its children. Do so recursively + if (root < fFreeIndex) + { + if ((root * 2) < fFreeIndex) + { + Assert(fHeap[root]->fValue <= fHeap[root * 2]->fValue); + SanityCheck(root * 2); + } + if (((root * 2) + 1) < fFreeIndex) + { + Assert(fHeap[root]->fValue <= fHeap[(root * 2) + 1]->fValue); + SanityCheck((root * 2) + 1); + } + } +} + + +Bool16 OSHeap::Test() +{ + OSHeap victim(2); + OSHeapElem elem1; + OSHeapElem elem2; + OSHeapElem elem3; + OSHeapElem elem4; + OSHeapElem elem5; + OSHeapElem elem6; + OSHeapElem elem7; + OSHeapElem elem8; + OSHeapElem elem9; + + OSHeapElem* max = victim.ExtractMin(); + if (max != NULL) + return false; + + elem1.SetValue(100); + victim.Insert(&elem1); + + max = victim.ExtractMin(); + if (max != &elem1) + return false; + max = victim.ExtractMin(); + if (max != NULL) + return false; + + elem1.SetValue(100); + elem2.SetValue(80); + + victim.Insert(&elem1); + victim.Insert(&elem2); + + max = victim.ExtractMin(); + if (max != &elem2) + return false; + max = victim.ExtractMin(); + if (max != &elem1) + return false; + max = victim.ExtractMin(); + if (max != NULL) + return false; + + victim.Insert(&elem2); + victim.Insert(&elem1); + + max = victim.ExtractMin(); + if (max != &elem2) + return false; + max = victim.ExtractMin(); + if (max != &elem1) + return false; + + elem3.SetValue(70); + elem4.SetValue(60); + + victim.Insert(&elem3); + victim.Insert(&elem1); + victim.Insert(&elem2); + victim.Insert(&elem4); + + max = victim.ExtractMin(); + if (max != &elem4) + return false; + max = victim.ExtractMin(); + if (max != &elem3) + return false; + max = victim.ExtractMin(); + if (max != &elem2) + return false; + max = victim.ExtractMin(); + if (max != &elem1) + return false; + + elem5.SetValue(50); + elem6.SetValue(40); + elem7.SetValue(30); + elem8.SetValue(20); + elem9.SetValue(10); + + victim.Insert(&elem5); + victim.Insert(&elem3); + victim.Insert(&elem1); + + max = victim.ExtractMin(); + if (max != &elem5) + return false; + + victim.Insert(&elem4); + victim.Insert(&elem2); + + max = victim.ExtractMin(); + if (max != &elem4) + return false; + max = victim.ExtractMin(); + if (max != &elem3) + return false; + + victim.Insert(&elem2); + + max = victim.ExtractMin(); + if (max != &elem2) + return false; + + victim.Insert(&elem2); + victim.Insert(&elem6); + + max = victim.ExtractMin(); + if (max != &elem6) + return false; + + victim.Insert(&elem6); + victim.Insert(&elem3); + victim.Insert(&elem4); + victim.Insert(&elem5); + + max = victim.ExtractMin(); + if (max != &elem6) + return false; + max = victim.ExtractMin(); + if (max != &elem5) + return false; + + victim.Insert(&elem8); + max = victim.ExtractMin(); + if (max != &elem8) + return false; + max = victim.ExtractMin(); + if (max != &elem4) + return false; + + victim.Insert(&elem5); + victim.Insert(&elem4); + victim.Insert(&elem9); + victim.Insert(&elem7); + victim.Insert(&elem8); + victim.Insert(&elem6); + + max = victim.ExtractMin(); + if (max != &elem9) + return false; + max = victim.ExtractMin(); + if (max != &elem8) + return false; + max = victim.ExtractMin(); + if (max != &elem7) + return false; + max = victim.ExtractMin(); + if (max != &elem6) + return false; + max = victim.ExtractMin(); + if (max != &elem5) + return false; + max = victim.ExtractMin(); + if (max != &elem4) + return false; + max = victim.ExtractMin(); + if (max != &elem3) + return false; + max = victim.ExtractMin(); + if (max != &elem2) + return false; + max = victim.ExtractMin(); + if (max != &elem2) + return false; + max = victim.ExtractMin(); + if (max != &elem1) + return false; + max = victim.ExtractMin(); + if (max != NULL) + return false; + + victim.Insert(&elem1); + victim.Insert(&elem2); + victim.Insert(&elem3); + victim.Insert(&elem4); + victim.Insert(&elem5); + victim.Insert(&elem6); + victim.Insert(&elem7); + victim.Insert(&elem8); + victim.Insert(&elem9); + + max = victim.Remove(&elem7); + if (max != &elem7) + return false; + max = victim.Remove(&elem9); + if (max != &elem9) + return false; + max = victim.ExtractMin(); + if (max != &elem8) + return false; + max = victim.Remove(&elem2); + if (max != &elem2) + return false; + max = victim.Remove(&elem2); + if (max != NULL) + return false; + max = victim.Remove(&elem8); + if (max != NULL) + return false; + max = victim.Remove(&elem5); + if (max != &elem5) + return false; + max = victim.Remove(&elem6); + if (max != &elem6) + return false; + max = victim.Remove(&elem1); + if (max != &elem1) + return false; + max = victim.ExtractMin(); + if (max != &elem4) + return false; + max = victim.Remove(&elem1); + if (max != NULL) + return false; + + return true; +} +#endif diff --git a/CommonUtilitiesLib/OSHeap.h b/CommonUtilitiesLib/OSHeap.h new file mode 100644 index 0000000..073f812 --- /dev/null +++ b/CommonUtilitiesLib/OSHeap.h @@ -0,0 +1,112 @@ +/* + * + * @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: OSHeap.h + + Contains: Implements a heap + + +*/ + +#ifndef _OSHEAP_H_ +#define _OSHEAP_H_ + +#define _OSHEAP_TESTING_ 0 + +#include "OSCond.h" + +class OSHeapElem; + +class OSHeap +{ + public: + + enum + { + kDefaultStartSize = 1024 //UInt32 + }; + + OSHeap(UInt32 inStartSize = kDefaultStartSize); + ~OSHeap() { if (fHeap != NULL) delete fHeap; } + + //ACCESSORS + UInt32 CurrentHeapSize() { return fFreeIndex - 1; } + OSHeapElem* PeekMin() { if (CurrentHeapSize() > 0) return fHeap[1]; return NULL; } + + //MODIFIERS + + //These are the two primary operations supported by the heap + //abstract data type. both run in log(n) time. + void Insert(OSHeapElem* inElem); + OSHeapElem* ExtractMin() { return Extract(1); } + //removes specified element from the heap + OSHeapElem* Remove(OSHeapElem* elem); + +#if _OSHEAP_TESTING_ + //returns true if it passed the test, false otherwise + static Bool16 Test(); +#endif + + private: + + OSHeapElem* Extract(UInt32 index); + +#if _OSHEAP_TESTING_ + //verifies that the heap is in fact a heap + void SanityCheck(UInt32 root); +#endif + + OSHeapElem** fHeap; + UInt32 fFreeIndex; + UInt32 fArraySize; +}; + +class OSHeapElem +{ + public: + OSHeapElem(void* enclosingObject = NULL) + : fValue(0), fEnclosingObject(enclosingObject), fCurrentHeap(NULL) {} + ~OSHeapElem() {} + + //This data structure emphasizes performance over extensibility + //If it were properly object-oriented, the compare routine would + //be virtual. However, to avoid the use of v-functions in this data + //structure, I am assuming that the objects are compared using a 64 bit number. + // + void SetValue(SInt64 newValue) { fValue = newValue; } + SInt64 GetValue() { return fValue; } + void* GetEnclosingObject() { return fEnclosingObject; } + void SetEnclosingObject(void* obj) { fEnclosingObject = obj; } + Bool16 IsMemberOfAnyHeap() { return fCurrentHeap != NULL; } + + private: + + SInt64 fValue; + void* fEnclosingObject; + OSHeap* fCurrentHeap; + + friend class OSHeap; +}; +#endif //_OSHEAP_H_ diff --git a/CommonUtilitiesLib/OSMemory.h b/CommonUtilitiesLib/OSMemory.h new file mode 100644 index 0000000..40e47a8 --- /dev/null +++ b/CommonUtilitiesLib/OSMemory.h @@ -0,0 +1,146 @@ +/* + * + * @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: OSMemory.h + + Contains: Prototypes for overridden new & delete, definition of OSMemory + class which implements some memory leak debugging features. + + +*/ + +#ifndef __OS_MEMORY_H__ +#define __OS_MEMORY_H__ + +#include "OSHeaders.h" +#include "OSQueue.h" +#include "OSMutex.h" + +class OSMemory +{ + public: + +#if MEMORY_DEBUGGING + //If memory debugging is on, clients can get access to data structures that give + //memory status. + static OSQueue* GetTagQueue() { return &sTagQueue; } + static OSMutex* GetTagQueueMutex() { return &sMutex; } + static UInt32 GetAllocatedMemory() { return sAllocatedBytes; } + + static void* DebugNew(size_t size, char* inFile, int inLine, Bool16 sizeCheck); + static void DebugDelete(void *mem); + static Bool16 MemoryDebuggingTest(); + static void ValidateMemoryQueue(); + + enum + { + kMaxFileNameSize = 48 + }; + + struct TagElem + { + OSQueueElem elem; + char fileName[kMaxFileNameSize]; + int line; + UInt32 tagSize; //how big are objects of this type? + UInt32 totMemory; //how much do they currently occupy + UInt32 numObjects;//how many are there currently? + }; +#endif + + // Provides non-debugging behaviour for new and delete + static void* New(size_t inSize); + static void Delete(void* inMemory); + + //When memory allocation fails, the server just exits. This sets the code + //the server exits with + static void SetMemoryError(SInt32 inErr); + +#if MEMORY_DEBUGGING + private: + + struct MemoryDebugging + { + OSQueueElem elem; + TagElem* tagElem; + UInt32 size; + }; + static OSQueue sMemoryQueue; + static OSQueue sTagQueue; + static UInt32 sAllocatedBytes; + static OSMutex sMutex; + +#endif +}; + + +// NEW MACRO +// When memory debugging is on, this macro transparently uses the memory debugging +// overridden version of the new operator. When memory debugging is off, it just compiles +// down to the standard new. + +#if MEMORY_DEBUGGING + +#ifdef NEW +#error Conflicting Macro "NEW" +#endif + +#define NEW new (__FILE__, __LINE__) + +#else + +#ifdef NEW +#error Conflicting Macro "NEW" +#endif + +#define NEW new + +#endif + + +// +// PLACEMENT NEW OPERATOR +inline void* operator new(size_t, void* ptr) { return ptr;} + +#if MEMORY_DEBUGGING + +// These versions of the new operator with extra arguments provide memory debugging +// features. + +void* operator new(size_t s, char* inFile, int inLine); +void* operator new[](size_t s, char* inFile, int inLine); + +#endif + +// When memory debugging is not on, these are overridden so that if new fails, +// the process will exit. +void* operator new (size_t s); +void* operator new[](size_t s); + +void operator delete(void* mem); +void operator delete[](void* mem); + + +#endif //__OS_MEMORY_H__ diff --git a/CommonUtilitiesLib/OSMutex.cpp b/CommonUtilitiesLib/OSMutex.cpp new file mode 100644 index 0000000..db1cb4e --- /dev/null +++ b/CommonUtilitiesLib/OSMutex.cpp @@ -0,0 +1,158 @@ +/* + * + * @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: OSMutex.cpp + + Contains: Platform - independent mutex header. The implementation of this object + is platform - specific. Each platform must define an independent + QTSSMutex.h & QTSSMutex.cpp file. + + This file is for Mac OS X Server only + + + +*/ + +#include "OSMutex.h" +#include +#include "SafeStdLib.h" +#include + +// Private globals +#if __PTHREADS_MUTEXES__ + static pthread_mutexattr_t *sMutexAttr=NULL; + static void MutexAttrInit(); + + #if __solaris__ + static pthread_once_t sMutexAttrInit = {PTHREAD_ONCE_INIT}; + #else + static pthread_once_t sMutexAttrInit = PTHREAD_ONCE_INIT; + #endif + +#endif + +OSMutex::OSMutex() +{ +#ifdef __Win32__ + ::InitializeCriticalSection(&fMutex); + fHolder = 0; + fHolderCount = 0; +#elif __PTHREADS_MUTEXES__ + (void)pthread_once(&sMutexAttrInit, MutexAttrInit); + (void)pthread_mutex_init(&fMutex, sMutexAttr); + + fHolder = 0; + fHolderCount = 0; +#else + fMutex = mymutex_alloc(); +#endif +} + +#if __PTHREADS_MUTEXES__ +void MutexAttrInit() +{ + sMutexAttr = (pthread_mutexattr_t*)malloc(sizeof(pthread_mutexattr_t)); + ::memset(sMutexAttr, 0, sizeof(pthread_mutexattr_t)); + pthread_mutexattr_init(sMutexAttr); +} +#endif + +OSMutex::~OSMutex() +{ +#ifdef __Win32__ + ::DeleteCriticalSection(&fMutex); +#elif __PTHREADS_MUTEXES__ + pthread_mutex_destroy(&fMutex); +#else + mymutex_free(fMutex); +#endif +} + +#if __PTHREADS_MUTEXES__ || __Win32__ +void OSMutex::RecursiveLock() +{ + // We already have this mutex. Just refcount and return + if (OSThread::GetCurrentThreadID() == fHolder) + { + fHolderCount++; + return; + } +#ifdef __Win32__ + ::EnterCriticalSection(&fMutex); +#else + (void)pthread_mutex_lock(&fMutex); +#endif + Assert(fHolder == 0); + fHolder = OSThread::GetCurrentThreadID(); + fHolderCount++; + Assert(fHolderCount == 1); +} + +void OSMutex::RecursiveUnlock() +{ + if (OSThread::GetCurrentThreadID() != fHolder) + return; + + Assert(fHolderCount > 0); + fHolderCount--; + if (fHolderCount == 0) + { + fHolder = 0; +#ifdef __Win32__ + ::LeaveCriticalSection(&fMutex); +#else + pthread_mutex_unlock(&fMutex); +#endif + } +} + +Bool16 OSMutex::RecursiveTryLock() +{ + // We already have this mutex. Just refcount and return + if (OSThread::GetCurrentThreadID() == fHolder) + { + fHolderCount++; + return true; + } + +#ifdef __Win32__ + Bool16 theErr = (Bool16)::TryEnterCriticalSection(&fMutex); // Return values of this function match our API + if (!theErr) + return theErr; +#else + int theErr = pthread_mutex_trylock(&fMutex); + if (theErr != 0) + { + Assert(theErr == EBUSY); + return false; + } +#endif + Assert(fHolder == 0); + fHolder = OSThread::GetCurrentThreadID(); + fHolderCount++; + Assert(fHolderCount == 1); + return true; +} +#endif //__PTHREADS_MUTEXES__ || __Win32__ diff --git a/CommonUtilitiesLib/OSMutex.h b/CommonUtilitiesLib/OSMutex.h new file mode 100644 index 0000000..6e21319 --- /dev/null +++ b/CommonUtilitiesLib/OSMutex.h @@ -0,0 +1,147 @@ +/* + * + * @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: OSMutex.h + + Contains: Platform - independent mutex header. The implementation of this object + is platform - specific. Each platform must define an independent + OSMutex.h & OSMutex.cpp file. + + This file is for Mac OS X Server only + + + +*/ + +#ifndef _OSMUTEX_H_ +#define _OSMUTEX_H_ + +#include +#include "SafeStdLib.h" +#ifndef __Win32__ +#include + #if __PTHREADS_MUTEXES__ + #if __MacOSX__ + #ifndef _POSIX_PTHREAD_H + #include + #endif + #else + #include + #endif + #include + + #else + #include "mymutex.h" + #endif +#endif + +#include "OSHeaders.h" +#include "OSThread.h" +#include "MyAssert.h" + +class OSCond; + +class OSMutex +{ + public: + + OSMutex(); + ~OSMutex(); + + inline void Lock(); + inline void Unlock(); + + // Returns true on successful grab of the lock, false on failure + inline Bool16 TryLock(); + + private: + +#ifdef __Win32__ + CRITICAL_SECTION fMutex; + + DWORD fHolder; + UInt32 fHolderCount; + +#elif !__PTHREADS_MUTEXES__ + mymutex_t fMutex; +#else + pthread_mutex_t fMutex; + // These two platforms don't implement pthreads recursive mutexes, so + // we have to do it manually + pthread_t fHolder; + UInt32 fHolderCount; +#endif + +#if __PTHREADS_MUTEXES__ || __Win32__ + void RecursiveLock(); + void RecursiveUnlock(); + Bool16 RecursiveTryLock(); +#endif + friend class OSCond; +}; + +class OSMutexLocker +{ + public: + + OSMutexLocker(OSMutex *inMutexP) : fMutex(inMutexP) { if (fMutex != NULL) fMutex->Lock(); } + ~OSMutexLocker() { if (fMutex != NULL) fMutex->Unlock(); } + + void Lock() { if (fMutex != NULL) fMutex->Lock(); } + void Unlock() { if (fMutex != NULL) fMutex->Unlock(); } + + private: + + OSMutex* fMutex; +}; + +void OSMutex::Lock() +{ +#if __PTHREADS_MUTEXES__ || __Win32__ + this->RecursiveLock(); +#else + mymutex_lock(fMutex); +#endif //!__PTHREADS__ +} + +void OSMutex::Unlock() +{ +#if __PTHREADS_MUTEXES__ || __Win32__ + this->RecursiveUnlock(); +#else + mymutex_unlock(fMutex); +#endif //!__PTHREADS__ +} + +Bool16 OSMutex::TryLock() +{ +#if __PTHREADS_MUTEXES__ || __Win32__ + return this->RecursiveTryLock(); +#else + return (Bool16)mymutex_try_lock(fMutex); +#endif //!__PTHREADS__ +} + +#endif //_OSMUTEX_H_ diff --git a/CommonUtilitiesLib/OSMutexRW.cpp b/CommonUtilitiesLib/OSMutexRW.cpp new file mode 100644 index 0000000..71e18ed --- /dev/null +++ b/CommonUtilitiesLib/OSMutexRW.cpp @@ -0,0 +1,182 @@ +/* + * + * @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: OSMutex.cpp + + Contains: + + + +*/ + +#include "OSMutexRW.h" +#include "OSMutex.h" +#include "OSCond.h" + +#include +#include "SafeStdLib.h" +#include + +#if DEBUGMUTEXRW + int OSMutexRW::fCount = 0; + int OSMutexRW::fMaxCount =0; +#endif + + +#if DEBUGMUTEXRW +void OSMutexRW::CountConflict(int i) +{ + fCount += i; + if (i == -1) qtss_printf("Num Conflicts: %d\n", fMaxCount); + if (fCount > fMaxCount) + fMaxCount = fCount; + +} +#endif + +void OSMutexRW::LockRead() +{ + OSMutexLocker locker(&fInternalLock); +#if DEBUGMUTEXRW + if (fState != 0) + { qtss_printf("LockRead(conflict) fState = %d active readers = %d, waiting writers = %d, waiting readers=%d\n",fState, fActiveReaders, fWriteWaiters, fReadWaiters); + CountConflict(1); + } + +#endif + + AddReadWaiter(); + while ( ActiveWriter() // active writer so wait + || WaitingWriters() // reader must wait for write waiters + ) + { + fReadersCond.Wait(&fInternalLock,OSMutexRW::eMaxWait); + } + + RemoveReadWaiter(); + AddActiveReader(); // add 1 to active readers + fActiveReaders = fState; + +#if DEBUGMUTEXRW +// qtss_printf("LockRead(conflict) fState = %d active readers = %d, waiting writers = %d, waiting readers=%d\n",fState, fActiveReaders, fWriteWaiters, fReadWaiters); + +#endif +} + +void OSMutexRW::LockWrite() +{ + OSMutexLocker locker(&fInternalLock); + AddWriteWaiter(); // 1 writer queued +#if DEBUGMUTEXRW + + if (Active()) + { qtss_printf("LockWrite(conflict) state = %d active readers = %d, waiting writers = %d, waiting readers=%d\n", fState, fActiveReaders, fWriteWaiters, fReadWaiters); + CountConflict(1); + } + + qtss_printf("LockWrite 'waiting' fState = %d locked active readers = %d, waiting writers = %d, waiting readers=%d\n",fState, fActiveReaders, fReadWaiters, fWriteWaiters); +#endif + + while (ActiveReaders()) // active readers + { + fWritersCond.Wait(&fInternalLock,OSMutexRW::eMaxWait); + } + + RemoveWriteWaiter(); // remove from waiting writers + SetState(OSMutexRW::eActiveWriterState); // this is the active writer + fActiveReaders = fState; +#if DEBUGMUTEXRW +// qtss_printf("LockWrite 'locked' fState = %d locked active readers = %d, waiting writers = %d, waiting readers=%d\n",fState, fActiveReaders, fReadWaiters, fWriteWaiters); +#endif + +} + + +void OSMutexRW::Unlock() +{ + OSMutexLocker locker(&fInternalLock); +#if DEBUGMUTEXRW +// qtss_printf("Unlock active readers = %d, waiting writers = %d, waiting readers=%d\n", fActiveReaders, fReadWaiters, fWriteWaiters); + +#endif + + if (ActiveWriter()) + { + SetState(OSMutexRW::eNoWriterState); // this was the active writer + if (WaitingWriters()) // there are waiting writers + { fWritersCond.Signal(); + } + else + { fReadersCond.Broadcast(); + } +#if DEBUGMUTEXRW + qtss_printf("Unlock(writer) active readers = %d, waiting writers = %d, waiting readers=%d\n", fActiveReaders, fReadWaiters, fWriteWaiters); +#endif + } + else + { + RemoveActiveReader(); // this was a reader + if (!ActiveReaders()) // no active readers + { SetState(OSMutexRW::eNoWriterState); // this was the active writer now no actives threads + fWritersCond.Signal(); + } + } + fActiveReaders = fState; + +} + + + +// Returns true on successful grab of the lock, false on failure +int OSMutexRW::TryLockWrite() +{ + int status = EBUSY; + OSMutexLocker locker(&fInternalLock); + + if ( !Active() && !WaitingWriters()) // no writers, no readers, no waiting writers + { + this->LockWrite(); + status = 0; + } + + return status; +} + +int OSMutexRW::TryLockRead() +{ + int status = EBUSY; + OSMutexLocker locker(&fInternalLock); + + if ( !ActiveWriter() && !WaitingWriters() ) // no current writers but other readers ok + { + this->LockRead(); + status = 0; + } + + return status; +} + + + diff --git a/CommonUtilitiesLib/OSMutexRW.h b/CommonUtilitiesLib/OSMutexRW.h new file mode 100644 index 0000000..d8fc576 --- /dev/null +++ b/CommonUtilitiesLib/OSMutexRW.h @@ -0,0 +1,135 @@ +/* + * + * @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: OSMutexRW.h + + Contains: + + + +*/ + +#ifndef _OSMUTEXRW_H_ +#define _OSMUTEXRW_H_ + +#include +#include "SafeStdLib.h" +#include "OSHeaders.h" +#include "OSThread.h" +#include "MyAssert.h" +#include "OSMutex.h" +#include "OSQueue.h" + +#define DEBUGMUTEXRW 0 + +class OSMutexRW +{ + public: + + OSMutexRW(): fState(0), fWriteWaiters(0),fReadWaiters(0),fActiveReaders(0) {} ; + + void LockRead(); + void LockWrite(); + void Unlock(); + + // Returns 0 on success, EBUSY on failure + int TryLockWrite(); + int TryLockRead(); + + private: + enum {eMaxWait = 0x0FFFFFFF, eMultiThreadCondition = true, }; + enum {eActiveWriterState = -1, eNoWriterState = 0 }; + + OSMutex fInternalLock; // the internal lock + OSCond fReadersCond; // the waiting readers + OSCond fWritersCond; // the waiting writers + int fState; // -1:writer,0:free,>0:readers + int fWriteWaiters; // number of waiting writers + int fReadWaiters; // number of waiting readers + int fActiveReaders; // number of active readers = fState >= 0; + + inline void AdjustState(int i) { fState += i; }; + inline void AdjustWriteWaiters(int i) { fWriteWaiters += i; }; + inline void AdjustReadWaiters(int i) { fReadWaiters += i; }; + inline void SetState(int i) { fState = i; }; + inline void SetWriteWaiters(int i) { fWriteWaiters = i; }; + inline void SetReadWaiters(int i) { fReadWaiters = i; }; + + inline void AddWriteWaiter() { AdjustWriteWaiters(1); }; + inline void RemoveWriteWaiter() { AdjustWriteWaiters(-1); }; + + inline void AddReadWaiter() { AdjustReadWaiters(1); }; + inline void RemoveReadWaiter() { AdjustReadWaiters(-1); }; + + inline void AddActiveReader() { AdjustState(1); }; + inline void RemoveActiveReader() { AdjustState(-1); }; + + + inline Bool16 WaitingWriters() {return (Bool16) (fWriteWaiters > 0) ; } + inline Bool16 WaitingReaders() {return (Bool16) (fReadWaiters > 0) ;} + inline Bool16 Active() {return (Bool16) (fState != 0) ;} + inline Bool16 ActiveReaders() {return (Bool16) (fState > 0) ;} + inline Bool16 ActiveWriter() {return (Bool16) (fState < 0) ;} // only one + + #if DEBUGMUTEXRW + static int fCount, fMaxCount; + static OSMutex sCountMutex; + void CountConflict(int i); + #endif + +}; + +class OSMutexReadWriteLocker +{ + public: + OSMutexReadWriteLocker(OSMutexRW *inMutexPtr): fRWMutexPtr(inMutexPtr) {}; + ~OSMutexReadWriteLocker() { if (fRWMutexPtr != NULL) fRWMutexPtr->Unlock(); } + + + void UnLock() { if (fRWMutexPtr != NULL) fRWMutexPtr->Unlock(); } + void SetMutex(OSMutexRW *mutexPtr) {fRWMutexPtr = mutexPtr;} + OSMutexRW* fRWMutexPtr; +}; + +class OSMutexReadLocker: public OSMutexReadWriteLocker +{ + public: + + OSMutexReadLocker(OSMutexRW *inMutexPtr) : OSMutexReadWriteLocker(inMutexPtr) { if (OSMutexReadWriteLocker::fRWMutexPtr != NULL) OSMutexReadWriteLocker::fRWMutexPtr->LockRead(); } + void Lock() { if (OSMutexReadWriteLocker::fRWMutexPtr != NULL) OSMutexReadWriteLocker::fRWMutexPtr->LockRead(); } +}; + +class OSMutexWriteLocker: public OSMutexReadWriteLocker +{ + public: + + OSMutexWriteLocker(OSMutexRW *inMutexPtr) : OSMutexReadWriteLocker(inMutexPtr) { if (OSMutexReadWriteLocker::fRWMutexPtr != NULL) OSMutexReadWriteLocker::fRWMutexPtr->LockWrite(); } + void Lock() { if (OSMutexReadWriteLocker::fRWMutexPtr != NULL) OSMutexReadWriteLocker::fRWMutexPtr->LockWrite(); } + +}; + + + +#endif //_OSMUTEX_H_ diff --git a/CommonUtilitiesLib/OSQueue.cpp b/CommonUtilitiesLib/OSQueue.cpp new file mode 100644 index 0000000..11800e1 --- /dev/null +++ b/CommonUtilitiesLib/OSQueue.cpp @@ -0,0 +1,262 @@ +/* + * + * @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: OSQueue.cpp + + Contains: implements OSQueue class + + +*/ + +#include "OSQueue.h" + + +OSQueue::OSQueue() : fLength(0) +{ + fSentinel.fNext = &fSentinel; + fSentinel.fPrev = &fSentinel; +} + +void OSQueue::EnQueue(OSQueueElem* elem) +{ + Assert(elem != NULL); + if (elem->fQueue == this) + return; + Assert(elem->fQueue == NULL); + elem->fNext = fSentinel.fNext; + elem->fPrev = &fSentinel; + elem->fQueue = this; + fSentinel.fNext->fPrev = elem; + fSentinel.fNext = elem; + fLength++; +} + +OSQueueElem* OSQueue::DeQueue() +{ + if (fLength > 0) + { + OSQueueElem* elem = fSentinel.fPrev; + Assert(fSentinel.fPrev != &fSentinel); + elem->fPrev->fNext = &fSentinel; + fSentinel.fPrev = elem->fPrev; + elem->fQueue = NULL; + fLength--; + return elem; + } + else + return NULL; +} + +void OSQueue::Remove(OSQueueElem* elem) +{ + Assert(elem != NULL); + Assert(elem != &fSentinel); + + if (elem->fQueue == this) + { + elem->fNext->fPrev = elem->fPrev; + elem->fPrev->fNext = elem->fNext; + elem->fQueue = NULL; + fLength--; + } +} + +#if OSQUEUETESTING +Bool16 OSQueue::Test() +{ + OSQueue theVictim; + void *x = (void*)1; + OSQueueElem theElem1(x); + x = (void*)2; + OSQueueElem theElem2(x); + x = (void*)3; + OSQueueElem theElem3(x); + + if (theVictim.GetHead() != NULL) + return false; + if (theVictim.GetTail() != NULL) + return false; + + theVictim.EnQueue(&theElem1); + if (theVictim.GetHead() != &theElem1) + return false; + if (theVictim.GetTail() != &theElem1) + return false; + + OSQueueElem* theElem = theVictim.DeQueue(); + if (theElem != &theElem1) + return false; + + if (theVictim.GetHead() != NULL) + return false; + if (theVictim.GetTail() != NULL) + return false; + + theVictim.EnQueue(&theElem1); + theVictim.EnQueue(&theElem2); + + if (theVictim.GetHead() != &theElem1) + return false; + if (theVictim.GetTail() != &theElem2) + return false; + + theElem = theVictim.DeQueue(); + if (theElem != &theElem1) + return false; + + if (theVictim.GetHead() != &theElem2) + return false; + if (theVictim.GetTail() != &theElem2) + return false; + + theElem = theVictim.DeQueue(); + if (theElem != &theElem2) + return false; + + theVictim.EnQueue(&theElem1); + theVictim.EnQueue(&theElem2); + theVictim.EnQueue(&theElem3); + + if (theVictim.GetHead() != &theElem1) + return false; + if (theVictim.GetTail() != &theElem3) + return false; + + theElem = theVictim.DeQueue(); + if (theElem != &theElem1) + return false; + + if (theVictim.GetHead() != &theElem2) + return false; + if (theVictim.GetTail() != &theElem3) + return false; + + theElem = theVictim.DeQueue(); + if (theElem != &theElem2) + return false; + + if (theVictim.GetHead() != &theElem3) + return false; + if (theVictim.GetTail() != &theElem3) + return false; + + theElem = theVictim.DeQueue(); + if (theElem != &theElem3) + return false; + + theVictim.EnQueue(&theElem1); + theVictim.EnQueue(&theElem2); + theVictim.EnQueue(&theElem3); + + OSQueueIter theIterVictim(&theVictim); + if (theIterVictim.IsDone()) + return false; + if (theIterVictim.GetCurrent() != &theElem3) + return false; + theIterVictim.Next(); + if (theIterVictim.IsDone()) + return false; + if (theIterVictim.GetCurrent() != &theElem2) + return false; + theIterVictim.Next(); + if (theIterVictim.IsDone()) + return false; + if (theIterVictim.GetCurrent() != &theElem1) + return false; + theIterVictim.Next(); + if (!theIterVictim.IsDone()) + return false; + if (theIterVictim.GetCurrent() != NULL) + return false; + + theVictim.Remove(&theElem1); + + if (theVictim.GetHead() != &theElem2) + return false; + if (theVictim.GetTail() != &theElem3) + return false; + + theVictim.Remove(&theElem1); + + if (theVictim.GetHead() != &theElem2) + return false; + if (theVictim.GetTail() != &theElem3) + return false; + + theVictim.Remove(&theElem3); + + if (theVictim.GetHead() != &theElem2) + return false; + if (theVictim.GetTail() != &theElem2) + return false; + + return true; +} +#endif + + + +void OSQueueIter::Next() +{ + if (fCurrentElemP == fQueueP->GetTail()) + fCurrentElemP = NULL; + else + fCurrentElemP = fCurrentElemP->Prev(); +} + + +OSQueueElem* OSQueue_Blocking::DeQueueBlocking(OSThread* inCurThread, SInt32 inTimeoutInMilSecs) +{ + OSMutexLocker theLocker(&fMutex); +#ifdef __Win32_ + if (fQueue.GetLength() == 0) + { fCond.Wait(&fMutex, inTimeoutInMilSecs); + return NULL; + } +#else + if (fQueue.GetLength() == 0) + fCond.Wait(&fMutex, inTimeoutInMilSecs); +#endif + + OSQueueElem* retval = fQueue.DeQueue(); + return retval; +} + +OSQueueElem* OSQueue_Blocking::DeQueue() +{ + OSMutexLocker theLocker(&fMutex); + OSQueueElem* retval = fQueue.DeQueue(); + return retval; +} + + +void OSQueue_Blocking::EnQueue(OSQueueElem* obj) +{ + { + OSMutexLocker theLocker(&fMutex); + fQueue.EnQueue(obj); + } + fCond.Signal(); +} diff --git a/CommonUtilitiesLib/OSQueue.h b/CommonUtilitiesLib/OSQueue.h new file mode 100644 index 0000000..80c167e --- /dev/null +++ b/CommonUtilitiesLib/OSQueue.h @@ -0,0 +1,151 @@ +/* + * + * @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: OSQueue.h + + Contains: implements OSQueue class + + +*/ + +#ifndef _OSQUEUE_H_ +#define _OSQUEUE_H_ + +#include "MyAssert.h" +#include "OSHeaders.h" +#include "OSMutex.h" +#include "OSCond.h" +#include "OSThread.h" + +#define OSQUEUETESTING 0 + +class OSQueue; + +class OSQueueElem { + public: + OSQueueElem(void* enclosingObject = NULL) : fNext(NULL), fPrev(NULL), fQueue(NULL), + fEnclosingObject(enclosingObject) {} + virtual ~OSQueueElem() { Assert(fQueue == NULL); } + + Bool16 IsMember(const OSQueue& queue) { return (&queue == fQueue); } + Bool16 IsMemberOfAnyQueue() { return fQueue != NULL; } + void* GetEnclosingObject() { return fEnclosingObject; } + void SetEnclosingObject(void* obj) { fEnclosingObject = obj; } + + OSQueueElem* Next() { return fNext; } + OSQueueElem* Prev() { return fPrev; } + OSQueue* InQueue() { return fQueue; } + inline void Remove(); + + private: + + OSQueueElem* fNext; + OSQueueElem* fPrev; + OSQueue * fQueue; + void* fEnclosingObject; + + friend class OSQueue; +}; + +class OSQueue { + public: + OSQueue(); + ~OSQueue() {} + + void EnQueue(OSQueueElem* object); + OSQueueElem* DeQueue(); + + OSQueueElem* GetHead() { if (fLength > 0) return fSentinel.fPrev; return NULL; } + OSQueueElem* GetTail() { if (fLength > 0) return fSentinel.fNext; return NULL; } + UInt32 GetLength() { return fLength; } + + void Remove(OSQueueElem* object); + +#if OSQUEUETESTING + static Bool16 Test(); +#endif + + protected: + + OSQueueElem fSentinel; + UInt32 fLength; +}; + +class OSQueueIter +{ + public: + OSQueueIter(OSQueue* inQueue) : fQueueP(inQueue), fCurrentElemP(inQueue->GetHead()) {} + OSQueueIter(OSQueue* inQueue, OSQueueElem* startElemP ) : fQueueP(inQueue) + { + if ( startElemP ) + { Assert( startElemP->IsMember(*inQueue ) ); + fCurrentElemP = startElemP; + + } + else + fCurrentElemP = NULL; + } + ~OSQueueIter() {} + + void Reset() { fCurrentElemP = fQueueP->GetHead(); } + + OSQueueElem* GetCurrent() { return fCurrentElemP; } + void Next(); + + Bool16 IsDone() { return fCurrentElemP == NULL; } + + private: + + OSQueue* fQueueP; + OSQueueElem* fCurrentElemP; +}; + +class OSQueue_Blocking +{ + public: + OSQueue_Blocking() {} + ~OSQueue_Blocking() {} + + OSQueueElem* DeQueueBlocking(OSThread* inCurThread, SInt32 inTimeoutInMilSecs); + OSQueueElem* DeQueue();//will not block + void EnQueue(OSQueueElem* obj); + + OSCond* GetCond() { return &fCond; } + OSQueue* GetQueue() { return &fQueue; } + + private: + + OSCond fCond; + OSMutex fMutex; + OSQueue fQueue; +}; + + +void OSQueueElem::Remove() +{ + if (fQueue != NULL) + fQueue->Remove(this); +} +#endif //_OSQUEUE_H_ diff --git a/CommonUtilitiesLib/OSRef.cpp b/CommonUtilitiesLib/OSRef.cpp new file mode 100644 index 0000000..35d0d92 --- /dev/null +++ b/CommonUtilitiesLib/OSRef.cpp @@ -0,0 +1,197 @@ +/* + * + * @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: OSRef.cpp + + Contains: Implementation of OSRef class. + + +*/ + +#include "OSRef.h" + +#include + +UInt32 OSRefTableUtils::HashString(StrPtrLen* inString) +{ + Assert(inString != NULL); + Assert(inString->Ptr != NULL); + Assert(inString->Len > 0); + if (inString == NULL || inString->Len == 0 || inString->Ptr == NULL) + return 0; + + //make sure to convert to unsigned here, as there may be binary + //data in this string + UInt8* theData = (UInt8*)inString->Ptr; + + //divide by 4 and take the characters at quarter points in the string, + //use those as the basis for the hash value + UInt32 quarterLen = inString->Len >> 2; + return (inString->Len * (theData[0] + theData[quarterLen] + + theData[quarterLen * 2] + theData[quarterLen * 3] + + theData[inString->Len - 1])); +} + +OS_Error OSRefTable::Register(OSRef* inRef) +{ + Assert(inRef != NULL); + if (inRef == NULL) + return EPERM; +#if DEBUG + Assert(!inRef->fInATable); +#endif + Assert(inRef->fRefCount == 0); + Assert(inRef->fString.Ptr != NULL); + Assert(inRef->fString.Len != 0); + + OSMutexLocker locker(&fMutex); + if (inRef->fString.Ptr == NULL || inRef->fString.Len == 0) + { //printf("OSRefTable::Register inRef is invalid \n"); + return EPERM; + } + + // Check for a duplicate. In this function, if there is a duplicate, + // return an error, don't resolve the duplicate + OSRefKey key(&inRef->fString); + OSRef* duplicateRef = fTable.Map(&key); + if (duplicateRef != NULL) + return EPERM; + + // There is no duplicate, so add this ref into the table +#if DEBUG + inRef->fInATable = true; +#endif + fTable.Add(inRef); + return OS_NoErr; +} + + +OSRef* OSRefTable::RegisterOrResolve(OSRef* inRef) +{ + Assert(inRef != NULL); +#if DEBUG + Assert(!inRef->fInATable); +#endif + Assert(inRef->fRefCount == 0); + + OSMutexLocker locker(&fMutex); + + // Check for a duplicate. If there is one, resolve it and return it to the caller + OSRef* duplicateRef = this->Resolve(&inRef->fString); + if (duplicateRef != NULL) + return duplicateRef; + + // There is no duplicate, so add this ref into the table +#if DEBUG + inRef->fInATable = true; +#endif + fTable.Add(inRef); + return NULL; +} + +void OSRefTable::UnRegister(OSRef* ref, UInt32 refCount) +{ + Assert(ref != NULL); + OSMutexLocker locker(&fMutex); + + //make sure that no one else is using the object + while (ref->fRefCount > refCount) + ref->fCond.Wait(&fMutex); + +#if DEBUG + OSRefKey key(&ref->fString); + if (ref->fInATable) + Assert(fTable.Map(&key) != NULL); + ref->fInATable = false; +#endif + + //ok, we now definitely have no one else using this object, so + //remove it from the table + fTable.Remove(ref); +} + +Bool16 OSRefTable::TryUnRegister(OSRef* ref, UInt32 refCount) +{ + OSMutexLocker locker(&fMutex); + if (ref->fRefCount > refCount) + return false; + + // At this point, this is guarenteed not to block, because + // we've already checked that the refCount is low. + this->UnRegister(ref, refCount); + return true; +} + + +OSRef* OSRefTable::Resolve(StrPtrLen* inUniqueID) +{ + Assert(inUniqueID != NULL); + OSRefKey key(inUniqueID); + + //this must be done atomically wrt the table + OSMutexLocker locker(&fMutex); + OSRef* ref = fTable.Map(&key); + if (ref != NULL) + { + ref->fRefCount++; + Assert(ref->fRefCount > 0); + } + return ref; +} + +void OSRefTable::Release(OSRef* ref) +{ + Assert(ref != NULL); + OSMutexLocker locker(&fMutex); + ref->fRefCount--; + // fRefCount is a UInt32 and QTSS should never run into + // a ref greater than 16 * 64K, so this assert just checks to + // be sure that we have not decremented the ref less than zero. + Assert( ref->fRefCount < 1048576L ); + //make sure to wakeup anyone who may be waiting for this resource to be released + ref->fCond.Signal(); +} + +void OSRefTable::Swap(OSRef* newRef) +{ + Assert(newRef != NULL); + OSMutexLocker locker(&fMutex); + + OSRefKey key(&newRef->fString); + OSRef* oldRef = fTable.Map(&key); + if (oldRef != NULL) + { + fTable.Remove(oldRef); + fTable.Add(newRef); +#if DEBUG + newRef->fInATable = true; + oldRef->fInATable = false; + oldRef->fSwapCalled = true; +#endif + } + else + Assert(0); +} + diff --git a/CommonUtilitiesLib/OSRef.h b/CommonUtilitiesLib/OSRef.h new file mode 100644 index 0000000..f7524eb --- /dev/null +++ b/CommonUtilitiesLib/OSRef.h @@ -0,0 +1,258 @@ +/* + * + * @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: OSRef.h + + Contains: Class supports creating unique string IDs to object pointers. A grouping + of an object and its string ID may be stored in an OSRefTable, and the + associated object pointer can be looked up by string ID. + + Refs can only be removed from the table when no one is using the ref, + therefore allowing clients to arbitrate access to objects in a preemptive, + multithreaded environment. + + + + +*/ + +#ifndef _OSREF_H_ +#define _OSREF_H_ + + +#include "StrPtrLen.h" +#include "OSHashTable.h" +#include "OSCond.h" + +class OSRefKey; + +class OSRefTableUtils +{ + private: + + static UInt32 HashString(StrPtrLen* inString); + + friend class OSRef; + friend class OSRefKey; +}; + +class OSRef +{ + public: + + OSRef() : fObjectP(NULL), fRefCount(0), fNextHashEntry(NULL) + { +#if DEBUG + fInATable = false; + fSwapCalled = false; +#endif + } + OSRef(const StrPtrLen &inString, void* inObjectP) + : fRefCount(0), fNextHashEntry(NULL) + { Set(inString, inObjectP); } + ~OSRef() {} + + void Set(const StrPtrLen& inString, void* inObjectP) + { +#if DEBUG + fInATable = false; + fSwapCalled = false; +#endif + fString = inString; fObjectP = inObjectP; + fHashValue = OSRefTableUtils::HashString(&fString); + } + +#if DEBUG + Bool16 IsInTable() { return fInATable; } +#endif + void** GetObjectPtr() { return &fObjectP; } + void* GetObject() { return fObjectP; } + UInt32 GetRefCount() { return fRefCount; } + StrPtrLen *GetString() { return &fString; } + private: + + //value + void* fObjectP; + //key + StrPtrLen fString; + + //refcounting + UInt32 fRefCount; +#if DEBUG + Bool16 fInATable; + Bool16 fSwapCalled; +#endif + OSCond fCond;//to block threads waiting for this ref. + + UInt32 fHashValue; + OSRef* fNextHashEntry; + + friend class OSRefKey; + friend class OSHashTable; + friend class OSHashTableIter; + friend class OSRefTable; + +}; + + +class OSRefKey +{ +public: + + //CONSTRUCTOR / DESTRUCTOR: + OSRefKey(StrPtrLen* inStringP) + : fStringP(inStringP) + { fHashValue = OSRefTableUtils::HashString(inStringP); } + + ~OSRefKey() {} + + + //ACCESSORS: + StrPtrLen* GetString() { return fStringP; } + + +private: + + //PRIVATE ACCESSORS: + SInt32 GetHashKey() { return fHashValue; } + + //these functions are only used by the hash table itself. This constructor + //will break the "Set" functions. + OSRefKey(OSRef *elem) : fStringP(&elem->fString), + fHashValue(elem->fHashValue) {} + + friend int operator ==(const OSRefKey &key1, const OSRefKey &key2) + { + if (key1.fStringP->Equal(*key2.fStringP)) + return true; + return false; + } + + //data: + StrPtrLen *fStringP; + UInt32 fHashValue; + + friend class OSHashTable; +}; + +typedef OSHashTable OSRefHashTable; +typedef OSHashTableIter OSRefHashTableIter; + +class OSRefTable +{ + public: + + enum + { + kDefaultTableSize = 1193 //UInt32 + }; + + //tableSize doesn't indicate the max number of Refs that can be added + //(it's unlimited), but is rather just how big to make the hash table + OSRefTable(UInt32 tableSize = kDefaultTableSize) : fTable(tableSize), fMutex() {} + ~OSRefTable() {} + + //Allows access to the mutex in case you need to lock the table down + //between operations + OSMutex* GetMutex() { return &fMutex; } + OSRefHashTable* GetHashTable() { return &fTable; } + + //Registers a Ref in the table. Once the Ref is in, clients may resolve + //the ref by using its string ID. You must setup the Ref before passing it + //in here, ie., setup the string and object pointers + //This function will succeed unless the string identifier is not unique, + //in which case it will return QTSS_DupName + //This function is atomic wrt this ref table. + OS_Error Register(OSRef* ref); + + // RegisterOrResolve + // If the ID of the input ref is unique, this function is equivalent to + // Register, and returns NULL. + // If there is a duplicate ID already in the map, this funcion + // leave it, resolves it, and returns it. + OSRef* RegisterOrResolve(OSRef* inRef); + + //This function may block. You can only remove a Ref from the table + //when the refCount drops to the level specified. If several threads have + //the ref currently, the calling thread will wait until the other threads + //stop using the ref (by calling Release, below) + //This function is atomic wrt this ref table. + void UnRegister(OSRef* ref, UInt32 refCount = 0); + + // Same as UnRegister, but guarenteed not to block. Will return + // true if ref was sucessfully unregistered, false otherwise + Bool16 TryUnRegister(OSRef* ref, UInt32 refCount = 0); + + //Resolve. This function uses the provided key string to identify and grab + //the Ref keyed by that string. Once the Ref is resolved, it is safe to use + //(it cannot be removed from the Ref table) until you call Release. Because + //of that, you MUST call release in a timely manner, and be aware of potential + //deadlocks because you now own a resource being contended over. + //This function is atomic wrt this ref table. + OSRef* Resolve(StrPtrLen* inString); + + //Release. Release a Ref, and drops its refCount. After calling this, the + //Ref is no longer safe to use, as it may be removed from the ref table. + void Release(OSRef* inRef); + + // Swap. This atomically removes any existing Ref in the table with the new + // ref's ID, and replaces it with this new Ref. If there is no matching Ref + // already in the table, this function does nothing. + // + // Be aware that this creates a situation where clients may have a Ref resolved + // that is no longer in the table. The old Ref must STILL be UnRegistered normally. + // Once Swap completes sucessfully, clients that call resolve on the ID will get + // the new OSRef object. + void Swap(OSRef* newRef); + + UInt32 GetNumRefsInTable() { UInt64 result = fTable.GetNumEntries(); Assert(result < kUInt32_Max); return (UInt32) result; } + + private: + + + //all this object needs to do its job is an atomic hashtable + OSRefHashTable fTable; + OSMutex fMutex; +}; + + +class OSRefReleaser +{ + public: + + OSRefReleaser(OSRefTable* inTable, OSRef* inRef) : fOSRefTable(inTable), fOSRef(inRef) {} + ~OSRefReleaser() { fOSRefTable->Release(fOSRef); } + + OSRef* GetRef() { return fOSRef; } + + private: + + OSRefTable* fOSRefTable; + OSRef* fOSRef; +}; + + + +#endif //_OSREF_H_ diff --git a/CommonUtilitiesLib/OSThread.cpp b/CommonUtilitiesLib/OSThread.cpp new file mode 100644 index 0000000..10c7e23 --- /dev/null +++ b/CommonUtilitiesLib/OSThread.cpp @@ -0,0 +1,347 @@ +/* + * + * @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: OSThread.cpp + + Contains: Thread abstraction implementation + + + +*/ + +#include +#include +#include "SafeStdLib.h" +#include +#include + +#ifdef __MacOSX__ +#include +#include +#endif + +#ifndef __Win32__ + #if __PTHREADS__ + #include + #if USE_THR_YIELD + #include + #endif + #else + #include + #include + #endif + #include + #include + #include +#endif + +#include "OSThread.h" +#include "MyAssert.h" + +#ifdef __sgi__ +#include +#endif + + +// +// OSThread.cp +// +void* OSThread::sMainThreadData = NULL; + +#ifdef __Win32__ +DWORD OSThread::sThreadStorageIndex = 0; +#elif __PTHREADS__ +pthread_key_t OSThread::gMainKey = 0; +#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING +pthread_attr_t OSThread::sThreadAttr; +#endif +#endif + +char OSThread::sUser[128]= ""; +char OSThread::sGroup[128]= ""; + +#if __linux__ || __MacOSX__ +Bool16 OSThread::sWrapSleep = true; +#endif + +void OSThread::Initialize() +{ + + +#ifdef __Win32__ + sThreadStorageIndex = ::TlsAlloc(); + Assert(sThreadStorageIndex >= 0); +#elif __PTHREADS__ + pthread_key_create(&OSThread::gMainKey, NULL); +#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING + + // + // Added for Solaris... + + pthread_attr_init(&sThreadAttr); + /* Indicate we want system scheduling contention scope. This + thread is permanently "bound" to an LWP */ + pthread_attr_setscope(&sThreadAttr, PTHREAD_SCOPE_SYSTEM); +#endif + +#endif +} + +OSThread::OSThread() +: fStopRequested(false), + fJoined(false), + fThreadData(NULL) +{ +} + +OSThread::~OSThread() +{ + this->StopAndWaitForThread(); +} + + + +void OSThread::Start() +{ +#ifdef __Win32__ + unsigned int theId = 0; // We don't care about the identifier + fThreadID = (HANDLE)_beginthreadex( NULL, // Inherit security + 0, // Inherit stack size + _Entry, // Entry function + (void*)this, // Entry arg + 0, // Begin executing immediately + &theId ); + Assert(fThreadID != NULL); +#elif __PTHREADS__ + pthread_attr_t* theAttrP; +#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING + //theAttrP = &sThreadAttr; + theAttrP = 0; +#else + theAttrP = NULL; +#endif + int err = pthread_create((pthread_t*)&fThreadID, theAttrP, _Entry, (void*)this); + Assert(err == 0); +#else + fThreadID = (UInt32)cthread_fork((cthread_fn_t)_Entry, (any_t)this); +#endif +} + +void OSThread::StopAndWaitForThread() +{ + fStopRequested = true; + if (!fJoined) + Join(); +} + +void OSThread::Join() +{ + // What we're trying to do is allow the thread we want to delete to complete + // running. So we wait for it to stop. + Assert(!fJoined); + fJoined = true; +#ifdef __Win32__ + DWORD theErr = ::WaitForSingleObject(fThreadID, INFINITE); + Assert(theErr == WAIT_OBJECT_0); +#elif __PTHREADS__ + void *retVal; + pthread_join((pthread_t)fThreadID, &retVal); +#else + cthread_join((cthread_t)fThreadID); +#endif +} + +void OSThread::ThreadYield() +{ + // on platforms who's threading is not pre-emptive yield + // to another thread +#if THREADING_IS_COOPERATIVE + #if __PTHREADS__ + #if USE_THR_YIELD + thr_yield(); + #else + sched_yield(); + #endif + #endif +#endif +} + +#include "OS.h" +void OSThread::Sleep(UInt32 inMsec) +{ + +#ifdef __Win32__ + ::Sleep(inMsec); +#elif __linux__ || __MacOSX__ + + if (inMsec == 0) + return; + + SInt64 startTime = OS::Milliseconds(); + SInt64 timeLeft = inMsec; + SInt64 timeSlept = 0; + UInt64 utimeLeft = 0; + + do { // loop in case we leave the sleep early + //qtss_printf("OSThread::Sleep time slept= %qd request sleep=%qd\n",timeSlept, timeLeft); + timeLeft = inMsec - timeSlept; + if (timeLeft < 1) + break; + + utimeLeft = timeLeft * 1000; + //qtss_printf("OSThread::Sleep usleep=%qd\n", utimeLeft); + ::usleep(utimeLeft); + + timeSlept = (OS::Milliseconds() - startTime); + if (timeSlept < 0) // system time set backwards + break; + + } while (timeSlept < inMsec); + + //qtss_printf("total sleep = %qd request sleep=%"_U32BITARG_"\n", timeSlept,inMsec); + +#elif defined(__osf__) || defined(__hpux__) + if (inMsec < 1000) + ::usleep(inMsec * 1000); // useconds must be less than 1,000,000 + else + ::sleep((inMsec + 500) / 1000); // round to the nearest whole second +#elif defined(__sgi__) + struct timespec ts; + + ts.tv_sec = 0; + ts.tv_nsec = inMsec * 1000000; + + nanosleep(&ts, 0); +#else + ::usleep(inMsec * 1000); +#endif +} + +#ifdef __Win32__ +unsigned int WINAPI OSThread::_Entry(LPVOID inThread) +#else +void* OSThread::_Entry(void *inThread) //static +#endif +{ + OSThread* theThread = (OSThread*)inThread; +#ifdef __Win32__ + BOOL theErr = ::TlsSetValue(sThreadStorageIndex, theThread); + Assert(theErr == TRUE); +#elif __PTHREADS__ + theThread->fThreadID = (pthread_t)pthread_self(); + pthread_setspecific(OSThread::gMainKey, theThread); +#else + theThread->fThreadID = (UInt32)cthread_self(); + cthread_set_data(cthread_self(), (any_t)theThread); +#endif + theThread->SwitchPersonality(); + // + // Run the thread + theThread->Entry(); + return NULL; +} + + +Bool16 OSThread::SwitchPersonality() +{ +#if __linux__ + if (::strlen(sGroup) > 0) + { + struct group* gr = ::getgrnam(sGroup); + if (gr == NULL || ::setgid(gr->gr_gid) == -1) + { + //qtss_printf("thread %"_U32BITARG_" setgid to group=%s FAILED \n", (UInt32) this, sGroup); + return false; + } + + //qtss_printf("thread %"_U32BITARG_" setgid to group=%s \n", (UInt32) this, sGroup); + } + + + if (::strlen(sUser) > 0) + { + struct passwd* pw = ::getpwnam(sUser); + if (pw == NULL || ::setuid(pw->pw_uid) == -1) + { + //qtss_printf("thread %"_U32BITARG_" setuid to user=%s FAILED \n", (UInt32) this, sUser); + return false; + } + + //qtss_printf("thread %"_U32BITARG_" setuid to user=%s \n", (UInt32) this, sUser); + } +#endif + + return true; +} + + +OSThread* OSThread::GetCurrent() +{ +#ifdef __Win32__ + return (OSThread *)::TlsGetValue(sThreadStorageIndex); +#elif __PTHREADS__ + return (OSThread *)pthread_getspecific(OSThread::gMainKey); +#else + return (OSThread*)cthread_data(cthread_self()); +#endif +} + +#ifdef __Win32__ +int OSThread::GetErrno() +{ + int winErr = ::GetLastError(); + + + // Convert to a POSIX errorcode. The *major* assumption is that + // the meaning of these codes is 1-1 and each Winsock, etc, etc + // function is equivalent in errors to the POSIX standard. This is + // a big assumption, but the server only checks for a small subset of + // the real errors, on only a small number of functions, so this is probably ok. + switch (winErr) + { + + case ERROR_FILE_NOT_FOUND: return ENOENT; + + case ERROR_PATH_NOT_FOUND: return ENOENT; + + + + + case WSAEINTR: return EINTR; + case WSAENETRESET: return EPIPE; + case WSAENOTCONN: return ENOTCONN; + case WSAEWOULDBLOCK:return EAGAIN; + case WSAECONNRESET: return EPIPE; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEMFILE: return EMFILE; + case WSAEINPROGRESS:return EINPROGRESS; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAECONNABORTED: return EPIPE; + case 0: return 0; + + default: return ENOTCONN; + } +} +#endif diff --git a/CommonUtilitiesLib/OSThread.h b/CommonUtilitiesLib/OSThread.h new file mode 100644 index 0000000..2b3e900 --- /dev/null +++ b/CommonUtilitiesLib/OSThread.h @@ -0,0 +1,172 @@ +/* + * + * @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: OSThread.h + + Contains: A thread abstraction + + + +*/ + +// OSThread.h +#ifndef __OSTHREAD__ +#define __OSTHREAD__ + +#ifndef __Win32__ +#if __PTHREADS__ +#if __solaris__ || __sgi__ || __hpux__ + #include +#else + #include +#endif + #include +#else + #include + #include +#endif +#endif + +#include "OSHeaders.h" +#include "DateTranslator.h" + +class OSThread +{ + +public: + // + // Call before calling any other OSThread function + static void Initialize(); + + OSThread(); + virtual ~OSThread(); + + // + // Derived classes must implement their own entry function + virtual void Entry() = 0; + void Start(); + + static void ThreadYield(); + static void Sleep(UInt32 inMsec); + + void Join(); + void SendStopRequest() { fStopRequested = true; } + Bool16 IsStopRequested() { return fStopRequested; } + void StopAndWaitForThread(); + + void* GetThreadData() { return fThreadData; } + void SetThreadData(void* inThreadData) { fThreadData = inThreadData; } + + // As a convienence to higher levels, each thread has its own date buffer + DateBuffer* GetDateBuffer() { return &fDateBuffer; } + + static void* GetMainThreadData() { return sMainThreadData; } + static void SetMainThreadData(void* inData) { sMainThreadData = inData; } + static void SetUser(char *user) {::strncpy(sUser,user, sizeof(sUser) -1); sUser[sizeof(sUser) -1]=0;} + static void SetGroup(char *group) {::strncpy(sGroup,group, sizeof(sGroup) -1); sGroup[sizeof(sGroup) -1]=0;} + static void SetPersonality(char *user, char* group) { SetUser(user); SetGroup(group); }; + Bool16 SwitchPersonality(); +#if DEBUG + UInt32 GetNumLocksHeld() { return 0; } + void IncrementLocksHeld() {} + void DecrementLocksHeld() {} +#endif + +#if __linux__ || __MacOSX__ + static void WrapSleep( Bool16 wrapSleep) {sWrapSleep = wrapSleep; } +#endif + +#ifdef __Win32__ + static int GetErrno(); + static DWORD GetCurrentThreadID() { return ::GetCurrentThreadId(); } +#elif __PTHREADS__ + static int GetErrno() { return errno; } + static pthread_t GetCurrentThreadID() { return ::pthread_self(); } +#else + static int GetErrno() { return cthread_errno(); } + static cthread_t GetCurrentThreadID() { return cthread_self(); } +#endif + + static OSThread* GetCurrent(); + +private: + +#ifdef __Win32__ + static DWORD sThreadStorageIndex; +#elif __PTHREADS__ + static pthread_key_t gMainKey; +#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING + static pthread_attr_t sThreadAttr; +#endif +#endif + + static char sUser[128]; + static char sGroup[128]; + + + Bool16 fStopRequested; + Bool16 fJoined; + +#ifdef __Win32__ + HANDLE fThreadID; +#elif __PTHREADS__ + pthread_t fThreadID; +#else + UInt32 fThreadID; +#endif + void* fThreadData; + DateBuffer fDateBuffer; + + static void* sMainThreadData; +#ifdef __Win32__ + static unsigned int WINAPI _Entry(LPVOID inThread); +#else + static void* _Entry(void* inThread); +#endif + +#if __linux__ || __MacOSX__ + static Bool16 sWrapSleep; +#endif + + +}; + +class OSThreadDataSetter +{ + public: + + OSThreadDataSetter(void* inInitialValue, void* inFinalValue) : fFinalValue(inFinalValue) + { OSThread::GetCurrent()->SetThreadData(inInitialValue); } + + ~OSThreadDataSetter() { OSThread::GetCurrent()->SetThreadData(fFinalValue); } + + private: + + void* fFinalValue; +}; + + +#endif + diff --git a/CommonUtilitiesLib/PLDoubleLinkedList.h b/CommonUtilitiesLib/PLDoubleLinkedList.h new file mode 100644 index 0000000..dae711f --- /dev/null +++ b/CommonUtilitiesLib/PLDoubleLinkedList.h @@ -0,0 +1,329 @@ +/* + * + * @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@ + * + */ + +#ifndef __pldoublelinkedlist__ +#define __pldoublelinkedlist__ + +#include +#include "SafeStdLib.h" +#include + +#include "OSHeaders.h" +#include "MyAssert.h" + +#ifndef __PLDoubleLinkedListDEBUG__ +#define __PLDoubleLinkedListDEBUG__ 0 +#endif + +template class PLDoubleLinkedList; + +template class PLDoubleLinkedListNode +{ + friend class PLDoubleLinkedList ; + + public: + PLDoubleLinkedListNode( S* element ) + { + // the node takes ownership of "element" + fElement = element; + fNext = NULL; + fPrev = NULL; + } + virtual ~PLDoubleLinkedListNode() + { + #if __PLDoubleLinkedListDEBUG__ + Assert( fPrev == NULL && fNext == NULL ); + #endif + + delete fElement; + } + + S* fElement; + + protected: + PLDoubleLinkedListNode *fNext; + PLDoubleLinkedListNode *fPrev; +}; + + + +template class PLDoubleLinkedList +{ + + public: + PLDoubleLinkedList() + { + fHead = NULL; + fTail = NULL; + fNumNodes = 0; + } + + virtual ~PLDoubleLinkedList() + { + ClearList(); + } + + #if __PLDoubleLinkedListDEBUG__ + + void ValidateLinks() + { + PLDoubleLinkedListNode *nextNode; + + Assert( fHead == NULL || fHead->fPrev == NULL ); + Assert( fTail == NULL || fTail->fNext == NULL ); + + + if ( fTail == fHead && fTail != NULL ) + { + Assert( fTail->fPrev == NULL && fTail->fNext == NULL ); + } + + if ( fHead ) + { + Assert( fTail != NULL ); + } + + if ( fTail ) + { + Assert( fHead != NULL ); + } + + + if ( fTail && fTail->fPrev ) + Assert( fTail->fPrev->fNext == fTail ); + + if ( fHead && fHead->fNext ) + Assert( fHead->fNext->fPrev == fHead ); + + + + nextNode = fHead; + + while ( nextNode ) + { + Assert( fHead == nextNode || nextNode->fPrev->fNext == nextNode ); + Assert( fTail == nextNode || nextNode->fNext->fPrev == nextNode ); + + if ( !nextNode->fNext ) + Assert( fTail == nextNode ); + + nextNode = nextNode->fNext; + } + + nextNode = fTail; + + while ( nextNode ) + { + Assert( fHead == nextNode || nextNode->fPrev->fNext == nextNode ); + Assert( fTail == nextNode || nextNode->fNext->fPrev == nextNode ); + + if ( !nextNode->fPrev ) + Assert( fHead == nextNode ); + + nextNode = nextNode->fPrev; + } + } + #endif // __PLDoubleLinkedListDEBUG__ + + PLDoubleLinkedListNode * GetFirst() { return fHead; }; + + void AddNodeToTail(PLDoubleLinkedListNode *node) + { + + + #if __PLDoubleLinkedListDEBUG__ + // must not be associated with another list + Assert( node->fPrev == NULL && node->fNext == NULL ); + #endif + + + if ( fTail ) + fTail->fNext = node; + + + node->fPrev = fTail; + node->fNext = NULL; + + fTail = node; + + if ( !fHead ) + fHead = node; + + fNumNodes++; + + #if __PLDoubleLinkedListDEBUG__ + ValidateLinks(); + #endif + } + + void AddNode(PLDoubleLinkedListNode *node ) + { + #if __PLDoubleLinkedListDEBUG__ + // must not be associated with another list + Assert( node->fPrev == NULL && node->fNext == NULL ); + #endif + + if ( fHead ) + fHead->fPrev = node; + + + node->fPrev = NULL; + node->fNext = fHead; + + fHead = node; + + if ( !fTail ) + fTail = node; + + fNumNodes++; + + #if __PLDoubleLinkedListDEBUG__ + ValidateLinks(); + #endif + + } + + void RemoveNode(PLDoubleLinkedListNode *node) + { + + #if __PLDoubleLinkedListDEBUG__ + // must be associated with this list + Assert( fHead == node || node->fPrev->fNext == node ); + + // must be associated with this list + Assert( fTail == node || node->fNext->fPrev == node ); + #endif + + + if ( fHead == node) + fHead = node->fNext; + else + node->fPrev->fNext = node->fNext; + + if ( fTail == node) + fTail = node->fPrev; + else + node->fNext->fPrev = node->fPrev; + + + node->fPrev = NULL; + node->fNext = NULL; + + + + fNumNodes--; + + #if __PLDoubleLinkedListDEBUG__ + ValidateLinks(); + #endif + + } + + PLDoubleLinkedListNode *ForEachUntil( bool (*doFunc)( PLDoubleLinkedListNode *node, void *userData), void *userData ) + { + PLDoubleLinkedListNode *nextElement, *curElement; + bool stopIteration = false; + + curElement = fHead; + + while ( curElement && !stopIteration ) + { + nextElement = curElement->fNext; + + stopIteration = (*doFunc)( curElement, userData); + + if ( !stopIteration ) + curElement = nextElement; + } + + return curElement; + } + + void ForEach( void (*doFunc)( PLDoubleLinkedListNode *node, void *userData), void *userData ) + { + PLDoubleLinkedListNode *nextElement, *curElement; + + curElement = fHead; + + while ( curElement ) + { + nextElement = curElement->fNext; + + (*doFunc)( curElement, userData); + + curElement = nextElement; + } + + } + + + void ClearList() + { + ForEach( DoClearList, this ); + } + + + PLDoubleLinkedListNode *GetNthNode( int nodeIndex ) + + { + return ForEachUntil( CompareIndexToZero, &nodeIndex ); + } + UInt32 GetNumNodes() { return fNumNodes; } + + protected: + static bool CompareIndexToZero( PLDoubleLinkedListNode *, void * nodeIndex ) // (node, nodeIndex) + { + int val = *(int*)nodeIndex; + + if ( val == 0 ) + return true; + + *(int*)nodeIndex = val -1; + + return false; + } + + static void DoClearList( PLDoubleLinkedListNode *node, void * listPtr ) + { + PLDoubleLinkedList *list = (PLDoubleLinkedList *)listPtr; + + list->RemoveNode( node ); + + delete node; + + } + PLDoubleLinkedListNode *fHead; + PLDoubleLinkedListNode *fTail; + UInt32 fNumNodes; + + + +}; + +#endif + + + + diff --git a/CommonUtilitiesLib/PathDelimiter.h b/CommonUtilitiesLib/PathDelimiter.h new file mode 100644 index 0000000..9c5a719 --- /dev/null +++ b/CommonUtilitiesLib/PathDelimiter.h @@ -0,0 +1,41 @@ +/* + * + * @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@ + * + */ + +#ifndef __pathdelimiter__ +#define __pathdelimiter__ + + +#if __MACOS__ + #define kPathDelimiterString ":" + #define kPathDelimiterChar ':' + #define kPartialPathBeginsWithDelimiter 1 +#else + #define kPathDelimiterString "/" + #define kPathDelimiterChar '/' + #define kPartialPathBeginsWithDelimiter 0 +#endif + + +#endif diff --git a/CommonUtilitiesLib/QueryParamList.cpp b/CommonUtilitiesLib/QueryParamList.cpp new file mode 100644 index 0000000..dd77da0 --- /dev/null +++ b/CommonUtilitiesLib/QueryParamList.cpp @@ -0,0 +1,264 @@ + +/* + * + * @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@ + * + */ + +#include "QueryParamList.h" + +#include "StringParser.h" +#include "OSMemory.h" + +#include +#include +#include "SafeStdLib.h" +QueryParamList::QueryParamList( StrPtrLen* querySPL ) +{ + // ctor from StrPtrLen + fNameValueQueryParamlist = NEW PLDoubleLinkedList; + + this->BulidList( querySPL ); +} + + +QueryParamList::QueryParamList( char* queryString ) +{ + // ctor from char* + StrPtrLen querySPL( queryString ); + + fNameValueQueryParamlist = NEW PLDoubleLinkedList; + + this->BulidList( &querySPL ); +} + + +void QueryParamList::BulidList( StrPtrLen* querySPL ) +{ + // parse the string and build the name/value list from the tokens. + // the string is a 'form' encoded query string ( see rfc - 1808 ) + + StringParser queryParser( querySPL ); + + while ( queryParser.GetDataRemaining() > 0 ) + { + StrPtrLen theCGIParamName; + StrPtrLen theCGIParamValue; + + queryParser.ConsumeUntil(&theCGIParamName, '='); // leaves "=..." in cgiParser, puts item keywd in theCGIParamName + + if ( queryParser.GetDataRemaining() > 1 ) + { + queryParser.ConsumeLength(&theCGIParamValue, 1 ); // the '=' + + queryParser.ConsumeUntil(&theCGIParamValue, '&'); // our value will end by here... + + AddNameValuePairToList( theCGIParamName.GetAsCString(), theCGIParamValue.GetAsCString() ); + + queryParser.ConsumeLength(&theCGIParamValue, 1 ); // the '=' + + } + } +} + + +static void PrintNameAndValue( PLDoubleLinkedListNode *node, void *userData ) +{ + // used by QueryParamList::PrintAll + QueryParamListElement* nvPair = node->fElement; + + qtss_printf( "qpl: %s, name %s, val %s\n", (char*)userData, nvPair->mName, nvPair->mValue ); +} + + +void QueryParamList::PrintAll( char *idString ) +{ + // print name and value of each item in the list, print each pair preceded by "idString" + fNameValueQueryParamlist->ForEach( PrintNameAndValue, idString ); +} + + +static bool CompareStrToName( PLDoubleLinkedListNode *node, void *userData ) +{ + /* + make a case insenstitive comparison between "node" name and the userData + + used by QueryParamList::DoFindCGIValueForParam + */ + + QueryParamListElement* nvPair = node->fElement; + StrPtrLen name( nvPair->mName ); + + if ( name.EqualIgnoreCase( (char*)userData, strlen( (char*)userData ) ) ) + return true; + + return false; +} + + +const char *QueryParamList::DoFindCGIValueForParam( char *name ) +{ + /* + return the first value where the paramter name matches "name" + use case insenstitive comparison + + */ + PLDoubleLinkedListNode* node; + + node = fNameValueQueryParamlist->ForEachUntil( CompareStrToName, name ); + + if ( node != NULL ) + { + QueryParamListElement* nvPair = (QueryParamListElement*)node->fElement; + + return nvPair->mValue; + } + + return NULL; + +} + + +void QueryParamList::AddNameValuePairToList( char* name, char* value ) +{ + // add the name/value pair to the by creating the holder struct + // then adding that as the element in the linked list + + PLDoubleLinkedListNode* nvNode; + QueryParamListElement* nvPair; + + this->DecodeArg( name ); + this->DecodeArg( value ); + + nvPair = NEW QueryParamListElement( name, value ); + + + // create a node to hold the pair + nvNode = NEW PLDoubleLinkedListNode ( nvPair ); + + // add it to the list + fNameValueQueryParamlist->AddNode( nvNode ); +} + + + +void QueryParamList::DecodeArg( char *ioCodedPtr ) +{ + // perform In-Place &hex and + to space decoding of the parameter + // on input, ioCodedPtr mau contain encoded text, on exit ioCodedPtr will be plain text + // on % decoding errors, the + + if ( !ioCodedPtr ) + return; + + char* destPtr; + char* curChar; + short lineState = kLastWasText; + char hexBuff[32]; + + destPtr = curChar = ioCodedPtr; + + while( *curChar ) + { + switch( lineState ) + { + case kRcvHexDigitOne: + if ( IsHex( *curChar ) ) + { + hexBuff[3] = *curChar; + hexBuff[4] = 0; + + *destPtr++ = (char)::strtoul( hexBuff, NULL, 0 ); + } + else + { // not a valid encoding + *destPtr++ = '%'; // put back the pct sign + *destPtr++ = hexBuff[2]; // put back the first digit too. + *destPtr++ = *curChar; // and this one! + + } + lineState = kLastWasText; + break; + + case kLastWasText: + if ( *curChar == '%' ) + lineState = kPctEscape; + else + { + if ( *curChar == '+' ) + *destPtr++ = ' '; + else + *destPtr++ = *curChar; + } + break; + + case kPctEscape: + if ( *curChar == '%' ) + { + *destPtr++ = '%'; + lineState = kLastWasText; + } + else + { + if ( IsHex( *curChar ) ) + { + hexBuff[0] = '0'; + hexBuff[1] = 'x'; + hexBuff[2] = *curChar; + lineState = kRcvHexDigitOne; + } + else + { + *destPtr++ = '%'; // put back the pct sign + *destPtr++ = *curChar; // and this one! + lineState = kLastWasText; + } + } + break; + + + } + + curChar++; + } + + *destPtr = *curChar; +} + +Bool16 QueryParamList::IsHex( char c ) +{ + // return true if char c is a valid hexidecimal digit + // false otherwise. + + if ( c >= '0' && c <= '9' ) + return true; + + if ( c >= 'A' && c <= 'F' ) + return true; + + if ( c >= 'a' && c <= 'f' ) + return true; + + return false; +} + + diff --git a/CommonUtilitiesLib/QueryParamList.h b/CommonUtilitiesLib/QueryParamList.h new file mode 100644 index 0000000..7ae52f5 --- /dev/null +++ b/CommonUtilitiesLib/QueryParamList.h @@ -0,0 +1,100 @@ +/* + * + * @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: QueryParamList.cpp + + Contains: Implementation of QueryParamList class + + The QueryParamList class is used to parse and build a searchable list + of name/value pairs from a RFC1808 QueryString that has been encoded + using the html 'form encoding' rules. + +*/ + +#ifndef __query_param_list__ +#define __query_param_list__ + + +//#include "QueryParamList.h" + +#include "PLDoubleLinkedList.h" +#include "StrPtrLen.h" + + +class QueryParamListElement { + + public: + QueryParamListElement( char* name, char* value ) + { + mName = name; + mValue = value; + + } + + virtual ~QueryParamListElement() + { + delete [] mName; + delete [] mValue; + } + + char *mName; + char *mValue; + +}; + + +class QueryParamList +{ + public: + QueryParamList( char* queryString ); + QueryParamList( StrPtrLen* querySPL ); + ~QueryParamList() { delete fNameValueQueryParamlist; } + + void AddNameValuePairToList( char* name, char* value ); + const char *DoFindCGIValueForParam( char *name ); + void PrintAll( char *idString ); + + protected: + void BulidList( StrPtrLen* querySPL ); + void DecodeArg( char *ioCodedPtr ); + enum { + // escaping states + kLastWasText + , kPctEscape + , kRcvHexDigitOne + }; + + Bool16 IsHex( char c ); + + PLDoubleLinkedList *fNameValueQueryParamlist; + + +}; + + + + + +#endif diff --git a/CommonUtilitiesLib/ResizeableStringFormatter.cpp b/CommonUtilitiesLib/ResizeableStringFormatter.cpp new file mode 100644 index 0000000..c205d4e --- /dev/null +++ b/CommonUtilitiesLib/ResizeableStringFormatter.cpp @@ -0,0 +1,54 @@ +/* + * + * @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: ResizeableStringFormatter.cpp + + Contains: Implements object defined in ResizeableStringFormatter.h + + +*/ + +#include "ResizeableStringFormatter.h" +#include "OSMemory.h" + +Bool16 ResizeableStringFormatter::BufferIsFull(char* inBuffer, UInt32 inBufferLen) +{ + //allocate a buffer twice as big as the old one, and copy over the contents + UInt32 theNewBufferSize = this->GetTotalBufferSize() * 2; + if (theNewBufferSize == 0) + theNewBufferSize = 64; + + char* theNewBuffer = NEW char[theNewBufferSize]; + ::memcpy(theNewBuffer, inBuffer, inBufferLen); + + //if the old buffer was dynamically allocated also, we'd better delete it. + if (inBuffer != fOriginalBuffer) + delete [] inBuffer; + + fStartPut = theNewBuffer; + fCurrentPut = theNewBuffer + inBufferLen; + fEndPut = theNewBuffer + theNewBufferSize; + return true; +} diff --git a/CommonUtilitiesLib/ResizeableStringFormatter.h b/CommonUtilitiesLib/ResizeableStringFormatter.h new file mode 100644 index 0000000..80f3e54 --- /dev/null +++ b/CommonUtilitiesLib/ResizeableStringFormatter.h @@ -0,0 +1,63 @@ +/* + * + * @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: ResizeableStringFormatter.h + + Contains: Derived from StringFormatter, this transparently grows the + output buffer if the original buffer is too small to hold all + the data being placed in it + + + +*/ + +#ifndef __RESIZEABLE_STRING_FORMATTER_H__ +#define __RESIZEABLE_STRING_FORMATTER_H__ + +#include "StringFormatter.h" + +class ResizeableStringFormatter : public StringFormatter +{ + public: + // Pass in inBuffer=NULL and inBufSize=0 to dynamically allocate the initial buffer. + ResizeableStringFormatter(char* inBuffer = NULL, UInt32 inBufSize = 0) + : StringFormatter(inBuffer, inBufSize), fOriginalBuffer(inBuffer) {} + + //If we've been forced to increase the buffer size, fStartPut WILL be a dynamically allocated + //buffer, and it WON'T be equal to fOriginalBuffer (obviously). + virtual ~ResizeableStringFormatter() { if (fStartPut != fOriginalBuffer) delete [] fStartPut; } + + private: + + // This function will get called by StringFormatter if the current + // output buffer is full. This object allocates a buffer that's twice + // as big as the old one. + virtual Bool16 BufferIsFull(char* inBuffer, UInt32 inBufferLen); + + char* fOriginalBuffer; + +}; + +#endif //__RESIZEABLE_STRING_FORMATTER_H__ diff --git a/CommonUtilitiesLib/SDPUtils.cpp b/CommonUtilitiesLib/SDPUtils.cpp new file mode 100644 index 0000000..d03028d --- /dev/null +++ b/CommonUtilitiesLib/SDPUtils.cpp @@ -0,0 +1,370 @@ +/* + * + * @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@ + * + */ + +#include "SDPUtils.h" + +#include "OS.h" +#include "StrPtrLen.h" +#include "ResizeableStringFormatter.h" +#include "StringParser.h" +#include "ResizeableStringFormatter.h" +#include "StringParser.h" +#include "OSMemory.h" + + +SInt32 SDPContainer::AddHeaderLine (StrPtrLen *theLinePtr) +{ + Assert(theLinePtr); + UInt32 thisLine = fNumUsedLines; + Assert(fNumUsedLines < fNumSDPLines); + fSDPLineArray[thisLine].Set(theLinePtr->Ptr, theLinePtr->Len); + fNumUsedLines++; + if (fNumUsedLines == fNumSDPLines) + { + SDPLine *tempSDPLineArray = NEW SDPLine[fNumSDPLines * 2]; + for (int i = 0; i < fNumSDPLines; i++) + { + tempSDPLineArray[i].Set(fSDPLineArray[i].Ptr,fSDPLineArray[i].Len); + } + delete [] fSDPLineArray; + fSDPLineArray = tempSDPLineArray; + fNumSDPLines = (fNumUsedLines * 2); + } + + return thisLine; +} + +SInt32 SDPContainer::FindHeaderLineType(char id, SInt32 start) +{ + SInt32 theIndex = -1; + + if (start >= fNumUsedLines || start < 0) + return -1; + + for (int i = start; i < fNumUsedLines; i++) + { if (fSDPLineArray[i].GetHeaderType() == id) + { theIndex = i; + fCurrentLine = theIndex; + break; + } + } + + return theIndex; +} + +SDPLine* SDPContainer::GetNextLine() +{ + if (fCurrentLine < fNumUsedLines) + { fCurrentLine ++; + return &fSDPLineArray[fCurrentLine]; + } + + return NULL; + +} + +SDPLine* SDPContainer::GetLine(SInt32 lineIndex) +{ + + if (lineIndex > -1 && lineIndex < fNumUsedLines) + { return &fSDPLineArray[lineIndex]; + } + + return NULL; +} + +void SDPContainer::SetLine(SInt32 index) +{ + if (index > -1 && index < fNumUsedLines) + { fCurrentLine = index; + } + else + Assert(0); + +} + +void SDPContainer::Parse() +{ + char* validChars = "vosiuepcbtrzkam"; + char nameValueSeparator = '='; + + Bool16 valid = true; + + StringParser sdpParser(&fSDPBuffer); + StrPtrLen line; + StrPtrLen fieldName; + StrPtrLen space; + Bool16 foundLine = false; + + while ( sdpParser.GetDataRemaining() != 0 ) + { + foundLine = sdpParser.GetThruEOL(&line); // Read each line + if (!foundLine) + { break; + } + StringParser lineParser(&line); + + lineParser.ConsumeWhitespace();//skip over leading whitespace + if (lineParser.GetDataRemaining() == 0) // must be an empty line + continue; + + char firstChar = lineParser.PeekFast(); + if (firstChar == '\0') + continue; //skip over blank lines + + fFieldStr[ (UInt8)firstChar] = firstChar; + switch (firstChar) + { + case 'v': fReqLines |= kV; + break; + + case 's': fReqLines |= kS ; + break; + + case 't': fReqLines |= kT ; + break; + + case 'o': fReqLines |= kO ; + break; + + } + + lineParser.ConsumeUntil(&fieldName, nameValueSeparator); + if ((fieldName.Len != 1) || (::strchr(validChars, fieldName.Ptr[0]) == NULL)) + { + valid = false; // line doesn't begin with one of the valid characters followed by an "=" + break; + } + + if (!lineParser.Expect(nameValueSeparator)) + { + valid = false; // line doesn't have the "=" after the first char + break; + } + + lineParser.ConsumeUntil(&space, StringParser::sWhitespaceMask); + + if (space.Len != 0) + { + valid = false; // line has whitespace after the "=" + break; + } + AddHeaderLine(&line); + } + + if (fNumUsedLines == 0) // didn't add any lines + { valid = false; + } + fValid = valid; + +} + +void SDPContainer::Initialize() +{ + fCurrentLine = 0; + fNumUsedLines = 0; + delete [] fSDPLineArray; + fSDPLineArray = NEW SDPLine[fNumSDPLines]; + fValid = false; + fReqLines = 0; + ::memset(fFieldStr, sizeof(fFieldStr), 0); +} + +Bool16 SDPContainer::SetSDPBuffer(char *sdpBuffer) +{ + + Initialize(); + if (sdpBuffer != NULL) + { fSDPBuffer.Set(sdpBuffer); + Parse(); + } + + return IsSDPBufferValid(); +} + +Bool16 SDPContainer::SetSDPBuffer(StrPtrLen *sdpBufferPtr) +{ + Initialize(); + if (sdpBufferPtr != NULL) + { fSDPBuffer.Set(sdpBufferPtr->Ptr, sdpBufferPtr->Len); + Parse(); + } + + return IsSDPBufferValid(); +} + + +void SDPContainer::PrintLine(SInt32 lineIndex) +{ + StrPtrLen *printLinePtr = GetLine(lineIndex); + if (printLinePtr) + { printLinePtr->PrintStr(); + qtss_printf("\n"); + } + +} + +void SDPContainer::PrintAllLines() +{ + if (fNumUsedLines > 0) + { for (int i = 0; i < fNumUsedLines; i++) + PrintLine(i); + } + else + qtss_printf("SDPContainer::PrintAllLines no lines\n"); +} + +Bool16 SDPLineSorter::ValidateSessionHeader(StrPtrLen *theHeaderLinePtr) +{ + if (NULL == theHeaderLinePtr || 0 == theHeaderLinePtr->Len || NULL== theHeaderLinePtr->Ptr) + return false; + + // check for a duplicate range line. + StrPtrLen currentSessionHeader(fSDPSessionHeaders.GetBufPtr(), fSDPSessionHeaders.GetBytesWritten()); + if ( 'a' == theHeaderLinePtr->Ptr[0] && theHeaderLinePtr->FindString("a=range") && currentSessionHeader.FindString("a=range")) + { + return false; + } + + return true; + +} + + +char SDPLineSorter::sSessionOrderedLines[] = "vosiuepcbtrzka"; // chars are order dependent: declared by rfc 2327 +char SDPLineSorter::sessionSingleLines[] = "vtosiuepcbzk"; // return only 1 of each of these session field types +StrPtrLen SDPLineSorter::sEOL("\r\n"); +StrPtrLen SDPLineSorter::sMaxBandwidthTag("b=AS:"); + +SDPLineSorter::SDPLineSorter(SDPContainer *rawSDPContainerPtr, Float32 adjustMediaBandwidthPercent, SDPContainer *insertMediaLinesArray) : fSessionLineCount(0),fSDPSessionHeaders(NULL,0), fSDPMediaHeaders(NULL,0) +{ + + Assert(rawSDPContainerPtr != NULL); + if (NULL == rawSDPContainerPtr) + return; + + StrPtrLen theSDPData(rawSDPContainerPtr->fSDPBuffer.Ptr,rawSDPContainerPtr->fSDPBuffer.Len); + StrPtrLen *theMediaStart = rawSDPContainerPtr->GetLine(rawSDPContainerPtr->FindHeaderLineType('m',0)); + if (theMediaStart && theMediaStart->Ptr && theSDPData.Ptr) + { + UInt32 mediaLen = theSDPData.Len - (UInt32) (theMediaStart->Ptr - theSDPData.Ptr); + char *mediaStartPtr= theMediaStart->Ptr; + fMediaHeaders.Set(mediaStartPtr,mediaLen); + StringParser sdpParser(&fMediaHeaders); + SDPLine sdpLine; + Bool16 foundLine = false; + Bool16 newMediaSection = true; + SDPLine *insertLine = NULL; + + while (sdpParser.GetDataRemaining() > 0) + { + foundLine = sdpParser.GetThruEOL(&sdpLine); + if (!foundLine) + { break; + } + if (sdpLine.GetHeaderType() == 'm' ) + newMediaSection = true; + + if (insertMediaLinesArray && newMediaSection && (sdpLine.GetHeaderType() == 'a') ) + { + newMediaSection = false; + for (insertLine = insertMediaLinesArray->GetLine(0); insertLine ; insertLine = insertMediaLinesArray->GetNextLine() ) + fSDPMediaHeaders.Put(*insertLine); + } + + if ( ( 'b' == sdpLine.GetHeaderType()) && (1.0 != adjustMediaBandwidthPercent) ) + { + StringParser bLineParser(&sdpLine); + bLineParser.ConsumeUntilDigit(); + UInt32 bandwidth = (UInt32) (.5 + (adjustMediaBandwidthPercent * (Float32) bLineParser.ConsumeInteger() ) ); + if (bandwidth < 1) + bandwidth = 1; + + char bandwidthStr[10]; + qtss_snprintf(bandwidthStr,sizeof(bandwidthStr) -1, "%"_U32BITARG_"", bandwidth); + bandwidthStr[sizeof(bandwidthStr) -1] = 0; + + fSDPMediaHeaders.Put(sMaxBandwidthTag); + fSDPMediaHeaders.Put(bandwidthStr); + } + else + fSDPMediaHeaders.Put(sdpLine); + + fSDPMediaHeaders.Put(SDPLineSorter::sEOL); + } + fMediaHeaders.Set(fSDPMediaHeaders.GetBufPtr(),fSDPMediaHeaders.GetBytesWritten()); + } + + fSessionLineCount = rawSDPContainerPtr->FindHeaderLineType('m',0); + if (fSessionLineCount < 0) // didn't find it use the whole buffer + { fSessionLineCount = rawSDPContainerPtr->GetNumLines(); + } + + for (SInt16 sessionLineIndex = 0; sessionLineIndex < fSessionLineCount; sessionLineIndex++) + fSessionSDPContainer.AddHeaderLine( (StrPtrLen *) rawSDPContainerPtr->GetLine(sessionLineIndex)); + + //qtss_printf("\nSession raw Lines:\n"); fSessionSDPContainer.PrintAllLines(); + + SInt16 numHeaderTypes = sizeof(SDPLineSorter::sSessionOrderedLines) -1; + Bool16 addLine = true; + for (SInt16 fieldTypeIndex = 0; fieldTypeIndex < numHeaderTypes; fieldTypeIndex ++) + { + SInt32 lineIndex = fSessionSDPContainer.FindHeaderLineType(SDPLineSorter::sSessionOrderedLines[fieldTypeIndex], 0); + StrPtrLen *theHeaderLinePtr = fSessionSDPContainer.GetLine(lineIndex); + + while (theHeaderLinePtr != NULL) + { + addLine = this->ValidateSessionHeader(theHeaderLinePtr); + if (addLine) + { + fSDPSessionHeaders.Put(*theHeaderLinePtr); + fSDPSessionHeaders.Put(SDPLineSorter::sEOL); + } + + if (NULL != ::strchr(sessionSingleLines, theHeaderLinePtr->Ptr[0] ) ) // allow 1 of this type: use first found + break; // move on to next line type + + lineIndex = fSessionSDPContainer.FindHeaderLineType(SDPLineSorter::sSessionOrderedLines[fieldTypeIndex], lineIndex + 1); + theHeaderLinePtr = fSessionSDPContainer.GetLine(lineIndex); + } + } + fSessionHeaders.Set(fSDPSessionHeaders.GetBufPtr(),fSDPSessionHeaders.GetBytesWritten()); + +} + +char* SDPLineSorter::GetSortedSDPCopy() +{ + char* fullbuffCopy = NEW char[fSessionHeaders.Len + fMediaHeaders.Len + 2]; + SInt32 buffPos = 0; + memcpy(&fullbuffCopy[buffPos], fSessionHeaders.Ptr,fSessionHeaders.Len); + buffPos += fSessionHeaders.Len; + memcpy(&fullbuffCopy[buffPos], fMediaHeaders.Ptr,fMediaHeaders.Len); + buffPos += fMediaHeaders.Len; + fullbuffCopy[buffPos] = 0; + + return fullbuffCopy; +} + + diff --git a/CommonUtilitiesLib/SDPUtils.h b/CommonUtilitiesLib/SDPUtils.h new file mode 100644 index 0000000..29c23cf --- /dev/null +++ b/CommonUtilitiesLib/SDPUtils.h @@ -0,0 +1,139 @@ +/* + * + * @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: SDPUtils.h + + Contains: Some static routines for dealing with SDPs + + + */ + +#ifndef __SDPUtilsH__ +#define __SDPUtilsH__ + +#include "OS.h" +#include "StrPtrLen.h" +#include "ResizeableStringFormatter.h" +#include "StringParser.h" +#include "OSMemory.h" + +class SDPLine : public StrPtrLen +{ +public: + SDPLine() {} + virtual ~SDPLine() {} + + char GetHeaderType() {if (Ptr && Len) return this->Ptr[0]; return 0;} +}; + +class SDPContainer +{ + enum { kBaseLines = 20, kLineTypeArraySize = 256}; + + enum { + kVPos = 0, + kSPos, + kTPos, + kOPos + }; + + enum { + kV = 1 << kVPos, + kS = 1 << kSPos, + kT = 1 << kTPos, + kO = 1 << kOPos, + kAllReq = kV | kS | kT | kO + }; + + +public: + + SDPContainer(UInt32 numStrPtrs = SDPContainer::kBaseLines) : + fNumSDPLines(numStrPtrs), + fSDPLineArray(NULL) + { + Initialize(); + } + + ~SDPContainer() {delete [] fSDPLineArray;} + void Initialize(); + SInt32 AddHeaderLine (StrPtrLen *theLinePtr); + SInt32 FindHeaderLineType(char id, SInt32 start); + SDPLine* GetNextLine(); + SDPLine* GetLine(SInt32 lineIndex); + void SetLine(SInt32 index); + void Parse(); + Bool16 SetSDPBuffer(char *sdpBuffer); + Bool16 SetSDPBuffer(StrPtrLen *sdpBufferPtr); + Bool16 IsSDPBufferValid() {return fValid;} + Bool16 HasReqLines() { return (Bool16) (fReqLines == kAllReq) ; } + Bool16 HasLineType( char lineType ) { return (Bool16) (lineType == fFieldStr[ (UInt8) lineType]) ; } + char* GetReqLinesArray; + void PrintLine(SInt32 lineIndex); + void PrintAllLines(); + SInt32 GetNumLines() { return fNumUsedLines; } + + SInt32 fCurrentLine; + SInt32 fNumSDPLines; + SInt32 fNumUsedLines; + SDPLine* fSDPLineArray; + Bool16 fValid; + StrPtrLen fSDPBuffer; + UInt16 fReqLines; + + char fFieldStr[kLineTypeArraySize]; // + char* fLineSearchTypeArray; +}; + + + +class SDPLineSorter { + +public: + SDPLineSorter(): fSessionLineCount(0),fSDPSessionHeaders(NULL,0), fSDPMediaHeaders(NULL,0) {}; + SDPLineSorter(SDPContainer *rawSDPContainerPtr, Float32 adjustMediaBandwidthPercent = 1.0, SDPContainer *insertMediaLinesArray = NULL); + + StrPtrLen* GetSessionHeaders() { return &fSessionHeaders; } + StrPtrLen* GetMediaHeaders() { return &fMediaHeaders; } + char* GetSortedSDPCopy(); + Bool16 ValidateSessionHeader(StrPtrLen *theHeaderLinePtr); + + + StrPtrLen fullSDPBuffSPL; + SInt32 fSessionLineCount; + SDPContainer fSessionSDPContainer; + ResizeableStringFormatter fSDPSessionHeaders; + ResizeableStringFormatter fSDPMediaHeaders; + StrPtrLen fSessionHeaders; + StrPtrLen fMediaHeaders; + static char sSessionOrderedLines[];// = "vosiuepcbtrzka"; // chars are order dependent: declared by rfc 2327 + static char sessionSingleLines[];// = "vosiuepcbzk"; // return only 1 of each of these session field types + static StrPtrLen sEOL; + static StrPtrLen sMaxBandwidthTag; +}; + + +#endif + diff --git a/CommonUtilitiesLib/SVector.h b/CommonUtilitiesLib/SVector.h new file mode 100644 index 0000000..6a74ada --- /dev/null +++ b/CommonUtilitiesLib/SVector.h @@ -0,0 +1,227 @@ +/* + * + * @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@ + * + */ +/* + * SVector.h + * + * An simple, non-exception safe implementation of vector + */ + +#ifndef _SVECTOR_H_ +#define _SVECTOR_H_ + +#include"OSHeaders.h" +#include"OSMemory.h" + +//T must be default and copy constructable; does not have to be assignable +template +class SVector +{ + public: + explicit SVector(UInt32 newCapacity = 0) + : fCapacity(0), fSize(0), fData(NULL) + { + reserve(newCapacity); + } + + SVector(const SVector &rhs) + : fCapacity(0), fSize(0), fData(NULL) + { + reserve(rhs.size()); + fSize = rhs.size(); + for(UInt32 i = 0; i < rhs.size(); ++i) + NEW(fData + i) T(rhs[i]); + } + + ~SVector() + { + clear(); + operator delete[](fData); + } + + SVector &operator=(const SVector &rhs) + { + clear(); + reserve(rhs.size()); + fSize = rhs.size(); + for(UInt32 i = 0; i < rhs.size(); ++i) + NEW(fData + i) T(rhs[i]); + return *this; + } + + T &operator[](UInt32 i) const { return fData[i]; } + T &front() const { return fData[0]; } + T &back() const { return fData[fSize - 1]; } + T *begin() const { return fData; } + T *end() const { return fData + fSize; } + + //Returns searchEnd if target is not found; uses == for equality comparison + UInt32 find(UInt32 searchStart, UInt32 searchEnd, const T &target) { return find(searchStart, searchEnd, target); } + + //Allows you to specify an equality comparison functor + template + UInt32 find(UInt32 searchStart, UInt32 searchEnd, const T &target, Eq eq = Eq()) + { + UInt32 i = searchStart; + for(; i < searchEnd; ++i) + if (eq(target, fData[i])) + break; + return i; + } + + //returns size() if the element is not found + UInt32 find(const T &target) { return find(target); } + + template + UInt32 find(const T &target, Eq eq = Eq()) { return find(0, size(), target, eq); } + + //Doubles the capacity as needed + void push_back(const T &newItem) + { + reserve(fSize + 1); + NEW (fData + fSize) T(newItem); + fSize++; + } + + void pop_back() { fData[--fSize].~T(); } + + void swap(SVector &rhs) + { + UInt32 tmpCapacity = fCapacity; + UInt32 tmpSize = fSize; + T *tmpData = fData; + fCapacity = rhs.fCapacity; + fSize = rhs.fSize; + fData = rhs.fData; + rhs.fCapacity = tmpCapacity; + rhs.fSize = tmpSize; + rhs.fData = tmpData; + } + + void insert(UInt32 position, const T &newItem) { insert(position, 1, newItem); } + + void insert(UInt32 position, UInt32 count, const T &newItem) + { + reserve(fSize + count); + for(UInt32 i = fSize; i > position; --i) + { + NEW (fData + i - 1 + count) T(fData[i - 1]); + fData[i - 1].~T(); + } + for(UInt32 i = position; i < position + count; ++i) + NEW (fData + i) T(newItem); + fSize += count; + } + + //can accept count of 0 - which results in a NOP + void erase(UInt32 position, UInt32 count = 1) + { + if(count == 0) + return; + for(UInt32 i = position; i < position + count; ++i) + fData[i].~T(); + for(UInt32 i = position + count; i < fSize; ++i) + { + NEW (fData + i - count) T(fData[i]); + fData[i].~T(); + } + fSize -= count; + } + + //Removes 1 element by swapping it with the last item. + void swap_erase(UInt32 position) + { + fData[position].~T(); + if (position < --fSize) + { + NEW(fData + position) T(fData[fSize]); + fData[fSize].~T(); + } + } + + Bool16 empty() const { return fSize == 0; } + UInt32 capacity() const { return fCapacity; } + UInt32 size() const { return fSize; } + + void clear() { resize(0); } + + //unlike clear(), this will free the memories + void wipe() + { + this->~SVector(); + fCapacity = fSize = 0; + fData = NULL; + } + + //Doubles the capacity on a reallocation to preserve linear time semantics + void reserve(UInt32 newCapacity) + { + if (newCapacity > fCapacity) + { + UInt32 targetCapacity = fCapacity == 0 ? 4 : fCapacity; + while(targetCapacity < newCapacity) + targetCapacity *= 2; + reserveImpl(targetCapacity); + } + } + + void resize(UInt32 newSize, const T &newItem = T()) + { + if (newSize > fSize) + { + reserve(newSize); + for(UInt32 i = fSize; i < newSize; ++i) + NEW(fData + i) T(newItem); + } + else if (newSize < fSize) + { + for(UInt32 i = newSize; i < fSize; ++i) + fData[i].~T(); + } + fSize = newSize; + } + + private: + void reserveImpl(UInt32 newCapacity) + { + T *newData = static_cast(operator new[](sizeof(T) * newCapacity)); + fCapacity = newCapacity; + for(UInt32 i = 0; i < fSize; ++i) + NEW (newData + i) T(fData[i]); + operator delete[](fData); + fData = newData; + } + + UInt32 fCapacity; + UInt32 fSize; + T *fData; + + struct EqualOp + { + bool operator()(const T &left, const T &right) { return left == right; } + }; +}; + +#endif //_SVECTOR_H_ + diff --git a/CommonUtilitiesLib/SafeStdLib.h b/CommonUtilitiesLib/SafeStdLib.h new file mode 100644 index 0000000..bab8f86 --- /dev/null +++ b/CommonUtilitiesLib/SafeStdLib.h @@ -0,0 +1,117 @@ +/* + * + * @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: SafeStdLib.h + + Contains: Thread safe std lib calls + + +*/ + +#ifndef _INTERNAL_STDLIB_H_ +#define _INTERNAL_STDLIB_H_ + +#include +#include "OSHeaders.h" + +#define kTimeStrSize 32 +#define kErrorStrSize 256 + extern int qtss_maxprintf(const char *fmt, ...); + extern void qtss_setmaxprintfcharsinK(UInt32 newMaxCharsInK); + extern UInt32 qtss_getmaxprintfcharsinK(); + +#ifndef USE_DEFAULT_STD_LIB + + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + + +#ifdef __USE_MAX_PRINTF__ + #define qtss_printf qtss_maxprintf +#else + extern int qtss_printf(const char *fmt, ...); + +#endif + +extern int qtss_sprintf(char *buffer, const char *fmt,...); +extern int qtss_fprintf(FILE *file, const char *fmt, ...); +extern int qtss_snprintf(char *str, size_t size, const char *format, ...); +extern size_t qtss_strftime(char *buf, size_t maxsize, const char *format, const struct tm *timeptr); + +// These calls return the pointer passed into the call as the result. + +extern char *qtss_strerror(int errnum, char* buffer, int buffLen); +extern char *qtss_ctime(const time_t *timep, char* buffer, int buffLen); +extern char *qtss_asctime(const struct tm *timeptr, char* buffer, int buffLen); +extern struct tm *qtss_gmtime (const time_t *, struct tm *result); +extern struct tm *qtss_localtime (const time_t *, struct tm *result); + +#ifdef __cplusplus +} +#endif + + +#else //USE_DEFAULT_STD_LIB + +#define qtss_sprintf sprintf + +#define qtss_fprintf fprintf + +#ifdef __USE_MAX_PRINTF__ + #define qtss_printf qtss_maxprintf +#else + #define qtss_printf printf +#endif + +#if __Win32__ + #define qtss_snprintf _snprintf +#else + + #define qtss_snprintf snprintf + +#endif + + + +#define qtss_strftime strftime + +// Use our calls for the following. +// These calls return the pointer passed into the call as the result. + + extern char *qtss_strerror(int errnum, char* buffer, int buffLen); + extern char *qtss_ctime(const time_t *timep, char* buffer, int buffLen); + extern char *qtss_asctime(const struct tm *timeptr, char* buffer, int buffLen); + extern struct tm *qtss_gmtime (const time_t *, struct tm *result); + extern struct tm *qtss_localtime (const time_t *, struct tm *result); + +#endif //USE_DEFAULT_STD_LIB +#endif //_INTERNAL_STDLIB_H_ diff --git a/CommonUtilitiesLib/Socket.cpp b/CommonUtilitiesLib/Socket.cpp new file mode 100644 index 0000000..c96b1f9 --- /dev/null +++ b/CommonUtilitiesLib/Socket.cpp @@ -0,0 +1,391 @@ +/* + * + * @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: Socket.cpp + + Contains: implements Socket class + + + +*/ + +#include + +#ifndef __Win32__ +#include +#include +#include +#include +#include +#include + +#endif + +#include + +#include "Socket.h" +#include "SocketUtils.h" +#include "OSMemory.h" + +#ifdef USE_NETLOG + #include +#else + #if defined(__Win32__) || defined(__sgi__) || defined(__osf__) || defined(__hpux__) + typedef int socklen_t; // missing from some platform includes + #endif +#endif + + +EventThread* Socket::sEventThread = NULL; + +Socket::Socket(Task *notifytask, UInt32 inSocketType) +: EventContext(EventContext::kInvalidFileDesc, sEventThread), + fState(inSocketType), + fLocalAddrStrPtr(NULL), + fLocalDNSStrPtr(NULL), + fPortStr(fPortBuffer, kPortBufSizeInBytes) +{ + fLocalAddr.sin_addr.s_addr = 0; + fLocalAddr.sin_port = 0; + + fDestAddr.sin_addr.s_addr = 0; + fDestAddr.sin_port = 0; + + this->SetTask(notifytask); + +#if SOCKET_DEBUG + fLocalAddrStr.Set(fLocalAddrBuffer,sizeof(fLocalAddrBuffer)); +#endif + +} + +OS_Error Socket::Open(int theType) +{ + Assert(fFileDesc == EventContext::kInvalidFileDesc); + fFileDesc = ::socket(PF_INET, theType, 0); + if (fFileDesc == EventContext::kInvalidFileDesc) + return (OS_Error)OSThread::GetErrno(); + + // + // Setup this socket's event context + if (fState & kNonBlockingSocketType) + this->InitNonBlocking(fFileDesc); + + return OS_NoErr; +} + +void Socket::ReuseAddr() +{ + int one = 1; + int err = ::setsockopt(fFileDesc, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(int)); + Assert(err == 0); +} + +void Socket::NoDelay() +{ + int one = 1; + int err = ::setsockopt(fFileDesc, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(int)); + Assert(err == 0); +} + +void Socket::KeepAlive() +{ + int one = 1; + int err = ::setsockopt(fFileDesc, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(int)); + Assert(err == 0); +} + +void Socket::SetSocketBufSize(UInt32 inNewSize) +{ + +#if SOCKET_DEBUG + int value; + int buffSize = sizeof(value); + int error = ::getsockopt(fFileDesc, SOL_SOCKET, SO_SNDBUF, (void*)&value, (socklen_t*)&buffSize); +#endif + + int bufSize = inNewSize; + int err = ::setsockopt(fFileDesc, SOL_SOCKET, SO_SNDBUF, (char*)&bufSize, sizeof(int)); + AssertV(err == 0, OSThread::GetErrno()); + +#if SOCKET_DEBUG + int setValue; + error = ::getsockopt(fFileDesc, SOL_SOCKET, SO_SNDBUF, (void*)&setValue, (socklen_t*)&buffSize); + qtss_printf("Socket::SetSocketBufSize "); + if (fState & kBound) + { if (NULL != this->GetLocalAddrStr()) + this->GetLocalAddrStr()->PrintStr(":"); + if (NULL != this->GetLocalPortStr()) + this->GetLocalPortStr()->PrintStr(" "); + } + else + qtss_printf("unbound "); + qtss_printf("socket=%d old SO_SNDBUF =%d inNewSize=%d setValue=%d\n", (int) fFileDesc, value, bufSize, setValue); +#endif + +} + +OS_Error Socket::SetSocketRcvBufSize(UInt32 inNewSize) +{ +#if SOCKET_DEBUG + int value; + int buffSize = sizeof(value); + int error = ::getsockopt(fFileDesc, SOL_SOCKET, SO_RCVBUF, (void*)&value, (socklen_t*)&buffSize); +#endif + + int bufSize = inNewSize; + int err = ::setsockopt(fFileDesc, SOL_SOCKET, SO_RCVBUF, (char*)&bufSize, sizeof(int)); + +#if SOCKET_DEBUG + int setValue; + error = ::getsockopt(fFileDesc, SOL_SOCKET, SO_RCVBUF, (void*)&setValue, (socklen_t*)&buffSize); + qtss_printf("Socket::SetSocketRcvBufSize "); + if (fState & kBound) + { if (NULL != this->GetLocalAddrStr()) + this->GetLocalAddrStr()->PrintStr(":"); + if (NULL != this->GetLocalPortStr()) + this->GetLocalPortStr()->PrintStr(" "); + } + else + qtss_printf("unbound "); + qtss_printf("socket=%d old SO_RCVBUF =%d inNewSize=%d setValue=%d\n",(int) fFileDesc, value, bufSize, setValue); +#endif + + + if (err == -1) + return OSThread::GetErrno(); + + return OS_NoErr; +} + + +OS_Error Socket::Bind(UInt32 addr, UInt16 port, UInt16 test) +{ + socklen_t len = sizeof(fLocalAddr); + ::memset(&fLocalAddr, 0, sizeof(fLocalAddr)); + fLocalAddr.sin_family = AF_INET; + fLocalAddr.sin_port = htons(port); + fLocalAddr.sin_addr.s_addr = htonl(addr); + + int err; + +#if 0 + if (test) // pick some ports or conditions to return an error on. + { + if (6971 == port) + { + fLocalAddr.sin_port = 0; + fLocalAddr.sin_addr.s_addr = 0; + return EINVAL; + } + else + { + err = ::bind(fFileDesc, (sockaddr *)&fLocalAddr, sizeof(fLocalAddr)); + } + } + else +#endif + err = ::bind(fFileDesc, (sockaddr *)&fLocalAddr, sizeof(fLocalAddr)); + + + if (err == -1) + { + fLocalAddr.sin_port = 0; + fLocalAddr.sin_addr.s_addr = 0; + return (OS_Error)OSThread::GetErrno(); + } + else ::getsockname(fFileDesc, (sockaddr *)&fLocalAddr, &len); // get the kernel to fill in unspecified values + fState |= kBound; + return OS_NoErr; +} + +StrPtrLen* Socket::GetLocalAddrStr() +{ + //Use the array of IP addr strings to locate the string formatted version + //of this IP address. + if (fLocalAddrStrPtr == NULL) + { + for (UInt32 x = 0; x < SocketUtils::GetNumIPAddrs(); x++) + { + if (SocketUtils::GetIPAddr(x) == ntohl(fLocalAddr.sin_addr.s_addr)) + { + fLocalAddrStrPtr = SocketUtils::GetIPAddrStr(x); + break; + } + } + } + +#if SOCKET_DEBUG + if (fLocalAddrStrPtr == NULL) + { // shouldn't happen but no match so it was probably a failed socket connection or accept. addr is probably 0. + + fLocalAddrBuffer[0]=0; + fLocalAddrStrPtr = &fLocalAddrStr; + struct in_addr theAddr; + theAddr.s_addr =ntohl(fLocalAddr.sin_addr.s_addr); + SocketUtils::ConvertAddrToString(theAddr, &fLocalAddrStr); + + printf("Socket::GetLocalAddrStr Search IPs failed, numIPs=%d\n",SocketUtils::GetNumIPAddrs()); + for (UInt32 x = 0; x < SocketUtils::GetNumIPAddrs(); x++) + { printf("ip[%"_U32BITARG_"]=",x); SocketUtils::GetIPAddrStr(x)->PrintStr("\n"); + } + printf("this ip = %d = ",theAddr.s_addr); fLocalAddrStrPtr->PrintStr("\n"); + + if (theAddr.s_addr == 0 || fLocalAddrBuffer[0] == 0) + fLocalAddrStrPtr = NULL; // so the caller can test for failure + } +#endif + + Assert(fLocalAddrStrPtr != NULL); + return fLocalAddrStrPtr; +} + +StrPtrLen* Socket::GetLocalDNSStr() +{ + //Do the same thing as the above function, but for DNS names + Assert(fLocalAddr.sin_addr.s_addr != INADDR_ANY); + if (fLocalDNSStrPtr == NULL) + { + for (UInt32 x = 0; x < SocketUtils::GetNumIPAddrs(); x++) + { + if (SocketUtils::GetIPAddr(x) == ntohl(fLocalAddr.sin_addr.s_addr)) + { + fLocalDNSStrPtr = SocketUtils::GetDNSNameStr(x); + break; + } + } + } + + //if we weren't able to get this DNS name, make the DNS name the same as the IP addr str. + if (fLocalDNSStrPtr == NULL) + fLocalDNSStrPtr = this->GetLocalAddrStr(); + + Assert(fLocalDNSStrPtr != NULL); + return fLocalDNSStrPtr; +} + +StrPtrLen* Socket::GetLocalPortStr() +{ + if (fPortStr.Len == kPortBufSizeInBytes) + { + int temp = ntohs(fLocalAddr.sin_port); + qtss_sprintf(fPortBuffer, "%d", temp); + fPortStr.Len = ::strlen(fPortBuffer); + } + return &fPortStr; +} + +OS_Error Socket::Send(const char* inData, const UInt32 inLength, UInt32* outLengthSent) +{ + Assert(inData != NULL); + + if (!(fState & kConnected)) + return (OS_Error)ENOTCONN; + + int err; + do { + err = ::send(fFileDesc, inData, inLength, 0);//flags?? + } while((err == -1) && (OSThread::GetErrno() == EINTR)); + if (err == -1) + { + //Are there any errors that can happen if the client is connected? + //Yes... EAGAIN. Means the socket is now flow-controleld + int theErr = OSThread::GetErrno(); + if ((theErr != EAGAIN) && (this->IsConnected())) + fState ^= kConnected;//turn off connected state flag + return (OS_Error)theErr; + } + + *outLengthSent = err; + return OS_NoErr; +} + +OS_Error Socket::WriteV(const struct iovec* iov, const UInt32 numIOvecs, UInt32* outLenSent) +{ + Assert(iov != NULL); + + if (!(fState & kConnected)) + return (OS_Error)ENOTCONN; + + int err; + do { +#ifdef __Win32__ + DWORD theBytesSent = 0; + err = ::WSASend(fFileDesc, (LPWSABUF)iov, numIOvecs, &theBytesSent, 0, NULL, NULL); + if (err == 0) + err = theBytesSent; +#else + err = ::writev(fFileDesc, iov, numIOvecs);//flags?? +#endif + } while((err == -1) && (OSThread::GetErrno() == EINTR)); + if (err == -1) + { + // Are there any errors that can happen if the client is connected? + // Yes... EAGAIN. Means the socket is now flow-controleld + int theErr = OSThread::GetErrno(); + if ((theErr != EAGAIN) && (this->IsConnected())) + fState ^= kConnected;//turn off connected state flag + return (OS_Error)theErr; + } + if (outLenSent != NULL) + *outLenSent = (UInt32)err; + + return OS_NoErr; +} + +OS_Error Socket::Read(void *buffer, const UInt32 length, UInt32 *outRecvLenP) +{ + Assert(outRecvLenP != NULL); + Assert(buffer != NULL); + + if (!(fState & kConnected)) + return (OS_Error)ENOTCONN; + + //int theRecvLen = ::recv(fFileDesc, buffer, length, 0);//flags?? + int theRecvLen; + do { + theRecvLen = ::recv(fFileDesc, (char*)buffer, length, 0);//flags?? + } while((theRecvLen == -1) && (OSThread::GetErrno() == EINTR)); + + if (theRecvLen == -1) + { + // Are there any errors that can happen if the client is connected? + // Yes... EAGAIN. Means the socket is now flow-controleld + int theErr = OSThread::GetErrno(); + if ((theErr != EAGAIN) && (this->IsConnected())) + fState ^= kConnected;//turn off connected state flag + return (OS_Error)theErr; + } + //if we get 0 bytes back from read, that means the client has disconnected. + //Note that and return the proper error to the caller + else if (theRecvLen == 0) + { + fState ^= kConnected; + return (OS_Error)ENOTCONN; + } + Assert(theRecvLen > 0); + *outRecvLenP = (UInt32)theRecvLen; + return OS_NoErr; +} diff --git a/CommonUtilitiesLib/Socket.h b/CommonUtilitiesLib/Socket.h new file mode 100644 index 0000000..ef9dc50 --- /dev/null +++ b/CommonUtilitiesLib/Socket.h @@ -0,0 +1,160 @@ +/* + * + * @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: Socket.h + + Contains: Provides a simple, object oriented socket abstraction, also + hides the details of socket event handling. Sockets can post + events (such as S_DATA, S_CONNECTIONCLOSED) to Tasks. + + + +*/ + +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#ifndef __Win32__ +#include +#endif + +#include "EventContext.h" +#include "ev.h" + +#define SOCKET_DEBUG 1 +//#define SOCKET_DEBUG 0 + +class Socket : public EventContext +{ + public: + + enum + { + // Pass this in on socket constructors to specify whether the + // socket should be non-blocking or blocking + kNonBlockingSocketType = 1 + }; + + // This class provides a global event thread. + static void Initialize() { sEventThread = new EventThread(); } + static void StartThread() { sEventThread->Start(); } + static EventThread* GetEventThread() { return sEventThread; } + + //Binds the socket to the following address. + //Returns: QTSS_FileNotOpen, QTSS_NoErr, or POSIX errorcode. + OS_Error Bind(UInt32 addr, UInt16 port,Bool16 test = false); + //The same. but in reverse + void Unbind(); + + void ReuseAddr(); + void NoDelay(); + void KeepAlive(); + void SetSocketBufSize(UInt32 inNewSize); + + // + // Returns an error if the socket buffer size is too big + OS_Error SetSocketRcvBufSize(UInt32 inNewSize); + + //Send + //Returns: QTSS_FileNotOpen, QTSS_NoErr, or POSIX errorcode. + OS_Error Send(const char* inData, const UInt32 inLength, UInt32* outLengthSent); + + //Read + //Reads some data. + //Returns: QTSS_FileNotOpen, QTSS_NoErr, or POSIX errorcode. + OS_Error Read(void *buffer, const UInt32 length, UInt32 *rcvLen); + + //WriteV: same as send, but takes an iovec + //Returns: QTSS_FileNotOpen, QTSS_NoErr, or POSIX errorcode. + OS_Error WriteV(const struct iovec* iov, const UInt32 numIOvecs, UInt32* outLengthSent); + + //You can query for the socket's state + Bool16 IsConnected() { return (Bool16) (fState & kConnected); } + Bool16 IsBound() { return (Bool16) (fState & kBound); } + + //If the socket is bound, you may find out to which addr it is bound + UInt32 GetLocalAddr() { return ntohl(fLocalAddr.sin_addr.s_addr); } + UInt16 GetLocalPort() { return ntohs(fLocalAddr.sin_port); } + + StrPtrLen* GetLocalAddrStr(); + StrPtrLen* GetLocalPortStr(); + StrPtrLen* GetLocalDNSStr(); + + enum + { + kMaxNumSockets = 4096 //UInt32 + }; + + protected: + + //TCPSocket takes an optional task object which will get notified when + //certain events happen on this socket. Those events are: + // + //S_DATA: Data is currently available on the socket. + //S_CONNECTIONCLOSING: Client is closing the connection. No longer necessary + // to call Close or Disconnect, Snd & Rcv will fail. + + Socket(Task *notifytask, UInt32 inSocketType); + virtual ~Socket() {} + + //returns QTSS_NoErr, or appropriate posix error + OS_Error Open(int theType); + + UInt32 fState; + + enum + { + kPortBufSizeInBytes = 8, //UInt32 + kMaxIPAddrSizeInBytes = 20 //UInt32 + }; + +#if SOCKET_DEBUG + StrPtrLen fLocalAddrStr; + char fLocalAddrBuffer[kMaxIPAddrSizeInBytes]; +#endif + + //address information (available if bound) + //these are always stored in network order. Conver + struct sockaddr_in fLocalAddr; + struct sockaddr_in fDestAddr; + + StrPtrLen* fLocalAddrStrPtr; + StrPtrLen* fLocalDNSStrPtr; + char fPortBuffer[kPortBufSizeInBytes]; + StrPtrLen fPortStr; + + //State flags. Be careful when changing these values, as subclasses add their own + enum + { + kBound = 0x0004, + kConnected = 0x0008 + }; + + static EventThread* sEventThread; + +}; + +#endif // __SOCKET_H__ + diff --git a/CommonUtilitiesLib/SocketUtils.cpp b/CommonUtilitiesLib/SocketUtils.cpp new file mode 100644 index 0000000..80c55e1 --- /dev/null +++ b/CommonUtilitiesLib/SocketUtils.cpp @@ -0,0 +1,605 @@ +/* + * + * @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: SocketUtils.cpp + + Contains: Implements utility functions defined in SocketUtils.h + + + + +*/ + +#include + +#ifndef __Win32__ +#include +#include +#include +#include +#include +#include + +#if __FreeBSD__ +#include +#endif +#include +#include + +#if __solaris__ +#include +#endif +#endif + +#include "SocketUtils.h" + +#ifdef SIOCGIFNUM +#define USE_SIOCGIFNUM 1 +#endif + +#ifdef TRUCLUSTER /* Tru64 Cluster Alias support */ + +#include +#include + +static clua_status_t (*clua_getaliasaddress_vector) (struct sockaddr *, int *); +static char *(*clua_error_vector) (clua_status_t); + +#define clua_getaliasaddress (*clua_getaliasaddress_vector) +#define clua_error (*clua_error_vector) + +struct clucall_vector clua_vectors[] = { + { "clua_getaliasaddress", &clua_getaliasaddress_vector }, + { "clua_error", &clua_error_vector }, + { NULL, NULL } /* END OF LIST */ +}; + +#endif /* TRUCLUSTER */ + +UInt32 SocketUtils::sNumIPAddrs = 0; +SocketUtils::IPAddrInfo* SocketUtils::sIPAddrInfoArray = NULL; +OSMutex SocketUtils::sMutex; + +#if __FreeBSD__ + +//Complete rewrite for FreeBSD. +//The non-FreeBSD version really needs to be rewritten - it's a bit of a mess... +void SocketUtils::Initialize(Bool16 lookupDNSName) +{ + struct ifaddrs* ifap; + struct ifaddrs* currentifap; + struct sockaddr_in* sockaddr; + int result = 0; + + result = getifaddrs(&ifap); + + //Count them first + currentifap = ifap; + while( currentifap != NULL ) + { + sockaddr = (struct sockaddr_in*)currentifap->ifa_addr; + if (sockaddr->sin_family == AF_INET) + sNumIPAddrs++; + currentifap = currentifap->ifa_next; + } + + + //allocate the IPAddrInfo array. Unfortunately we can't allocate this + //array the proper way due to a GCC bug + UInt8* addrInfoMem = new UInt8[sizeof(IPAddrInfo) * sNumIPAddrs]; + ::memset(addrInfoMem, 0, sizeof(IPAddrInfo) * sNumIPAddrs); + sIPAddrInfoArray = (IPAddrInfo*)addrInfoMem; + + int addrArrayIndex = 0; + currentifap = ifap; + while( currentifap != NULL ) + { + sockaddr = (struct sockaddr_in*)currentifap->ifa_addr; + + if (sockaddr->sin_family == AF_INET) + { + char* theAddrStr = ::inet_ntoa(sockaddr->sin_addr); + + //store the IP addr + sIPAddrInfoArray[addrArrayIndex].fIPAddr = ntohl(sockaddr->sin_addr.s_addr); + + //store the IP addr as a string + sIPAddrInfoArray[addrArrayIndex].fIPAddrStr.Len = ::strlen(theAddrStr); + sIPAddrInfoArray[addrArrayIndex].fIPAddrStr.Ptr = new char[sIPAddrInfoArray[addrArrayIndex].fIPAddrStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[addrArrayIndex].fIPAddrStr.Ptr, theAddrStr); + + struct hostent* theDNSName = NULL; + if (lookupDNSName) //convert this addr to a dns name, and store it + { theDNSName = ::gethostbyaddr((char *)&sockaddr->sin_addr, sizeof(sockaddr->sin_addr), AF_INET); + } + + if (theDNSName != NULL) + { + sIPAddrInfoArray[addrArrayIndex].fDNSNameStr.Len = ::strlen(theDNSName->h_name); + sIPAddrInfoArray[addrArrayIndex].fDNSNameStr.Ptr = new char[sIPAddrInfoArray[addrArrayIndex].fDNSNameStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[addrArrayIndex].fDNSNameStr.Ptr, theDNSName->h_name); + } + else + { + //if we failed to look up the DNS name, just store the IP addr as a string + sIPAddrInfoArray[addrArrayIndex].fDNSNameStr.Len = sIPAddrInfoArray[addrArrayIndex].fIPAddrStr.Len; + sIPAddrInfoArray[addrArrayIndex].fDNSNameStr.Ptr = new char[sIPAddrInfoArray[addrArrayIndex].fDNSNameStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[addrArrayIndex].fDNSNameStr.Ptr, sIPAddrInfoArray[addrArrayIndex].fIPAddrStr.Ptr); + } + + addrArrayIndex++; + } + + currentifap = currentifap->ifa_next; + } + + +} + +#else //__FreeBSD__ + +//Version for all non-FreeBSD platforms. + +void SocketUtils::Initialize(Bool16 lookupDNSName) +{ +#if defined(__Win32__) || defined(USE_SIOCGIFNUM) + + int tempSocket = ::socket(AF_INET, SOCK_DGRAM, 0); + if (tempSocket == -1) + return; + +#ifdef __Win32__ + + static const UInt32 kMaxAddrBufferSize = 2048; + char inBuffer[kMaxAddrBufferSize]; + char outBuffer[kMaxAddrBufferSize]; + UInt32 theReturnedSize = 0; + + // + // Use the WSAIoctl function call to get a list of IP addresses + int theErr = ::WSAIoctl( tempSocket, SIO_GET_INTERFACE_LIST, + inBuffer, kMaxAddrBufferSize, + outBuffer, kMaxAddrBufferSize, + &theReturnedSize, + NULL, + NULL); + Assert(theErr == 0); + if (theErr != 0) + return; + + Assert((theReturnedSize % sizeof(INTERFACE_INFO)) == 0); + LPINTERFACE_INFO addrListP = (LPINTERFACE_INFO)&outBuffer[0]; + + sNumIPAddrs = theReturnedSize / sizeof(INTERFACE_INFO); +#else +#if defined(USE_SIOCGIFNUM) + if (::ioctl(tempSocket, SIOCGIFNUM, (char*)&sNumIPAddrs) == -1) + { +#ifdef MAXIFS + sNumIPAddrs = MAXIFS; +#else + sNumIPAddrs = 64; +#endif + } +#else +#error +#endif + struct ifconf ifc; + ::memset(&ifc,0,sizeof(ifc)); + ifc.ifc_len = sNumIPAddrs * sizeof(struct ifreq); + ifc.ifc_buf = (caddr_t)new struct ifreq[sNumIPAddrs]; + Assert(ifc.ifc_buf != NULL); + + ::memset(ifc.ifc_buf, '\0', ifc.ifc_len); + int theErr = ::ioctl(tempSocket, SIOCGIFCONF, (char*)&ifc); + Assert(theErr == 0); + if (theErr != 0) + return; + struct ifreq* ifr = (struct ifreq*)ifc.ifc_buf; +#endif + + //allocate the IPAddrInfo array. Unfortunately we can't allocate this + //array the proper way due to a GCC bug + UInt8* addrInfoMem = new UInt8[sizeof(IPAddrInfo) * sNumIPAddrs]; + ::memset(addrInfoMem, 0, sizeof(IPAddrInfo) * sNumIPAddrs); + sIPAddrInfoArray = (IPAddrInfo*)addrInfoMem; + + //for (UInt32 addrCount = 0; addrCount < sNumIPAddrs; addrCount++) + UInt32 currentIndex = 0; + for (UInt32 theIfCount = sNumIPAddrs, addrCount = 0; + addrCount < theIfCount; addrCount++) + { +#ifdef __Win32__ + // We *should* count the loopback interface as a valid interface. + //if (addrListP[addrCount].iiFlags & IFF_LOOPBACK) + //{ + // Don't count loopback addrs + // sNumIPAddrs--; + // continue; + //} + //if (addrListP[addrCount].iiFlags & IFF_LOOPBACK) + // if (lookupDNSName) // The playlist broadcaster doesn't care + // Assert(addrCount > 0); // If the loopback interface is interface 0, we've got problems + + struct sockaddr_in* theAddr = (struct sockaddr_in*)&addrListP[addrCount].iiAddress; +#elif defined(USE_SIOCGIFNUM) + if (ifr[addrCount].ifr_addr.sa_family != AF_INET) + { + sNumIPAddrs--; + continue; + } + struct ifreq ifrf; + ::memset(&ifrf,0,sizeof(ifrf)); + ::strncpy(ifrf.ifr_name, ifr[addrCount].ifr_name, sizeof(ifrf.ifr_name)); + theErr = ::ioctl(tempSocket, SIOCGIFFLAGS, (char *) &ifrf); + Assert(theErr != -1); + +#ifndef __solaris__ + /* Skip things which aren't interesting */ + if ((ifrf.ifr_flags & IFF_UP) == 0 || + (ifrf.ifr_flags & (IFF_BROADCAST | IFF_POINTOPOINT)) == 0) + { + sNumIPAddrs--; + continue; + } + if (ifrf.ifr_flags & IFF_LOOPBACK) + { + Assert(addrCount > 0); // If the loopback interface is interface 0, we've got problems + } +#endif + + struct sockaddr_in* theAddr = (struct sockaddr_in*)&ifr[addrCount].ifr_addr; + #if 0 + puts(ifr[addrCount].ifr_name); + #endif +#else +#error +#endif + + char* theAddrStr = ::inet_ntoa(theAddr->sin_addr); + + //store the IP addr + sIPAddrInfoArray[currentIndex].fIPAddr = ntohl(theAddr->sin_addr.s_addr); + + //store the IP addr as a string + sIPAddrInfoArray[currentIndex].fIPAddrStr.Len = ::strlen(theAddrStr); + sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fIPAddrStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr, theAddrStr); + + + struct hostent* theDNSName = NULL; + if (lookupDNSName) //convert this addr to a dns name, and store it + { theDNSName = ::gethostbyaddr((char *)&theAddr->sin_addr, sizeof(theAddr->sin_addr), AF_INET); + } + + if (theDNSName != NULL) + { + sIPAddrInfoArray[currentIndex].fDNSNameStr.Len = ::strlen(theDNSName->h_name); + sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fDNSNameStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr, theDNSName->h_name); + } + else + { + //if we failed to look up the DNS name, just store the IP addr as a string + sIPAddrInfoArray[currentIndex].fDNSNameStr.Len = sIPAddrInfoArray[currentIndex].fIPAddrStr.Len; + sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fDNSNameStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr, sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr); + } + //move onto the next array index + currentIndex++; + + } +#ifdef __Win32__ + ::closesocket(tempSocket); +#elif defined(USE_SIOCGIFNUM) + delete[] ifc.ifc_buf; + ::close(tempSocket); +#else +#error +#endif + +#else // !__Win32__ + + //Most of this code is similar to the SIOCGIFCONF code presented in Stevens, + //Unix Network Programming, section 16.6 + + //Use the SIOCGIFCONF ioctl call to iterate through the network interfaces + static const UInt32 kMaxAddrBufferSize = 2048; + + struct ifconf ifc; + ::memset(&ifc,0,sizeof(ifc)); + struct ifreq* ifr; + char buffer[kMaxAddrBufferSize]; + + int tempSocket = ::socket(AF_INET, SOCK_DGRAM, 0); + if (tempSocket == -1) + return; + + ifc.ifc_len = kMaxAddrBufferSize; + ifc.ifc_buf = buffer; + +#if __linux__ || __linuxppc__ || __solaris__ || __MacOSX__ || __sgi__ || __osf__ + int err = ::ioctl(tempSocket, SIOCGIFCONF, (char*)&ifc); +#elif __FreeBSD__ + int err = ::ioctl(tempSocket, OSIOCGIFCONF, (char*)&ifc); +#else + #error +#endif + if (err == -1) + return; + +#if __FreeBSD__ + int netdev1, netdev2; + struct ifreq *netdevifr; + netdevifr = ifc.ifc_req; + netdev1 = ifc.ifc_len / sizeof(struct ifreq); + for (netdev2=netdev1-1; netdev2>=0; netdev2--) + { + if (ioctl(tempSocket, SIOCGIFADDR, &netdevifr[netdev2]) != 0) + continue; + } +#endif + + ::close(tempSocket); + tempSocket = -1; + + //walk through the list of IP addrs twice. Once to find out how many, + //the second time to actually grab their information + char* ifReqIter = NULL; + sNumIPAddrs = 0; + + for (ifReqIter = buffer; ifReqIter < (buffer + ifc.ifc_len);) + { + ifr = (struct ifreq*)ifReqIter; + if (!SocketUtils::IncrementIfReqIter(&ifReqIter, ifr)) + return; + + // Some platforms have lo as the first interface, so we have code to + // work around this problem below + //if (::strncmp(ifr->ifr_name, "lo", 2) == 0) + // Assert(sNumIPAddrs > 0); // If the loopback interface is interface 0, we've got problems + + //Only count interfaces in the AF_INET family. + if (ifr->ifr_addr.sa_family == AF_INET) + sNumIPAddrs++; + } + +#ifdef TRUCLUSTER + + int clusterAliases = 0; + + if (clu_is_member()) + { + /* loading the vector table */ + if (clua_getaliasaddress_vector == NULL) + { + clucall_stat clustat; + struct sockaddr_in sin; + + clustat = clucall_load("libclua.so", clua_vectors); + int context = 0; + clua_status_t addr_err; + + if (clua_getaliasaddress_vector != NULL) + while ( (addr_err = clua_getaliasaddress + ((struct sockaddr*)&sin, &context)) == CLUA_SUCCESS ) + { + sNumIPAddrs++; + clusterAliases++; + } + } + + } + +#endif // TRUCLUSTER + + //allocate the IPAddrInfo array. Unfortunately we can't allocate this + //array the proper way due to a GCC bug + UInt8* addrInfoMem = new UInt8[sizeof(IPAddrInfo) * sNumIPAddrs]; + ::memset(addrInfoMem, 0, sizeof(IPAddrInfo) * sNumIPAddrs); + sIPAddrInfoArray = (IPAddrInfo*)addrInfoMem; + + //Now extract all the necessary information about each interface + //and put it into the array + UInt32 currentIndex = 0; + +#ifdef TRUCLUSTER + // Do these cluster aliases first so they'll be first in the list + if (clusterAliases > 0) + { + int context = 0; + struct sockaddr_in sin; + clua_status_t addr_err; + + while ( (addr_err = clua_getaliasaddress ((struct sockaddr*)&sin, &context)) == CLUA_SUCCESS ) + { + char* theAddrStr = ::inet_ntoa(sin.sin_addr); + + //store the IP addr + sIPAddrInfoArray[currentIndex].fIPAddr = ntohl(sin.sin_addr.s_addr); + + //store the IP addr as a string + sIPAddrInfoArray[currentIndex].fIPAddrStr.Len = ::strlen(theAddrStr); + sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fIPAddrStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr, theAddrStr); + + //convert this addr to a dns name, and store it + struct hostent* theDNSName = ::gethostbyaddr((char *)&sin.sin_addr, + sizeof(sin.sin_addr), AF_INET); + if (theDNSName != NULL) + { + sIPAddrInfoArray[currentIndex].fDNSNameStr.Len = ::strlen(theDNSName->h_name); + sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fDNSNameStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr, theDNSName->h_name); + } + else + { + //if we failed to look up the DNS name, just store the IP addr as a string + sIPAddrInfoArray[currentIndex].fDNSNameStr.Len = sIPAddrInfoArray[currentIndex].fIPAddrStr.Len; + sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fDNSNameStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr, sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr); + } + + currentIndex++; + } + } +#endif // TRUCLUSTER + + for (ifReqIter = buffer; ifReqIter < (buffer + ifc.ifc_len);) + { + ifr = (struct ifreq*)ifReqIter; + if (!SocketUtils::IncrementIfReqIter(&ifReqIter, ifr)) + { + Assert(0);//we should have already detected this error + return; + } + + //Only count interfaces in the AF_INET family + if (ifr->ifr_addr.sa_family == AF_INET) + { + struct sockaddr_in* addrPtr = (struct sockaddr_in*)&ifr->ifr_addr; + char* theAddrStr = ::inet_ntoa(addrPtr->sin_addr); + + //store the IP addr + sIPAddrInfoArray[currentIndex].fIPAddr = ntohl(addrPtr->sin_addr.s_addr); + + //store the IP addr as a string + sIPAddrInfoArray[currentIndex].fIPAddrStr.Len = ::strlen(theAddrStr); + sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fIPAddrStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr, theAddrStr); + + struct hostent* theDNSName = NULL; + if (lookupDNSName) //convert this addr to a dns name, and store it + { theDNSName = ::gethostbyaddr((char *)&addrPtr->sin_addr, sizeof(addrPtr->sin_addr), AF_INET); + } + + if (theDNSName != NULL) + { + sIPAddrInfoArray[currentIndex].fDNSNameStr.Len = ::strlen(theDNSName->h_name); + sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fDNSNameStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr, theDNSName->h_name); + } + else + { + //if we failed to look up the DNS name, just store the IP addr as a string + sIPAddrInfoArray[currentIndex].fDNSNameStr.Len = sIPAddrInfoArray[currentIndex].fIPAddrStr.Len; + sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr = new char[sIPAddrInfoArray[currentIndex].fDNSNameStr.Len + 2]; + ::strcpy(sIPAddrInfoArray[currentIndex].fDNSNameStr.Ptr, sIPAddrInfoArray[currentIndex].fIPAddrStr.Ptr); + } + + //move onto the next array index + currentIndex++; + } + } + + Assert(currentIndex == sNumIPAddrs); +#endif + + // + // If LocalHost is the first element in the array, switch it to be the second. + // The first element is supposed to be the "default" interface on the machine, + // which should really always be en0. + if ((sNumIPAddrs > 1) && (::strcmp(sIPAddrInfoArray[0].fIPAddrStr.Ptr, "127.0.0.1") == 0)) + { + UInt32 tempIP = sIPAddrInfoArray[1].fIPAddr; + sIPAddrInfoArray[1].fIPAddr = sIPAddrInfoArray[0].fIPAddr; + sIPAddrInfoArray[0].fIPAddr = tempIP; + StrPtrLen tempIPStr(sIPAddrInfoArray[1].fIPAddrStr); + sIPAddrInfoArray[1].fIPAddrStr = sIPAddrInfoArray[0].fIPAddrStr; + sIPAddrInfoArray[0].fIPAddrStr = tempIPStr; + StrPtrLen tempDNSStr(sIPAddrInfoArray[1].fDNSNameStr); + sIPAddrInfoArray[1].fDNSNameStr = sIPAddrInfoArray[0].fDNSNameStr; + sIPAddrInfoArray[0].fDNSNameStr = tempDNSStr; + } +} +#endif //__FreeBSD__ + + + +#ifndef __Win32__ +Bool16 SocketUtils::IncrementIfReqIter(char** inIfReqIter, ifreq* ifr) +{ + //returns true if successful, false otherwise + +#if __MacOSX__ + *inIfReqIter += sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len; + + //if the length of the addr is 0, use the family to determine + //what the addr size is + if (ifr->ifr_addr.sa_len == 0) +#else + *inIfReqIter += sizeof(ifr->ifr_name) + 0; +#endif + { + switch (ifr->ifr_addr.sa_family) + { + case AF_INET: + *inIfReqIter += sizeof(struct sockaddr_in); + break; + default: + *inIfReqIter += sizeof(struct sockaddr); +// Assert(0); +// sNumIPAddrs = 0; +// return false; + } + } + return true; +} +#endif + +Bool16 SocketUtils::IsMulticastIPAddr(UInt32 inAddress) +{ + return ((inAddress>>8) & 0x00f00000) == 0x00e00000; // multicast addresses == "class D" == 0xExxxxxxx == 1,1,1,0,<28 bits> +} + +Bool16 SocketUtils::IsLocalIPAddr(UInt32 inAddress) +{ + for (UInt32 x = 0; x < sNumIPAddrs; x++) + if (sIPAddrInfoArray[x].fIPAddr == inAddress) + return true; + return false; +} + +void SocketUtils::ConvertAddrToString(const struct in_addr& theAddr, StrPtrLen* ioStr) +{ + //re-entrant version of code below + //inet_ntop(AF_INET, &theAddr, ioStr->Ptr, ioStr->Len); + //ioStr->Len = ::strlen(ioStr->Ptr); + + sMutex.Lock(); + char* addr = inet_ntoa(theAddr); + strcpy(ioStr->Ptr, addr); + ioStr->Len = ::strlen(ioStr->Ptr); + sMutex.Unlock(); +} + +UInt32 SocketUtils::ConvertStringToAddr(const char* inAddrStr) +{ + if (inAddrStr == NULL) + return 0; + + return ntohl(::inet_addr(inAddrStr)); +} + diff --git a/CommonUtilitiesLib/SocketUtils.h b/CommonUtilitiesLib/SocketUtils.h new file mode 100644 index 0000000..d2c8731 --- /dev/null +++ b/CommonUtilitiesLib/SocketUtils.h @@ -0,0 +1,122 @@ +/* + * + * @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: SocketUtils.h + + Contains: Some static routines for dealing with networking + + +*/ + +#ifndef __SOCKETUTILS_H__ +#define __SOCKETUTILS_H__ + +#ifndef __Win32__ +#include +#include +#include +#endif + +#include "ev.h" + +#include "OSHeaders.h" +#include "MyAssert.h" +#include "StrPtrLen.h" +#include "OSMutex.h" + +#ifdef __solaris__ + #ifndef INADDR_NONE + #define INADDR_NONE 0xffffffff /* -1 return from inet_addr */ + #endif +#endif + +class SocketUtils +{ + public: + + // Call initialize before using any socket functions. + // (pass true for lookupDNSName if you want the hostname + // looked up via DNS during initialization -- %%sfu) + static void Initialize(Bool16 lookupDNSName = true); + + //static utility routines + static Bool16 IsMulticastIPAddr(UInt32 inAddress); + static Bool16 IsLocalIPAddr(UInt32 inAddress); + + //This function converts an integer IP address to a dotted-decimal string. + //This function is NOT THREAD SAFE!!! + static void ConvertAddrToString(const struct in_addr& theAddr, StrPtrLen* outAddr); + + // This function converts a dotted-decimal string IP address to a UInt32 + static UInt32 ConvertStringToAddr(const char* inAddr); + + //You can get at all the IP addrs and DNS names on this machine this way + static UInt32 GetNumIPAddrs() { return sNumIPAddrs; } + static inline UInt32 GetIPAddr(UInt32 inAddrIndex); + static inline StrPtrLen* GetIPAddrStr(UInt32 inAddrIndex); + static inline StrPtrLen* GetDNSNameStr(UInt32 inDNSIndex); + + private: + + //Utility function used by Initialize +#ifndef __Win32__ + static Bool16 IncrementIfReqIter(char** inIfReqIter, ifreq* ifr); +#endif + //For storing relevent information about each IP interface + struct IPAddrInfo + { + UInt32 fIPAddr; + StrPtrLen fIPAddrStr; + StrPtrLen fDNSNameStr; + }; + + static IPAddrInfo* sIPAddrInfoArray; + static UInt32 sNumIPAddrs; + static OSMutex sMutex; +}; + +inline UInt32 SocketUtils::GetIPAddr(UInt32 inAddrIndex) +{ + Assert(sIPAddrInfoArray != NULL); + Assert(inAddrIndex < sNumIPAddrs); + return sIPAddrInfoArray[inAddrIndex].fIPAddr; +} + +inline StrPtrLen* SocketUtils::GetIPAddrStr(UInt32 inAddrIndex) +{ + Assert(sIPAddrInfoArray != NULL); + Assert(inAddrIndex < sNumIPAddrs); + return &sIPAddrInfoArray[inAddrIndex].fIPAddrStr; +} + +inline StrPtrLen* SocketUtils::GetDNSNameStr(UInt32 inDNSIndex) +{ + Assert(sIPAddrInfoArray != NULL); + Assert(inDNSIndex < sNumIPAddrs); + return &sIPAddrInfoArray[inDNSIndex].fDNSNameStr; +} + +#endif // __SOCKETUTILS_H__ + diff --git a/CommonUtilitiesLib/StopWatch.h b/CommonUtilitiesLib/StopWatch.h new file mode 100644 index 0000000..5c815fe --- /dev/null +++ b/CommonUtilitiesLib/StopWatch.h @@ -0,0 +1,66 @@ +/* + * + * @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: StopWatch.h + + Contains: classes for stopwatch timers + +*/ + + +#include "OS.h" + +class MilliSecondStopWatch { + + public: + MilliSecondStopWatch () { fStartedAt = -1; fStoppedAt = -1; } + + void Start() { fStartedAt = OS::Milliseconds(); } + void Stop() { fStoppedAt = OS::Milliseconds(); } + SInt64 Duration() { return fStoppedAt - fStartedAt; } + private: + SInt64 fStartedAt; + SInt64 fStoppedAt; + +}; + + + +class MicroSecondStopWatch { + + public: + MicroSecondStopWatch () { fStartedAt = -1; fStoppedAt = -1; } + + void Start() { fStartedAt = OS::Microseconds(); } + void Stop() { fStoppedAt = OS::Microseconds(); } + SInt64 Duration() { return fStoppedAt - fStartedAt; } + private: + SInt64 fStartedAt; + SInt64 fStoppedAt; + +}; + diff --git a/CommonUtilitiesLib/StrPtrLen.cpp b/CommonUtilitiesLib/StrPtrLen.cpp new file mode 100644 index 0000000..464460e --- /dev/null +++ b/CommonUtilitiesLib/StrPtrLen.cpp @@ -0,0 +1,565 @@ +/* + * + * @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: StrPtrLen.cpp + + Contains: Implementation of class defined in StrPtrLen.h. + + + + +*/ + + +#include +#include "StrPtrLen.h" +#include "MyAssert.h" +#include "OS.h" +#include "OSMemory.h" + + +UInt8 StrPtrLen::sCaseInsensitiveMask[] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, //0-9 + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, //10-19 + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, //20-29 + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, //30-39 + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, //40-49 + 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, //50-59 + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, //60-69 //stop on every character except a letter + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, //70-79 + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, //80-89 + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, //90-99 + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, //100-109 + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, //110-119 + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129 //120-129 +}; + +UInt8 StrPtrLen::sNonPrintChars[] = +{ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, //0-9 // stop + 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, //10-19 //'\r' & '\n' are not stop conditions + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //20-29 + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, //30-39 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, //170-179 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //180-189 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //190-199 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //200-209 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //210-219 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //220-229 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //230-239 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //240-249 + 1, 1, 1, 1, 1, 1 //250-255 +}; + +char* StrPtrLen::GetAsCString() const +{ + // convert to a "NEW'd" zero terminated char array + // caler is reponsible for the newly allocated memory + char *theString = NEW char[Len+1]; + + if ( Ptr && Len > 0 ) + ::memcpy( theString, Ptr, Len ); + + theString[Len] = 0; + + return theString; +} + + +Bool16 StrPtrLen::Equal(const StrPtrLen &compare) const +{ + if (NULL == compare.Ptr && NULL == Ptr ) + return true; + + if ((NULL == compare.Ptr) || (NULL == Ptr)) + return false; + + if ((compare.Len == Len) && (memcmp(compare.Ptr, Ptr, Len) == 0)) + return true; + else + return false; +} + +Bool16 StrPtrLen::Equal(const char* compare) const +{ + if (NULL == compare && NULL == Ptr ) + return true; + + if ((NULL == compare) || (NULL == Ptr)) + return false; + + if ((::strlen(compare) == Len) && (memcmp(compare, Ptr, Len) == 0)) + return true; + else + return false; +} + + + +Bool16 StrPtrLen::NumEqualIgnoreCase(const char* compare, const UInt32 len) const +{ + // compare thru the first "len: bytes + Assert(compare != NULL); + + if (len <= Len) + { + for (UInt32 x = 0; x < len; x++) + if (sCaseInsensitiveMask[ (UInt8) Ptr[x]] != sCaseInsensitiveMask[(UInt8) compare[x]]) + return false; + return true; + } + return false; +} + +Bool16 StrPtrLen::EqualIgnoreCase(const char* compare, const UInt32 len) const +{ + Assert(compare != NULL); + if (len == Len) + { + for (UInt32 x = 0; x < len; x++) + if (sCaseInsensitiveMask[(UInt8) Ptr[x]] != sCaseInsensitiveMask[(UInt8) compare[x]]) + return false; + return true; + } + return false; +} + +char *StrPtrLen::FindStringCase(char *queryCharStr, StrPtrLen *resultStr, Bool16 caseSensitive) const +{ + // Be careful about exiting this method from the middle. This routine deletes allocated memory at the end. + // + + if (resultStr) + resultStr->Set(NULL,0); + + Assert (NULL != queryCharStr); + if (NULL == queryCharStr) return NULL; + if (NULL == Ptr) return NULL; + if (0 == Len) return NULL; + + + StrPtrLen queryStr(queryCharStr); + char *editSource = NULL; + char *resultChar = NULL; + char lastSourceChar = Ptr[Len -1]; + + if (lastSourceChar != 0) // need to modify for termination. + { editSource = NEW char[Len + 1]; // Ptr could be a static string so make a copy + ::memcpy( editSource, Ptr, Len ); + editSource[Len] = 0; // this won't work on static strings so we are modifing a new string here + } + + char *queryString = queryCharStr; + char *dupSourceString = NULL; + char *dupQueryString = NULL; + char *sourceString = Ptr; + UInt32 foundLen = 0; + + if (editSource != NULL) // a copy of the source ptr and len 0 terminated + sourceString = editSource; + + if (!caseSensitive) + { dupSourceString = ::strdup(sourceString); + dupQueryString = ::strdup(queryCharStr); + if (dupSourceString && dupQueryString) + { sourceString = StrPtrLen(dupSourceString).ToUpper(); + queryString = StrPtrLen(dupQueryString).ToUpper(); + resultChar = ::strstr(sourceString,queryString); + + ::free(dupSourceString); + ::free(dupQueryString); + } + } + else + { resultChar = ::strstr(sourceString,queryString); + } + + if (resultChar != NULL) // get the start offset + { foundLen = resultChar - sourceString; + resultChar = Ptr + foundLen; // return a pointer in the source buffer + if (resultChar > (Ptr + Len)) // make sure it is in the buffer + resultChar = NULL; + } + + if (editSource != NULL) + delete [] editSource; + + if (resultStr != NULL && resultChar != NULL) + resultStr->Set(resultChar,queryStr.Len); + +#if STRPTRLENTESTING + qtss_printf("StrPtrLen::FindStringCase found string=%s\n",resultChar); +#endif + + return resultChar; +} + + +UInt32 StrPtrLen::RemoveWhitespace() +{ + if (Ptr == NULL || Len == 0) + return 0; + + char *EndPtr = Ptr + Len; // one past last char + char *destPtr = Ptr; + char *srcPtr = Ptr; + + Len = 0; + while (srcPtr < EndPtr) + { + + if (*srcPtr != ' ' && *srcPtr != '\t') + { + if (srcPtr != destPtr) + *destPtr = *srcPtr; + + destPtr++; + Len ++; + } + srcPtr ++; + } + + return Len; +} + +UInt32 StrPtrLen::TrimLeadingWhitespace() +{ + if (Ptr == NULL || Len == 0) + return 0; + + char *EndPtr = Ptr + Len; //one past last char + + while (Ptr < EndPtr) + { + if (*Ptr != ' ' && *Ptr != '\t') + break; + + Ptr += 1; + Len -= 1; + } + + return Len; +} + +UInt32 StrPtrLen::TrimTrailingWhitespace() +{ + if (Ptr == NULL || Len == 0) + return 0; + + char *theCharPtr = Ptr + (Len - 1); // last char + + while (theCharPtr >= Ptr) + { + if (*theCharPtr != ' ' && *theCharPtr != '\t') + break; + + theCharPtr -= 1; + Len -= 1; + } + + return Len; +} + +void StrPtrLen::PrintStr() +{ + char *thestr = GetAsCString(); + + UInt32 i = 0; + for (; i < Len; i ++) + { + if (StrPtrLen::sNonPrintChars[(UInt8) Ptr[i]]) + { thestr[i] = 0; + break; + } + + } + + if (thestr != NULL) + { + qtss_printf(thestr); + delete thestr; + } +} + +void StrPtrLen::PrintStr(char *appendStr) +{ + StrPtrLen::PrintStr(); + if (appendStr != NULL) + qtss_printf(appendStr); +} + +void StrPtrLen::PrintStr(char* prependStr, char *appendStr) +{ + if (prependStr != NULL) + qtss_printf(prependStr); + + StrPtrLen::PrintStr(); + + if (appendStr != NULL) + qtss_printf(appendStr); +} + + +void StrPtrLen::PrintStrEOL(char* stopStr, char *appendStr) +{ + + + char *thestr = GetAsCString(); + + SInt32 i = 0; + for (; i < (SInt32) Len; i ++) + { + if (StrPtrLen::sNonPrintChars[(UInt8) Ptr[i]]) + { thestr[i] = 0; + break; + } + + } + + for (i = 0; thestr[i] != 0 ; i ++) + { + if (thestr[i] == '%' && thestr[i+1] != '%' ) + { thestr[i] = '$'; + } + } + + SInt32 stopLen = 0; + if (stopStr != NULL) + stopLen = ::strlen(stopStr); + + if (stopLen > 0 && stopLen <= i) + { + char* stopPtr = ::strstr(thestr, stopStr); + if (stopPtr != NULL) + { stopPtr += stopLen; + *stopPtr = 0; + i = stopPtr - thestr; + } + } + + char * theStrLine = thestr; + char * nextLine = NULL; + char * theChar = NULL; + static char *cr="\\r"; + static char *lf="\\n\n"; + SInt32 tempLen = i; + for (i = 0; i < tempLen; i ++) + { + if (theStrLine[i] == '\r') + { theChar = cr; + theStrLine[i] = 0; + nextLine = &theStrLine[i+1]; + } + else if (theStrLine[i] == '\n') + { theChar = lf; + theStrLine[i] = 0; + nextLine = &theStrLine[i+1]; + } + + if (nextLine != NULL) + { + qtss_printf(theStrLine); + qtss_printf(theChar); + + theStrLine = nextLine; + nextLine = NULL; + tempLen -= (i+1); + i = -1; + } + } + qtss_printf(theStrLine); + delete thestr; + + if (appendStr != NULL) + qtss_printf(appendStr); + +} + + + + +#if STRPTRLENTESTING +Bool16 StrPtrLen::Test() +{ + static char* test1 = "2347.;.][';[;]abcdefghijklmnopqrstuvwxyz#%#$$#"; + static char* test2 = "2347.;.][';[;]ABCDEFGHIJKLMNOPQRSTUVWXYZ#%#$$#"; + static char* test3 = "Content-Type:"; + static char* test4 = "cONTent-TYPe:"; + static char* test5 = "cONTnnt-TYPe:"; + static char* test6 = "cONTent-TY"; + + static char* test7 = "ontent-Type:"; + static char* test8 = "ONTent-TYPe:"; + static char* test9 = "-TYPe:"; + static char* test10 = ":"; + + StrPtrLen theVictim1(test1, strlen(test1)); + if (!theVictim1.EqualIgnoreCase(test2, strlen(test2))) + return false; + + if (theVictim1.EqualIgnoreCase(test3, strlen(test3))) + return false; + if (!theVictim1.EqualIgnoreCase(test1, strlen(test1))) + return false; + + StrPtrLen theVictim2(test3, strlen(test3)); + if (!theVictim2.EqualIgnoreCase(test4, strlen(test4))) + return false; + if (theVictim2.EqualIgnoreCase(test5, strlen(test5))) + return false; + if (theVictim2.EqualIgnoreCase(test6, strlen(test6))) + return false; + + StrPtrLen outResultStr; + if (!theVictim1.FindStringIgnoreCase(test2, &outResultStr)) + return false; + if (theVictim1.FindStringIgnoreCase(test3, &outResultStr)) + return false; + if (!theVictim1.FindStringIgnoreCase(test1, &outResultStr)) + return false; + if (!theVictim2.FindStringIgnoreCase(test4)) + return false; + if (theVictim2.FindStringIgnoreCase(test5)) + return false; + if (!theVictim2.FindStringIgnoreCase(test6)) + return false; + if (!theVictim2.FindStringIgnoreCase(test7)) + return false; + if (!theVictim2.FindStringIgnoreCase(test8)) + return false; + if (!theVictim2.FindStringIgnoreCase(test9)) + return false; + if (!theVictim2.FindStringIgnoreCase(test10)) + return false; + + if (theVictim1.FindString(test2, &outResultStr)) + return false; + if (theVictim1.FindString(test3, &outResultStr)) + return false; + if (!theVictim1.FindString(test1, &outResultStr)) + return false; + if (theVictim2.FindString(test4)) + return false; + if (theVictim2.FindString(test5)) + return false; + if (theVictim2.FindString(test6)) + return false; + if (!theVictim2.FindString(test7)) + return false; + if (theVictim2.FindString(test8)) + return false; + if (theVictim2.FindString(test9)) + return false; + if (!theVictim2.FindString(test10)) + return false; + + StrPtrLen query; + query.Set(test2); + if (theVictim1.FindString(query, &outResultStr)) + return false; + if (outResultStr.Len > 0) + return false; + if (outResultStr.Ptr != NULL) + return false; + + query.Set(test3); + if (theVictim1.FindString(query, &outResultStr)) + return false; + if (outResultStr.Len > 0) + return false; + if (outResultStr.Ptr != NULL) + return false; + + query.Set(test1); + if (!theVictim1.FindString(query, &outResultStr)) + return false; + if (!outResultStr.Equal(query)) + return false; + + query.Set(test4); + if (query.Equal(theVictim2.FindString(query))) + return false; + + query.Set(test5); + if (query.Equal(theVictim2.FindString(query))) + return false; + + query.Set(test6); + if (query.Equal(theVictim2.FindString(query))) + return false; + + query.Set(test7); + if (!query.Equal(theVictim2.FindString(query))) + return false; + + query.Set(test8); + if (query.Equal(theVictim2.FindString(query))) + return false; + + query.Set(test9); + if (query.Equal(theVictim2.FindString(query))) + return false; + + query.Set(test10); + if (!query.Equal(theVictim2.FindString(query))) + return false; + + query.Set(test10); + if (!query.Equal(theVictim2.FindString(query))) + return false; + + StrPtrLen partialStaticSource(test1,5); + query.Set("abcd"); + if (query.Equal(partialStaticSource.FindString(query))) + return false; + + query.Set("47"); + if (query.Equal(partialStaticSource.FindString(query))) // success = !equal because the char str is longer than len + return false; + + if (query.FindString(partialStaticSource.FindString(query))) // success = !found because the 0 term src is not in query + return false; + + partialStaticSource.FindString(query,&outResultStr); + if (!outResultStr.Equal(query)) // success =found the result Ptr and Len is the same as the query + return false; + + return true; +} +#endif diff --git a/CommonUtilitiesLib/StrPtrLen.h b/CommonUtilitiesLib/StrPtrLen.h new file mode 100644 index 0000000..0e4c8bd --- /dev/null +++ b/CommonUtilitiesLib/StrPtrLen.h @@ -0,0 +1,146 @@ +/* + * + * @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: StrPtrLen.h + + Contains: Definition of class that tracks a string ptr and a length. + Note: this is NOT a string container class! It is a string PTR container + class. It therefore does not copy the string and store it internally. If + you deallocate the string to which this object points to, and continue + to use it, you will be in deep doo-doo. + + It is also non-encapsulating, basically a struct with some simple methods. + + +*/ + +#ifndef __STRPTRLEN_H__ +#define __STRPTRLEN_H__ + +#include +#include "OSHeaders.h" +#include +#include "MyAssert.h" +#include "SafeStdLib.h" + +#define STRPTRLENTESTING 0 + +class StrPtrLen +{ + public: + + //CONSTRUCTORS/DESTRUCTOR + //These are so tiny they can all be inlined + StrPtrLen() : Ptr(NULL), Len(0) {} + StrPtrLen(char* sp) : Ptr(sp), Len(sp != NULL ? strlen(sp) : 0) {} + StrPtrLen(char *sp, UInt32 len) : Ptr(sp), Len(len) {} + virtual ~StrPtrLen() {} + + //OPERATORS: + Bool16 Equal(const StrPtrLen &compare) const; + Bool16 EqualIgnoreCase(const char* compare, const UInt32 len) const; + Bool16 EqualIgnoreCase(const StrPtrLen &compare) const { return EqualIgnoreCase(compare.Ptr, compare.Len); } + Bool16 Equal(const char* compare) const; + Bool16 NumEqualIgnoreCase(const char* compare, const UInt32 len) const; + + void Delete() { delete [] Ptr; Ptr = NULL; Len = 0; } + char *ToUpper() { for (UInt32 x = 0; x < Len ; x++) Ptr[x] = toupper (Ptr[x]); return Ptr;} + + char *FindStringCase(char *queryCharStr, StrPtrLen *resultStr, Bool16 caseSensitive) const; + + char *FindString(StrPtrLen *queryStr, StrPtrLen *outResultStr) { Assert(queryStr != NULL); Assert(queryStr->Ptr != NULL); Assert(0 == queryStr->Ptr[queryStr->Len]); + return FindStringCase(queryStr->Ptr, outResultStr,true); + } + + char *FindStringIgnoreCase(StrPtrLen *queryStr, StrPtrLen *outResultStr) { Assert(queryStr != NULL); Assert(queryStr->Ptr != NULL); Assert(0 == queryStr->Ptr[queryStr->Len]); + return FindStringCase(queryStr->Ptr, outResultStr,false); + } + + char *FindString(StrPtrLen *queryStr) { Assert(queryStr != NULL); Assert(queryStr->Ptr != NULL); Assert(0 == queryStr->Ptr[queryStr->Len]); + return FindStringCase(queryStr->Ptr, NULL,true); + } + + char *FindStringIgnoreCase(StrPtrLen *queryStr) { Assert(queryStr != NULL); Assert(queryStr->Ptr != NULL); Assert(0 == queryStr->Ptr[queryStr->Len]); + return FindStringCase(queryStr->Ptr, NULL,false); + } + + char *FindString(char *queryCharStr) { return FindStringCase(queryCharStr, NULL,true); } + char *FindStringIgnoreCase(char *queryCharStr) { return FindStringCase(queryCharStr, NULL,false); } + char *FindString(char *queryCharStr, StrPtrLen *outResultStr) { return FindStringCase(queryCharStr, outResultStr,true); } + char *FindStringIgnoreCase(char *queryCharStr, StrPtrLen *outResultStr) { return FindStringCase(queryCharStr, outResultStr,false); } + + char *FindString(StrPtrLen &query, StrPtrLen *outResultStr) { return FindString( &query, outResultStr); } + char *FindStringIgnoreCase(StrPtrLen &query, StrPtrLen *outResultStr) { return FindStringIgnoreCase( &query, outResultStr); } + char *FindString(StrPtrLen &query) { return FindString( &query); } + char *FindStringIgnoreCase(StrPtrLen &query) { return FindStringIgnoreCase( &query); } + + StrPtrLen& operator=(const StrPtrLen& newStr) { Ptr = newStr.Ptr; Len = newStr.Len; + return *this; } + char operator[](int i) { /*Assert(i +#include +#include "StringFormatter.h" +#include "MyAssert.h" + +char* StringFormatter::sEOL = "\r\n"; +UInt32 StringFormatter::sEOLLen = 2; + +void StringFormatter::Put(const SInt32 num) +{ + char buff[32]; + qtss_sprintf(buff, "%"_S32BITARG_"", num); + Put(buff); +} + +void StringFormatter::Put(char* buffer, UInt32 bufferSize) +{ + //optimization for writing 1 character + if((bufferSize == 1) && (fCurrentPut != fEndPut)) { + *(fCurrentPut++) = *buffer; + fBytesWritten++; + return; + } + + //loop until the input buffer size is smaller than the space in the output + //buffer. Call BufferIsFull at each pass through the loop + UInt32 spaceLeft = this->GetSpaceLeft(); + UInt32 spaceInBuffer = spaceLeft - 1; + UInt32 resizedSpaceLeft = 0; + + while ( (spaceInBuffer < bufferSize) || (spaceLeft == 0) ) // too big for destination + { + if (spaceLeft > 0) + { + //copy as much as possible; truncating the result + ::memcpy(fCurrentPut, buffer, spaceInBuffer); + fCurrentPut += spaceInBuffer; + fBytesWritten += spaceInBuffer; + buffer += spaceInBuffer; + bufferSize -= spaceInBuffer; + } + this->BufferIsFull(fStartPut, this->GetCurrentOffset()); // resize buffer + resizedSpaceLeft = this->GetSpaceLeft(); + if (spaceLeft == resizedSpaceLeft) // couldn't resize, nothing left to do + { + return; // done. There is either nothing to do or nothing we can do because the BufferIsFull + } + spaceLeft = resizedSpaceLeft; + spaceInBuffer = spaceLeft - 1; + } + + //copy the remaining chunk into the buffer + ::memcpy(fCurrentPut, buffer, bufferSize); + fCurrentPut += bufferSize; + fBytesWritten += bufferSize; + +} + +//Puts a printf-style formatted string; except that the NUL terminator is not written. If the buffer is too small, returns false and does not +//Alter the buffer. Will not count the '\0' terminator as among the bytes written +Bool16 StringFormatter::PutFmtStr(const char *fmt, ...) +{ + Assert(fmt != NULL); + + va_list args; + for(;;) + { + va_start(args,fmt); + int length = ::vsnprintf(fCurrentPut, this->GetSpaceLeft(), fmt, args); + va_end(args); + + if (length < 0) + return false; + if (static_cast(length) >= this->GetSpaceLeft()) //was not able to write all the output + { + if (this->BufferIsFull(fStartPut, this->GetCurrentOffset())) + continue; + //can only output a portion of the string + UInt32 bytesWritten = fEndPut - fCurrentPut - 1; //We don't want to include the NUL terminator + fBytesWritten += bytesWritten; + fCurrentPut += bytesWritten; + return false; + } + else + { + fBytesWritten += length; + fCurrentPut += length; + } + return true; + } +} + diff --git a/CommonUtilitiesLib/StringFormatter.h b/CommonUtilitiesLib/StringFormatter.h new file mode 100644 index 0000000..5cf1292 --- /dev/null +++ b/CommonUtilitiesLib/StringFormatter.h @@ -0,0 +1,167 @@ +/* + * + * @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: StringFormatter.h + + Contains: Utility class for formatting text to a buffer. + Construct object with a buffer, then call one + of many Put methods to write into that buffer. + + + +*/ + +#ifndef __STRINGFORMATTER_H__ +#define __STRINGFORMATTER_H__ + +#include +#include "StrPtrLen.h" +#include "MyAssert.h" + + +//Use a class like the ResizeableStringFormatter if you want a buffer that will dynamically grow +class StringFormatter +{ + public: + + //pass in a buffer and length for writing + StringFormatter(char *buffer, UInt32 length) : fCurrentPut(buffer), + fStartPut(buffer), + fEndPut(buffer + length), + fBytesWritten(0) {} + + StringFormatter(StrPtrLen &buffer) : fCurrentPut(buffer.Ptr), + fStartPut(buffer.Ptr), + fEndPut(buffer.Ptr + buffer.Len), + fBytesWritten(0) {} + virtual ~StringFormatter() {} + + void Set(char *buffer, UInt32 length) { fCurrentPut = buffer; + fStartPut = buffer; + fEndPut = buffer + length; + fBytesWritten= 0; + } + + //"erases" all data in the output stream save this number + void Reset(UInt32 inNumBytesToLeave = 0) + { fCurrentPut = fStartPut + inNumBytesToLeave; } + + //Object does no bounds checking on the buffer. That is your responsibility! + //Put truncates to the buffer size + void Put(const SInt32 num); + void Put(char* buffer, UInt32 bufferSize); + void Put(char* str) { Put(str, strlen(str)); } + void Put(const StrPtrLen &str) { Put(str.Ptr, str.Len); } + void PutSpace() { PutChar(' '); } + void PutEOL() { Put(sEOL, sEOLLen); } + void PutChar(char c) { Put(&c, 1); } + void PutTerminator() { PutChar('\0'); } + + //Writes a printf style formatted string + Bool16 PutFmtStr(const char *fmt, ...); + + + //the number of characters in the buffer + inline UInt32 GetCurrentOffset(); + inline UInt32 GetSpaceLeft(); + inline UInt32 GetTotalBufferSize(); + char* GetCurrentPtr() { return fCurrentPut; } + char* GetBufPtr() { return fStartPut; } + + // Counts total bytes that have been written to this buffer (increments + // even when the buffer gets reset) + void ResetBytesWritten() { fBytesWritten = 0; } + UInt32 GetBytesWritten() { return fBytesWritten; } + + inline void PutFilePath(StrPtrLen *inPath, StrPtrLen *inFileName); + inline void PutFilePath(char *inPath, char *inFileName); + + //Return a NEW'd copy of the buffer as a C string + char *GetAsCString() + { + StrPtrLen str(fStartPut, this->GetCurrentOffset()); + return str.GetAsCString(); + } + + protected: + + //If you fill up the StringFormatter buffer, this function will get called. By + //default, the function simply returns false. But derived objects can clear out the data, + //reset the buffer, and then returns true. + //Use the ResizeableStringFormatter if you want a buffer that will dynamically grow. + //Returns true if the buffer has been resized. + virtual Bool16 BufferIsFull(char* /*inBuffer*/, UInt32 /*inBufferLen*/) { return false; } + + char* fCurrentPut; + char* fStartPut; + char* fEndPut; + + // A way of keeping count of how many bytes have been written total + UInt32 fBytesWritten; + + static char* sEOL; + static UInt32 sEOLLen; +}; + +inline UInt32 StringFormatter::GetCurrentOffset() +{ + Assert(fCurrentPut >= fStartPut); + return (UInt32)(fCurrentPut - fStartPut); +} + +inline UInt32 StringFormatter::GetSpaceLeft() +{ + Assert(fEndPut >= fCurrentPut); + return (UInt32)(fEndPut - fCurrentPut); +} + +inline UInt32 StringFormatter::GetTotalBufferSize() +{ + Assert(fEndPut >= fStartPut); + return (UInt32)(fEndPut - fStartPut); +} + +inline void StringFormatter::PutFilePath(StrPtrLen *inPath, StrPtrLen *inFileName) +{ + if (inPath != NULL && inPath->Len > 0) + { + Put(inPath->Ptr, inPath->Len); + if (kPathDelimiterChar != inPath->Ptr[inPath->Len -1] ) + Put(kPathDelimiterString); + } + if (inFileName != NULL && inFileName->Len > 0) + Put(inFileName->Ptr, inFileName->Len); +} + +inline void StringFormatter::PutFilePath(char *inPath, char *inFileName) +{ + StrPtrLen pathStr(inPath); + StrPtrLen fileStr(inFileName); + + PutFilePath(&pathStr,&fileStr); +} + +#endif // __STRINGFORMATTER_H__ + diff --git a/CommonUtilitiesLib/StringParser.cpp b/CommonUtilitiesLib/StringParser.cpp new file mode 100644 index 0000000..d2ab72c --- /dev/null +++ b/CommonUtilitiesLib/StringParser.cpp @@ -0,0 +1,510 @@ +/* + * + * @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: StringParser.cpp + + Contains: Implementation of StringParser class. + + + +*/ + +#include "StringParser.h" + +UInt8 StringParser::sNonWordMask[] = +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //0-9 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //10-19 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //20-29 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //30-39 + 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, //40-49 - is a word + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //50-59 + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, //60-69 //stop on every character except a letter + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, //90-99 _ is a word + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, //120-129 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //130-139 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //140-149 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //150-159 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //160-169 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //170-179 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //180-189 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //190-199 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //200-209 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //210-219 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //220-229 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //230-239 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //240-249 + 1, 1, 1, 1, 1, 1 //250-255 +}; + +UInt8 StringParser::sWordMask[] = +{ + // Inverse of the above + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0-9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //10-19 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //30-39 + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, //40-49 - is a word + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, //60-69 //stop on every character except a letter + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //70-79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //80-89 + 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, //90-99 _ is a word + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //100-109 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //110-119 + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + +UInt8 StringParser::sDigitMask[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0-9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //10-19 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //30-39 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, //40-49 //stop on every character except a number + 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, //50-59 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + +UInt8 StringParser::sEOLMask[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0-9 + 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //30-39 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + +UInt8 StringParser::sWhitespaceMask[] = +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, //0-9 // stop on '\t' + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, //10-19 // '\r', \v', '\f' & '\n' + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //20-29 + 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, //30-39 // ' ' + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //40-49 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //50-59 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //60-69 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //70-79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //80-89 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //90-99 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //100-109 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //110-119 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //120-129 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //130-139 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //140-149 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //150-159 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //160-169 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //170-179 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //180-189 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //190-199 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //200-209 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //210-219 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //220-229 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //230-239 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //240-249 + 1, 1, 1, 1, 1, 1 //250-255 +}; + +UInt8 StringParser::sEOLWhitespaceMask[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 // \t is a stop + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, //30-39 ' ' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + + +UInt8 StringParser::sEOLWhitespaceQueryMask[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 // \t is a stop + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, //30-39 ' ' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, //60-69 ? is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + +void StringParser::ConsumeUntil(StrPtrLen* outString, char inStop) +{ + if (this->ParserIsEmpty(outString)) + return; + + char *originalStartGet = fStartGet; + + while ((fStartGet < fEndGet) && (*fStartGet != inStop)) + AdvanceMark(); + + if (outString != NULL) + { + outString->Ptr = originalStartGet; + outString->Len = fStartGet - originalStartGet; + } +} + +void StringParser::ConsumeUntil(StrPtrLen* outString, UInt8 *inMask) +{ + if (this->ParserIsEmpty(outString)) + return; + + char *originalStartGet = fStartGet; + + while ((fStartGet < fEndGet) && (!inMask[(unsigned char) (*fStartGet)]))//make sure inMask is indexed with an unsigned char + AdvanceMark(); + + if (outString != NULL) + { + outString->Ptr = originalStartGet; + outString->Len = fStartGet - originalStartGet; + } +} + +void StringParser::ConsumeLength(StrPtrLen* spl, SInt32 inLength) +{ + if (this->ParserIsEmpty(spl)) + return; + + //sanity check to make sure we aren't being told to run off the end of the + //buffer + if ((fEndGet - fStartGet) < inLength) + inLength = fEndGet - fStartGet; + + if (spl != NULL) + { + spl->Ptr = fStartGet; + spl->Len = inLength; + } + if (inLength > 0) + { + for (short i=0; iParserIsEmpty(outString)) + return 0; + + UInt32 theValue = 0; + char *originalStartGet = fStartGet; + + while ((fStartGet < fEndGet) && (*fStartGet >= '0') && (*fStartGet <= '9')) + { + theValue = (theValue * 10) + (*fStartGet - '0'); + AdvanceMark(); + } + + if (outString != NULL) + { + outString->Ptr = originalStartGet; + outString->Len = fStartGet - originalStartGet; + } + return theValue; +} + +Float32 StringParser::ConsumeFloat() +{ + if (this->ParserIsEmpty(NULL)) + return 0.0; + + Float32 theFloat = 0; + while ((fStartGet < fEndGet) && (*fStartGet >= '0') && (*fStartGet <= '9')) + { + theFloat = (theFloat * 10) + (*fStartGet - '0'); + AdvanceMark(); + } + if ((fStartGet < fEndGet) && (*fStartGet == '.')) + AdvanceMark(); + Float32 multiplier = (Float32) .1; + while ((fStartGet < fEndGet) && (*fStartGet >= '0') && (*fStartGet <= '9')) + { + theFloat += (multiplier * (*fStartGet - '0')); + multiplier *= (Float32).1; + + AdvanceMark(); + } + return theFloat; +} + +Float32 StringParser::ConsumeNPT() +{ + if (this->ParserIsEmpty(NULL)) + return 0.0; + + Float32 valArray[4] = {0, 0, 0, 0}; + Float32 divArray[4] = {1, 1, 1, 1}; + UInt32 valType = 0; // 0 == npt-sec, 1 == npt-hhmmss + UInt32 index; + + for (index = 0; index < 4; index ++) + { + while ((fStartGet < fEndGet) && (*fStartGet >= '0') && (*fStartGet <= '9')) + { + valArray[index] = (valArray[index] * 10) + (*fStartGet - '0'); + divArray[index] *= 10; + AdvanceMark(); + } + + if (fStartGet >= fEndGet || valType == 0 && index >= 1) + break; + + if (*fStartGet == '.' && valType == 0 && index == 0) + ; + else if (*fStartGet == ':' && index < 2) + valType = 1; + else if (*fStartGet == '.' && index == 2) + ; + else + break; + AdvanceMark(); + } + + if (valType == 0) + return valArray[0] + (valArray[1] / divArray[1]); + else + return (valArray[0] * 3600) + (valArray[1] * 60) + valArray[2] + (valArray[3] / divArray[3]); +} + + +Bool16 StringParser::Expect(char stopChar) +{ + if (this->ParserIsEmpty(NULL)) + return false; + + if (fStartGet >= fEndGet) + return false; + if(*fStartGet != stopChar) + return false; + else + { + AdvanceMark(); + return true; + } +} +Bool16 StringParser::ExpectEOL() +{ + if (this->ParserIsEmpty(NULL)) + return false; + + //This function processes all legal forms of HTTP / RTSP eols. + //They are: \r (alone), \n (alone), \r\n + Bool16 retVal = false; + if ((fStartGet < fEndGet) && ((*fStartGet == '\r') || (*fStartGet == '\n'))) + { + retVal = true; + AdvanceMark(); + //check for a \r\n, which is the most common EOL sequence. + if ((fStartGet < fEndGet) && ((*(fStartGet - 1) == '\r') && (*fStartGet == '\n'))) + AdvanceMark(); + } + return retVal; +} + +void StringParser::ConsumeEOL(StrPtrLen* outString) +{ + if (this->ParserIsEmpty(outString)) + return; + + //This function processes all legal forms of HTTP / RTSP eols. + //They are: \r (alone), \n (alone), \r\n + char *originalStartGet = fStartGet; + + if ((fStartGet < fEndGet) && ((*fStartGet == '\r') || (*fStartGet == '\n'))) + { + AdvanceMark(); + //check for a \r\n, which is the most common EOL sequence. + if ((fStartGet < fEndGet) && ((*(fStartGet - 1) == '\r') && (*fStartGet == '\n'))) + AdvanceMark(); + } + + if (outString != NULL) + { + outString->Ptr = originalStartGet; + outString->Len = fStartGet - originalStartGet; + } +} + +void StringParser::UnQuote(StrPtrLen* outString) +{ + // If a string is contained within double or single quotes + // then UnQuote() will remove them. - [sfu] + + // sanity check + if (outString->Ptr == NULL || outString->Len < 2) + return; + + // remove begining quote if it's there. + if (outString->Ptr[0] == '"' || outString->Ptr[0] == '\'') + { + outString->Ptr++; outString->Len--; + } + // remove ending quote if it's there. + if ( outString->Ptr[outString->Len-1] == '"' || + outString->Ptr[outString->Len-1] == '\'' ) + { + outString->Len--; + } +} + +void StringParser::AdvanceMark() +{ + if (this->ParserIsEmpty(NULL)) + return; + + if ((*fStartGet == '\n') || ((*fStartGet == '\r') && (fStartGet[1] != '\n'))) + { + // we are progressing beyond a line boundary (don't count \r\n twice) + fCurLineNumber++; + } + fStartGet++; +} + +#if STRINGPARSERTESTING +Bool16 StringParser::Test() +{ + static char* string1 = "RTSP 200 OK\r\nContent-Type: MeowMix\r\n\t \n3450"; + + StrPtrLen theString(string1, strlen(string1)); + + StringParser victim(&theString); + + StrPtrLen rtsp; + SInt32 theInt = victim.ConsumeInteger(); + if (theInt != 0) + return false; + victim.ConsumeWord(&rtsp); + if ((rtsp.len != 4) && (strncmp(rtsp.Ptr, "RTSP", 4) != 0)) + return false; + + victim.ConsumeWhiteSpace(); + theInt = victim.ConsumeInteger(); + if (theInt != 200) + return false; + + return true; +} +#endif diff --git a/CommonUtilitiesLib/StringParser.h b/CommonUtilitiesLib/StringParser.h new file mode 100644 index 0000000..c992511 --- /dev/null +++ b/CommonUtilitiesLib/StringParser.h @@ -0,0 +1,189 @@ +/* + * + * @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: StringParser.h + + Contains: A couple of handy utilities for parsing a stream. + + + +*/ + + +#ifndef __STRINGPARSER_H__ +#define __STRINGPARSER_H__ + +#include "StrPtrLen.h" +#include "MyAssert.h" + +#define STRINGPARSERTESTING 0 + + +class StringParser +{ + public: + + StringParser(StrPtrLen *inStream) + : fStartGet(inStream == NULL ? NULL : inStream->Ptr), + fEndGet(inStream == NULL ? NULL : inStream->Ptr + inStream->Len), + fCurLineNumber(1), + fStream(inStream) {} + ~StringParser() {} + + // Built-in masks for common stop conditions + static UInt8 sDigitMask[]; // stop when you hit a digit + static UInt8 sWordMask[]; // stop when you hit a word + static UInt8 sEOLMask[]; // stop when you hit an eol + static UInt8 sEOLWhitespaceMask[]; // stop when you hit an EOL or whitespace + static UInt8 sEOLWhitespaceQueryMask[]; // stop when you hit an EOL, ? or whitespace + + static UInt8 sWhitespaceMask[]; // skip over whitespace + + + //GetBuffer: + //Returns a pointer to the string object + StrPtrLen* GetStream() { return fStream; } + + //Expect: + //These functions consume the given token/word if it is in the stream. + //If not, they return false. + //In all other situations, true is returned. + //NOTE: if these functions return an error, the object goes into a state where + //it cannot be guarenteed to function correctly. + Bool16 Expect(char stopChar); + Bool16 ExpectEOL(); + + //Returns the next word + void ConsumeWord(StrPtrLen* outString = NULL) + { ConsumeUntil(outString, sNonWordMask); } + + //Returns all the data before inStopChar + void ConsumeUntil(StrPtrLen* outString, char inStopChar); + + //Returns whatever integer is currently in the stream + UInt32 ConsumeInteger(StrPtrLen* outString = NULL); + Float32 ConsumeFloat(); + Float32 ConsumeNPT(); + + //Keeps on going until non-whitespace + void ConsumeWhitespace() + { ConsumeUntil(NULL, sWhitespaceMask); } + + //Assumes 'stop' is a 255-char array of booleans. Set this array + //to a mask of what the stop characters are. true means stop character. + //You may also pass in one of the many prepackaged masks defined above. + void ConsumeUntil(StrPtrLen* spl, UInt8 *stop); + + + //+ rt 8.19.99 + //returns whatever is avaliable until non-whitespace + void ConsumeUntilWhitespace(StrPtrLen* spl = NULL) + { ConsumeUntil( spl, sEOLWhitespaceMask); } + + void ConsumeUntilDigit(StrPtrLen* spl = NULL) + { ConsumeUntil( spl, sDigitMask); } + + void ConsumeLength(StrPtrLen* spl, SInt32 numBytes); + + void ConsumeEOL(StrPtrLen* outString); + + //GetThru: + //Works very similar to ConsumeUntil except that it moves past the stop token, + //and if it can't find the stop token it returns false + inline Bool16 GetThru(StrPtrLen* spl, char stop); + inline Bool16 GetThruEOL(StrPtrLen* spl); + inline Bool16 ParserIsEmpty(StrPtrLen* outString); + //Returns the current character, doesn't move past it. + inline char PeekFast() { if (fStartGet) return *fStartGet; else return '\0'; } + char operator[](int i) { Assert((fStartGet+i) < fEndGet);return fStartGet[i]; } + + //Returns some info about the stream + UInt32 GetDataParsedLen() + { Assert(fStartGet >= fStream->Ptr); return (UInt32)(fStartGet - fStream->Ptr); } + UInt32 GetDataReceivedLen() + { Assert(fEndGet >= fStream->Ptr); return (UInt32)(fEndGet - fStream->Ptr); } + UInt32 GetDataRemaining() + { Assert(fEndGet >= fStartGet); return (UInt32)(fEndGet - fStartGet); } + char* GetCurrentPosition() { return fStartGet; } + int GetCurrentLineNumber() { return fCurLineNumber; } + + // A utility for extracting quotes from the start and end of a parsed + // string. (Warning: Do not call this method if you allocated your own + // pointer for the Ptr field of the StrPtrLen class.) - [sfu] + // + // Not sure why this utility is here and not in the StrPtrLen class - [jm] + static void UnQuote(StrPtrLen* outString); + + +#if STRINGPARSERTESTING + static Bool16 Test(); +#endif + + private: + + void AdvanceMark(); + + //built in masks for some common stop conditions + static UInt8 sNonWordMask[]; + + char* fStartGet; + char* fEndGet; + int fCurLineNumber; + StrPtrLen* fStream; + +}; + + +Bool16 StringParser::GetThru(StrPtrLen* outString, char inStopChar) +{ + ConsumeUntil(outString, inStopChar); + return Expect(inStopChar); +} + +Bool16 StringParser::GetThruEOL(StrPtrLen* outString) +{ + ConsumeUntil(outString, sEOLMask); + return ExpectEOL(); +} + +Bool16 StringParser::ParserIsEmpty(StrPtrLen* outString) +{ + if (NULL == fStartGet || NULL == fEndGet) + { + if (NULL != outString) + { outString->Ptr = NULL; + outString->Len = 0; + } + + return true; + } + + Assert(fStartGet <= fEndGet); + + return false; // parser ok to parse +} + + +#endif // __STRINGPARSER_H__ diff --git a/CommonUtilitiesLib/StringTranslator.cpp b/CommonUtilitiesLib/StringTranslator.cpp new file mode 100644 index 0000000..1b75427 --- /dev/null +++ b/CommonUtilitiesLib/StringTranslator.cpp @@ -0,0 +1,281 @@ +/* + * + * @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: StringTranslator.cpp + + Contains: implements StringTranslator class + + +*/ + + +#include +#include +#include +#include "StringTranslator.h" +#include "MyAssert.h" +#include "SafeStdLib.h" +#include + +SInt32 StringTranslator::DecodeURL(const char* inSrc, SInt32 inSrcLen, char* ioDest, SInt32 inDestLen) +{ + // return the number of chars written to ioDest + // or OS_BadURLFormat in the case of any error. + + // inSrcLen must be > inSrcLen and the first character must be a '/' + if ( inSrcLen <= 0 || *inSrc != '/' ) + return OS_BadURLFormat; + + //Assert(*inSrc == '/'); //For the purposes of '..' stripping, we assume first char is a / + + SInt32 theLengthWritten = 0; + int tempChar = 0; + int numDotChars = 0; + Bool16 inQuery = false; + + while (inSrcLen > 0) + { + if (theLengthWritten == inDestLen) + return OS_NotEnoughSpace; + + if (*inSrc == '?') + inQuery = true; + + if (*inSrc == '%') + { + if (inSrcLen < 3) + return OS_BadURLFormat; + + //if there is a special character in this URL, extract it + char tempbuff[3]; + inSrc++; + if (!isxdigit(*inSrc)) + return OS_BadURLFormat; + tempbuff[0] = *inSrc; + inSrc++; + if (!isxdigit(*inSrc)) + return OS_BadURLFormat; + tempbuff[1] = *inSrc; + inSrc++; + tempbuff[2] = '\0'; + sscanf(tempbuff, "%x", &tempChar); + Assert(tempChar < 256); + inSrcLen -= 3; + } + else if (*inSrc == '\0') + return OS_BadURLFormat; + else + { + // Any normal character just gets copied into the destination buffer + tempChar = *inSrc; + inSrcLen--; + inSrc++; + } + + if (!inQuery) // don't do seperator parsing or .. parsing in query + { + // + // If we are in a file system that uses a character besides '/' as a + // path delimiter, we should not allow this character to appear in the URL. + // In URLs, only '/' has control meaning. + if ((tempChar == kPathDelimiterChar) && (kPathDelimiterChar != '/')) + return OS_BadURLFormat; + + // Check to see if this character is a path delimiter ('/') + // If so, we need to further check whether backup is required due to + // dot chars that need to be stripped + if ((tempChar == '/') && (numDotChars <= 2) && (numDotChars > 0)) + { + Assert(theLengthWritten > numDotChars); + ioDest -= (numDotChars + 1); + theLengthWritten -= (numDotChars + 1); + } + + *ioDest = tempChar; + + // Note that because we are doing this dotchar check here, we catch dotchars + // even if they were encoded to begin with. + + // If this is a . , check to see if it's one of those cases where we need to track + // how many '.'s in a row we've gotten, for stripping out later on + if (*ioDest == '.') + { + Assert(theLengthWritten > 0);//first char is always '/', right? + if ((numDotChars == 0) && (*(ioDest - 1) == '/')) + numDotChars++; + else if ((numDotChars > 0) && (*(ioDest - 1) == '.')) + numDotChars++; + } + // If this isn't a dot char, we don't care at all, reset this value to 0. + else + numDotChars = 0; + } + else + *ioDest = tempChar; + + theLengthWritten++; + ioDest++; + } + + // Before returning, "strip" any trailing "." or ".." by adjusting "theLengthWritten + // accordingly + if (numDotChars <= 2) + theLengthWritten -= numDotChars; + return theLengthWritten; +} + +SInt32 StringTranslator::EncodeURL(const char* inSrc, SInt32 inSrcLen, char* ioDest, SInt32 inDestLen) +{ + // return the number of chars written to ioDest + + SInt32 theLengthWritten = 0; + + while (inSrcLen > 0) + { + if (theLengthWritten == inDestLen) + return OS_NotEnoughSpace; + + // + // Always encode 8-bit characters + if ((unsigned char)*inSrc > 127) + { + if (inDestLen - theLengthWritten < 3) + return OS_NotEnoughSpace; + + qtss_sprintf(ioDest,"%%%X",(unsigned char)*inSrc); + ioDest += 3; + theLengthWritten += 3; + inSrc++; + inSrcLen--; + continue; + } + + // + // Only encode certain 7-bit characters + switch (*inSrc) + { + // This is the URL RFC list of illegal characters. + case (' '): + case ('\r'): + case ('\n'): + case ('\t'): + case ('<'): + case ('>'): + case ('#'): + case ('%'): + case ('{'): + case ('}'): + case ('|'): + case ('\\'): + case ('^'): + case ('~'): + case ('['): + case (']'): + case ('`'): + case (';'): +// case ('/'): // this isn't really an illegal character, it's legitimatly used as a seperator in the url + case ('?'): + case ('@'): + case ('='): + case ('&'): + case ('$'): + case ('"'): + { + if ((inDestLen - theLengthWritten) < 3) + return OS_NotEnoughSpace; + + qtss_sprintf(ioDest,"%%%X",(int)*inSrc); + ioDest += 3; + theLengthWritten += 3; + break; + } + default: + { + *ioDest = *inSrc; + ioDest++; + theLengthWritten++; + } + } + + inSrc++; + inSrcLen--; + } + + return theLengthWritten; +} + +void StringTranslator::DecodePath(char* inSrc, UInt32 inSrcLen) +{ + for (UInt32 x = 0; x < inSrcLen; x++) + if (inSrc[x] == '/') + inSrc[x] = kPathDelimiterChar; +} + + + +#if STRINGTRANSLATORTESTING +Bool16 StringTranslator::Test() +{ + //static char* test1 = "/%5D%3f%7eAveryweird%7C/and/long/path/ya/%5d%3F%7eAveryweird%7C/and/long/p%40/ya/%5D%3F%7EAveryweird%7C/and/long/path/ya/%5D%3F%7EAveryweird%7C/and/long/path/ya/%2560%2526a%20strange%3B%23%3D%25filename" + static char dest[1000]; + static char* test1 = "/Hello%23%20 I want%28don't%29"; + SInt32 err = DecodeURL(test1, strlen(test1), dest, 1000); + if (err != 22) + return false; + if (strcmp(dest, "/Hello# I want(don't)") != 0) + return false; + err = DecodeURL(test1, 15, dest, 1000); + if (err != 11) + return false; + if (strncmp(dest, "/Hello# I ", 11) != 0) + return false; + err = DecodeURL(test1, 50, dest, 1000); + if (err != OS_BadURLFormat) + return false; + if (strncmp(dest, "/Hello# I want(don't)", 22) != 0) + if (strcmp(dest, "/Hello# I want(don't)") != 0) + return false; + + err = DecodeURL(test1, strlen(test1), dest, 20); + if (err != OS_BadURLFormat) + return false; + static char* test2 = "/THis%2h is a bad %28 URL!"; + err = DecodeURL(test2, strlen(test2), dest, 1000); + if (err != OS_BadURLFormat) + return false; + + static char* test3 = "/...whoa/../is./meeee%3e/./"; + static char* test4 = "/I want/to/sleep/.."; + static char* test5 = "/ve....rr/tire.././../.."; + static char* test6 = "/../beginnings/and/."; + static char* test7 = "/../begin/%2e./../nin/%2e/gs/an/%2e%2e/fklf/%2e%2e./dfds%2e/%2e%2e/d/.%2e"; + err = DecodeURL(test3, strlen(test3), dest, 1000); + err = DecodeURL(test4, strlen(test4), dest, 1000); + err = DecodeURL(test5, strlen(test5), dest, 1000); + err = DecodeURL(test6, strlen(test6), dest, 1000); + err = DecodeURL(test7, strlen(test7), dest, 1000); + return true; +} +#endif diff --git a/CommonUtilitiesLib/StringTranslator.h b/CommonUtilitiesLib/StringTranslator.h new file mode 100644 index 0000000..07497c3 --- /dev/null +++ b/CommonUtilitiesLib/StringTranslator.h @@ -0,0 +1,79 @@ +/* + * + * @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@ + * + */ + /* + Contains: Static utilities for translating strings from one encoding scheme to + another. For example, routines for encoding and decoding URLs + + + +*/ + +#ifndef __STRINGTRANSLATOR_H__ +#define __STRINGTRANSLATOR_H__ + +#include "OSHeaders.h" + +#define STRINGTRANSLATORTESTING 0 + +class StringTranslator +{ + public: + + //DecodeURL: + // + // This function does 2 things: Decodes % encoded characters in URLs, and strips out + // any ".." or "." complete filenames from the URL. Writes the result into ioDest. + // + //If successful, returns the length of the destination string. + //If failure, returns an OS errorcode: OS_BadURLFormat, OS_NotEnoughSpace + + static SInt32 DecodeURL(const char* inSrc, SInt32 inSrcLen, char* ioDest, SInt32 inDestLen); + + //EncodeURL: + // + // This function takes a character string and % encodes any special URL characters. + // In general, the output buffer will be longer than the input buffer, so caller should + // be aware of that. + // + //If successful, returns the length of the destination string. + //If failure, returns an QTSS errorcode: OS_NotEnoughSpace + // + // If function returns E2BIG, ioDest will be valid, but will contain + // only the portion of the URL that fit. + static SInt32 EncodeURL(const char* inSrc, SInt32 inSrcLen, char* ioDest, SInt32 inDestLen); + + // DecodePath: + // + // This function converts "network" or "URL" path delimiters (the '/' char) to + // the path delimiter of the local file system. It does this conversion in place, + // so the old data will be overwritten + static void DecodePath(char* inSrc, UInt32 inSrcLen); + +#if STRINGTRANSLATORTESTING + static Bool16 Test(); +#endif +}; +#endif // __STRINGTRANSLATOR_H__ + diff --git a/CommonUtilitiesLib/TCPListenerSocket.cpp b/CommonUtilitiesLib/TCPListenerSocket.cpp new file mode 100644 index 0000000..863461e --- /dev/null +++ b/CommonUtilitiesLib/TCPListenerSocket.cpp @@ -0,0 +1,232 @@ +/* + * + * @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: TCPListenerSocket.cpp + + Contains: implements TCPListenerSocket class + + +*/ + +#ifndef __Win32__ +#include +#include +#include +#include +#include +#ifndef __Win32__ + #include "QTSSModuleUtils.h" +#endif + +#endif + +#include + +#include "TCPListenerSocket.h" +#include "Task.h" + + + +OS_Error TCPListenerSocket::Listen(UInt32 queueLength) +{ + if (fFileDesc == EventContext::kInvalidFileDesc) + return EBADF; + + int err = ::listen(fFileDesc, queueLength); + if (err != 0) + return (OS_Error)OSThread::GetErrno(); + return OS_NoErr; +} + +OS_Error TCPListenerSocket::Initialize(UInt32 addr, UInt16 port) +{ + OS_Error err = this->TCPSocket::Open(); + if (0 == err) do + { + // set SO_REUSEADDR socket option before calling bind. +#ifndef __Win32__ + // this causes problems on NT (multiple processes can bind simultaneously), + // so don't do it on NT. + this->ReuseAddr(); +#endif + err = this->Bind(addr, port); + if (err != 0) break; // don't assert this is just a port already in use. + + // + // Unfortunately we need to advertise a big buffer because our TCP sockets + // can be used for incoming broadcast data. This could force the server + // to run out of memory faster if it gets bogged down, but it is unavoidable. + this->SetSocketRcvBufSize(96 * 1024); + err = this->Listen(kListenQueueLength); + AssertV(err == 0, OSThread::GetErrno()); + if (err != 0) break; + + } while (false); + + return err; +} + +void TCPListenerSocket::ProcessEvent(int /*eventBits*/) +{ + //we are executing on the same thread as every other + //socket, so whatever you do here has to be fast. + + struct sockaddr_in addr; +#if __Win32__ || __osf__ || __sgi__ || __hpux__ + int size = sizeof(addr); +#else + socklen_t size = sizeof(addr); +#endif + Task* theTask = NULL; + TCPSocket* theSocket = NULL; + + //fSocket data member of TCPSocket. + int osSocket = accept(fFileDesc, (struct sockaddr*)&addr, &size); + +//test osSocket = -1; + if (osSocket == -1) + { + //take a look at what this error is. + int acceptError = OSThread::GetErrno(); + if (acceptError == EAGAIN) + { + //If it's EAGAIN, there's nothing on the listen queue right now, + //so modwatch and return + this->RequestEvent(EV_RE); + return; + } + +//test acceptError = ENFILE; +//test acceptError = EINTR; +//test acceptError = ENOENT; + + //if these error gets returned, we're out of file desciptors, + //the server is going to be failing on sockets, logs, qtgroups and qtuser auth file accesses and movie files. The server is not functional. + if (acceptError == EMFILE || acceptError == ENFILE) + { +#ifndef __Win32__ + + QTSSModuleUtils::LogErrorStr(qtssFatalVerbosity, "Out of File Descriptors. Set max connections lower and check for competing usage from other processes. Exiting."); +#endif + + exit (EXIT_FAILURE); + } + else + { + char errStr[256]; + errStr[sizeof(errStr) -1] = 0; + qtss_snprintf(errStr, sizeof(errStr) -1, "accept error = %d '%s' on socket. Clean up and continue.", acceptError, strerror(acceptError)); + WarnV( (acceptError == 0), errStr); + + theTask = this->GetSessionTask(&theSocket); + if (theTask == NULL) + { + close(osSocket); + } + else + { + theTask->Signal(Task::kKillEvent); // just clean up the task + } + + if (theSocket) + theSocket->fState &= ~kConnected; // turn off connected state + + return; + } + } + + theTask = this->GetSessionTask(&theSocket); + if (theTask == NULL) + { //this should be a disconnect. do an ioctl call? + close(osSocket); + if (theSocket) + theSocket->fState &= ~kConnected; // turn off connected state + } + else + { + Assert(osSocket != EventContext::kInvalidFileDesc); + + //set options on the socket + //we are a server, always disable nagle algorithm + int one = 1; + int err = ::setsockopt(osSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&one, sizeof(int)); + AssertV(err == 0, OSThread::GetErrno()); + + err = ::setsockopt(osSocket, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(int)); + AssertV(err == 0, OSThread::GetErrno()); + + int sndBufSize = 96L * 1024L; + err = ::setsockopt(osSocket, SOL_SOCKET, SO_SNDBUF, (char*)&sndBufSize, sizeof(int)); + AssertV(err == 0, OSThread::GetErrno()); + + //setup the socket. When there is data on the socket, + //theTask will get an kReadEvent event + theSocket->Set(osSocket, &addr); + theSocket->InitNonBlocking(osSocket); + theSocket->SetTask(theTask); + theSocket->RequestEvent(EV_RE); + + theTask->SetThreadPicker(Task::GetBlockingTaskThreadPicker()); //The RTSP Task processing threads + } + + + + if (fSleepBetweenAccepts) + { + // We are at our maximum supported sockets + // slow down so we have time to process the active ones (we will respond with errors or service). + // wake up and execute again after sleeping. The timer must be reset each time through + //qtss_printf("TCPListenerSocket slowing down\n"); + this->SetIdleTimer(kTimeBetweenAcceptsInMsec); //sleep 1 second + } + else + { + // sleep until there is a read event outstanding (another client wants to connect) + //qtss_printf("TCPListenerSocket normal speed\n"); + this->RequestEvent(EV_RE); + } + + fOutOfDescriptors = false; // always false for now we don't properly handle this elsewhere in the code +} + +SInt64 TCPListenerSocket::Run() +{ + EventFlags events = this->GetEvents(); + + // + // ProcessEvent cannot be going on when this object gets deleted, because + // the resolve / release mechanism of EventContext will ensure this thread + // will block before destructing stuff. + if (events & Task::kKillEvent) + return -1; + + + //This function will get called when we have run out of file descriptors. + //All we need to do is check the listen queue to see if the situation has + //cleared up. + (void)this->GetEvents(); + this->ProcessEvent(Task::kReadEvent); + return 0; +} diff --git a/CommonUtilitiesLib/TCPListenerSocket.h b/CommonUtilitiesLib/TCPListenerSocket.h new file mode 100644 index 0000000..4f74e85 --- /dev/null +++ b/CommonUtilitiesLib/TCPListenerSocket.h @@ -0,0 +1,87 @@ +/* + * + * @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: TCPListenerSocket.h + + Contains: A TCP listener socket. When a new connection comes in, the listener + attempts to assign the new connection to a socket object and a Task + object. Derived classes must implement a method of getting new + Task & socket objects + + +*/ + + +#ifndef __TCPLISTENERSOCKET_H__ +#define __TCPLISTENERSOCKET_H__ + +#include "TCPSocket.h" +#include "IdleTask.h" + +class TCPListenerSocket : public TCPSocket, public IdleTask +{ + public: + + TCPListenerSocket() : TCPSocket(NULL, Socket::kNonBlockingSocketType), IdleTask(), + fAddr(0), fPort(0), fOutOfDescriptors(false), fSleepBetweenAccepts(false) {this->SetTaskName("TCPListenerSocket");} + virtual ~TCPListenerSocket() {} + + // + // Send a TCPListenerObject a Kill event to delete it. + + //addr = listening address. port = listening port. Automatically + //starts listening + OS_Error Initialize(UInt32 addr, UInt16 port); + + //You can query the listener to see if it is failing to accept + //connections because the OS is out of descriptors. + Bool16 IsOutOfDescriptors() { return fOutOfDescriptors; } + + void SlowDown() { fSleepBetweenAccepts = true; } + void RunNormal() { fSleepBetweenAccepts = false; } + //derived object must implement a way of getting tasks & sockets to this object + virtual Task* GetSessionTask(TCPSocket** outSocket) = 0; + + virtual SInt64 Run(); + + private: + + enum + { + kTimeBetweenAcceptsInMsec = 1000, //UInt32 + kListenQueueLength = 128 //UInt32 + }; + + virtual void ProcessEvent(int eventBits); + OS_Error Listen(UInt32 queueLength); + + UInt32 fAddr; + UInt16 fPort; + + Bool16 fOutOfDescriptors; + Bool16 fSleepBetweenAccepts; +}; +#endif // __TCPLISTENERSOCKET_H__ + diff --git a/CommonUtilitiesLib/TCPSocket.cpp b/CommonUtilitiesLib/TCPSocket.cpp new file mode 100644 index 0000000..4fa790f --- /dev/null +++ b/CommonUtilitiesLib/TCPSocket.cpp @@ -0,0 +1,120 @@ +/* + * + * @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: TCPSocket.cpp + + Contains: implements TCPSocket class + + + +*/ + +#ifndef __Win32__ +#include +#include +#include +#endif + +#include + +#include "TCPSocket.h" +#include "SocketUtils.h" +#include "OS.h" + +#ifdef USE_NETLOG +#include +#endif + +void TCPSocket::SnarfSocket( TCPSocket & fromSocket ) +{ + // take the connection away from the other socket + // and use it as our own. + Assert(fFileDesc == EventContext::kInvalidFileDesc); + this->Set( fromSocket.fFileDesc, &fromSocket.fRemoteAddr ); + + // clear the old socket so he doesn't close and the like + struct sockaddr_in remoteaddr; + + ::memset( &remoteaddr, 0, sizeof( remoteaddr ) ); + + fromSocket.Set( EventContext::kInvalidFileDesc, &remoteaddr ); + + // get the event context too + this->SnarfEventContext( fromSocket ); + +} + +void TCPSocket::Set(int inSocket, struct sockaddr_in* remoteaddr) +{ + fRemoteAddr = *remoteaddr; + fFileDesc = inSocket; + + if ( inSocket != EventContext::kInvalidFileDesc ) + { + //make sure to find out what IP address this connection is actually occuring on. That + //way, we can report correct information to clients asking what the connection's IP is +#if __Win32__ || __osf__ || __sgi__ || __hpux__ + int len = sizeof(fLocalAddr); +#else + socklen_t len = sizeof(fLocalAddr); +#endif + int err = ::getsockname(fFileDesc, (struct sockaddr*)&fLocalAddr, &len); + AssertV(err == 0, OSThread::GetErrno()); + fState |= kBound; + fState |= kConnected; + } + else + fState = 0; +} + +StrPtrLen* TCPSocket::GetRemoteAddrStr() +{ + if (fRemoteStr.Len == kIPAddrBufSize) + SocketUtils::ConvertAddrToString(fRemoteAddr.sin_addr, &fRemoteStr); + return &fRemoteStr; +} + +OS_Error TCPSocket::Connect(UInt32 inRemoteAddr, UInt16 inRemotePort) +{ + ::memset(&fRemoteAddr, 0, sizeof(fRemoteAddr)); + fRemoteAddr.sin_family = AF_INET; /* host byte order */ + fRemoteAddr.sin_port = htons(inRemotePort); /* short, network byte order */ + fRemoteAddr.sin_addr.s_addr = htonl(inRemoteAddr); + + /* don't forget to error check the connect()! */ + int err = ::connect(fFileDesc, (sockaddr *)&fRemoteAddr, sizeof(fRemoteAddr)); + fState |= kConnected; + + if (err == -1) + { + fRemoteAddr.sin_port = 0; + fRemoteAddr.sin_addr.s_addr = 0; + return (OS_Error)OSThread::GetErrno(); + } + + return OS_NoErr; + +} + diff --git a/CommonUtilitiesLib/TCPSocket.h b/CommonUtilitiesLib/TCPSocket.h new file mode 100644 index 0000000..24ab1d6 --- /dev/null +++ b/CommonUtilitiesLib/TCPSocket.h @@ -0,0 +1,106 @@ +/* + * + * @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: TCPSocket.h + + Contains: TCP socket object + + + + +*/ + +#ifndef __TCPSOCKET_H__ +#define __TCPSOCKET_H__ + +#include +#include +#include "SafeStdLib.h" +#ifndef __Win32__ +#include +#include +#endif + +#include "Socket.h" +#include "Task.h" +#include "StrPtrLen.h" + +class TCPSocket : public Socket +{ + public: + + //TCPSocket takes an optional task object which will get notified when + //certain events happen on this socket. Those events are: + // + //S_DATA: Data is currently available on the socket. + //S_CONNECTIONCLOSING: Client is closing the connection. No longer necessary + // to call Close or Disconnect, Snd & Rcv will fail. + TCPSocket(Task *notifytask, UInt32 inSocketType) + : Socket(notifytask, inSocketType), + fRemoteStr(fRemoteBuffer, kIPAddrBufSize) {} + virtual ~TCPSocket() {} + + //Open + OS_Error Open() { return Socket::Open(SOCK_STREAM); } + + // Connect. Attempts to connect to the specified remote host. If this + // is a non-blocking socket, this function may return EINPROGRESS, in which + // case caller must wait for either an EV_RE or an EV_WR. You may call + // CheckAsyncConnect at any time, which will return OS_NoErr if the connect + // has completed, EINPROGRESS if it is still in progress, or an appropriate error + // if the connect failed. + OS_Error Connect(UInt32 inRemoteAddr, UInt16 inRemotePort); + //OS_Error CheckAsyncConnect(); + + // Basically a copy constructor for this object, also NULLs out the data + // in tcpSocket. + void SnarfSocket( TCPSocket & tcpSocket ); + + //ACCESSORS: + //Returns NULL if not currently available. + + UInt32 GetRemoteAddr() { return ntohl(fRemoteAddr.sin_addr.s_addr); } + UInt16 GetRemotePort() { return ntohs(fRemoteAddr.sin_port); } + //This function is NOT thread safe! + StrPtrLen* GetRemoteAddrStr(); + + protected: + + void Set(int inSocket, struct sockaddr_in* remoteaddr); + + enum + { + kIPAddrBufSize = 20 //UInt32 + }; + + struct sockaddr_in fRemoteAddr; + char fRemoteBuffer[kIPAddrBufSize]; + StrPtrLen fRemoteStr; + + + friend class TCPListenerSocket; +}; +#endif // __TCPSOCKET_H__ + diff --git a/CommonUtilitiesLib/Task.cpp b/CommonUtilitiesLib/Task.cpp new file mode 100644 index 0000000..5ee8dfc --- /dev/null +++ b/CommonUtilitiesLib/Task.cpp @@ -0,0 +1,415 @@ +/* + * + * @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: Task.cpp + + Contains: implements Task class + + +*/ + +#include "Task.h" +#include "OS.h" +#include "OSMemory.h" +#include "atomic.h" +#include "OSMutexRW.h" + + +unsigned int Task::sShortTaskThreadPicker = 0; +unsigned int Task::sBlockingTaskThreadPicker = 0; + +OSMutexRW TaskThreadPool::sMutexRW; +static char* sTaskStateStr="live_"; //Alive + +Task::Task() +: fEvents(0), fUseThisThread(NULL),fDefaultThread(NULL), fWriteLock(false), fTimerHeapElem(), fTaskQueueElem(), pickerToUse(&Task::sShortTaskThreadPicker) +{ +#if DEBUG + fInRunCount = 0; +#endif + this->SetTaskName("unknown"); + + fTaskQueueElem.SetEnclosingObject(this); + fTimerHeapElem.SetEnclosingObject(this); + +} + +void Task::SetTaskName(char* name) +{ + if (name == NULL) + return; + + ::strncpy(fTaskName,sTaskStateStr,sizeof(fTaskName)); + ::strncat(fTaskName,name,sizeof(fTaskName)); + fTaskName[sizeof(fTaskName) -1] = 0; //terminate in case it is longer than ftaskname. + +} + +Bool16 Task::Valid() +{ + if ( (this->fTaskName == NULL) + || (0 != ::strncmp(sTaskStateStr,this->fTaskName, 5)) + ) + { + if (TASK_DEBUG) qtss_printf(" Task::Valid Found invalid task = %p\n", (void *)this); + + return false; + } + + return true; +} + +Task::EventFlags Task::GetEvents() +{ + //Mask off every event currently in the mask except for the alive bit, of course, + //which should remain unaffected and unreported by this call. + EventFlags events = fEvents & kAliveOff; + (void)atomic_sub(&fEvents, events); + return events; +} + +void Task::Signal(EventFlags events) +{ + if (!this->Valid()) + return; + + //Fancy no mutex implementation. We atomically mask the new events into + //the event mask. Because atomic_or returns the old state of the mask, + //we only schedule this task once. + events |= kAlive; + EventFlags oldEvents = atomic_or(&fEvents, events); + if ((!(oldEvents & kAlive)) && (TaskThreadPool::sNumTaskThreads > 0)) + { + if (fDefaultThread != NULL && fUseThisThread == NULL) + fUseThisThread = fDefaultThread; + + if (fUseThisThread != NULL) + // Task needs to be placed on a particular thread. + { + + if (TASK_DEBUG) + { + if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); + qtss_printf("Task::Signal enque TaskName=%s fUseThisThread=%p q elem=%p enclosing=%p\n", fTaskName, (void *) fUseThisThread, (void *) &fTaskQueueElem, (void *) this); + if (TaskThreadPool::sTaskThreadArray[0] == fUseThisThread) qtss_printf("Task::Signal RTSP Thread running TaskName=%s \n", fTaskName); + } + + fUseThisThread->fTaskQueue.EnQueue(&fTaskQueueElem); + } + else + { + //find a thread to put this task on + unsigned int theThreadIndex = atomic_add( (unsigned int *) pickerToUse, 1); + + if (&Task::sShortTaskThreadPicker == pickerToUse) + { + theThreadIndex %= TaskThreadPool::sNumShortTaskThreads; + + if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s using Task::sShortTaskThreadPicker=%u numShortTaskThreads=%"_U32BITARG_" short task range=[0-%"_U32BITARG_"] thread index =%u \n",fTaskName, Task::sShortTaskThreadPicker, TaskThreadPool::sNumShortTaskThreads,TaskThreadPool::sNumShortTaskThreads -1, theThreadIndex); + } + else if (&Task::sBlockingTaskThreadPicker == pickerToUse) + { + theThreadIndex %= TaskThreadPool::sNumBlockingTaskThreads; + theThreadIndex += TaskThreadPool::sNumShortTaskThreads; //don't pick from lower non-blocking (short task) threads. + + if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s using Task::sBlockingTaskThreadPicker=%u numBlockingThreads=%"_U32BITARG_" blocking thread range=[%"_U32BITARG_"-%"_U32BITARG_"] thread index =%u \n",fTaskName, Task::sBlockingTaskThreadPicker, TaskThreadPool::sNumBlockingTaskThreads, TaskThreadPool::sNumShortTaskThreads, TaskThreadPool::sNumBlockingTaskThreads+TaskThreadPool::sNumShortTaskThreads-1, theThreadIndex); + } + else + { + if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); + + return; + } + + + if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); + if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s theThreadIndex=%u thread=%p q elem=%p enclosing=%p\n", fTaskName,theThreadIndex, (void *)TaskThreadPool::sTaskThreadArray[theThreadIndex],(void *) &fTaskQueueElem,(void *) this); + + + TaskThreadPool::sTaskThreadArray[theThreadIndex]->fTaskQueue.EnQueue(&fTaskQueueElem); + } + } + else + if (TASK_DEBUG) qtss_printf("Task::Signal sent to dead TaskName=%s q elem=%p enclosing=%p\n", fTaskName, (void *) &fTaskQueueElem, (void *) this); + + +} + + +void Task::GlobalUnlock() +{ + if (this->fWriteLock) + { this->fWriteLock = false; + TaskThreadPool::sMutexRW.Unlock(); + } +} + +void Task::SetThreadPicker(unsigned int* picker) +{ + pickerToUse = picker; + Assert(pickerToUse != NULL); + if (TASK_DEBUG) + { + if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); + + if (&Task::sShortTaskThreadPicker == pickerToUse) + { + qtss_printf("Task::SetThreadPicker sShortTaskThreadPicker for task=%s\n", fTaskName); + } + else if (&Task::sBlockingTaskThreadPicker == pickerToUse) + { + qtss_printf("Task::SetThreadPicker sBlockingTaskThreadPicker for task=%s\n",fTaskName); + } + else + { qtss_printf("Task::SetThreadPicker ERROR unknown picker for task=%s\n",fTaskName); + } + } + +} + + + + +void TaskThread::Entry() +{ + Task* theTask = NULL; + + while (true) + { + theTask = this->WaitForTask(); + + // + // WaitForTask returns NULL when it is time to quit + if (theTask == NULL || false == theTask->Valid() ) + return; + + Bool16 doneProcessingEvent = false; + + while (!doneProcessingEvent) + { + //If a task holds locks when it returns from its Run function, + //that would be catastrophic and certainly lead to a deadlock +#if DEBUG + Assert(this->GetNumLocksHeld() == 0); + Assert(theTask->fInRunCount == 0); + theTask->fInRunCount++; +#endif + theTask->fUseThisThread = NULL; // Each invocation of Run must independently + // request a specific thread. + SInt64 theTimeout = 0; + + if (theTask->fWriteLock) + { + OSMutexWriteLocker mutexLocker(&TaskThreadPool::sMutexRW); + if (TASK_DEBUG) qtss_printf("TaskThread::Entry run global locked TaskName=%s CurMSec=%.3f thread=%p task=%p\n", theTask->fTaskName, OS::StartTimeMilli_Float() ,(void *) this,(void *) theTask); + + theTimeout = theTask->Run(); + theTask->fWriteLock = false; + } + else + { + OSMutexReadLocker mutexLocker(&TaskThreadPool::sMutexRW); + if (TASK_DEBUG) qtss_printf("TaskThread::Entry run TaskName=%s CurMSec=%.3f thread=%p task=%p\n", theTask->fTaskName, OS::StartTimeMilli_Float(), (void *) this,(void *) theTask); + + theTimeout = theTask->Run(); + + } +#if DEBUG + Assert(this->GetNumLocksHeld() == 0); + theTask->fInRunCount--; + Assert(theTask->fInRunCount == 0); +#endif + if (theTimeout < 0) + { + if (TASK_DEBUG) + { + qtss_printf("TaskThread::Entry delete TaskName=%s CurMSec=%.3f thread=%p task=%p\n", theTask->fTaskName, OS::StartTimeMilli_Float(), (void *) this, (void *) theTask); + + theTask->fUseThisThread = NULL; + + if (NULL != fHeap.Remove(&theTask->fTimerHeapElem)) + qtss_printf("TaskThread::Entry task still in heap before delete\n"); + + if (NULL != theTask->fTaskQueueElem.InQueue()) + qtss_printf("TaskThread::Entry task still in queue before delete\n"); + + theTask->fTaskQueueElem.Remove(); + + if (theTask->fEvents &~ Task::kAlive) + qtss_printf ("TaskThread::Entry flags still set before delete\n"); + + (void)atomic_sub(&theTask->fEvents, 0); + + ::strncat (theTask->fTaskName, " deleted", sizeof(theTask->fTaskName) -1); + } + theTask->fTaskName[0] = 'D'; //mark as dead + delete theTask; + theTask = NULL; + doneProcessingEvent = true; + + } + else if (theTimeout == 0) + { + //We want to make sure that 100% definitely the task's Run function WILL + //be invoked when another thread calls Signal. We also want to make sure + //that if an event sneaks in right as the task is returning from Run() + //(via Signal) that the Run function will be invoked again. + doneProcessingEvent = compare_and_store(Task::kAlive, 0, &theTask->fEvents); + if (doneProcessingEvent) + theTask = NULL; + } + else + { + //note that if we get here, we don't reset theTask, so it will get passed into + //WaitForTask + if (TASK_DEBUG) qtss_printf("TaskThread::Entry insert TaskName=%s in timer heap thread=%p elem=%p task=%p timeout=%.2f\n", theTask->fTaskName, (void *) this, (void *) &theTask->fTimerHeapElem,(void *) theTask, (float)theTimeout / (float) 1000); + theTask->fTimerHeapElem.SetValue(OS::Milliseconds() + theTimeout); + fHeap.Insert(&theTask->fTimerHeapElem); + (void)atomic_or(&theTask->fEvents, Task::kIdleEvent); + doneProcessingEvent = true; + } + + + #if TASK_DEBUG + SInt64 yieldStart = OS::Milliseconds(); + #endif + + this->ThreadYield(); + #if TASK_DEBUG + SInt64 yieldDur = OS::Milliseconds() - yieldStart; + static SInt64 numZeroYields; + + if ( yieldDur > 1 ) + { + if (TASK_DEBUG) qtss_printf( "TaskThread::Entry time in Yield %qd, numZeroYields %qd \n", yieldDur, numZeroYields ); + numZeroYields = 0; + } + else + numZeroYields++; + #endif + + } + } +} + +Task* TaskThread::WaitForTask() +{ + while (true) + { + SInt64 theCurrentTime = OS::Milliseconds(); + + if ((fHeap.PeekMin() != NULL) && (fHeap.PeekMin()->GetValue() <= theCurrentTime)) + { + if (TASK_DEBUG) qtss_printf("TaskThread::WaitForTask found timer-task=%s thread %p fHeap.CurrentHeapSize(%"_U32BITARG_") taskElem = %p enclose=%p\n",((Task*)fHeap.PeekMin()->GetEnclosingObject())->fTaskName, (void *) this, fHeap.CurrentHeapSize(), (void *) fHeap.PeekMin(), (void *) fHeap.PeekMin()->GetEnclosingObject()); + return (Task*)fHeap.ExtractMin()->GetEnclosingObject(); + } + + //if there is an element waiting for a timeout, figure out how long we should wait. + SInt64 theTimeout = 0; + if (fHeap.PeekMin() != NULL) + theTimeout = fHeap.PeekMin()->GetValue() - theCurrentTime; + Assert(theTimeout >= 0); + + // + // Make sure we can't go to sleep for some ridiculously short + // period of time + // Do not allow a timeout below 10 ms without first verifying reliable udp 1-2mbit live streams. + // Test with streamingserver.xml pref reliablUDP printfs enabled and look for packet loss and check client for buffer ahead recovery. + if (theTimeout < 10) + theTimeout = 10; + + //wait... + OSQueueElem* theElem = fTaskQueue.DeQueueBlocking(this, (SInt32) theTimeout); + if (theElem != NULL) + { + if (TASK_DEBUG) qtss_printf("TaskThread::WaitForTask found signal-task=%s thread %p fTaskQueue.GetLength(%"_U32BITARG_") taskElem = %p enclose=%p\n", ((Task*)theElem->GetEnclosingObject())->fTaskName, (void *) this, fTaskQueue.GetQueue()->GetLength(), (void *) theElem, (void *)theElem->GetEnclosingObject() ); + return (Task*)theElem->GetEnclosingObject(); + } + + // + // If we are supposed to stop, return NULL, which signals the caller to stop + if (OSThread::GetCurrent()->IsStopRequested()) + return NULL; + } +} + +TaskThread** TaskThreadPool::sTaskThreadArray = NULL; +UInt32 TaskThreadPool::sNumTaskThreads = 0; +UInt32 TaskThreadPool::sNumShortTaskThreads = 0; +UInt32 TaskThreadPool::sNumBlockingTaskThreads = 0; + +Bool16 TaskThreadPool::AddThreads(UInt32 numToAdd) +{ + Assert(sTaskThreadArray == NULL); + sTaskThreadArray = new TaskThread*[numToAdd]; + + for (UInt32 x = 0; x < numToAdd; x++) + { + sTaskThreadArray[x] = NEW TaskThread(); + sTaskThreadArray[x]->Start(); + if (TASK_DEBUG) qtss_printf("TaskThreadPool::AddThreads sTaskThreadArray[%"_U32BITARG_"]=%p\n",x, sTaskThreadArray[x]); + } + sNumTaskThreads = numToAdd; + + if (0 == sNumShortTaskThreads) + sNumShortTaskThreads = numToAdd; + + return true; +} + + + +TaskThread* TaskThreadPool::GetThread(UInt32 index) +{ + + Assert(sTaskThreadArray != NULL); + if (index >= sNumTaskThreads) + return NULL; + + return sTaskThreadArray[index]; + +} + + + + +void TaskThreadPool::RemoveThreads() +{ + //Tell all the threads to stop + for (UInt32 x = 0; x < sNumTaskThreads; x++) + sTaskThreadArray[x]->SendStopRequest(); + + //Because any (or all) threads may be blocked on the queue, cycle through + //all the threads, signalling each one + for (UInt32 y = 0; y < sNumTaskThreads; y++) + sTaskThreadArray[y]->fTaskQueue.GetCond()->Signal(); + + //Ok, now wait for the selected threads to terminate, deleting them and removing + //them from the queue. + for (UInt32 z = 0; z < sNumTaskThreads; z++) + delete sTaskThreadArray[z]; + + sNumTaskThreads = 0; +} diff --git a/CommonUtilitiesLib/Task.h b/CommonUtilitiesLib/Task.h new file mode 100644 index 0000000..b0c2b8c --- /dev/null +++ b/CommonUtilitiesLib/Task.h @@ -0,0 +1,222 @@ +/* + * + * @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: Task.h + + Contains: Tasks are objects that can be scheduled. To schedule a task, you call its + signal method, and pass in an event (events are bits and all events are defined + below). + + Once Signal() is called, the task object will be scheduled. When it runs, its + Run() function will get called. In order to clear the event, the derived task + object must call GetEvents() (which returns the events that were sent). + + Calling GetEvents() implicitly "clears" the events returned. All events must + be cleared before the Run() function returns, or Run() will be invoked again + immediately. + + + + +*/ + +#ifndef __TASK_H__ +#define __TASK_H__ + +#include "OSQueue.h" +#include "OSHeap.h" +#include "OSThread.h" +#include "OSMutexRW.h" + +#define TASK_DEBUG 0 + +class TaskThread; + +class Task +{ + public: + + typedef unsigned int EventFlags; + + //EVENTS + //here are all the events that can be sent to a task + enum + { + kKillEvent = 0x1 << 0x0, //these are all of type "EventFlags" + kIdleEvent = 0x1 << 0x1, + kStartEvent = 0x1 << 0x2, + kTimeoutEvent = 0x1 << 0x3, + + //socket events + kReadEvent = 0x1 << 0x4, //All of type "EventFlags" + kWriteEvent = 0x1 << 0x5, + + //update event + kUpdateEvent = 0x1 << 0x6 + }; + + //CONSTRUCTOR / DESTRUCTOR + //You must assign priority at create time. + Task(); + virtual ~Task() {} + + //return: + // >0-> invoke me after this number of MilSecs with a kIdleEvent + // 0 don't reinvoke me at all. + //-1 delete me + //Suggested practice is that any task should be deleted by returning true from the + //Run function. That way, we know that the Task is not running at the time it is + //deleted. This object provides no protection against calling a method, such as Signal, + //at the same time the object is being deleted (because it can't really), so watch + //those dangling references! + virtual SInt64 Run() = 0; + + //Send an event to this task. + void Signal(EventFlags eventFlags); + void GlobalUnlock(); + Bool16 Valid(); // for debugging + char fTaskName[48]; + void SetTaskName(char* name); + + void SetDefaultThread(TaskThread* defaultThread) { fDefaultThread = defaultThread; } + void SetThreadPicker(unsigned int* picker); + static unsigned int* GetBlockingTaskThreadPicker() {return &sBlockingTaskThreadPicker; } + + protected: + + //Only the tasks themselves may find out what events they have received + EventFlags GetEvents(); + + // ForceSameThread + // + // A task, inside its run function, may want to ensure that the same task thread + // is used for subsequent calls to Run(). This may be the case if the task is holding + // a mutex between calls to run. By calling this function, the task ensures that the + // same task thread will be used for the next call to Run(). It only applies to the + // next call to run. + void ForceSameThread() { + fUseThisThread = (TaskThread*)OSThread::GetCurrent(); + Assert(fUseThisThread != NULL); + if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task"); + if (TASK_DEBUG) qtss_printf("Task::ForceSameThread fUseThisThread %p task %s enque elem=%p enclosing %p\n", (void*) fUseThisThread, fTaskName,(void *)&fTaskQueueElem, (void *)this); + } + SInt64 CallLocked() { ForceSameThread(); + fWriteLock = true; + return (SInt64) 10; // minimum of 10 milliseconds between locks + } + + private: + + enum + { + kAlive = 0x80000000, //EventFlags, again + kAliveOff = 0x7fffffff + }; + + void SetTaskThread(TaskThread *thread); + + EventFlags fEvents; + TaskThread* fUseThisThread; + TaskThread* fDefaultThread; + Bool16 fWriteLock; + +#if DEBUG + //The whole premise of a task is that the Run function cannot be re-entered. + //This debugging variable ensures that that is always the case + volatile UInt32 fInRunCount; +#endif + + //This could later be optimized by using a timing wheel instead of a heap, + //and that way we wouldn't need both a heap elem and a queue elem here (just queue elem) + OSHeapElem fTimerHeapElem; + OSQueueElem fTaskQueueElem; + + unsigned int *pickerToUse; + //Variable used for assigning tasks to threads in a round-robin fashion + static unsigned int sShortTaskThreadPicker; //default picker + static unsigned int sBlockingTaskThreadPicker; + + friend class TaskThread; +}; + +class TaskThread : public OSThread +{ + public: + + //Implementation detail: all tasks get run on TaskThreads. + + TaskThread() : OSThread(), fTaskThreadPoolElem() + {fTaskThreadPoolElem.SetEnclosingObject(this);} + virtual ~TaskThread() { this->StopAndWaitForThread(); } + + private: + + enum + { + kMinWaitTimeInMilSecs = 10 //UInt32 + }; + + virtual void Entry(); + Task* WaitForTask(); + + OSQueueElem fTaskThreadPoolElem; + + OSHeap fHeap; + OSQueue_Blocking fTaskQueue; + + + friend class Task; + friend class TaskThreadPool; +}; + +//Because task threads share a global queue of tasks to execute, +//there can only be one pool of task threads. That is why this object +//is static. +class TaskThreadPool { +public: + + //Adds some threads to the pool + static Bool16 AddThreads(UInt32 numToAdd); // creates the threads: takes NumShortTaskThreads + NumBLockingThreads, sets num short task threads. + static void SwitchPersonality( char *user = NULL, char *group = NULL); + static void RemoveThreads(); + static TaskThread* GetThread(UInt32 index); + static UInt32 GetNumThreads() { return sNumTaskThreads; } + static void SetNumShortTaskThreads(UInt32 numToAdd) { sNumShortTaskThreads = numToAdd; } + static void SetNumBlockingTaskThreads(UInt32 numToAdd) { sNumBlockingTaskThreads = numToAdd; } + +private: + + static TaskThread** sTaskThreadArray; + static UInt32 sNumTaskThreads; + static UInt32 sNumShortTaskThreads; + static UInt32 sNumBlockingTaskThreads; + + static OSMutexRW sMutexRW; + + friend class Task; + friend class TaskThread; +}; + +#endif diff --git a/CommonUtilitiesLib/TimeoutTask.cpp b/CommonUtilitiesLib/TimeoutTask.cpp new file mode 100644 index 0000000..535db56 --- /dev/null +++ b/CommonUtilitiesLib/TimeoutTask.cpp @@ -0,0 +1,116 @@ +/* + * + * @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: TimeoutTask.cpp + + Contains: Implementation of TimeoutTask + + +*/ + +#include "TimeoutTask.h" +#include "OSMemory.h" + +TimeoutTaskThread* TimeoutTask::sThread = NULL; + +void TimeoutTask::Initialize() +{ + if (sThread == NULL) + { + sThread = NEW TimeoutTaskThread(); + sThread->Signal(Task::kStartEvent); + } + +} + + +TimeoutTask::TimeoutTask(Task* inTask, SInt64 inTimeoutInMilSecs) +: fTask(inTask), fQueueElem() +{ + fQueueElem.SetEnclosingObject(this); + this->SetTimeout(inTimeoutInMilSecs); + if (NULL == inTask) + fTask = (Task *) this; + Assert(sThread != NULL); // this can happen if RunServer intializes tasks in the wrong order + + OSMutexLocker locker(&sThread->fMutex); + sThread->fQueue.EnQueue(&fQueueElem); +} + +TimeoutTask::~TimeoutTask() +{ + OSMutexLocker locker(&sThread->fMutex); + sThread->fQueue.Remove(&fQueueElem); +} + +void TimeoutTask::SetTimeout(SInt64 inTimeoutInMilSecs) +{ + fTimeoutInMilSecs = inTimeoutInMilSecs; + if (inTimeoutInMilSecs == 0) + fTimeoutAtThisTime = 0; + else + fTimeoutAtThisTime = OS::Milliseconds() + fTimeoutInMilSecs; +} + +SInt64 TimeoutTaskThread::Run() +{ + //ok, check for timeouts now. Go through the whole queue + OSMutexLocker locker(&fMutex); + SInt64 curTime = OS::Milliseconds(); + SInt64 intervalMilli = kIntervalSeconds * 1000;//always default to 60 seconds but adjust to smallest interval > 0 + SInt64 taskInterval = intervalMilli; + + for (OSQueueIter iter(&fQueue); !iter.IsDone(); iter.Next()) + { + TimeoutTask* theTimeoutTask = (TimeoutTask*)iter.GetCurrent()->GetEnclosingObject(); + + //if it's time to time this task out, signal it + if ((theTimeoutTask->fTimeoutAtThisTime > 0) && (curTime >= theTimeoutTask->fTimeoutAtThisTime)) + { +#if TIMEOUT_DEBUGGING + qtss_printf("TimeoutTask %"_S32BITARG_" timed out. Curtime = %"_64BITARG_"d, timeout time = %"_64BITARG_"d\n",(SInt32)theTimeoutTask, curTime, theTimeoutTask->fTimeoutAtThisTime); +#endif + theTimeoutTask->fTask->Signal(Task::kTimeoutEvent); + } + else + { + taskInterval = theTimeoutTask->fTimeoutAtThisTime - curTime; + if ( (taskInterval > 0) && (theTimeoutTask->fTimeoutInMilSecs > 0) && (intervalMilli > taskInterval) ) + intervalMilli = taskInterval + 1000; // set timeout to 1 second past this task's timeout +#if TIMEOUT_DEBUGGING + qtss_printf("TimeoutTask %"_S32BITARG_" not being timed out. Curtime = %"_64BITARG_"d. timeout time = %"_64BITARG_"d\n", (SInt32)theTimeoutTask, curTime, theTimeoutTask->fTimeoutAtThisTime); +#endif + } + } + (void)this->GetEvents();//we must clear the event mask! + + OSThread::ThreadYield(); + +#if TIMEOUT_DEBUGGING + qtss_printf ("TimeoutTaskThread::Run interval seconds= %"_S32BITARG_"\n", (SInt32) intervalMilli/1000); +#endif + + return intervalMilli;//don't delete me! +} diff --git a/CommonUtilitiesLib/TimeoutTask.h b/CommonUtilitiesLib/TimeoutTask.h new file mode 100644 index 0000000..9671632 --- /dev/null +++ b/CommonUtilitiesLib/TimeoutTask.h @@ -0,0 +1,113 @@ +/* + * + * @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: TimeoutTask.h + + Contains: Just like a normal task, but can be scheduled for timeouts. Unlike + IdleTask, which is VERY aggressive about being on time, but high + overhead for maintaining the timing information, this is a low overhead, + low priority timing mechanism. Timeouts may not happen exactly when + they are supposed to, but who cares? + + + + +*/ + +#ifndef __TIMEOUTTASK_H__ +#define __TIMEOUTTASK_H__ + + +#include "StrPtrLen.h" +#include "IdleTask.h" + +#include "OSThread.h" +#include "OSQueue.h" +#include "OSMutex.h" +#include "OS.h" + +#define TIMEOUT_DEBUGGING 0 //messages to help debugging timeouts + +class TimeoutTaskThread : public IdleTask +{ + public: + + //All timeout tasks get timed out from this thread + TimeoutTaskThread() : IdleTask(), fMutex() {this->SetTaskName("TimeoutTask");} + virtual ~TimeoutTaskThread(){} + + private: + + //this thread runs every minute and checks for timeouts + enum + { + kIntervalSeconds = 60 //UInt32 + }; + + virtual SInt64 Run(); + OSMutex fMutex; + OSQueue fQueue; + + friend class TimeoutTask; +}; + +class TimeoutTask +{ + //TimeoutTask is not a derived object off of Task, to add flexibility as + //to how this object can be utilitized + + public: + + //Call Initialize before using this class + static void Initialize(); + //Pass in the task you'd like to send timeouts to. + //Also pass in the timeout you'd like to use. By default, the timeout is 0 (NEVER). + TimeoutTask(Task* inTask, SInt64 inTimeoutInMilSecs = 60); + ~TimeoutTask(); + + //MODIFIERS + + // Changes the timeout time, also refreshes the timeout + void SetTimeout(SInt64 inTimeoutInMilSecs); + + // Specified task will get a Task::kTimeoutEvent if this + // function isn't called within the timeout period + void RefreshTimeout() { fTimeoutAtThisTime = OS::Milliseconds() + fTimeoutInMilSecs; Assert(fTimeoutAtThisTime > 0); } + + void SetTask(Task* inTask) { fTask = inTask; } + private: + + Task* fTask; + SInt64 fTimeoutAtThisTime; + SInt64 fTimeoutInMilSecs; + //for putting on our global queue of timeout tasks + OSQueueElem fQueueElem; + + static TimeoutTaskThread* sThread; + + friend class TimeoutTaskThread; +}; +#endif //__TIMEOUTTASK_H__ + diff --git a/CommonUtilitiesLib/Trim.c b/CommonUtilitiesLib/Trim.c new file mode 100644 index 0000000..98ab7ba --- /dev/null +++ b/CommonUtilitiesLib/Trim.c @@ -0,0 +1,35 @@ +#include "Trim.h" + +/* + * + * @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@ + * + */ + +char* TrimLeft(char* fromStrPtr ) +{ + char* tmp = &fromStrPtr[0]; + // trim any leading white space + while ( (*tmp <= ' ') && (*tmp != 0) ) + tmp++; + return tmp; +} diff --git a/CommonUtilitiesLib/Trim.h b/CommonUtilitiesLib/Trim.h new file mode 100644 index 0000000..cfc0a19 --- /dev/null +++ b/CommonUtilitiesLib/Trim.h @@ -0,0 +1,44 @@ + +#ifndef __trim__ +#define __trim__ + +/* + * + * @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@ + * + */ + + + + #ifdef __cplusplus + extern "C" { + #endif + + + char* TrimLeft( char* fromStrPtr ); + + #ifdef __cplusplus + } + #endif + + +#endif diff --git a/CommonUtilitiesLib/UDPDemuxer.cpp b/CommonUtilitiesLib/UDPDemuxer.cpp new file mode 100644 index 0000000..db2ce6f --- /dev/null +++ b/CommonUtilitiesLib/UDPDemuxer.cpp @@ -0,0 +1,71 @@ +/* + * + * @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: UDPDemuxer.cpp + + Contains: Implements objects defined in UDPDemuxer.h + + + +*/ + +#include "UDPDemuxer.h" + +#include + + +OS_Error UDPDemuxer::RegisterTask(UInt32 inRemoteAddr, UInt16 inRemotePort, + UDPDemuxerTask *inTaskP) +{ + Assert(NULL != inTaskP); + OSMutexLocker locker(&fMutex); + if (this->GetTask(inRemoteAddr, inRemotePort) != NULL) + return EPERM; + inTaskP->Set(inRemoteAddr, inRemotePort); + fHashTable.Add(inTaskP); + return OS_NoErr; +} + +OS_Error UDPDemuxer::UnregisterTask(UInt32 inRemoteAddr, UInt16 inRemotePort, + UDPDemuxerTask *inTaskP) +{ + OSMutexLocker locker(&fMutex); + //remove by executing a lookup based on key information + UDPDemuxerTask* theTask = this->GetTask(inRemoteAddr, inRemotePort); + + if ((NULL != theTask) && (theTask == inTaskP)) + { + fHashTable.Remove(theTask); + return OS_NoErr; + } + else + return EPERM; +} + +UDPDemuxerTask* UDPDemuxer::GetTask(UInt32 inRemoteAddr, UInt16 inRemotePort) +{ + UDPDemuxerKey theKey(inRemoteAddr, inRemotePort); + return fHashTable.Map(&theKey); +} diff --git a/CommonUtilitiesLib/UDPDemuxer.h b/CommonUtilitiesLib/UDPDemuxer.h new file mode 100644 index 0000000..9797dab --- /dev/null +++ b/CommonUtilitiesLib/UDPDemuxer.h @@ -0,0 +1,176 @@ +/* + * + * @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: UDPDemuxer.h + + Contains: Provides a "Listener" socket for UDP. Blocks on a local IP & port, + waiting for data. When it gets data, it passes it off to a UDPDemuxerTask + object depending on where it came from. + + +*/ + +#ifndef __UDPDEMUXER_H__ +#define __UDPDEMUXER_H__ + +#include "OSHashTable.h" +#include "OSMutex.h" +#include "StrPtrLen.h" + +class Task; +class UDPDemuxerKey; + +//IMPLEMENTATION ONLY: +//HASH TABLE CLASSES USED ONLY IN IMPLEMENTATION + + +class UDPDemuxerUtils +{ + private: + + static UInt32 ComputeHashValue(UInt32 inRemoteAddr, UInt16 inRemotePort) + { return ((inRemoteAddr << 16) + inRemotePort); } + + friend class UDPDemuxerTask; + friend class UDPDemuxerKey; +}; + +class UDPDemuxerTask +{ + public: + + UDPDemuxerTask() + : fRemoteAddr(0), fRemotePort(0), + fHashValue(0), fNextHashEntry(NULL) {} + virtual ~UDPDemuxerTask() {} + + UInt32 GetRemoteAddr() { return fRemoteAddr; } + + private: + + void Set(UInt32 inRemoteAddr, UInt16 inRemotePort) + { fRemoteAddr = inRemoteAddr; fRemotePort = inRemotePort; + fHashValue = UDPDemuxerUtils::ComputeHashValue(fRemoteAddr, fRemotePort); + } + + //key values + UInt32 fRemoteAddr; + UInt16 fRemotePort; + + //precomputed for performance + UInt32 fHashValue; + + UDPDemuxerTask *fNextHashEntry; + + friend class UDPDemuxerKey; + friend class UDPDemuxer; + friend class OSHashTable; +}; + + + +class UDPDemuxerKey +{ + private: + + //CONSTRUCTOR / DESTRUCTOR: + UDPDemuxerKey(UInt32 inRemoteAddr, UInt16 inRemotePort) + : fRemoteAddr(inRemoteAddr), fRemotePort(inRemotePort) + { fHashValue = UDPDemuxerUtils::ComputeHashValue(inRemoteAddr, inRemotePort); } + + ~UDPDemuxerKey() {} + + + private: + + //PRIVATE ACCESSORS: + UInt32 GetHashKey() { return fHashValue; } + + //these functions are only used by the hash table itself. This constructor + //will break the "Set" functions. + UDPDemuxerKey(UDPDemuxerTask *elem) : fRemoteAddr(elem->fRemoteAddr), + fRemotePort(elem->fRemotePort), + fHashValue(elem->fHashValue) {} + + friend int operator ==(const UDPDemuxerKey &key1, const UDPDemuxerKey &key2) { + if ((key1.fRemoteAddr == key2.fRemoteAddr) && + (key1.fRemotePort == key2.fRemotePort)) + return true; + return false; + } + + //data: + UInt32 fRemoteAddr; + UInt16 fRemotePort; + UInt32 fHashValue; + + friend class OSHashTable; + friend class UDPDemuxer; +}; + +//CLASSES USED ONLY IN IMPLEMENTATION +typedef OSHashTable UDPDemuxerHashTable; + +class UDPDemuxer +{ + public: + + UDPDemuxer() : fHashTable(kMaxHashTableSize), fMutex() {} + ~UDPDemuxer() {} + + //These functions grab the mutex and are therefore premptive safe + + // Return values: OS_NoErr, or EPERM if there is already a task registered + // with this address combination + OS_Error RegisterTask(UInt32 inRemoteAddr, UInt16 inRemotePort, + UDPDemuxerTask *inTaskP); + + // Return values: OS_NoErr, or EPERM if this task / address combination + // is not registered + OS_Error UnregisterTask(UInt32 inRemoteAddr, UInt16 inRemotePort, + UDPDemuxerTask *inTaskP); + + //Assumes that parent has grabbed the mutex! + UDPDemuxerTask* GetTask(UInt32 inRemoteAddr, UInt16 inRemotePort); + + Bool16 AddrInMap(UInt32 inRemoteAddr, UInt16 inRemotePort) + { return (this->GetTask(inRemoteAddr, inRemotePort) != NULL); } + + OSMutex* GetMutex() { return &fMutex; } + UDPDemuxerHashTable* GetHashTable() { return &fHashTable; } + + private: + + enum + { + kMaxHashTableSize = 2747//is this prime? it should be... //UInt32 + }; + UDPDemuxerHashTable fHashTable; + OSMutex fMutex;//this data structure is shared! +}; + +#endif // __UDPDEMUXER_H__ + + diff --git a/CommonUtilitiesLib/UDPSocket.cpp b/CommonUtilitiesLib/UDPSocket.cpp new file mode 100644 index 0000000..6570f1c --- /dev/null +++ b/CommonUtilitiesLib/UDPSocket.cpp @@ -0,0 +1,176 @@ +/* + * + * @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: UDPSocket.cpp + + Contains: Implementation of object defined in UDPSocket.h. + + + +*/ + +#ifndef __Win32__ +#include +#include + +#if __solaris__ +#include "SocketUtils.h" +#endif + +#if NEED_SOCKETBITS +#if __GLIBC__ >= 2 +#include +#else +#include +#endif +#endif +#endif + +#include +#include "UDPSocket.h" +#include "OSMemory.h" + +#ifdef USE_NETLOG +#include +#endif + +UDPSocket::UDPSocket(Task* inTask, UInt32 inSocketType) +: Socket(inTask, inSocketType), fDemuxer(NULL) +{ + if (inSocketType & kWantsDemuxer) + fDemuxer = NEW UDPDemuxer(); + + //setup msghdr + ::memset(&fMsgAddr, 0, sizeof(fMsgAddr)); +} + + +OS_Error +UDPSocket::SendTo(UInt32 inRemoteAddr, UInt16 inRemotePort, void* inBuffer, UInt32 inLength) +{ + Assert(inBuffer != NULL); + + struct sockaddr_in theRemoteAddr; + theRemoteAddr.sin_family = AF_INET; + theRemoteAddr.sin_port = htons(inRemotePort); + theRemoteAddr.sin_addr.s_addr = htonl(inRemoteAddr); + +#ifdef __sgi__ + int theErr = ::sendto(fFileDesc, inBuffer, inLength, 0, (sockaddr*)&theRemoteAddr, sizeof(theRemoteAddr)); +#else + // Win32 says that inBuffer is a char* + int theErr = ::sendto(fFileDesc, (char*)inBuffer, inLength, 0, (sockaddr*)&theRemoteAddr, sizeof(theRemoteAddr)); +#endif + + if (theErr == -1) + return (OS_Error)OSThread::GetErrno(); + return OS_NoErr; +} + +OS_Error UDPSocket::RecvFrom(UInt32* outRemoteAddr, UInt16* outRemotePort, + void* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen) +{ + Assert(outRecvLen != NULL); + Assert(outRemoteAddr != NULL); + Assert(outRemotePort != NULL); + +#if __Win32__ || __osf__ || __sgi__ || __hpux__ + int addrLen = sizeof(fMsgAddr); +#else + socklen_t addrLen = sizeof(fMsgAddr); +#endif + +#ifdef __sgi__ + SInt32 theRecvLen = ::recvfrom(fFileDesc, ioBuffer, inBufLen, 0, (sockaddr*)&fMsgAddr, &addrLen); +#else + // Win32 says that ioBuffer is a char* + SInt32 theRecvLen = ::recvfrom(fFileDesc, (char*)ioBuffer, inBufLen, 0, (sockaddr*)&fMsgAddr, &addrLen); +#endif + + if (theRecvLen == -1) + return (OS_Error)OSThread::GetErrno(); + + *outRemoteAddr = ntohl(fMsgAddr.sin_addr.s_addr); + *outRemotePort = ntohs(fMsgAddr.sin_port); + Assert(theRecvLen >= 0); + *outRecvLen = (UInt32)theRecvLen; + return OS_NoErr; +} + +OS_Error UDPSocket::JoinMulticast(UInt32 inRemoteAddr) +{ + struct ip_mreq theMulti; + UInt32 localAddr = fLocalAddr.sin_addr.s_addr; // Already in network byte order + +#if __solaris__ + if( localAddr == htonl(INADDR_ANY) ) + localAddr = htonl(SocketUtils::GetIPAddr(0)); +#endif + theMulti.imr_multiaddr.s_addr = htonl(inRemoteAddr); + theMulti.imr_interface.s_addr = localAddr; + int err = setsockopt(fFileDesc, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&theMulti, sizeof(theMulti)); + //AssertV(err == 0, OSThread::GetErrno()); + if (err == -1) + return (OS_Error)OSThread::GetErrno(); + else + return OS_NoErr; +} + +OS_Error UDPSocket::SetTtl(UInt16 timeToLive) +{ + // set the ttl + u_char nOptVal = (u_char)timeToLive;//dms - stevens pp. 496. bsd implementations barf + //unless this is a u_char + int err = setsockopt(fFileDesc, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&nOptVal, sizeof(nOptVal)); + if (err == -1) + return (OS_Error)OSThread::GetErrno(); + else + return OS_NoErr; +} + +OS_Error UDPSocket::SetMulticastInterface(UInt32 inLocalAddr) +{ + // set the outgoing interface for multicast datagrams on this socket + in_addr theLocalAddr; + theLocalAddr.s_addr = inLocalAddr; + int err = setsockopt(fFileDesc, IPPROTO_IP, IP_MULTICAST_IF, (char*)&theLocalAddr, sizeof(theLocalAddr)); + AssertV(err == 0, OSThread::GetErrno()); + if (err == -1) + return (OS_Error)OSThread::GetErrno(); + else + return OS_NoErr; +} + +OS_Error UDPSocket::LeaveMulticast(UInt32 inRemoteAddr) +{ + struct ip_mreq theMulti; + theMulti.imr_multiaddr.s_addr = htonl(inRemoteAddr); + theMulti.imr_interface.s_addr = htonl(fLocalAddr.sin_addr.s_addr); + int err = setsockopt(fFileDesc, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&theMulti, sizeof(theMulti)); + if (err == -1) + return (OS_Error)OSThread::GetErrno(); + else + return OS_NoErr; +} diff --git a/CommonUtilitiesLib/UDPSocket.h b/CommonUtilitiesLib/UDPSocket.h new file mode 100644 index 0000000..7798e65 --- /dev/null +++ b/CommonUtilitiesLib/UDPSocket.h @@ -0,0 +1,88 @@ +/* + * + * @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: UDPSocket.h + + Contains: Adds additional Socket functionality specific to UDP. + + + + +*/ + + +#ifndef __UDPSOCKET_H__ +#define __UDPSOCKET_H__ + +#ifndef __Win32__ +#include +#include +#endif + +#include "Socket.h" +#include "UDPDemuxer.h" + + +class UDPSocket : public Socket +{ + public: + + //Another socket type flag (in addition to the ones defined in Socket.h). + //The value of this can't conflict with those! + enum + { + kWantsDemuxer = 0x0100 //UInt32 + }; + + UDPSocket(Task* inTask, UInt32 inSocketType); + virtual ~UDPSocket() { if (fDemuxer != NULL) delete fDemuxer; } + + //Open + OS_Error Open() { return Socket::Open(SOCK_DGRAM); } + + OS_Error JoinMulticast(UInt32 inRemoteAddr); + OS_Error LeaveMulticast(UInt32 inRemoteAddr); + OS_Error SetTtl(UInt16 timeToLive); + OS_Error SetMulticastInterface(UInt32 inLocalAddr); + + //returns an ERRNO + OS_Error SendTo(UInt32 inRemoteAddr, UInt16 inRemotePort, + void* inBuffer, UInt32 inLength); + + OS_Error RecvFrom(UInt32* outRemoteAddr, UInt16* outRemotePort, + void* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen); + + //A UDP socket may or may not have a demuxer associated with it. The demuxer + //is a data structure so the socket can associate incoming data with the proper + //task to process that data (based on source IP addr & port) + UDPDemuxer* GetDemuxer() { return fDemuxer; } + + private: + + UDPDemuxer* fDemuxer; + struct sockaddr_in fMsgAddr; +}; +#endif // __UDPSOCKET_H__ + diff --git a/CommonUtilitiesLib/UDPSocketPool.cpp b/CommonUtilitiesLib/UDPSocketPool.cpp new file mode 100644 index 0000000..5dda884 --- /dev/null +++ b/CommonUtilitiesLib/UDPSocketPool.cpp @@ -0,0 +1,152 @@ +/* + * + * @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: UDPSocketPool.cpp + + Contains: Object that creates & maintains UDP socket pairs in a pool. + + +*/ + +#include "UDPSocketPool.h" + +UDPSocketPair* UDPSocketPool::GetUDPSocketPair(UInt32 inIPAddr, UInt16 inPort, + UInt32 inSrcIPAddr, UInt16 inSrcPort) +{ + OSMutexLocker locker(&fMutex); + if ((inSrcIPAddr != 0) || (inSrcPort != 0)) + { + for (OSQueueIter qIter(&fUDPQueue); !qIter.IsDone(); qIter.Next()) + { + //If we find a pair that is a) on the right IP address, and b) doesn't + //have this source IP & port in the demuxer already, we can return this pair + UDPSocketPair* theElem = (UDPSocketPair*)qIter.GetCurrent()->GetEnclosingObject(); + if ((theElem->fSocketA->GetLocalAddr() == inIPAddr) && + ((inPort == 0) || (theElem->fSocketA->GetLocalPort() == inPort))) + { + //check to make sure this source IP & port is not already in the demuxer. + //If not, we can return this socket pair. + if ((theElem->fSocketB->GetDemuxer() == NULL) || + ((!theElem->fSocketB->GetDemuxer()->AddrInMap(0, 0)) && + (!theElem->fSocketB->GetDemuxer()->AddrInMap(inSrcIPAddr, inSrcPort)))) + { + theElem->fRefCount++; + return theElem; + } + //If port is specified, there is NO WAY a socket pair can exist that matches + //the criteria (because caller wants a specific ip & port combination) + else if (inPort != 0) + return NULL; + } + } + } + //if we get here, there is no pair already in the pool that matches the specified + //criteria, so we have to create a new pair. + return this->CreateUDPSocketPair(inIPAddr, inPort); +} + +void UDPSocketPool::ReleaseUDPSocketPair(UDPSocketPair* inPair) +{ + OSMutexLocker locker(&fMutex); + inPair->fRefCount--; + if (inPair->fRefCount == 0) + { + fUDPQueue.Remove(&inPair->fElem); + this->DestructUDPSocketPair(inPair); + } +} + +UDPSocketPair* UDPSocketPool::CreateUDPSocketPair(UInt32 inAddr, UInt16 inPort) +{ + //try to find an open pair of ports to bind these suckers tooo + OSMutexLocker locker(&fMutex); + UDPSocketPair* theElem = NULL; + Bool16 foundPair = false; + UInt16 curPort = kLowestUDPPort; + UInt16 stopPort = kHighestUDPPort -1; // prevent roll over when iterating over port nums + UInt16 socketBPort = kLowestUDPPort + 1; + + //If port is 0, then the caller doesn't care what port # we bind this socket to. + //Otherwise, ONLY attempt to bind this socket to the specified port + if (inPort != 0) + curPort = inPort; + if (inPort != 0) + stopPort = inPort; + + + while ((!foundPair) && (curPort < kHighestUDPPort)) + { + socketBPort = curPort +1; // make socket pairs adjacent to one another + + theElem = ConstructUDPSocketPair(); + Assert(theElem != NULL); + if (theElem->fSocketA->Open() != OS_NoErr) + { + this->DestructUDPSocketPair(theElem); + return NULL; + } + if (theElem->fSocketB->Open() != OS_NoErr) + { + this->DestructUDPSocketPair(theElem); + return NULL; + } + + // Set socket options on these new sockets + this->SetUDPSocketOptions(theElem); + + OS_Error theErr = theElem->fSocketA->Bind(inAddr, curPort); + if (theErr == OS_NoErr) + { //qtss_printf("fSocketA->Bind ok on port%u\n", curPort); + theErr = theElem->fSocketB->Bind(inAddr, socketBPort); + if (theErr == OS_NoErr) + { //qtss_printf("fSocketB->Bind ok on port%u\n", socketBPort); + foundPair = true; + fUDPQueue.EnQueue(&theElem->fElem); + theElem->fRefCount++; + return theElem; + } + //else qtss_printf("fSocketB->Bind failed on port%u\n", socketBPort); + } + //else qtss_printf("fSocketA->Bind failed on port%u\n", curPort); + + //If we are looking to bind to a specific port set, and we couldn't then + //just break here. + if (inPort != 0) + break; + + if (curPort >= stopPort) //test for stop condition + break; + + curPort += 2; //try a higher port pair + + this->DestructUDPSocketPair(theElem); //a bind failure + theElem = NULL; + } + //if we couldn't find a pair of sockets, make sure to clean up our mess + if (theElem != NULL) + this->DestructUDPSocketPair(theElem); + + return NULL; +} diff --git a/CommonUtilitiesLib/UDPSocketPool.h b/CommonUtilitiesLib/UDPSocketPool.h new file mode 100644 index 0000000..d7b9fb4 --- /dev/null +++ b/CommonUtilitiesLib/UDPSocketPool.h @@ -0,0 +1,114 @@ +/* + * + * @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: UDPSocketPool.h + + Contains: Object that creates & maintains UDP socket pairs in a pool. + + + +*/ + +#ifndef __UDPSOCKETPOOL_H__ +#define __UDPSOCKETPOOL_H__ + +#include "UDPDemuxer.h" +#include "UDPSocket.h" +#include "OSMutex.h" +#include "OSQueue.h" + +class UDPSocketPair; + +class UDPSocketPool +{ + public: + + UDPSocketPool() : fMutex() {} + virtual ~UDPSocketPool() {} + + //Skanky access to member data + OSMutex* GetMutex() { return &fMutex; } + OSQueue* GetSocketQueue() { return &fUDPQueue; } + + //Gets a UDP socket out of the pool. + //inIPAddr = IP address you'd like this pair to be bound to. + //inPort = port you'd like this pair to be bound to, or 0 if you don't care + //inSrcIPAddr = srcIP address of incoming packets for the demuxer. + //inSrcPort = src port of incoming packets for the demuxer. + //This may return NULL if no pair is available that meets the criteria. + UDPSocketPair* GetUDPSocketPair(UInt32 inIPAddr, UInt16 inPort, + UInt32 inSrcIPAddr, UInt16 inSrcPort); + + //When done using a UDP socket pair retrieved via GetUDPSocketPair, you must + //call this function. Doing so tells the pool which UDP sockets are in use, + //keeping the number of UDP sockets allocated at a minimum. + void ReleaseUDPSocketPair(UDPSocketPair* inPair); + + UDPSocketPair* CreateUDPSocketPair(UInt32 inAddr, UInt16 inPort); + + protected: + + //Because UDPSocket is a base class, and this pool class is intended to be + //a general purpose class for all types of UDP sockets (reflector, standard), + //there must be a virtual fuction for actually constructing the derived UDP sockets + virtual UDPSocketPair* ConstructUDPSocketPair() = 0; + virtual void DestructUDPSocketPair(UDPSocketPair* inPair) = 0; + + virtual void SetUDPSocketOptions(UDPSocketPair* /*inPair*/) {} + + private: + + enum + { + kLowestUDPPort = 6970, //UInt16 + kHighestUDPPort = 65535 //UInt16 + }; + + OSQueue fUDPQueue; + OSMutex fMutex; +}; + +class UDPSocketPair +{ + public: + + UDPSocketPair(UDPSocket* inSocketA, UDPSocket* inSocketB) + : fSocketA(inSocketA), fSocketB(inSocketB), fRefCount(0), fElem() {fElem.SetEnclosingObject(this);} + ~UDPSocketPair() {} + + UDPSocket* GetSocketA() { return fSocketA; } + UDPSocket* GetSocketB() { return fSocketB; } + + private: + + UDPSocket* fSocketA; + UDPSocket* fSocketB; + UInt32 fRefCount; + OSQueueElem fElem; + + friend class UDPSocketPool; +}; +#endif // __UDPSOCKETPOOL_H__ + diff --git a/CommonUtilitiesLib/UserAgentParser.cpp b/CommonUtilitiesLib/UserAgentParser.cpp new file mode 100644 index 0000000..d64975c --- /dev/null +++ b/CommonUtilitiesLib/UserAgentParser.cpp @@ -0,0 +1,183 @@ +/* + * + * @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: UserAgentParser.cpp + + Contains: Parse the User-Agent: entry of an RTSP request. + + + + +*/ + +#include "StringParser.h" +#include "StringFormatter.h" +#include "StrPtrLen.h" +#include "UserAgentParser.h" + +UserAgentParser::UserAgentFields UserAgentParser::sFieldIDs[] = +{ /* fAttrName, len, id */ + { "qtid", strlen("qtid"), eQtid }, + { "qtver", strlen("qtver"), eQtver }, + { "lang", strlen("lang"), eLang }, + { "os", strlen("os"), eOs }, + { "osver", strlen("osver"), eOsver }, + { "cpu", strlen("cpu"), eCpu } +}; + + +UInt8 UserAgentParser::sEOLWhitespaceEqualMask[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 // \t is a stop + 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, //30-39 ' ' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 '=' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + +UInt8 UserAgentParser::sEOLSemicolonCloseParenMask[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 // \t is a stop + 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //30-39 + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 ')' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //50-59 ';' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + + +void UserAgentParser::Parse(StrPtrLen *inStream) +{ + StrPtrLen tempID; + StrPtrLen tempData; + StringParser parser(inStream); + StrPtrLen startFields; + + + memset(&fFieldData,0,sizeof(fFieldData) ); + + parser.ConsumeUntil(&startFields, '(' ); // search for '(' if not found does nothing + + // parse through everything between the '(' and ')'. + while (startFields.Len != 0) + { + //stop when we reach an empty line. + tempID.Set(NULL,0); + tempData.Set(NULL,0); + + parser.ConsumeLength(NULL, 1); // step past '(' or ';' if not found or at end of line does nothing + parser.ConsumeWhitespace(); // search for non-white space if not found does nothing + parser.ConsumeUntil(&tempID, sEOLWhitespaceEqualMask ); // search for end of id (whitespace or =)if not found does nothing + if (tempID.Len == 0) break; + + parser.ConsumeUntil(NULL, '=' ); // find the '=' + parser.ConsumeLength(NULL, 1); // step past if not found or at end of line does nothing + parser.ConsumeUntil(&tempData, sEOLSemicolonCloseParenMask ); // search for end of data if not found does nothing + if (tempData.Len == 0) break; + + StrPtrLen testID; + UInt32 fieldID; + for (short testField = 0; testField < UserAgentParser::eNumAttributes; testField++) + { + testID.Set(sFieldIDs[testField].fFieldName,sFieldIDs[testField].fLen); + fieldID = sFieldIDs[testField].fID; + if ( (fFieldData[fieldID].fFound == false) && testID.Equal(tempID) ) + { + fFieldData[fieldID].fData = tempData; + fFieldData[fieldID].fFound = true; + } + } + + } + // If we parsed the OS field but not the OSVer field then check and see if + // the OS field contains the OS version. If it does copy it from there. + // (e.g. 'os=Mac%209.2.2' or 'os=Windows%20NT%204.0'.) + if (fFieldData[eOs].fFound && !fFieldData[eOsver].fFound) + { + UInt16 len = (UInt16)fFieldData[eOs].fData.Len; + char* cp = (char*)fFieldData[eOs].fData.Ptr; + // skip up to the blank space if it exists. + // (i.e. the blank is URL encoded as '%20') + while(*cp != '%') + { + len--; + if (*cp == '\0' || len == 0) + { + // no blank space...so we're all done. + return; + } + cp++; + } + // skip over the blank space. + cp += 3; len -= 3; + // the remaining string is the OS version. + fFieldData[eOsver].fData.Set(cp, len); + fFieldData[eOsver].fFound = true; + // and truncate the version from the OS field. + fFieldData[eOs].fData.Len -= len+3; + } +} diff --git a/CommonUtilitiesLib/UserAgentParser.h b/CommonUtilitiesLib/UserAgentParser.h new file mode 100644 index 0000000..8030fee --- /dev/null +++ b/CommonUtilitiesLib/UserAgentParser.h @@ -0,0 +1,92 @@ +/* + * + * @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: UserAgentParser.h + + Contains: API interface for parsing the user agent field received from RTSP clients. + + Change History (most recent first): + + + + +*/ +#ifndef _USERAGENTPARSER_H_ +#define _USERAGENTPARSER_H_ + +#include "StringParser.h" +#include "StringFormatter.h" +#include "StrPtrLen.h" + +class UserAgentParser +{ + public: + enum{ eMaxAttributeSize = 60 }; + struct UserAgentFields + { + char fFieldName[eMaxAttributeSize + 1]; + UInt32 fLen; + UInt32 fID; + }; + + struct UserAgentData + { + StrPtrLen fData; + bool fFound; + }; + + enum + { eQtid = 0, + eQtver = 1, + eLang = 2, + eOs = 3, + eOsver = 4, + eCpu = 5, + eNumAttributes = 6 + }; + + static UserAgentFields sFieldIDs[]; + static UInt8 sEOLWhitespaceEqualMask[]; + static UInt8 sEOLSemicolonCloseParenMask[]; + static UInt8 sWhitespaceMask[]; + + UserAgentData fFieldData[eNumAttributes]; + + void Parse(StrPtrLen *inStream); + + StrPtrLen* GetUserID() { return &(fFieldData[eQtid].fData); }; + StrPtrLen* GetUserVersion() { return &(fFieldData[eQtver].fData); }; + StrPtrLen* GetUserLanguage() { return &(fFieldData[eLang].fData); }; + StrPtrLen* GetrUserOS() { return &(fFieldData[eOs].fData); }; + StrPtrLen* GetUserOSVersion() { return &(fFieldData[eOsver].fData); }; + StrPtrLen* GetUserCPU() { return &(fFieldData[eCpu].fData); }; + + UserAgentParser (StrPtrLen *inStream) { if (inStream != NULL) Parse(inStream); } + + +}; + + +#endif // _USERAGENTPARSER_H_ diff --git a/CommonUtilitiesLib/atomic.cpp b/CommonUtilitiesLib/atomic.cpp new file mode 100644 index 0000000..9b75356 --- /dev/null +++ b/CommonUtilitiesLib/atomic.cpp @@ -0,0 +1,66 @@ +/* + * + * @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@ + * + */ + +#include "atomic.h" +#include "OSMutex.h" + +static OSMutex sAtomicMutex; + + +unsigned int atomic_add(unsigned int *area, int val) +{ + OSMutexLocker locker(&sAtomicMutex); + *area += val; + return *area; +} + +unsigned int atomic_sub(unsigned int *area,int val) +{ + return atomic_add(area,-val); +} + +unsigned int atomic_or(unsigned int *area, unsigned int val) +{ + unsigned int oldval; + + OSMutexLocker locker(&sAtomicMutex); + oldval=*area; + *area = oldval | val; + return oldval; +} + +unsigned int compare_and_store(unsigned int oval, unsigned int nval, unsigned int *area) +{ + int rv; + OSMutexLocker locker(&sAtomicMutex); + if( oval == *area ) + { + rv=1; + *area = nval; + } + else + rv=0; + return rv; +} diff --git a/CommonUtilitiesLib/atomic.h b/CommonUtilitiesLib/atomic.h new file mode 100644 index 0000000..7d8c09d --- /dev/null +++ b/CommonUtilitiesLib/atomic.h @@ -0,0 +1,97 @@ +/* + * + * @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@ + * + */ +/* + * + * History: + * 11-Feb-1999 Umesh Vaishampayan (umeshv@apple.com) + * Added atomic_or(). + * + * 26-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Made the header c++ friendly. + * + * 12-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Changed simple_ to spin_ so as to coexist with cthreads till + * the merge to the system framework. + * + * 8-Oct-1998 Umesh Vaishampayan (umeshv@apple.com) + * Created from the kernel code to be in a dynamic shared library. + * Kernel code created by: Bill Angell (angell@apple.com) + */ + +#ifndef _ATOMIC_H_ +#define _ATOMIC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Locking routines */ + +struct spin_lock { /* sizeof cache line */ + unsigned int lock_data; + unsigned int pad[7]; +}; + +typedef struct spin_lock *spin_lock_t; + +extern void spin_lock_init(spin_lock_t); + +extern void spin_lock_unlock(spin_lock_t); + +extern unsigned int spin_lock_lock(spin_lock_t); + +extern unsigned int spin_lock_bit(spin_lock_t, unsigned int bits); + +extern unsigned int spin_unlock_bit(spin_lock_t, unsigned int bits); + +extern unsigned int spin_lock_try(spin_lock_t); + +extern unsigned int spin_lock_held(spin_lock_t); + +/* Other atomic routines */ + +extern unsigned int compare_and_store(unsigned int oval, + unsigned int nval, unsigned int *area); + +extern unsigned int atomic_add(unsigned int *area, int val); + +extern unsigned int atomic_or(unsigned int *area, unsigned int mask); + +extern unsigned int atomic_sub(unsigned int *area, int val); + +extern void queue_atomic(unsigned int *anchor, + unsigned int *elem, unsigned int disp); + +extern void queue_atomic_list(unsigned int *anchor, + unsigned int *first, unsigned int *last, + unsigned int disp); + +extern unsigned int *dequeue_atomic(unsigned int *anchor, unsigned int disp); + +#ifdef __cplusplus +} +#endif + +#endif /* _ATOMIC_H_ */ diff --git a/CommonUtilitiesLib/base64.c b/CommonUtilitiesLib/base64.c new file mode 100644 index 0000000..c400b62 --- /dev/null +++ b/CommonUtilitiesLib/base64.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* ==================================================================== + * Copyright (c) 1995-1999 The Apache Group. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * 4. The names "Apache Server" and "Apache Group" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Group and was originally based + * on public domain software written at the National Center for + * Supercomputing Applications, University of Illinois, Urbana-Champaign. + * For more information on the Apache Group and the Apache HTTP server + * project, please see . + * + */ + +/* Base64 encoder/decoder. Originally Apache file ap_base64.c + */ + +#include + +#include "base64.h" + +/* aaaack but it's fast and const should make it shared text page. */ +static const unsigned char pr2six[256] = +{ + /* ASCII table */ + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}; + +int Base64decode_len(const char *bufcoded) +{ + int nbytesdecoded; + register const unsigned char *bufin; + register int nprbytes; + + bufin = (const unsigned char *) bufcoded; + while (pr2six[*(bufin++)] <= 63); + + nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; + nbytesdecoded = ((nprbytes + 3) / 4) * 3; + + return nbytesdecoded + 1; +} + +int Base64decode(char *bufplain, const char *bufcoded) +{ + int nbytesdecoded; + register const unsigned char *bufin; + register unsigned char *bufout; + register int nprbytes; + + bufin = (const unsigned char *) bufcoded; + while (pr2six[*(bufin++)] <= 63); + nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; + nbytesdecoded = ((nprbytes + 3) / 4) * 3; + + bufout = (unsigned char *) bufplain; + bufin = (const unsigned char *) bufcoded; + + while (nprbytes > 4) { + *(bufout++) = + (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); + *(bufout++) = + (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); + *(bufout++) = + (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); + bufin += 4; + nprbytes -= 4; + } + + /* Note: (nprbytes == 1) would be an error, so just ingore that case */ + if (nprbytes > 1) { + *(bufout++) = + (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); + } + if (nprbytes > 2) { + *(bufout++) = + (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); + } + if (nprbytes > 3) { + *(bufout++) = + (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); + } + + *(bufout++) = '\0'; + nbytesdecoded -= (4 - nprbytes) & 3; + return nbytesdecoded; +} + +static const char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int Base64encode_len(int len) +{ + return ((len + 2) / 3 * 4) + 1; +} + +int Base64encode(char *encoded, const char *string, int len) +{ + int i; + char *p; + + p = encoded; + for (i = 0; i < len - 2; i += 3) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int) (string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2) | + ((int) (string[i + 2] & 0xC0) >> 6)]; + *p++ = basis_64[string[i + 2] & 0x3F]; + } + if (i < len) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + if (i == (len - 1)) { + *p++ = basis_64[((string[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int) (string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } + + *p++ = '\0'; + return p - encoded; +} diff --git a/CommonUtilitiesLib/base64.h b/CommonUtilitiesLib/base64.h new file mode 100644 index 0000000..5e8dc08 --- /dev/null +++ b/CommonUtilitiesLib/base64.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* ==================================================================== + * Copyright (c) 1995-1999 The Apache Group. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * 4. The names "Apache Server" and "Apache Group" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Group and was originally based + * on public domain software written at the National Center for + * Supercomputing Applications, University of Illinois, Urbana-Champaign. + * For more information on the Apache Group and the Apache HTTP server + * project, please see . + * + */ + + + +#ifndef _BASE64_H_ +#define _BASE64_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int Base64encode_len(int len); +int Base64encode(char * coded_dst, const char *plain_src,int len_plain_src); + +int Base64decode_len(const char * coded_src); +int Base64decode(char * plain_dst, const char *coded_src); + +#ifdef __cplusplus +} +#endif + +#endif //_BASE64_H_ diff --git a/CommonUtilitiesLib/daemon.c b/CommonUtilitiesLib/daemon.c new file mode 100644 index 0000000..ed76cbf --- /dev/null +++ b/CommonUtilitiesLib/daemon.c @@ -0,0 +1,89 @@ +/* + * + * @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@ + * + */ +/* + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include "daemon.h" + +int daemon(int nochdir, int noclose) +{ + int fd; + + switch (fork()) { + case -1: + return (-1); + case 0: + break; + default: + _exit(0); + } + + if (setsid() == -1) + return (-1); + + if (!nochdir) + (void)chdir("/"); + + if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > 2) + (void)close (fd); + } + return (0); +} diff --git a/CommonUtilitiesLib/daemon.h b/CommonUtilitiesLib/daemon.h new file mode 100644 index 0000000..aeca6ba --- /dev/null +++ b/CommonUtilitiesLib/daemon.h @@ -0,0 +1,73 @@ +/* + * + * @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@ + * + */ +/* + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _DAEMON_H_ +#define _DAEMON_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int daemon(int nochdir, int noclose); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/CommonUtilitiesLib/ev.cpp b/CommonUtilitiesLib/ev.cpp new file mode 100644 index 0000000..f4f23a4 --- /dev/null +++ b/CommonUtilitiesLib/ev.cpp @@ -0,0 +1,475 @@ +/* + * + * @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: ev.cpp + + Contains: POSIX select implementation of MacOS X event queue functions. + + + + +*/ + +#if !MACOSXEVENTQUEUE + +#define EV_DEBUGGING 0 //Enables a lot of printfs + +#if SET_SELECT_SIZE + #ifndef FD_SETSIZE + #define FD_SETSIZE SET_SELECT_SIZE + #endif +#endif + + #include + #include + +#ifndef __MACOS__ +#ifndef __hpux__ + #include +#endif +#endif + +#include +#include +#include +#include + +#include "ev.h" +#include "OS.h" +#include "OSHeaders.h" +#include "MyAssert.h" +#include "OSThread.h" +#include "OSMutex.h" + +static fd_set sReadSet; +static fd_set sWriteSet; +static fd_set sReturnedReadSet; +static fd_set sReturnedWriteSet; +static void** sCookieArray = NULL; +static int* sFDsToCloseArray = NULL; +static int sPipes[2]; + +static int sCurrentFDPos = 0; +static int sMaxFDPos = 0; +static bool sInReadSet = true; +static int sNumFDsBackFromSelect = 0; +static UInt32 sNumFDsProcessed = 0; +static OSMutex sMaxFDPosMutex; + + +static bool selecthasdata(); +static int constructeventreq(struct eventreq* req, int fd, int event); + + +void select_startevents() +{ + FD_ZERO(&sReadSet); + FD_ZERO(&sWriteSet); + FD_ZERO(&sReturnedReadSet); + FD_ZERO(&sReturnedWriteSet); + + //qtss_printf("FD_SETSIZE=%d sizeof(fd_set) * 8 ==%ld\n", FD_SETSIZE, sizeof(fd_set) * 8); + //We need to associate cookies (void*)'s with our file descriptors. + //We do so by storing cookies in this cookie array. Because an fd_set is + //a big array of bits, we should have as many entries in the array as + //there are bits in the fd set + sCookieArray = new void*[sizeof(fd_set) * 8]; + ::memset(sCookieArray, 0, sizeof(void *) * sizeof(fd_set) * 8); + + //We need to close all fds from the select thread. Once an fd is passed into + //removeevent, its added to this array so it may be deleted from the select thread + sFDsToCloseArray = new int[sizeof(fd_set) * 8]; + for (int i = 0; i < (int) (sizeof(fd_set) * 8); i++) + sFDsToCloseArray[i] = -1; + + //We need to wakeup select when the masks have changed. In order to do this, + //we create a pipe that gets written to from modwatch, and read when select returns + int theErr = ::pipe((int*)&sPipes); + Assert(theErr == 0); + + //Add the read end of the pipe to the read mask + FD_SET(sPipes[0], &sReadSet); + sMaxFDPos = sPipes[0]; +} + +int select_removeevent(int which) +{ + + { + //Manipulating sMaxFDPos is not pre-emptive safe, so we have to wrap it in a mutex + //I believe this is the only variable that is not preemptive safe.... + OSMutexLocker locker(&sMaxFDPosMutex); + + //Clear this fd out of both sets + FD_CLR(which, &sWriteSet); + FD_CLR(which, &sReadSet); + + FD_CLR(which, &sReturnedReadSet); + FD_CLR(which, &sReturnedWriteSet); + + sCookieArray[which] = NULL; // Clear out the cookie + + if (which == sMaxFDPos) + { + //We've just deleted the highest numbered fd in our set, + //so we need to recompute what the highest one is. + while (!FD_ISSET(sMaxFDPos, &sReadSet) && !FD_ISSET(sMaxFDPos, &sWriteSet) && + (sMaxFDPos > 0)) + { +#if EV_DEBUGGING + qtss_printf("removeevent: reset MaxFDPos = %d to %d\n", sMaxFDPos , sMaxFDPos -1); +#endif + sMaxFDPos--; + } + } + + //We also need to keep the mutex locked during any manipulation of the + //sFDsToCloseArray, because it's definitely not preemptive safe. + + //put this fd into the fd's to close array, so that when select wakes up, it will + //close the fd + UInt32 theIndex = 0; + while ((sFDsToCloseArray[theIndex] != -1) && (theIndex < sizeof(fd_set) * 8)) + theIndex++; + Assert(sFDsToCloseArray[theIndex] == -1); + sFDsToCloseArray[theIndex] = which; +#if EV_DEBUGGING + qtss_printf("removeevent: Disabled %d \n", which); +#endif + } + + //write to the pipe so that select wakes up and registers the new mask + int theErr = ::write(sPipes[1], "p", 1); + Assert(theErr == 1); + + return 0; +} + +int select_watchevent(struct eventreq *req, int which) +{ + return select_modwatch(req, which); +} + +int select_modwatch(struct eventreq *req, int which) +{ + { + //Manipulating sMaxFDPos is not pre-emptive safe, so we have to wrap it in a mutex + //I believe this is the only variable that is not preemptive safe.... + OSMutexLocker locker(&sMaxFDPosMutex); + + //Add or remove this fd from the specified sets + if (which & EV_RE) + { + #if EV_DEBUGGING + qtss_printf("modwatch: Enabling %d in readset\n", req->er_handle); + #endif + FD_SET(req->er_handle, &sReadSet); + } + else + { + #if EV_DEBUGGING + qtss_printf("modwatch: Disbling %d in readset\n", req->er_handle); + #endif + FD_CLR(req->er_handle, &sReadSet); + } + if (which & EV_WR) + { + #if EV_DEBUGGING + qtss_printf("modwatch: Enabling %d in writeset\n", req->er_handle); + #endif + FD_SET(req->er_handle, &sWriteSet); + } + else + { + #if EV_DEBUGGING + qtss_printf("modwatch: Disabling %d in writeset\n", req->er_handle); + #endif + FD_CLR(req->er_handle, &sWriteSet); + } + + if (req->er_handle > sMaxFDPos) + sMaxFDPos = req->er_handle; + +#if EV_DEBUGGING + qtss_printf("modwatch: MaxFDPos=%d\n", sMaxFDPos); +#endif + // + // Also, modifying the cookie is not preemptive safe. This must be + // done atomically wrt setting the fd in the set. Otherwise, it is + // possible to have a NULL cookie on a fd. + Assert(req->er_handle < (int)(sizeof(fd_set) * 8)); + Assert(req->er_data != NULL); + sCookieArray[req->er_handle] = req->er_data; + } + + //write to the pipe so that select wakes up and registers the new mask + int theErr = ::write(sPipes[1], "p", 1); + Assert(theErr == 1); + + return 0; +} + +int constructeventreq(struct eventreq* req, int fd, int event) +{ + Assert(fd < (int)(sizeof(fd_set) * 8)); + if (fd >=(int)(sizeof(fd_set) * 8) ) + { + #if EV_DEBUGGING + qtss_printf("constructeventreq: invalid fd=%d\n", fd); + #endif + return 0; + } + req->er_handle = fd; + req->er_eventbits = event; + req->er_data = sCookieArray[fd]; + sCurrentFDPos++; + sNumFDsProcessed++; + + //don't want events on this fd until modwatch is called. + FD_CLR(fd, &sWriteSet); + FD_CLR(fd, &sReadSet); + + return 0; +} + +int select_waitevent(struct eventreq *req, void* /*onlyForMacOSX*/) +{ + //Check to see if we still have some select descriptors to process + int theFDsProcessed = (int)sNumFDsProcessed; + bool isSet = false; + + if (theFDsProcessed < sNumFDsBackFromSelect) + { + if (sInReadSet) + { + OSMutexLocker locker(&sMaxFDPosMutex); +#if EV_DEBUGGING + qtss_printf("waitevent: Looping through readset starting at %d\n", sCurrentFDPos); +#endif + while((!(isSet = FD_ISSET(sCurrentFDPos, &sReturnedReadSet))) && (sCurrentFDPos < sMaxFDPos)) + sCurrentFDPos++; + + if (isSet) + { +#if EV_DEBUGGING + qtss_printf("waitevent: Found an fd: %d in readset max=%d\n", sCurrentFDPos, sMaxFDPos); +#endif + FD_CLR(sCurrentFDPos, &sReturnedReadSet); + return constructeventreq(req, sCurrentFDPos, EV_RE); + } + else + { +#if EV_DEBUGGING + qtss_printf("waitevent: Stopping traverse of readset at %d\n", sCurrentFDPos); +#endif + sInReadSet = false; + sCurrentFDPos = 0; + } + } + if (!sInReadSet) + { + OSMutexLocker locker(&sMaxFDPosMutex); +#if EV_DEBUGGING + qtss_printf("waitevent: Looping through writeset starting at %d\n", sCurrentFDPos); +#endif + while((!(isSet = FD_ISSET(sCurrentFDPos, &sReturnedWriteSet))) && (sCurrentFDPos < sMaxFDPos)) + sCurrentFDPos++; + + if (isSet) + { +#if EV_DEBUGGING + qtss_printf("waitevent: Found an fd: %d in writeset\n", sCurrentFDPos); +#endif + FD_CLR(sCurrentFDPos, &sReturnedWriteSet); + return constructeventreq(req, sCurrentFDPos, EV_WR); + } + else + { + // This can happen if another thread calls select_removeevent at just the right + // time, setting sMaxFDPos lower than it was when select() was last called. + // Becase sMaxFDPos is used as the place to stop iterating over the read & write + // masks, setting it lower can cause file descriptors in the mask to get skipped. + // If they are skipped, that's ok, because those file descriptors were removed + // by select_removeevent anyway. We need to make sure to finish iterating over + // the masks and call select again, which is why we set sNumFDsProcessed + // artificially here. + sNumFDsProcessed = sNumFDsBackFromSelect; + Assert(sNumFDsBackFromSelect > 0); + } + } + } + + if (sNumFDsProcessed > 0) + { + OSMutexLocker locker(&sMaxFDPosMutex); +#if DEBUG + // + // In a very bizarre circumstance (sMaxFDPos goes down & then back up again, these + // asserts could hit. + // + //for (int x = 0; x < sMaxFDPos; x++) + // Assert(!FD_ISSET(x, &sReturnedReadSet)); + //for (int y = 0; y < sMaxFDPos; y++) + // Assert(!FD_ISSET(y, &sReturnedWriteSet)); +#endif +#if EV_DEBUGGING + qtss_printf("waitevent: Finished with all fds in set. Stopped traverse of writeset at %d maxFD = %d\n", sCurrentFDPos,sMaxFDPos); +#endif + //We've just cycled through one select result. Re-init all the counting states + sNumFDsProcessed = 0; + sNumFDsBackFromSelect = 0; + sCurrentFDPos = 0; + sInReadSet = true; + } + + + + while(!selecthasdata()) + { + { + OSMutexLocker locker(&sMaxFDPosMutex); + //Prepare to call select. Preserve the read and write sets by copying their contents + //into the corresponding "returned" versions, and then pass those into select + ::memcpy(&sReturnedReadSet, &sReadSet, sizeof(fd_set)); + ::memcpy(&sReturnedWriteSet, &sWriteSet, sizeof(fd_set)); + } + + SInt64 yieldDur = 0; + SInt64 yieldStart; + + //Periodically time out the select call just in case we + //are deaf for some reason + // on platforw's where our threading is non-preemptive, just poll select + + struct timeval tv; + tv.tv_usec = 0; + + #if THREADING_IS_COOPERATIVE + tv.tv_sec = 0; + + if ( yieldDur > 4 ) + tv.tv_usec = 0; + else + tv.tv_usec = 5000; + #else + tv.tv_sec = 15; + #endif + +#if EV_DEBUGGING + qtss_printf("waitevent: about to call select\n"); +#endif + + yieldStart = OS::Milliseconds(); + OSThread::ThreadYield(); + + yieldDur = OS::Milliseconds() - yieldStart; +#if EV_DEBUGGING + static SInt64 numZeroYields; + + if ( yieldDur > 1 ) + { + qtss_printf( "select_waitevent time in OSThread::Yield() %i, numZeroYields %i\n", (SInt32)yieldDur, (SInt32)numZeroYields ); + numZeroYields = 0; + } + else + numZeroYields++; + +#endif + + sNumFDsBackFromSelect = ::select(sMaxFDPos+1, &sReturnedReadSet, &sReturnedWriteSet, NULL, &tv); + +#if EV_DEBUGGING + qtss_printf("waitevent: back from select. Result = %d\n", sNumFDsBackFromSelect); +#endif + } + + + if (sNumFDsBackFromSelect >= 0) + return EINTR; //either we've timed out or gotten some events. Either way, force caller + //to call waitevent again. + return sNumFDsBackFromSelect; +} + +bool selecthasdata() +{ + if (sNumFDsBackFromSelect < 0) + { + int err=OSThread::GetErrno(); + +#if EV_DEBUGGING + if (err == ENOENT) + { + qtss_printf("selectHasdata: found error ENOENT==2 \n"); + } +#endif + + if ( +#if __solaris__ + err == ENOENT || // this happens on Solaris when an HTTP fd is closed +#endif + err == EBADF || //this might happen if a fd is closed right before calling select + err == EINTR + ) // this might happen if select gets interrupted + return false; + return true;//if there is an error from select, we want to make sure and return to the caller + } + + if (sNumFDsBackFromSelect == 0) + return false;//if select returns 0, we've simply timed out, so recall select + + if (FD_ISSET(sPipes[0], &sReturnedReadSet)) + { +#if EV_DEBUGGING + qtss_printf("selecthasdata: Got some data on the pipe fd\n"); +#endif + //we've gotten data on the pipe file descriptor. Clear the data. + // increasing the select buffer fixes a hanging problem when the Darwin server is under heavy load + // CISCO contribution + char theBuffer[4096]; + (void)::read(sPipes[0], &theBuffer[0], 4096); + + FD_CLR(sPipes[0], &sReturnedReadSet); + sNumFDsBackFromSelect--; + + { + //Check the fds to close array, and if there are any in it, close those descriptors + OSMutexLocker locker(&sMaxFDPosMutex); + for (UInt32 theIndex = 0; ((sFDsToCloseArray[theIndex] != -1) && (theIndex < sizeof(fd_set) * 8)); theIndex++) + { + (void)::close(sFDsToCloseArray[theIndex]); + sFDsToCloseArray[theIndex] = -1; + } + } + } + Assert(!FD_ISSET(sPipes[0], &sReturnedWriteSet)); + + if (sNumFDsBackFromSelect == 0) + return false;//if the pipe file descriptor is the ONLY data we've gotten, recall select + else + return true;//we've gotten a real event, return that to the caller +} + +#endif //!MACOSXEVENTQUEUE + diff --git a/CommonUtilitiesLib/ev.h b/CommonUtilitiesLib/ev.h new file mode 100644 index 0000000..e9079df --- /dev/null +++ b/CommonUtilitiesLib/ev.h @@ -0,0 +1,93 @@ +/* + * + * @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@ + * + */ + +#ifndef _DSS_SYS_EV_H_ +#define _DSS_SYS_EV_H_ + + + +#if !defined(__Win32__) && !defined(__solaris__) && !defined(__sgi__) && !defined(__osf__) && !defined(__hpux__) + #include +#endif + +#if MACOSXEVENTQUEUE + #include +#else + + +struct eventreq { + int er_type; +#define EV_FD 1 // file descriptor + int er_handle; + void *er_data; + int er_rcnt; + int er_wcnt; + int er_ecnt; + int er_eventbits; +#define EV_RE 1 +#define EV_WR 2 +#define EV_EX 4 +#define EV_RM 8 +}; + +typedef struct eventreq *er_t; + + +#ifdef _KERNEL + +#define EV_RBYTES 0x1 +#define EV_WBYTES 0x2 +#define EV_RWBYTES (EV_RBYTES|EV_WBYTES) +#define EV_RCLOSED 0x4 +#define EV_RCONN 0x8 +#define EV_ERRORS 0x10 +#define EV_WCLOSED 0x20 +#define EV_WCONN 0x40 +#define EV_OOBD 0x80 +#define EV_OOBM 0x100 + +struct eventqelt { + TAILQ_ENTRY(eventqelt) ee_slist; + TAILQ_ENTRY(eventqelt) ee_plist; + struct eventreq ee_req; + struct proc * ee_proc; + u_int ee_flags; +#define EV_QUEUED 1 + u_int ee_eventmask; + struct socket *ee_sp; +}; + +#endif /* _KERNEL */ + + +int select_watchevent(struct eventreq *req, int which); +int select_modwatch(struct eventreq *req, int which); +int select_waitevent(struct eventreq *req, void* onlyForMOSX); +void select_startevents(); +int select_removeevent(int which); + +#endif /* !MACOSXEVENTQUEUE */ + +#endif /* _DSS_SYS_EV_H_ */ diff --git a/CommonUtilitiesLib/getopt.c b/CommonUtilitiesLib/getopt.c new file mode 100644 index 0000000..af3f41f --- /dev/null +++ b/CommonUtilitiesLib/getopt.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +#ifdef __Win32__ + +#include +#include +#include +#include +#include "SafeStdLib.h" +#define OPTERRCOLON (1) +#define OPTERRNF (2) +#define OPTERRARG (3) + +char *optarg; +int optreset = 0; +int optind = 1; +int opterr = 1; +int optopt; + +static int +optiserr(int argc, char * const *argv, int oint, const char *optstr, + int optchr, int err) +{ + if(opterr) + { + qtss_fprintf(stderr, "Error in argument %d, char %d: ", oint, optchr+1); + switch(err) + { + case OPTERRCOLON: + qtss_fprintf(stderr, ": in flags\n"); + break; + case OPTERRNF: + qtss_fprintf(stderr, "option not found %c\n", argv[oint][optchr]); + break; + case OPTERRARG: + qtss_fprintf(stderr, "no argument for option %c\n", argv[oint][optchr]); + break; + default: + qtss_fprintf(stderr, "unknown\n"); + break; + } + } + optopt = argv[oint][optchr]; + return('?'); +} + + + +int +getopt(int argc, char* const *argv, const char *optstr) +{ + static int optchr = 0; + static int dash = 0; /* have already seen the - */ + + char *cp; + + if (optreset) + optreset = optchr = dash = 0; + if(optind >= argc) + return(EOF); + if(!dash && (argv[optind][0] != '-')) + return(EOF); + if(!dash && (argv[optind][0] == '-') && !argv[optind][1]) + { + /* + * use to specify stdin. Need to let pgm process this and + * the following args + */ + return(EOF); + } + if((argv[optind][0] == '-') && (argv[optind][1] == '-')) + { + /* -- indicates end of args */ + optind++; + return(EOF); + } + if(!dash) + { + assert((argv[optind][0] == '-') && argv[optind][1]); + dash = 1; + optchr = 1; + } + + /* Check if the guy tries to do a -: kind of flag */ + assert(dash); + if(argv[optind][optchr] == ':') + { + dash = 0; + optind++; + return(optiserr(argc, argv, optind-1, optstr, optchr, OPTERRCOLON)); + } + if(!(cp = strchr(optstr, argv[optind][optchr]))) + { + int errind = optind; + int errchr = optchr; + + if(!argv[optind][optchr+1]) + { + dash = 0; + optind++; + } + else + optchr++; + return(optiserr(argc, argv, errind, optstr, errchr, OPTERRNF)); + } + if(cp[1] == ':') + { + dash = 0; + optind++; + if(optind == argc) + return(optiserr(argc, argv, optind-1, optstr, optchr, OPTERRARG)); + optarg = argv[optind++]; + return(*cp); + } + else + { + if(!argv[optind][optchr+1]) + { + dash = 0; + optind++; + } + else + optchr++; + return(*cp); + } + assert(0); + return(0); +} + +#ifdef TESTGETOPT +int + main (int argc, char **argv) + { + int c; + extern char *optarg; + extern int optind; + int aflg = 0; + int bflg = 0; + int errflg = 0; + char *ofile = NULL; + + while ((c = getopt(argc, argv, "abo:")) != EOF) + switch (c) { + case 'a': + if (bflg) + errflg++; + else + aflg++; + break; + case 'b': + if (aflg) + errflg++; + else + bflg++; + break; + case 'o': + ofile = optarg; + (void)qtss_printf("ofile = %s\n", ofile); + break; + case '?': + errflg++; + } + if (errflg) { + (void)qtss_fprintf(stderr, + "usage: cmd [-a|-b] [-o ] files...\n"); + exit (2); + } + for ( ; optind < argc; optind++) + (void)qtss_printf("%s\n", argv[optind]); + return 0; + } + +#endif /* TESTGETOPT */ + +#endif /* WIN32 */ diff --git a/CommonUtilitiesLib/getopt.h b/CommonUtilitiesLib/getopt.h new file mode 100644 index 0000000..538c97e --- /dev/null +++ b/CommonUtilitiesLib/getopt.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +#ifndef GETOPT_H +#define GETOPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +//#ifdef __Win32__ + +extern char *optarg; +extern int optreset; +extern int optind; +extern int opterr; +extern int optopt; +int getopt(int argc, char* const *argv, const char *optstr); + +//#endif /* WIN32 */ + +#ifdef __cplusplus +} +#endif + +#endif /* GETOPT_H */ diff --git a/CommonUtilitiesLib/md5.c b/CommonUtilitiesLib/md5.c new file mode 100644 index 0000000..4c91d5b --- /dev/null +++ b/CommonUtilitiesLib/md5.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* MD5.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* + Note: Renamed the functions to avoid having same symbols in + the linked-in frameworks. + It is a hack to work around the problem. + */ + +#include "md5.h" +#include + +/* Constants for MD5Transform routine. + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform (UInt32 state[4], unsigned char block[64]); +static void Encode (unsigned char *output, UInt32 *input, unsigned int len); +static void Decode (UInt32 *output, unsigned char *input, unsigned int len); +static void MD5_memcpy (UInt8 * output, UInt8 * input, size_t len); +static void MD5_memset (UInt8 * output, int value, size_t len); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UInt32)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UInt32)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UInt32)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UInt32)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ + /* context */ +void MD5_Init (MD5_CTX *context) +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. +*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ + /* context input block length of input block*/ +void MD5_Update (MD5_CTX *context, unsigned char *input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UInt32)inputLen << 3)) + < ((UInt32)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UInt32)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. +*/ + if (inputLen >= partLen) { + MD5_memcpy + ((UInt8 *)&context->buffer[index], (UInt8 *)input, (size_t) partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy + ((UInt8 *)&context->buffer[index], (UInt8 *)&input[i], + (size_t) (inputLen-i) ); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ + /* message digest context */ +void MD5_Final (unsigned char digest[16], MD5_CTX *context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. +*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5_Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5_Update (context, bits, 8); + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. +*/ + MD5_memset ((UInt8 *)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform (UInt32 state[4], unsigned char block[64]) +{ + UInt32 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. +*/ + MD5_memset ((UInt8 *)x, 0, sizeof (x)); +} + +/* Encodes input (UInt32) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (unsigned char *output, UInt32 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UInt32). Assumes len is + a multiple of 4. + */ +static void Decode (UInt32 *output, unsigned char *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UInt32)input[j]) | (((UInt32)input[j+1]) << 8) | + (((UInt32)input[j+2]) << 16) | (((UInt32)input[j+3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ + +static void MD5_memcpy (UInt8 * output, UInt8 * input, size_t len) +{ +/* unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i]; +*/ + memcpy(output, input, len); +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset (UInt8 * output, int value, size_t len) +{ +/* unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; + */ + memset(output, value, len); +} + + + + + diff --git a/CommonUtilitiesLib/md5.h b/CommonUtilitiesLib/md5.h new file mode 100644 index 0000000..d40495a --- /dev/null +++ b/CommonUtilitiesLib/md5.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +/* MD5.H - header file for MD5.C + */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* + Note: Renamed the functions to avoid having same symbols in + the linked-in frameworks. + It is a hack to work around the problem. + */ + +#ifndef _MD5_H_ +#define _MD5_H_ + +#include "OSHeaders.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* MD5 context. */ +typedef struct { + UInt32 state[4]; /* state (ABCD) */ + UInt32 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5_Init(MD5_CTX *context); +void MD5_Update(MD5_CTX *context, unsigned char *input, unsigned int inputLen); +void MD5_Final(unsigned char digest[16], MD5_CTX *context); + +#ifdef __cplusplus +} +#endif + +#endif + + + + + + diff --git a/CommonUtilitiesLib/md5digest.cpp b/CommonUtilitiesLib/md5digest.cpp new file mode 100644 index 0000000..a71a8da --- /dev/null +++ b/CommonUtilitiesLib/md5digest.cpp @@ -0,0 +1,394 @@ +/* + * + * @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: md5digest.cpp + + Contains: Implements the function declared in md5digest.h +*/ + +#include "md5.h" +#include "md5digest.h" +#include "StrPtrLen.h" +#include +#include "OSMemory.h" + +static StrPtrLen sColon(":", 1); +static StrPtrLen sMD5Sess("md5-sess", 8); +static StrPtrLen sQopAuth("auth", 4); +static StrPtrLen sQopAuthInt("auth-int", 8); + +// allocates memory for hashStr->Ptr +void HashToString(unsigned char aHash[kHashLen], StrPtrLen* hashStr){ + UInt16 i; + UInt8 hexDigit; + // Allocating memory + char* str = NEW char[kHashHexLen+1]; + str[kHashHexLen] = 0; + + for(i = 0; i < kHashLen; i++) { + hexDigit = (aHash[i] >> 4) & 0xF; + str[i*2] = (hexDigit <= 9) ? (hexDigit + '0') : (hexDigit + 'a' - 10); + hexDigit = aHash[i] & 0xF; + str[i*2 + 1] = (hexDigit <= 9) ? (hexDigit + '0') : (hexDigit + 'a' - 10); + } + + hashStr->Ptr = str; + hashStr->Len = kHashHexLen; +} + +// allocates memory for hashA1Hex16Bit->Ptr +void CalcMD5HA1( StrPtrLen* userName, + StrPtrLen* realm, + StrPtrLen* userPassword, + StrPtrLen* hashA1Hex16Bit + ) +{ + // parameters must be valid pointers + // It is ok if parameter->Ptr is NULL as long as parameter->Len is 0 + Assert(userName); + Assert(realm); + Assert(userPassword); + Assert(hashA1Hex16Bit); + Assert(hashA1Hex16Bit->Ptr == NULL); //This is the result. A Ptr here will be replaced. Value should be NULL. + + MD5_CTX context; + unsigned char* aHash = NEW unsigned char[kHashLen]; + + // Calculate H(A1) for MD5 + // where A1 for algorithm = "md5" or if nothing is specified is + // A1 = userName:realm:userPassword + MD5_Init(&context); + MD5_Update(&context, (unsigned char *)userName->Ptr, userName->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)realm->Ptr, realm->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)userPassword->Ptr, userPassword->Len); + MD5_Final(aHash, &context); + hashA1Hex16Bit->Ptr = (char *)aHash; + hashA1Hex16Bit->Len = kHashLen; +} + +// allocates memory to hA1->Ptr +void CalcHA1( StrPtrLen* algorithm, + StrPtrLen* userName, + StrPtrLen* realm, + StrPtrLen* userPassword, + StrPtrLen* nonce, + StrPtrLen* cNonce, + StrPtrLen* hA1 + ) +{ + // parameters must be valid pointers + // It is ok if parameter->Ptr is NULL as long as parameter->Len is 0 + Assert(algorithm); + Assert(userName); + Assert(realm); + Assert(userPassword); + Assert(nonce); + Assert(cNonce); + Assert(hA1); + Assert(hA1->Ptr == NULL); //This is the result. A Ptr here will be replaced. Value should be NULL. + + MD5_CTX context; + unsigned char aHash[kHashLen]; + + // Calculate H(A1) + // where A1 for algorithm = "md5" or if nothing is specified is + // A1 = userName:realm:userPassword + // and for algorithm = "md5-sess" is + // A1 = H(userName:realm:userPassword):nonce:cnonce + MD5_Init(&context); + MD5_Update(&context, (unsigned char *)userName->Ptr, userName->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)realm->Ptr, realm->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)userPassword->Ptr, userPassword->Len); + MD5_Final(aHash, &context); + if(algorithm->Equal(sMD5Sess)) { + MD5_Init(&context); + MD5_Update(&context, aHash, kHashLen); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)nonce->Ptr, nonce->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)cNonce->Ptr, cNonce->Len); + MD5_Final(aHash, &context); + } + HashToString(aHash, hA1); +} + +// allocates memory to hA1->Ptr +void CalcHA1Md5Sess(StrPtrLen* hashA1Hex16Bit, StrPtrLen* nonce, StrPtrLen* cNonce, StrPtrLen* hA1) +{ + // parameters must be valid pointers + // It is ok if parameter->Ptr is NULL as long as parameter->Len is 0 + Assert(hashA1Hex16Bit); + Assert(hashA1Hex16Bit->Len == kHashLen); + Assert(nonce); + Assert(cNonce); + Assert(hA1); + Assert(hA1->Ptr == NULL); //This is the result. A Ptr here will be replaced. Value should be NULL. + + MD5_CTX context; + unsigned char aHash[kHashLen]; + + MD5_Init(&context); + MD5_Update(&context, (unsigned char *)hashA1Hex16Bit->Ptr, kHashLen); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)nonce->Ptr, nonce->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)cNonce->Ptr, cNonce->Len); + MD5_Final(aHash, &context); + + // allocates memory to hA1->Ptr + HashToString(aHash, hA1); +} + +// allocates memory for requestDigest->Ptr +void CalcRequestDigest( StrPtrLen* hA1, + StrPtrLen* nonce, + StrPtrLen* nonceCount, + StrPtrLen* cNonce, + StrPtrLen* qop, + StrPtrLen* method, + StrPtrLen* digestUri, + StrPtrLen* hEntity, + StrPtrLen* requestDigest + ) +{ + // parameters must be valid pointers + // It is ok if parameter->Ptr is NULL as long as parameter->Len is 0 + Assert(hA1); + Assert(nonce); + Assert(nonceCount); + Assert(cNonce); + Assert(qop); + Assert(method); + Assert(digestUri); + Assert(hEntity); + Assert(requestDigest); + Assert(requestDigest->Ptr == NULL); //This is the result. A Ptr here will be replaced. Value should be NULL. + + unsigned char aHash[kHashLen], requestHash[kHashLen]; + StrPtrLen hA2; + MD5_CTX context; + + + // H(data) = MD5(data) + // and KD(secret, data) = H(concat(secret, ":", data)) + + // Calculate H(A2) + // where A2 for qop="auth" or no qop is + // A2 = method:digestUri + // and for qop = "auth-int" is + // A2 = method:digestUri:H(entity-body) + MD5_Init(&context); + MD5_Update(&context, (unsigned char *)method->Ptr, method->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)digestUri->Ptr, digestUri->Len); + if(qop->Equal(sQopAuthInt)) { + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)hEntity->Ptr, hEntity->Len); + } + MD5_Final(aHash, &context); + + // HashToString allocates memory for hA2...delete it after request-digest is created + HashToString(aHash, &hA2); + // Calculate request-digest + // where request-digest for qop="auth" or qop="auth-int" is + // request-digest = KD( H(A1), nonce:nonceCount:cNonce:qop:H(A2) ) + // and if qop directive isn't present is + // request-digest = KD( H(A1), nonce:H(A2) ) + MD5_Init(&context); + MD5_Update(&context, (unsigned char *)hA1->Ptr, hA1->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)nonce->Ptr, nonce->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + if(qop->Ptr != NULL) { + MD5_Update(&context, (unsigned char *)nonceCount->Ptr, nonceCount->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)cNonce->Ptr, cNonce->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + MD5_Update(&context, (unsigned char *)qop->Ptr, qop->Len); + MD5_Update(&context, (unsigned char *)sColon.Ptr, sColon.Len); + } + MD5_Update(&context, (unsigned char *)hA2.Ptr, hA2.Len); + MD5_Final(requestHash, &context); + HashToString(requestHash, requestDigest); + + // Deleting memory allocated for hA2 + delete [] hA2.Ptr; +} + + + +/* From local_passwd.c (C) Regents of Univ. of California blah blah */ +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ +"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +void to64(register char *s, register SInt32 v, register int n) +{ + while (--n >= 0) { + *s++ = itoa64[v & 0x3f]; + v >>= 6; + } +} + +/* + * Define the Magic String prefix that identifies a password as being + * hashed using our algorithm. + */ +static char *dufr_id = "$dufr$"; + +// Doesn't allocate any memory. The size of the result buffer should be nbytes +void MD5Encode(char *pw, char *salt, char *result, int nbytes) +{ + /* + * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL, + * plus 4 for the '$' separators, plus the password hash itself. + * Let's leave a goodly amount of leeway. + */ + + char passwd[120], *p; + char *sp, *ep; + unsigned char final[kHashLen]; + int sl, pl, i; + MD5_CTX ctx, ctx1; + UInt32 l; + + /* + * Refine the salt first. It's possible we were given an already-hashed + * string as the salt argument, so extract the actual salt value from it + * if so. Otherwise just use the string up to the first '$' as the salt. + */ + sp = salt; + + //If it starts with the magic string, then skip that. + if (!strncmp(sp, dufr_id, strlen(dufr_id))) + { + sp += strlen(dufr_id); + } + + //It stops at the first '$' or 8 chars, whichever comes first + for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) + { + continue; + } + + //Get the length of the true salt + sl = ep - sp; + + //'Time to make the doughnuts..' + MD5_Init(&ctx); + + //The password first, since that is what is most unknown + MD5_Update(&ctx, (unsigned char *)pw, strlen(pw)); + + //Then our magic string + MD5_Update(&ctx, (unsigned char *)dufr_id, strlen(dufr_id)); + + //Then the raw salt + MD5_Update(&ctx, (unsigned char *)sp, sl); + + //Then just as many characters of the MD5(pw, salt, pw) + MD5_Init(&ctx1); + MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw)); + MD5_Update(&ctx1, (unsigned char *)sp, sl); + MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw)); + MD5_Final(final, &ctx1); + for (pl = strlen(pw); pl > 0; pl -= kHashLen) + { + MD5_Update(&ctx, (unsigned char *)final,(pl > kHashLen) ? kHashLen : pl); + } + + //Don't leave anything around in vm they could use. + memset(final, 0, sizeof(final)); + + //Then something really weird... + for (i = strlen(pw); i != 0; i >>= 1) + { + if (i & 1) { + MD5_Update(&ctx, (unsigned char *)final, 1); + } + else { + MD5_Update(&ctx, (unsigned char *)pw, 1); + } + } + + /* + * Now make the output string. We know our limitations, so we + * can use the string routines without bounds checking. + */ + strcpy(passwd, dufr_id); + strncat(passwd, sp, sl); + strcat(passwd, "$"); + + MD5_Final(final, &ctx); + + /* + * And now, just to make sure things don't run too fast.. + * On a 60 Mhz Pentium this takes 34 msec, so you would + * need 30 seconds to build a 1000 entry dictionary... + */ + for (i = 0; i < 1000; i++) + { + MD5_Init(&ctx1); + if (i & 1) { + MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw)); + } + else { + MD5_Update(&ctx1, final, kHashLen); + } + if (i % 3) { + MD5_Update(&ctx1, (unsigned char *)sp, sl); + } + + if (i % 7) { + MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw)); + } + + if (i & 1) { + MD5_Update(&ctx1, (unsigned char *)final, kHashLen); + } + else { + MD5_Update(&ctx1, (unsigned char *)pw, strlen(pw)); + } + MD5_Final(final,&ctx1); + } + + p = passwd + strlen(passwd); + + l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p, l, 4); p += 4; + l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p, l, 4); p += 4; + l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p, l, 4); p += 4; + l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p, l, 4); p += 4; + l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p, l, 4); p += 4; + l = final[11] ; to64(p, l, 2); p += 2; + *p = '\0'; + + //Don't leave anything around in vm they could use. + memset(final, 0, sizeof(final)); + + strncpy(result, passwd, nbytes - 1); +} diff --git a/CommonUtilitiesLib/md5digest.h b/CommonUtilitiesLib/md5digest.h new file mode 100644 index 0000000..406743f --- /dev/null +++ b/CommonUtilitiesLib/md5digest.h @@ -0,0 +1,80 @@ +/* + * + * @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: md5digest.h + + Contains: Provides a function to calculate the md5 digest + given all the authentication parameters. + +*/ + +#ifndef _MD5DIGEST_H_ +#define _MD5DIGEST_H_ + +#include "StrPtrLen.h" + +enum { + kHashHexLen = 32, + kHashLen = 16 +}; + +// HashToString allocates memory for hashStr->Ptr +void HashToString(unsigned char aHash[kHashLen], StrPtrLen* hashStr); + +// allocates memory for hashA1Hex16Bit->Ptr +void CalcMD5HA1(StrPtrLen* userName, StrPtrLen* realm, StrPtrLen* userPassword, StrPtrLen* hashA1Hex16Bit); + +// allocates memory to hA1->Ptr +void CalcHA1( StrPtrLen* algorithm, + StrPtrLen* userName, + StrPtrLen* realm, + StrPtrLen* userPassword, + StrPtrLen* nonce, + StrPtrLen* cNonce, + StrPtrLen* hA1 + ); + +// allocates memory to hA1->Ptr +void CalcHA1Md5Sess(StrPtrLen* hashA1Hex16Bit, StrPtrLen* nonce, StrPtrLen* cNonce, StrPtrLen* hA1); + +// allocates memory for requestDigest->Ptr +void CalcRequestDigest( StrPtrLen* hA1, + StrPtrLen* nonce, + StrPtrLen* nonceCount, + StrPtrLen* cNonce, + StrPtrLen* qop, + StrPtrLen* method, + StrPtrLen* digestUri, + StrPtrLen* hEntity, + StrPtrLen* requestDigest + ); + + +void to64(register char *s, register SInt32 v, register int n); + +// Doesn't allocate any memory. The size of the result buffer should be nbytes +void MD5Encode( char *pw, char *salt, char *result, int nbytes); + +#endif diff --git a/CommonUtilitiesLib/mycondition.cpp b/CommonUtilitiesLib/mycondition.cpp new file mode 100644 index 0000000..461ae92 --- /dev/null +++ b/CommonUtilitiesLib/mycondition.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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: mycondition.c + + Contains: xxx put contents here xxx + + Written by: Greg Vaughan + Writers: + + (GV) Greg Vaughan + (CNR) Christopher Ryan + + Change History (most recent first): + + <5> 7/24/00 GV changed Carbon to CarbonCore for header include + <4> 1/6/00 GBV got rid of libatomic + <3> 12/8/99 CNR Use OSAssert.h + <2> 10/27/99 GBV update for beaker + + To Do: +*/ + +#include "mycondition.h" +#include +#include "SafeStdLib.h" +#if __MacOSX__ + +#ifndef __CORESERVICES__ +#include +#endif + +#endif + +#include +#include "MyAssert.h" + +struct MyCondition +{ + mach_port_t fWaitPort; + SInt32 fNumWaiting; +}; + +typedef struct MyCondition MyCondition; + +MyCondition* MCAllocateCondition(); +void MCDisposeCondition(MyCondition* theCondition); +void MCBroadcast(MyCondition* theCondition); +void MCSignal(MyCondition* theCondition); +void MCWait(MyCondition* theCondition, mymutex_t theMutex, int timeout); +void MCBlockThread(MyCondition* theCondition, int timeout); +void MCUnblockThread(MyCondition* theCondition); + +mycondition_t mycondition_alloc() +{ + return (mycondition_t)MCAllocateCondition(); +} + +void mycondition_free(mycondition_t theCondition_t) +{ + MCDisposeCondition((MyCondition*)theCondition_t); +} + +void mycondition_broadcast(mycondition_t theCondition_t) +{ + MCBroadcast((MyCondition*)theCondition_t); +} + +void mycondition_signal(mycondition_t theCondition_t) +{ + MCSignal((MyCondition*)theCondition_t); +} + +void mycondition_wait(mycondition_t theCondition_t, mymutex_t theMutex_t, int timeout) +{ + MCWait((MyCondition*)theCondition_t, theMutex_t, timeout); +} + +SInt32 sNumConds = 0; + +MyCondition* MCAllocateCondition() +{ + kern_return_t ret; + MyCondition* newCondition = (MyCondition*)malloc(sizeof(MyCondition)); + if (newCondition == NULL) + { + Assert(newCondition != NULL); + return NULL; + } + + newCondition->fNumWaiting = 0; + + ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &newCondition->fWaitPort); + if (ret != KERN_SUCCESS) + { + Assert(0); + free(newCondition); + return NULL; + } + ret = mach_port_insert_right(mach_task_self(), newCondition->fWaitPort, newCondition->fWaitPort, MACH_MSG_TYPE_MAKE_SEND); + if (ret != KERN_SUCCESS) + { + Assert(0); + free(newCondition); + return NULL; + } + IncrementAtomic(&sNumConds); + return newCondition; +} + +void MCDisposeCondition(MyCondition* theCondition) +{ + kern_return_t ret = mach_port_destroy(mach_task_self(), theCondition->fWaitPort); + DecrementAtomic(&sNumConds); + Assert(ret == 0); + free(theCondition); +} + +void MCBroadcast(MyCondition* theCondition) +{ + int numToSignal = theCondition->fNumWaiting; + while (numToSignal > 0) + { + MCUnblockThread(theCondition); + numToSignal--; + } +} + +void MCSignal(MyCondition* theCondition) +{ + MCUnblockThread(theCondition); +} + +void MCWait(MyCondition* theCondition, mymutex_t theMutex, int timeout) +{ + mymutex_unlock(theMutex); + IncrementAtomic(&theCondition->fNumWaiting); + MCBlockThread(theCondition, timeout); + DecrementAtomic(&theCondition->fNumWaiting); + mymutex_lock(theMutex); +} + + +typedef struct { + mach_msg_header_t header; + mach_msg_trailer_t trailer; +} mHeader; + +void MCBlockThread(MyCondition* theCondition, int timeout) +{ + kern_return_t ret; + mHeader msg; + + memset(&msg, 0, sizeof(msg)); + if (timeout > 0) + ret = mach_msg(&msg.header,MACH_RCV_MSG | MACH_RCV_TIMEOUT,0, sizeof(msg), + theCondition->fWaitPort,timeout,MACH_PORT_NULL); + else + ret = mach_msg(&msg.header,MACH_RCV_MSG,0, sizeof(msg), + theCondition->fWaitPort,MACH_MSG_TIMEOUT_NONE,MACH_PORT_NULL); + AssertV((ret < 1) || (ret > 2000), ret); +} + +void MCUnblockThread(MyCondition* theCondition) +{ + kern_return_t ret; + mHeader msg; + + memset(&msg, 0, sizeof(msg)); + msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); + msg.header.msgh_size = sizeof msg - sizeof msg.trailer; + msg.header.msgh_local_port = MACH_PORT_NULL; + msg.header.msgh_remote_port = theCondition->fWaitPort; + msg.header.msgh_id = 0; + ret = mach_msg(&msg.header,MACH_SEND_MSG | MACH_SEND_TIMEOUT,msg.header.msgh_size,0,MACH_PORT_NULL,0, + MACH_PORT_NULL); + AssertV((ret < 1) || (ret > 2000), ret); +} + diff --git a/CommonUtilitiesLib/mycondition.h b/CommonUtilitiesLib/mycondition.h new file mode 100644 index 0000000..478d1d5 --- /dev/null +++ b/CommonUtilitiesLib/mycondition.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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: mycondition.h + + Contains: xxx put contents here xxx + + Written by: Greg Vaughan + + Change History (most recent first): + + <2> 10/27/99 GBV update for beaker + + To Do: +*/ + +#ifndef _MYCONDITION_H_ +#define _MYCONDITION_H_ + + +#include +#include + +#include "mymutex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* mycondition_t; +mycondition_t mycondition_alloc(); +void mycondition_free(mycondition_t); + +void mycondition_broadcast(mycondition_t); +void mycondition_signal(mycondition_t); +void mycondition_wait(mycondition_t, mymutex_t, int); //timeout as a msec offset from now (0 means no timeout) + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/CommonUtilitiesLib/mymutex.cpp b/CommonUtilitiesLib/mymutex.cpp new file mode 100644 index 0000000..e02fe1b --- /dev/null +++ b/CommonUtilitiesLib/mymutex.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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: mymutex.c + + Contains: xxx put contents here xxx + + Written by: Greg Vaughan + + Writers: + + (GV) Greg Vaughan + (CNR) Christopher Ryan + + Change History (most recent first): + + <7> 9/5/00 CNR Use mach_port_destroy instead of mach_port_deallocate in + MMDisposeMutex. + <6> 7/24/00 GV changed Carbon to CarbonCore for header include + <5> 1/11/00 CNR Fix for IncrementAtomic/DecrementAtomic return value. + <4> 1/6/00 GBV got rid of libatomic + <3> 12/8/99 CNR Use OSAssert.h + <2> 10/27/99 GBV update for beaker + + To Do: +*/ + +#include "mymutex.h" +#include +#include "SafeStdLib.h" +#if __MacOSX__ + +#ifndef __CORESERVICES__ +#include +#endif + +#endif + +#include "MyAssert.h" + +struct MyMutex +{ + SInt32 fCount; + pthread_t fHolder; + UInt32 fMutexLock; + mach_port_t fWaitPort; + SInt32 fNumWaiting; +}; + +typedef struct MyMutex MyMutex; + +MyMutex* MMAllocateMutex(); +void MMDisposeMutex(MyMutex* theMutex); +void MMGrab(MyMutex* theMutex); +int MMTryGrab(MyMutex* theMutex); +void MMRelease(MyMutex* theMutex); +pthread_t MMGetFirstWaitingThread(MyMutex* theMutex, int* listWasEmpty); +int MMAlreadyHaveControl(MyMutex* theMutex, pthread_t thread); +int MMTryAndGetControl(MyMutex* theMutex, pthread_t thread); +void MMReleaseControl(MyMutex* theMutex); +void MMStripOffWaitingThread(MyMutex* theMutex); +void MMAddToWaitList(MyMutex* theMutex, pthread_t thread); +void MMBlockThread(MyMutex* theMutex); +void MMUnblockThread(MyMutex* theMutex); + +mymutex_t mymutex_alloc() +{ + return (mymutex_t)MMAllocateMutex(); +} + +void mymutex_free(mymutex_t theMutex_t) +{ + MMDisposeMutex((MyMutex*)theMutex_t); +} + +void mymutex_lock(mymutex_t theMutex_t) +{ + MMGrab((MyMutex*)theMutex_t); +} + +int mymutex_try_lock(mymutex_t theMutex_t) +{ + return MMTryGrab((MyMutex*)theMutex_t); +} + +void mymutex_unlock(mymutex_t theMutex_t) +{ + MMRelease((MyMutex*)theMutex_t); +} + +SInt32 sNumMutexes = 0; + +MyMutex* MMAllocateMutex() +{ + kern_return_t ret; + MyMutex* newMutex = (MyMutex*)malloc(sizeof(MyMutex)); + if (newMutex == NULL) + { + Assert(newMutex != NULL); + return NULL; + } + + newMutex->fCount = 0; + newMutex->fHolder = 0; + newMutex->fNumWaiting = 0; + newMutex->fMutexLock = 0; + ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &newMutex->fWaitPort); + if (ret != KERN_SUCCESS) + { + AssertV(ret == 0, ret); + free(newMutex); + return NULL; + } + ret = mach_port_insert_right(mach_task_self(), newMutex->fWaitPort, newMutex->fWaitPort, MACH_MSG_TYPE_MAKE_SEND); + if (ret != KERN_SUCCESS) + { + AssertV(ret == 0, ret); + free(newMutex); + return NULL; + } + AssertV(ret == 0, ret); + IncrementAtomic(&sNumMutexes); + return newMutex; +} + +void MMDisposeMutex(MyMutex* theMutex) +{ + int err = noErr; + err = mach_port_destroy(mach_task_self(), theMutex->fWaitPort); + DecrementAtomic(&sNumMutexes); + AssertV(err == noErr, err); + free(theMutex); +} + +void MMGrab(MyMutex* theMutex) +{ + pthread_t thread = pthread_self(); + + if (theMutex->fHolder != thread) + { + int waiting = IncrementAtomic(&theMutex->fNumWaiting) + 1; + + if ((waiting > 1) || !CompareAndSwap(0, 1, &theMutex->fMutexLock)) + { + do + { + // suspend ourselves until something happens + MMBlockThread(theMutex); + } while (!CompareAndSwap(0, 1, &theMutex->fMutexLock)); + } + + DecrementAtomic(&theMutex->fNumWaiting); + + // we just got control, so reset fCount + theMutex->fCount = 0; // gets incremented below... + theMutex->fHolder = thread; + } + + // we have control now, so increment the count + ++theMutex->fCount; +} + +int MMTryGrab(MyMutex* theMutex) +{ + pthread_t thread = pthread_self(); + int haveControl; + + haveControl = (theMutex->fHolder == thread); + if (!haveControl) + haveControl = CompareAndSwap(0, 1, &theMutex->fMutexLock); + + if (haveControl) + { + theMutex->fHolder = thread; + ++theMutex->fCount; + } + + return haveControl; +} + +void MMRelease(MyMutex* theMutex) +{ + pthread_t thread = pthread_self(); + if (theMutex->fHolder != thread) + return; + + if (!--theMutex->fCount) + { + theMutex->fHolder = NULL; + theMutex->fMutexLock = 0; // let someone else deal with it + if (theMutex->fNumWaiting > 0) + MMUnblockThread(theMutex); + } +} + +typedef struct { + mach_msg_header_t header; + mach_msg_trailer_t trailer; +} mHeader; + +void MMBlockThread(MyMutex* theMutex) +{ + kern_return_t ret; + mHeader msg; + + memset(&msg, 0, sizeof(msg)); + ret = mach_msg(&msg.header,MACH_RCV_MSG,0,sizeof(msg), + theMutex->fWaitPort,MACH_MSG_TIMEOUT_NONE,MACH_PORT_NULL); + AssertV(ret == 0, ret); +} + +void MMUnblockThread(MyMutex* theMutex) +{ + kern_return_t ret; + mHeader msg; + + memset(&msg, 0, sizeof(msg)); + msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); + msg.header.msgh_size = sizeof msg - sizeof msg.trailer; + msg.header.msgh_local_port = MACH_PORT_NULL; + msg.header.msgh_remote_port = theMutex->fWaitPort; + msg.header.msgh_id = 0; + ret = mach_msg(&msg.header,MACH_SEND_MSG,msg.header.msgh_size,0,MACH_PORT_NULL,MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + AssertV(ret == 0, ret); +} + diff --git a/CommonUtilitiesLib/mymutex.h b/CommonUtilitiesLib/mymutex.h new file mode 100644 index 0000000..d495544 --- /dev/null +++ b/CommonUtilitiesLib/mymutex.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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: mymutex.h + + Contains: xxx put contents here xxx + + Written by: Greg Vaughan + + Change History (most recent first): + + <2> 10/27/99 GBV update for beaker + + To Do: +*/ + +#ifndef _MYMUTEX_H_ +#define _MYMUTEX_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef void* mymutex_t; +mymutex_t mymutex_alloc(); +void mymutex_free(mymutex_t); + +void mymutex_lock(mymutex_t); +int mymutex_try_lock(mymutex_t); +void mymutex_unlock(mymutex_t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/CommonUtilitiesLib/tempcalls.h b/CommonUtilitiesLib/tempcalls.h new file mode 100644 index 0000000..1d849dc --- /dev/null +++ b/CommonUtilitiesLib/tempcalls.h @@ -0,0 +1,40 @@ +/* + * + * @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@ + * + + */ +#ifdef AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER +// these optimized calls are implemented in stdclib and declared in sys/ev.h on 10.5 and later for better performance. + +/* + int watchevent(struct eventreq *u_req, int u_eventmask); + int waitevent(struct eventreq *u_req, struct timeval *tv); + int modwatch(struct eventreq *u_req, int u_eventmask); +*/ + +#else + // use syscall for the performance calls. + #define watchevent(a,b) syscall(231,(a),(b)) + #define waitevent(a,b) syscall(232,(a),(b)) + #define modwatch(a,b) syscall(233,(a),(b)) +#endif //AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER diff --git a/CommonUtilitiesLib/win32ev.cpp b/CommonUtilitiesLib/win32ev.cpp new file mode 100644 index 0000000..87f359b --- /dev/null +++ b/CommonUtilitiesLib/win32ev.cpp @@ -0,0 +1,188 @@ +/* + * + * @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: win32ev.cpp + + Contains: WSA implementation of socket event queue functions. + + Written By: Denis Serenyi + + +*/ + +#include "ev.h" +#include "OSHeaders.h" +#include "OSThread.h" +#include "MyAssert.h" + +// +// You have to create a window to get socket events? What's up with that? +static HWND sMsgWindow = NULL; + +// +LRESULT CALLBACK select_wndproc(HWND inWIndow, UINT inMsg, WPARAM inParam, LPARAM inOtherParam); + +void select_startevents() +{ + // + // This call occurs from the main thread. In Win32, apparently, you + // have to create your WSA window from the same thread that calls GetMessage. + // So, we have to create the window from select_waitevent +} + +int select_removeevent(int /*which*/) +{ + // + // Not needed for WSA. + return 0; +} + +int select_watchevent(struct eventreq *req, int which) +{ + return select_modwatch(req, which); +} + +int select_modwatch(struct eventreq *req, int which) +{ + // + // If our WSAAsyncSelect window is not constructed yet, wait + // until it is construected. The window gets constructed when the server + // is done starting up, so this should only happen when select_modwatch + // is being called as the server is starting up. + while (sMsgWindow == NULL) + OSThread::Sleep(10); + + // Convert EV_RE and EV_WR to the proper WSA event codes. + // WSA event codes are more specific than what POSIX provides, so + // just wait on any kind of read related event for EV_RE, same for EV_WR + SInt32 theEvent = 0; + + if (which & EV_RE) + theEvent |= FD_READ | FD_ACCEPT | FD_CLOSE; + if (which & EV_WR) + theEvent |= FD_WRITE | FD_CONNECT; + + // This is a little bit of a hack, because we are assuming that the caller + // is actually putting a UInt32 in the void*, not a void*, and we are also + // assuming caller is not using the 0 - WM_USER range of values, but + // both of these things are true in the EventContext.cpp code, and this + // mechanism of passing around cookies is just too convienent to ignore. + unsigned int theMsg = (unsigned int)(req->er_data); + + return ::WSAAsyncSelect(req->er_handle, sMsgWindow, theMsg, theEvent); +} + +int select_waitevent(struct eventreq *req, void* /*onlyForMacOSX*/) +{ + if (sMsgWindow == NULL) + { + // + // This is the first time we've called this function. Do our + // window initialization now. + + // We basically just want the simplest window possible. + WNDCLASSEX theWndClass; + theWndClass.cbSize = sizeof(theWndClass); + theWndClass.style = 0; + theWndClass.lpfnWndProc = &select_wndproc; + theWndClass.cbClsExtra = 0; + theWndClass.cbWndExtra = 0; + theWndClass.hInstance = NULL; + theWndClass.hIcon = NULL; + theWndClass.hCursor = NULL; + theWndClass.hbrBackground = NULL; + theWndClass.lpszMenuName = NULL; + theWndClass.lpszClassName = "DarwinStreamingServerWindow"; + theWndClass.hIconSm = NULL; + + ATOM theWndAtom = ::RegisterClassEx(&theWndClass); + Assert(theWndAtom != NULL); + if (theWndAtom == NULL) + ::exit(-1); // Poor error recovery, but this should never happen. + + sMsgWindow = ::CreateWindow( "DarwinStreamingServerWindow", // Window class name + "DarwinStreamingServerWindow", // Window title bar + WS_POPUP, // Window style ( a popup doesn't need a parent ) + 0, // x pos + 0, // y pos + CW_USEDEFAULT, // default width + CW_USEDEFAULT, // default height + NULL, // No parent + NULL, // No menu handle + NULL, // Ignored on WinNT + NULL); // data for message proc. Who cares? + Assert(sMsgWindow != NULL); + if (sMsgWindow == NULL) + ::exit(-1); + } + + MSG theMessage; + + // + // Get a message for my goofy window. 0, 0 indicates that we + // want any message for that window. + // + // Convienently, this function blocks until there is a message, so it works + // much like waitevent would on Mac OS X. + UInt32 theErr = ::GetMessage(&theMessage, sMsgWindow, 0, 0); + + if (theErr > 0) + { + UInt32 theSelectErr = WSAGETSELECTERROR(theMessage.lParam); + UInt32 theEvent = WSAGETSELECTEVENT(theMessage.lParam); + + req->er_handle = theMessage.wParam; // the wParam is the FD + req->er_eventbits = EV_RE; // WSA events & socket events don't map... + // but the server state machines never care + // what the event is anyway. + + // we use the message # as our way of passing around the user data. + req->er_data = (void*)(theMessage.message); + + // + // We should prevent this socket from getting events until modwatch is called. + (void)::WSAAsyncSelect(req->er_handle, sMsgWindow, 0, 0); + + return 0; + } + else + { + // + // Do we ever get WM_QUIT messages? Can there ever be an error? + Assert(0); + return EINTR; + } +} + + +LRESULT CALLBACK select_wndproc(HWND /*inWIndow*/, UINT inMsg, WPARAM /*inParam*/, LPARAM /*inOtherParam*/) +{ + // If we don't return true for this message, window creation will not proceed + if (inMsg == WM_NCCREATE) + return TRUE; + + // All other messages we can ignore and return 0 + return 0; +} diff --git a/Documentation/3rdPartyAcknowledgements.rtf b/Documentation/3rdPartyAcknowledgements.rtf new file mode 100644 index 0000000..1d1bea2 --- /dev/null +++ b/Documentation/3rdPartyAcknowledgements.rtf @@ -0,0 +1,63 @@ +{\rtf1\mac\ansicpg10000\cocoartf100 +{\fonttbl\f0\froman\fcharset77 Times-Bold;\f1\froman\fcharset77 Times-Roman;} +{\colortbl;\red255\green255\blue255;} +\margl1318\margr1318\vieww14420\viewh16040\viewkind0 +\pard\ql\qnatural + +\f0\b\fs28 \cf0 Acknowledgments\ +\pard\ql\qnatural + +\f1\b0\fs20 \cf0 \ +\pard\ql\qnatural + +\fs24 \cf0 Portions of this Apple Software may utilize the following copyrighted material, the use of which is hereby acknowledged.\ +\ +\pard\ql\qnatural + +\f0\b \cf0 Apache Group +\f1\b0 ( Apache) +\f0\b \ +\pard\ql\qnatural + +\f1\b0 \cf0 Copyright (c) 1995-2000 The Apache Software Foundation. All rights reserved.\ +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by the Apache Software Foundation (http://www.apache.org/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear. 4. The names "Apache" and "Apache Software Foundation" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact apache@apache.org. 5. Products derived from this software may not be called "Apache", nor may "Apache" appear in their name, without prior written permission of the Apache Software Foundation.\ +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF, SUCH DAMAGE.\ +This software consists of voluntary contributions made by many individuals on behalf of the Apache Software Foundation. For more information on the Apache Software Foundation, please see . Portions of this software are based upon public domain software originally written at the National Center for Supercomputing Applications, University of Illinois, Urbana-Champaign.\ +\ +\pard\ql\qnatural + +\f0\b \cf0 Jamie Cameron ( perl-based web server )\ +\pard\ql\qnatural + +\f1\b0 \cf0 Copyright (c) Jamie Cameron. All rights reserved.\ +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the developer nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission.\ +THIS SOFTWARE IS PROVIDED BY THE DEVELOPER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE DEVELOPER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\ +\ +\pard\ql\qnatural + +\f0\b \cf0 Sampo Kellomaki (Perl Net SSLeay module)\ +\pard\ql\qnatural + +\f1\b0 \cf0 Copyright (c) 1996 -2001 Sampo Kellomaki . All rights reserved.\ +Distribution and use of this module is under the same terms as the OpenSSL package itself (i.e. free, but mandatory attribution; NO WARRANTY). Please consult -1COPYRIGHT file in the root of the SSLeay distribution. While the source distribution of this perl module does not contain Eric's or OpenSSL's code, if you use this module you will use OpenSSL library. Please give Eric and OpenSSL team credit (as required by their licenses).\ +\ +\pard\ql\qnatural + +\f0\b \cf0 Tomoyuki SADAHIRO (Perl MapUTF module)\ +\pard\ql\qnatural + +\f1\b0 \cf0 Copyright(C) 2001, SADAHIRO Tomoyuki. Japan. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.\ +\ +\pard\ql\qnatural + +\f0\b \cf0 RSA Data Security, Inc. ( MD5)\ +\pard\ql\qnatural + +\f1\b0 \cf0 MD5 Message-Digest Algorithm (c) 1991-1992 RSA Data Security, Inc.\ +\pard\ql\qnatural + +\f0\b \cf0 \ +\pard\ql\qnatural + +\f1\b0\fs18 \cf0 QTSS 101601\ +} \ No newline at end of file diff --git a/Documentation/AboutQTFileTools.html b/Documentation/AboutQTFileTools.html new file mode 100644 index 0000000..d6625a6 --- /dev/null +++ b/Documentation/AboutQTFileTools.html @@ -0,0 +1 @@ + About QTFileTools

About QTFileTools

 

QTFileTools are movie inspection utilities using the Darwin QTFileLib.

QTBroadcaster
QTFileInfo
QTFileTest
QTRTPFileTest
QTRTPGen
QTSampleLister
QTTrackInfo

QTBroadcaster:

Requires a target ip address, a source movie, one or more source hint track ids in movie, and an initial port. Every packet referenced by the hint track(s) is broadcasted to the specified ip address.

QTFileInfo:

Requires a movie name. Displays each track id, name, create date, and mod date. If the track is a hint track, additional information is displayed: the total rtp bytes and packets, the average bit rate and packet size, and the total header percentage of the stream.

QTFileTest:

Requires a movie name. Parses the Movie Header Atom and displays a trace of the output.

QTRTPFileTest:

Requires a movie and a hint track id in the movie. Displays the RTP header (TransmitTime, Cookie, SeqNum, and TimeStamp) for each packet.

QTRTPGen:

Requires a movie and a hint track id. Displays the number of packets in each hint track sample and writes the RTP packets to file "track.cache"

QTSampleLister:

Requires a movie and a track id. Displays track media sample number, media time, Data offset, and sample size for each sample in the track.

QTSDPGen:

Requires a list of 1 or more movies. Displays the SDP information for all of the hinted tracks in each movie. Use -f to save the SDP information to the file [movie].sdp in the same directory as the source movie.

QTTrackInfo:

Requires a movie, sample table atom type, and track id. Displays the information in the sample table atom of the specified track. Supports "stco", "stsc", "stsz", "stts" as the atom type.

Example: "./QTTrackInfo -T stco /movies/mystery.mov 3" dumps the chunk offset sample table in track 3.

\ No newline at end of file diff --git a/Documentation/AboutTheSource.html b/Documentation/AboutTheSource.html new file mode 100644 index 0000000..f769aeb --- /dev/null +++ b/Documentation/AboutTheSource.html @@ -0,0 +1,48 @@ + + + +Darwin Streaming Server - About the Source Code + + + +

Darwin Streaming Server

+

Source Code Description

+

 

+

This document provides detailed information on the internals of the Darwin Streaming Server. The server implements four standard IETF protocols, RTSP (Real-time streaming protocol, RFC 2326), RTP (Real-time Transport Protocol, RFC 1889), RTCP (Real-time transport control protocol, RFC 1889), and SDP (Session description protocol, RFC 2327). Before making modifications to the server code is may help to be familiar with those RFCs.

+

 

+

Introduction

+

The Darwin Streaming Server source code is written entirely in C++, and pervasively uses object-oriented concepts such as inheritance and polymorphism. Almost exclusively, there is one C++ class per .h / .cpp file pair, and those file names match the class name.

+

The code for each of the server's subsystems is separated out into separate folders in the source hierarchy. The following is a short description of each subsytem.

+

 

+

Common Utilities (in CommonUtilitiesLib)

+

Common Utilities is a toolkit of thread management, data structure, networking, and text parsing utilities. Darwin Streaming Server and associated tools use these classes to accomplish the following three goals:

+ +
    +
  • Reduce repeated code by abstracting similar or identical tasks,
  • +
  • Make the higher level code simpler through encapsulation,
  • +
  • Separate out any and all platform-specific code.
+ +
+

Here is a short description of all the classes in the Common Utilities subsystem by group:

+ +
    +
  • OS Classes: Platform-specific code abstractions for timing, condition variables, mutexes, and threads: OS, OSCond, OSMutex, OSThread, OSFileSource. Data structures: OSQueue, OSHashTable, OSHeap, OSRef.

  • +
  • Sockets: Platform-specific code abstractions for TCP and UDP networking. Socket classes are generally asynchronous (or non-blocking), and can send events to "Task objects". For a complete description on how this works, see "What are task objects?" in the FAQ section. Classes are: EventContext, Socket, UDPSocket, UDPDemuxer, UDPSocketPool, TCPSocket, TCPListenerSocket.

  • +
  • Parsing Utilities: These classes serve as a toolkit for parsing and formatting text. Classes are: StringParser, StringFormatter, StrPtrLen, StringTranslator.

  • +
  • Tasks: These classes implement the asynchronous event mechanism the server uses. For a complete description of how tasks work, see "What are task objects?" in the FAQ section. Classes include: Task, TimeoutTask, IdleTask.

  • +
  • RTCPUtilitiesLib: Classes for encoding and decoding RTCP packets(BR QTFileLib - Simple API for parsing the Hint Track format and generating packets.

+ +

 

+

Server Core (in Server.tproj)

+

This folder contains the core server code, which can be divided into three categories: server core, RTSP specific, and RTP specific.

+ +
    +
  • RTSP subsystem: These classes handle the parsing & processing of RTSP requests, and implement the RTSP part of the QTSS module API. Several of the classes correspond directly to elements of the QTSS API (for instance, RTSPRequestInterface is a QTSS_RTSPRequestObject). There is one RTSP session object per RTSP TCP connection. The RTSPSession object is a Task object that processes RTSP related events.

  • +
  • RTP subsystem: These classes handle the sending of media data. The RTPSession object contains the data associated with each RTSP session ID. Each RTPSession is a Task object that can be scheduled to send RTP packets. The RTPStream object represents a single RTP stream. Any number of RTPStream objects can be associated with a single RTPSession. These two objects implement the RTP specific parts of the QTSS module API.

  • +
  • Server core: Classes in this subsystem are prefixed by QTSS. QTSServer handles startup and shutdown. QTSServerInterface stores server globals and compiles server statistics. QTSSPrefs is a data store for server preferences. QTSSModule, QTSSModuleInterface, and QTSSCallbacks are classes whose sole purpose is to support the QTSS module API.

  • +
+ +

 

+

© 1999-2003 Apple Computer, Inc. All rights reserved. Apple, the Apple logo, Mac, Macintosh, PowerBook, Power Macintosh, and QuickTime are trademarks of Apple Computer, Inc., registered in the United States and other countries. iBook, iMac, and Power Mac are trademarks of Apple Computer, Inc. All other product names are trademarks or registered trademarks of their respective holders.

+

 

+ diff --git a/Documentation/CachingProxyProtocol-README.txt b/Documentation/CachingProxyProtocol-README.txt new file mode 100644 index 0000000..84141c6 --- /dev/null +++ b/Documentation/CachingProxyProtocol-README.txt @@ -0,0 +1,128 @@ +Support For Stream Caching in Streaming Server 3 + +Introduction + +Streaming Server 3 adds new features to RTSP and RTP in order to make +it as easy as possible for a caching proxy server to capture and +manage a pristine copy of a media stream. Some of these features are +elements of the RTSP protocol that were not supported in previous +versions. Some are additions to RTSP and RTP - these extensions have +already been, or will be soon, submitted to the IETF for consideration +as additions to the standard. + +This document provides a complete description of the stream caching +support added to Streaming Server 3. It is intended as a guide for +RTSP / RTP caching vendors to use. + +Four major features have been added: + +1. Speed RTSP Header + +Streaming Server 3 supports the RTSP Speed header wherever possible. +This allows a caching proxy server to request a stream to be delivered +faster than real time, so it can move a stream into the cache as +quickly as possible. For complete documentation on the Speed header, +see the RTSP RFC. + +2. x-Transport-Options RTSP Header + +Streaming Server 3 supports a non-standard RTSP header, +x-Transport-Options. This header has one currently supported argument, +"late-tolerance". This argument allows a caching proxy to tell the +server how late packets may be sent and have them still be useful to +the cache. A complete description of this header, with examples, is +included in this document as Appendix A. + +3. RTP-Meta-Info RTP Payload + +Streaming Server 3 fully supports the RTP-Meta-Info RTP payload +format, described separately in an Internet Draft (draft-serenyi-avt-rtp-meta-00). +RTP packets of this payload type include important information such as +the packet transmission time, unique packet number, and video frame +type. Caching proxies can use this information to provide the same +quality of service to clients as the origin server. + +4. x-Packet-Range RTSP Header + +Streaming Server 3 supports a non-standard RTSP header, +x-Packet-Range. This header is similar to the Range RTSP header, but +allows the client to specify a specific range of packets instead of a +range of time. This allows a caching proxy to tell the origin server +to selectively retransmit only those packets which it needs to fill in +holes in its cached copy of the stream. A complete description of this +header, with examples, is included in this document as Appendix B. + + +Appendix A: Description of the x-Transport-Options RTSP header. + +This optional header should be sent from a client to server on a +SETUP, and echoed by the server. If the header is not echoed the +client must assume that the server does not support this header. + +The body of this header contains one or more semi-colon delimited +arguments. Each argument modifies the contents or delivery RTP packets +for the RTP stream specified by the SETUP in a particular way. In the +SETUP request, the arguments represent the options the client would +like applied to the stream. In the SETUP response, the arguments +represent what the server will actually apply to the RTP stream. The +response may contain a subset of the arguments requested by the +client. This may happen in the event the server doesn't support all +the requested arguments, or some don't apply to the RTP stream +specified by the SETUP. The server may also modify the values of any +arguments it returns. + +There is currently only one possible argument for the X-Transport-Options +header. More arguments may be added later in the event that more +fine-grain control is required for the RTSP server's RTP streams. + +Argument 1: late-tolerance + +The value of this argument should be a positive integer, representing +the number of seconds late a media packet may be sent by the server +and still have it be useful to the client. It should be used as a +guide to the server to make a best-effort attempt to deliver all media +data not older than late-tolerance value. + +Example: + +x-Transport-Options: late-tolerance=30 + +If this is being passed to an RTSP server as part of a SETUP for a +video stream, the server should attempt to deliver all video frames +unless they are more than 30 seconds old. Frames that are older than +30 seconds can be dropped by the server because they are stale. + +This can be used by a caching proxy to prevent the media server from +dropping frames or lowering the stream bit rate in the event it falls +behind sending media data. If the caching proxy knows the duration of +the media, setting late-tolerance to that value will prevent the +server from ever dropping frames, allowing the cache to receive a +complete copy of the media data. For a live broadcast, a caching proxy +may want to do extra buffering to improve quality for its clients. The +proxy could advertise the length of its buffer to the server this way. + + +Appendix B: Description of the x-Packet-Range RTSP Header + +This header should be sent from a client to server on a PLAY in place +of the Range header. If the server does not support this header it +should respond with a 501 Header Not Implemented. + +The body of this header contains a start and stop packet number for +this PLAY request. The specified packet numbers must be based on the +packet number RTP-Meta-Info field. For information on how to request +packet numbers as part of the RTP stream, see the RTP-Meta-Info +payload format Internet Draft (draft-serenyi-avt-rtp-meta-00). + +The header format consists of two semi-colon delimited arguments. The +first argument must be the packet number range, with the start and +stop packet numbers separated by a '-'. The second argument must be +the stream URL to which these packet numbers belong. + +Example: + +x-Packet-Range: pn=4551-4689;url=trackID3 + +The stop packet number must be equal to or greater than the start +packet number. Otherwise, the server may return an error or simply not +send any media data following the PLAY response. diff --git a/Documentation/DSS_QT_Logo_License.pdf b/Documentation/DSS_QT_Logo_License.pdf new file mode 100644 index 0000000..63f3121 Binary files /dev/null and b/Documentation/DSS_QT_Logo_License.pdf differ diff --git a/Documentation/DevNotes.html b/Documentation/DevNotes.html new file mode 100644 index 0000000..88cd0a0 --- /dev/null +++ b/Documentation/DevNotes.html @@ -0,0 +1,146 @@ + + + +Darwin Streaming Server - Developer Notes + + +

Darwin Streaming Server Source Code + + + + + + + + + + + + + + + + + +

+

Developer Notes + + + + + + + + + + + + + + + + + +

+

Table of Contents

+Building
+Installation
+Configuration and Testing
+

About the Source

+

Licensing

+

The Darwin Streaming Server is distributed under the terms of the Apple Public Source License. For more information, refer to the license terms at www.publicsource.apple.com. Note that the Apple Public Source License does not allow you to use the terms "QuickTime" or "QuickTime Streaming Server" in descriptions of products developed using Darwin Streaming Server open source code, nor use any Apple trademarks or logos associated with QuickTime and QuickTime Streaming Server.

+

Building

+

If you downloaded a binary package of Darwin Streaming Server, skip to the "Installing" section.

+

To build Darwin Streaming Server on UNIX platforms including MacOS X, type "./Buildit" from within the Darwin Streaming source directory. The script determines the current OS and hardware platform, and builds all of the targets for that platform: DarwinStreamingServer, PlaylistBroadcaster, qtpasswd, and dynamic modules included in the "StreamingServerModules" folder.

+

To build Darwin Streaming Server on Windows NT or Windows 2000, you must have a copy of Visual C++. There is a VC++ workspace file located inside the WinNTSupport directory. Once the workspace is open, from the Build menu select Batch Build.

+

Building the Installer

+

On OS X, at the top level of the source directory, type "./Buildit install dss_fat" to build for 32bit and 64bit ppc and intel processors or type "./Buildit install dss" to build an installer for the current processor only. An OS X installer package will be created in the source directory.

+

On Unix platforms, type "./Buildit install". A DarwinStreamingSrvr-Platform install directory and tar file will be created.

+

On Windows, after building using the WinNTSupport project, run the MakeZip.bat file inside WinNTSupport directory. A WinNTSupport\DarwinStreamingServer install directory will be created.

+

Adjusting the Mac OS X Installer

+

If you are building an installer for Mac OS X 10.4 you will need to change the installer's OS and Volume check script located inside the DarwinStreamingServer.pkg at DarwinStreamingServer.pkg/Contents/Resources/VolumeCheck.

+

You can simply remove the script to allow installs on any volume and OS or edit the file and change the OS version. Simply change the version number the script is checking.

+

Look for: my $REQUIRED_OS_VERS = "10.5.0";

+

Installing/b

<> +

After building the installer, on OS X, double click the DarwinStreamignServer.pkg.

+

On Unix platforms, type ./Install from within the DarwinStreamingSrvr-Platform install directory.

+

On Windows, run the Install script from within the DarwinStreamingServer directory.

+

How to build your dynamic QTSS API modules

+

For building new dynamic QTSS API modules on Mac OS X, follow the instructions in the QTSS API developer documentation, "QTSSAPIDocs.pdf".

+

For platforms other that Mac OS X, your module must be built as a shared object, and it must include APIStubLib/QTSS_Private.cpp. This file is the "stub library" that all modules must link against.

+

On most UNIX platforms, building a shared object is like building any other executable, with the addition of one command-line argument to the linker. On Linux, the -shared option tells the linker that it is producing a shared object, on Solaris, the option is -G.

+

The dynamic QTSS API modules that come with Darwin Streaming Server each have a Makefile.POSIX makefile in their respective source directories. These makefiles execute when the Buildit script runs, and contain the make rules to make the dynamic module for each currently supported UNIX platform. It is easiest to use these existing makefiles as a template to copy and modify for your new QTSS API module.

+

Installation

+

The next step is to install and configure the server. On all platforms, the server reads its preferences from a config file. A default config file, streamingserver.xml, comes with both the source and binary packages. On Mac OS X, the server looks for the file in /Library/QuickTimeStreaming/Config. On Windows, the server looks for the file in "C:\Darwin Streaming Server". On UNIX platforms, the server looks for this file in /etc/streaming. If the streamingserver.xml file isn't available in the directory, the server will create one with the default configuration settings.

+

Once the server is built, you will have an executable called DarwinStreamingServer. The server can be run from the directory where it is built, or you can copy it to another location. On Mac OS X, the binary is called QuickTimeStreamingServer and will be placed in the build directory.

+

To run the server from the directory where it is compiled, just type ./DarwinStreamingServer.

+

Configuration and Testing

+

The server serves all streaming content out of its "media folder". By default, on Mac OS X, this media folder is located at /Library/QuickTimeStreaming/Movies. This path is one of the preferences in the config file. If you want your media folder to be located somewhere else, you must edit the "movie_folder" preference before starting the server. You can also set the movie folder in the web based administration interface. You can start administering by going to http://localhost:1220/ on your server.

+

Once you have a media folder, you can place streaming media into the folder. All QuickTime files must be "hinted" with QuickTime Player Pro or other QuickTime authoring applications before they can be streamed. Once the movie is hinted, copy the file to the streaming media folder on your server.

+

Sample movies (sample_100kbitmov, sample_300kbit.mov, sample_100kbit.mp4, sample_300kbit.mp4) are included in the package and in the source directory. The sample movies are already hinted, so you can place them into your media folder. From the QuickTime client, attempt to access those movie through an rtsp URL (e.g. rtsp://my.server.com/sample_100kbit.mov), and the server will stream that movie to the client.

+

In order to "reflect" a live broadcast, you must setup and start a broadcaster application, such as the QuickTime Broadcaster. The broadcaster produces an SDP file that describes the live broadcast. Place that SDP file into your media folder, access the URL from a QuickTime Client, and the server will "reflect" the live broadcast to the client. You can also do an automatic unicast from broadcasters that support this feature. You do not have to copy the SDP file to the media folder if you select this option.

+

Also included with the Darwin Streaming Server is StreamingLoadTool, a stress tool for testing the server. StreamingLoadTool realistically simulates many RTSP clients viewing a movie from the server. The tool offers many command-line options that can create different types of simulations. Typing ./StreamingLoadTool will print a description of all available options.

+

StreamingLoadTool will only work if there is a movie called "StreamingLoadTool.mov" in the root movie folder of the target server. This file must be a valid, hinted QuickTime movie.

+

For more information about authoring, hinting, and streaming QuickTime media, refer to the QuickTime Streaming Server documentation.

+


+

+

Q&A

+

Q. What platforms does the source compile and run on?

+

The source currently compiles and runs on Mac OS X. It can be ported to other platforms by modifying a handful of platform specific source files:

+

OSThread, OSCond, OSMutex: Implements threads, mutexes, and condition variables. The implementations provided work on MacOS X as well as any platform that supports pthreads.

+

OS: Includes some platform-specific code for getting the current time. Implementations provided work on MacOS X as well as any platform that supports gettimeofday.

+

Socket: This class is C++ wrapper for the sockets API. On MacOS X, this class uses a set of APIs collectively called the Event Queue for receiving events from sockets in non-blocking mode. For other platforms, an implementation of the Event Queue APIs using select() has been provided in ev.cpp. For more details on the Event Queue, see "What is the Event Queue?" in the FAQ section.

+


+

Q. What is the QTFile library?

+

One of the major features of the Darwin Streaming Server is the ability to serve hinted QuickTime files over RTSP and RTP. All of the code for parsing hinted QuickTime files has been abstracted into the QTFile library. Separating the code in this way keeps both parts much simpler: QTFile only deals with file parsing, the Darwin Streaming Server only deals with networking and protocols. The RTPFileModule in the server calls the QTFile library to retrieve packets and meta-data from hinted QuickTime files.

+


+

Q. What is the reflector, and how does it work?

+


+

The reflector allows an administrator to deliver live broadcasts to RTSP clients. The reflector is implemented as an RTP module, and the source code is entirely in RTPReflectorModule.h/.cpp, and ReflectorSession.h/.cpp.

+


+

When a QuickTime client wants to view a broadcast, it first connects to the Darwin Streaming Server reflector module and directs the module to look for a proper incoming broadcast. If the broadcast is found, the Darwin Streaming Server will then "reflect" the broadcast to the client. The following is a detailed description of how this works. Readers may want to familiarize themselves with SDP (Session Description Protocol), and IP multicast before continuing.

+


+

In order to reflect something, there must be a live broadcast available to reflect. A broadcast is a stream of RTP packets generated by an application or process external to the Darwin Streaming Server and typically run on a separate machine. In this discussion we will call the live stream generator the "Broadcaster". The Broadcaster converts a live media source (like a camera, or microphone, or whatever) into RTP packets. It sends the packets over UDP, to either a multicast or unicast destination address. Broadcasters will usually create .sdp files containing all the SDP (Session Description Protocol), information about this live presentation needed by the client and reflector.

+


+

Most importantly, the .sdp file contains the (destination) IP address and ports for the live presentation. The IP address can define a multicast or unicast connection for the client. QuickTime clients can read .sdp files directly and use them to connect directly to a Broadcaster. When the IP address in the .sdp file specifies a multicast address, the client will join the multicast provided there are multicast-aware routers between it and the Broadcaster. When the IP address is a unicast type, the client will connect when the .sdp destination IP address is the IP address of the client. This is because the Broadcaster is sending UDP packets directly to that machine!

+


+

In order to reflect the broadcast stream, the .sdp file created by the Broadcaster must be located on the server, and inside the server's media folder. Let's say that on our server, there's a .sdp file called "fish.sdp" located at the root of the media folder.

+

An RTSP client will connect using "rtsp://ourserver.com/fish",

+


+

After the .sdp file is found, the reflector parses the file to get the source IP address and ports for the live presentation. When the server then makes the connection, the same rules apply to the server as to a real client. This is because the .sdp specified server connection is simply a client of the live presentation. This means the IP address must specify a multicast address, or the IP address of the server itself.

+


+

Once the source address for the live presentation is located, the reflector binds some sockets to the specified ports. If the specified IP address is a multicast type, the reflector will join the multicast. At this point, those sockets will begin receiving all the data being sent by the Broadcaster.

+


+

The reflector module allows a multicast client to view the broadcast stream as a normal unicast stream coming from the Darwin Streaming Server. The .sdp file is rewritten on the fly by the reflector to erase the source IP address and port information, to hide the information from the client. Once a PLAY request is issued by the client, the reflector begins sending all incoming packets from the Broadcaster to the client.

+


+

As additional clients connect to the same live stream, the reflector increments refcounts and adds each new client to its stream tracking data structures. This efficiently allows each client to receive identical copies of all incoming packets from the Broadcaster.

+


+

When all clients have disconnected, the reflector closes the source UDP sockets, and deallocates all resources for that broadcast.

+


+

There is no limit on the number of unique live broadcasts that a single server can reflect, nor on the number of clients that can be connected to a single Reflection, apart from the overhead offile descriptor limitations, CPU, memory & bandwidth constraints. The CPU & memory consumed by a reflected stream is typically much less than normal locally stored media. Note: each unique live broadcast must be represented on the server by a unique .sdp file.

+


+

Q. What is the Event Queue?

+

The Event Queue is an extension to the sockets API that exists on Mac OS X. It consists of three API calls:

+

watchevent: Watch for events on a file descriptor (socket). waitevent: Wait for events on any of sockets (this is a blocking call, it only returns when there is an event pending). modwatch: When waitevent returns an event for a socket, that socket won't receive any new events until modwatch is called for that socket.

+


+

The use of these API calls is almost exclusively contained within Socket.cpp. This file contains the implementation of a thread object called SocketEventQueueThread. This thread blocks on waitevent and notifies the proper Task object (See "What Are Task Objects?") when an event is received.

+


+

For other UNIX platforms, an implementation of these three Event Queue API calls is provided in terms of select(). This implementation is contained in ev.cpp.

+


+

Q. How does the Darwin Streaming Server (DSS) employ threads?

+


+

DSS has four main threads managing its subsystems: a single connection thread for managing all connections, a short duration task thread for servicing tasks like rtp packet transmission, a long duration task thread for handling rtsp session requests and authentication, and an idle thread for time based tasks. DSS does not dedicate a thread per connection because the cost of servicing multiple connections would become prohibitively expensive when hundreds or thousands of connections are active. Typically connections last anywhere from 5 minutes to hours. To allow the server to scale into the thousands of connections, the Darwin Streaming Server uses asynchronous I/O wherever possible so a given thread will never block.

+


+

Q. What are Task objects?

+


+

Because the server is largely asynchronous, there needs to be a communication mechanism for events. For instance, when a socket used for an RTSP connection gets data, something has to be notified so that data can be processed. The Task object is a generalized mechanism for performing this communication.

+


+

Each Task object has two major methods: Signal and Run. Signal is called by the server to send an event to a Task object. Run is called to give time to the Task for processing the event. The goal of each Task object is to implement server functionality using small non-blocking time slices. Run is a pure virtual function that is called when a Task object has events to process. Inside the Run function, the Task object can call GetEvents to receive and automatically dequeue all its current and previously Signaled events. All Task functions are atomic: if a Task object calls GetEvents in its Run function, and is then Signaled before the Run function completes, the Run function will be called again for the new event after exiting the function. In fact, the Task"s Run function will be called repeatedly until the task object"s event queue has been cleared with GetEvents. Run functions are not re-entered during execution due to new signaled events.

+


+

This core concept of event triggered high performance Tasks is integrated into almost every subsystem in DSS. For instance, a Task object can be associated with a Socket object. If the Socket gets an event -- through a select() notification, or through the Mac OS X Event Queue (see "What is the Event Queue?") -- the corresponding Task object will be Signaled. In this case the body of the Run function will contain the code for processing whatever event was received on that Socket.

+


+


+

+

© 1999-2008 Apple Computer, Inc. All rights reserved. Apple, the Apple logo, Mac, Macintosh, PowerBook, Power Macintosh, and QuickTime are trademarks of Apple Computer, Inc., registered in the United States and other countries. iBook, iMac, and Power Mac are trademarks of Apple Computer, Inc. All other product names are trademarks or registered trademarks of their respective holders.

+
+ diff --git a/Documentation/FAQ.html b/Documentation/FAQ.html new file mode 100644 index 0000000..a0089fc --- /dev/null +++ b/Documentation/FAQ.html @@ -0,0 +1,151 @@ +

What is Darwin Streaming Server?

+

 

+
+

Darwin Streaming Server is the open source version of the QuickTime Streaming Server allowing you to stream hinted QuickTime, MPEG-4, and 3GPP files over the Internet via the industry standard RTP and RTSP protocols.

+

Q. What's new in DSS 6.0.3?

+
+
- DSS now builds and runs 64-bit on MacOS X
+
- Updates for example modules:
- The example Authorization module has been updated.
+
+
Source code is available in: APIModules/QTSSDemoAuthorizationModule.bproj
+
+
- The example Spam Defense module has been updated.
+
+
Source code is available in: APIModules/QTSSSpamDefenseModule.bproj
+
+
- An example RTSP redirect module has been included
+
+
Source code is available in: APIModules/QTSSDemoRedirectModule.bproj
Example modules are installed into:
(MacOS X): /Library/QuickTimeStreamingServer/Modules.disabled
(other Unix): /usr/local/sbin/StreamingServerModules
+
+
- OS X based user account support is added using Apple Open Directory services and includes LDAP and Active Directory user account authentication and authorization
+
- Full source for the streamingloadtool the QT, MP4, and 3GPP rtsp/rtp test client.
- Separate thread pools are used for RTSP processing and RTP processing
- Performance is improved on OS X systems with 4 and 8 cores over previous releases.
- Supports hinted files greater than 4GB in size.
- The posted pre-built MacOS package runs on MacOS X 10.5 or later only
- No build support yet for non-Mac OS X systems (open source submissions are needed to build on other platforms)
- No longer posting pre-built install binaries for non-Mac OS X systems.
Version 5.5.5 for Mac OS X, Linux and Windows can be downloaded from http://developer.apple.com/opensource/server/streaming/index.html
+
+


This release contains open source submissions for the following issues:

+
+
- Fixed compilation problem on Solaris 10u3 (Stefan Parvu)
- Fixed access log c-bytes value (Amir Wolf)
- Fixed access log CPU utilization value
- Fixed compilation problem on FC6 linux PPC (Matthew McGillis)
- Fix to allow streaming of files with bad hint track references, allows compatibility with some popular encoders (Fredrik Widlund)
- Fix StreamingProxy compilation problems for some non-MacOSX platforms
- Added a HowTo for uninstalling DSS on a MacOS X system
- Misc. fixes for Debian linux (Ben Humpert)
- Fixed problem with video sync frame detection for MacOS X on Intel (Lorenzo Vicisano)
+
+


To submit your own Darwin Streaming Server modifications, please use http://dss.macosforge.org/trac/newticket

+

 

+

Q. Does the Darwin Streaming Server 6.0.3 install on Mac OS X 10.4 Tiger?

+

The posted installer will not install to 10.4.  The installer binaries are built for 10.5 and will not run on older versions of the Mac OS.

+

 

+

Q. Can Darwin Streaming Server 6.0.3 be built on Mac OS X 10.4 Tiger?

+

Yes, there are some functional differences but the 6.0.3 source can be built using the buildit script as well as the installer package on Mac OS X 10.4.  See the file: Documentation/DevNotes.html in the source code.

+

 

+

Q. What does the Darwin Streaming Server source include?

+

The package includes source files for a streaming server with web based administration that can serve on-disk "hinted" QuickTime, MPEG2-program streams, MPEG-4, and 3GPP files and reflect live broadcasts, as well as source for the proxy (except on Windows). See the Documentation directory included with the source for more information about the code.

+

 

+

 

+

Q. Where can I find information about streaming with Darwin Streaming Server (DSS)?

+

http://dss.macosforge.org

+

http://soundscreen.com/streaming

+

http://streaming411.com/wiki

+

http://streaming411.com/forums

+

http://lists.apple.com/mailman/listinfo/streaming-server-users

+

http://lists.apple.com/mailman/listinfo/streaming-server-dev

+

 

+

Q. What is the Darwin Streaming Server development cycle?

+

Darwin Streaming Server (DSS) is an ongoing project supported by Apple and the DSS the development community. Updates to the source code and pre-compiled binaries are delivered on an as needed basis. If you are interested in seeing the latest snaphshot of the codebase, visit http://dss.macosforge.org.

+

+

 

+

Q. Which CVS tag is the latest stable release

+

Future CVS use is under review while SVN or simple source tar postings are being evaluated.

+

You can download the latest Mac OS X 10.5 release from  http://dss.macosforge.org

+

The http://developer.apple.com/opensource/server/streaming/ CVS contains software for DSS Mac OS 10.4 and other OS platforms.

+

The latest CVS tag is DSS_5_5_5_Release. The Darwin Streaming Server branch tag is DSS_10_4_Branch. The QuickTime Streaming Server branch tag is MacOS_10_4_Branch.

+

Q. What is on the CVS top of tree?

+

The top of tree is reserved for merging QuickTime Streaming Server development and unreleased code with Darwin Streaming Server code to create a new major release branch.  Bug fixes and submissions are added to branches.

+

 

+

Q. I submitted a bug fix and/or feature request. When can I expect to see it incorporated into the DSS code base?

+

Bug fixes that have been submitted to the Darwin Streaming Server codebase are evaluated and tested by Apple prior to being incorporated into the codebase. Apple engineers make every effort to apply open source developer submitted code changes and fixes to the DSS code base where appropriate. The streaming server mailing list hosted by Apple is a forum for the  community to discuss important features, bugs and deployments. 

+

 

+

Q. My .mp4, .3gp, or .mov file won't stream. Why does the player show 415 invalid media?

+

The streaming server supports QuickTime Movie (MOV),  MPEG-4 (MP4), and 3GPP (3GP) "hinted" files.

+

Hinting is a post-process that you apply to your movies to make them RTSP-streamable. You can hint them with QuickTime Pro or the hinting tool available in the MPEG4IP package.

+

If you don't hint your .mov's or mp4's they will still be HTTP-downloadable but it will take them some seconds to start playing. You won't need a streaming server for this, just use good old Apache.

+

See also http://soundscreen.com/streaming/compress_hint.html

+

 

+

Q. Can I stream mp3 files with DSS?

+

No, not by default, but the server can be configured using the experimental module "QTSSHttpFileModule" located in the modules.disabled directory. The module must be moved to the modules directory and an http_folder must be defined in the server's preference xml file.

+

 

+

Q. Can I http download files with DSS?

+

No, not by default, but the server can be configured using the experimental module "QTSSHttpFileModule" located in the modules.disabled directory. The module must be moved to the modules directory and an http_folder must be defined in the server's preference xml file.

+

 

+

Q. Can I configure DSS to stream live mp3 streams to simulate a radio station to connected users?

+

Yes. Use the MP3Broadcaster that is part of DSS to broadcast mp3 v1, v2, and v2.3 files from a server side playlist to DSS. All files must have the same sample rate.

+

 

+

Q. Can I configure DSS to stream live .mp4, .3gp, or .mov streams to simulate a radio or tv station to connected users?

+

Yes. Use the PlaylistBroadcaster that is part of DSS, to stream hinted files from a server side playlist to DSS. All video files must have the same frame size and use the same codec and all audio files must have the same sample size and use the same codec.

+

 

+

Q. Can I update the server side playlists while the playlist is being broadcast?

+

Yes. Replace the playlist file and add a playlist file using the playlist name and the extension ".updatelist" in the same directory as the playlist file.

+

 

+

Q. Can I see what the upcoming files are while the server side playlist is playing?

+

Yes. Look for the file with the extensions ".upcoming" in the directory with the playlist.

+

 

+

Q. Can I see the name of the current file being played by the server side playlist?

+

Yes. Look for a file with the extension ".current" in the directory with the playlist.

+

 

+

Q. Can I tell the playlist broadcaster to stop playing a playlist after 0 or more files?

+

Yes.  Add a playlist file using the playlist name and the extension ".stoplist" in the same directory as the playlist file and it will be read at the next song and then deleted. The broadcast will stop playing at the end of the stoplist.

+

 

+

Q. Can I temporarily insert a set of files into the playlist while it is playing?

+

Yes.  Add a playlist file with the playlist name and the extension ".insertlist"  in the same directory as the playlist file and its list will be insert at the end of the next song and then deleted. The broadcast will revert back to the original list after playing the inserted list.

+

 

+

Q.Where is the streaming server's preference xml file?

+

OS X: /Library/QuickTimeStreaming/Config/streamingserver.xml

+

Windows: c:\Program Files\Darwin Streaming Server\streamingserver.xml

+

Unix style OS: /etc/streaming/streamingserver.xml

+

 

+

Q. Where are the default log, movie folder, modules, and configuration paths defined?

+

See the file defaultPaths.h in the source code.

+

 

+

 

+

Q. What codecs can I use with DSS?

+

Because DSS streams hinted streaming files, any file that has been successfully hinted can be used by DSS.  Hinted files remove the need for the server to understand the media information of the files it streams.

+

 

+

Q. How do I compile on Linux?

+

On Unix platforms, type "./Buildit" from within the source directory.

+

 

+

Q. How do I create an installer directory and tar package?

+

On Unix platforms, type "./Buildit install". A DarwinStreamingSrvr-Platform install directory and tar file will be created.

+

See the file DevNotes.html file  in the source code "Documentation" directory for more information.

+

 

+

 

+

Q. How do I compile on Windows?

+

DSS 5.5.5 or earlier

+

To build Darwin Streaming Server on Windows NT or Windows 2000, you must have a copy of Visual C++ version 6.0. There is a VC++ workspace file located inside the WinNTSupport directory that can be used to to build the server. Once the workspace is open, select Batch Build from the Build menu .

+

See the file DevNotes.html file  in the source code "Documentation" directory for more information.

+

 

+

Q. Why does it take so long (a few seconds) the first time I connect to my streaming server using the QuickTime Player?

+

The first time the player connects to an IP address it checks the bandwidth to the server which can take a few seconds.  

+

 

+

Q. Why does it take so long (30 seconds or more) the first time I connect to my streaming server using the QuickTime Player?

+

If there is a firewall or the default UDP port is unavailable the client will try alternate ports and protocols to connect to the server. This process can take up to a minute.

+

 

+

Q. How do I get through a firewall?

+

The best solution is to configure the firewall to allow streaming access minimally on port 554 and preferably with udp support on 6970-6999, and 1220 for web admin access, 8000 for mp3 streaming, and 7070 for some streaming players.   When that is not possible, the QuickTime Player will automatically try to switch to the HTTP protocol to stream from the server, this sometimes works but if the standard streaming ports are completely blocked by a firewall then streaming on port 80 should be tried.

+

 

+

Q. How do I stream on Port 80 and have a Web server on the same system.

+

This is not possible without changing either DSS or the web server's port to something other than 80.

+

 

+

Q. How do I set up Authenticated access?

+

Version 6.0 and later

+

Turn off guest access in the server preference xml file by setting the "enable_allow_guest_default" preference to "false".

+

The server will authenticate users against Directory Services and qtaccess files.

+

 

+

Please see DSS admin guide located at http://developer.apple.com/opensource/server/streaming/qtss_admin_guide.pdf

+

 

+

 

+

Q. What does this error mean?

+

 "415 - Unsupported Media Type" -- file is probably not hinted or corrupt. The server was found but the requested media couldn't be accessed. 

+

"-3285 disconnect" error -- a  firewall probably, the server was found but a network failure occured.

+

"-5420 connection failed" -- server not found maybe not running or the machine or network is not connected

+

"404 file not found"  -- means a file is not found on the server or a live stream is no longer broadcasting to the server.

+

 

+

 

+

Q. Can I use the QuickTime logo or web badge with my server?

+

Guidelines for use of the QuickTime logo and web badge are available at http://developer.apple.com/softwarelicensing/agreements/quicktime.html.

+
+

 

+

 

\ No newline at end of file diff --git a/Documentation/Howto/Uninstalling_for_OS_X.txt b/Documentation/Howto/Uninstalling_for_OS_X.txt new file mode 100644 index 0000000..b022b3f --- /dev/null +++ b/Documentation/Howto/Uninstalling_for_OS_X.txt @@ -0,0 +1,19 @@ +Here's a brief how-to guide to deinstall QTSS on the OS X platform. + +1. Back up config files sitting at /Library/QuickTimeStreaming/Config/ as needed. Also, the qtaccess privileges file present in the Movies directory. +2. Delete the configuration receipt at /Library/Receipts/DarwinStreamingServer.pkg +3. Remove the server and the webmin perl script: +/usr/sbin/QuickTimeStreamingServer +/usr/sbin/streamingadminserver.pl +4. For completeness, also the Playlist Broadcaster & friends, +/usr/bin/PlaylistBroadcaster +/usr/bin/MP3Broadcaster +/usr/bin/StreamingLoadTool +/usr/bin/createuserstreamingdir + +5. If deinstalling for good, also remove the relevant entries in +/etc/hostconfig +namely, +QTSSWEBADMIN=-YES- +QTSSRUNSERVER=-YES- + diff --git a/Documentation/License.rtf b/Documentation/License.rtf new file mode 100644 index 0000000..d25ae43 --- /dev/null +++ b/Documentation/License.rtf @@ -0,0 +1,385 @@ +{\rtf1\mac\ansicpg10000\cocoartf102 +{\fonttbl\f0\froman\fcharset77 TimesNewRomanPSMT;\f1\froman\fcharset77 Times-Bold;\f2\fnil\fcharset77 Geneva; +\f3\fswiss\fcharset77 Helvetica-Bold;} +{\colortbl;\red255\green255\blue255;\red210\green0\blue0;} +\margl1440\margr1440\vieww12240\viewh10200\viewkind1\viewscale100 +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural + +\f0\fs20 \cf0 \ + +\f1\b\fs24 ENGLISH\ +\ + +\fs28 Apple Public Source License\ +Apple Computer, Inc.\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural + +\f2\b0\fs20 \cf0 \ + +\f3\b\fs26 \cf2 Version 2.0 - August 6, 2003 +\f2\b0\fs20 \cf0 \ +Please read this License carefully before downloading this software.\ +By downloading or using this software, you are agreeing to be bound by\ +the terms of this License. If you do not or cannot agree to the terms\ +of this License, please do not download or use the software.\ +\ +1. General; Definitions. This License applies to any program or other\ +work which Apple Computer, Inc. ("Apple") makes publicly available and\ +which contains a notice placed by Apple identifying such program or\ +work as "Original Code" and stating that it is subject to the terms of\ +this Apple Public Source License version 2.0 ("License"). As used in\ +this License:\ +\ +1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is\ +the grantor of rights, (i) claims of patents that are now or hereafter\ +acquired, owned by or assigned to Apple and (ii) that cover subject\ +matter contained in the Original Code, but only to the extent\ +necessary to use, reproduce and/or distribute the Original Code\ +without infringement; and (b) in the case where You are the grantor of\ +rights, (i) claims of patents that are now or hereafter acquired,\ +owned by or assigned to You and (ii) that cover subject matter in Your\ +Modifications, taken alone or in combination with Original Code.\ +\ +1.2 "Contributor" means any person or entity that creates or\ +contributes to the creation of Modifications.\ +\ +1.3 "Covered Code" means the Original Code, Modifications, the\ +combination of Original Code and any Modifications, and/or any\ +respective portions thereof.\ +\ +1.4 "Externally Deploy" means: (a) to sublicense, distribute or\ +otherwise make Covered Code available, directly or indirectly, to\ +anyone other than You; and/or (b) to use Covered Code, alone or as\ +part of a Larger Work, in any way to provide a service, including but\ +not limited to delivery of content, through electronic communication\ +with a client other than You.\ +\ +1.5 "Larger Work" means a work which combines Covered Code or portions\ +thereof with code not governed by the terms of this License.\ +\ +1.6 "Modifications" mean any addition to, deletion from, and/or change\ +to, the substance and/or structure of the Original Code, any previous\ +Modifications, the combination of Original Code and any previous\ +Modifications, and/or any respective portions thereof. When code is\ +released as a series of files, a Modification is: (a) any addition to\ +or deletion from the contents of a file containing Covered Code;\ +and/or (b) any new file or other representation of computer program\ +statements that contains any part of Covered Code.\ +\ +1.7 "Original Code" means (a) the Source Code of a program or other\ +work as originally made available by Apple under this License,\ +including the Source Code of any updates or upgrades to such programs\ +or works made available by Apple under this License, and that has been\ +expressly identified by Apple as such in the header file(s) of such\ +work; and (b) the object code compiled from such Source Code and\ +originally made available by Apple under this License.\ +\ +1.8 "Source Code" means the human readable form of a program or other\ +work that is suitable for making modifications to it, including all\ +modules it contains, plus any associated interface definition files,\ +scripts used to control compilation and installation of an executable\ +(object code).\ +\ +1.9 "You" or "Your" means an individual or a legal entity exercising\ +rights under this License. For legal entities, "You" or "Your"\ +includes any entity which controls, is controlled by, or is under\ +common control with, You, where "control" means (a) the power, direct\ +or indirect, to cause the direction or management of such entity,\ +whether by contract or otherwise, or (b) ownership of fifty percent\ +(50%) or more of the outstanding shares or beneficial ownership of\ +such entity.\ +\ +2. Permitted Uses; Conditions & Restrictions. Subject to the terms\ +and conditions of this License, Apple hereby grants You, effective on\ +the date You accept this License and download the Original Code, a\ +world-wide, royalty-free, non-exclusive license, to the extent of\ +Apple's Applicable Patent Rights and copyrights covering the Original\ +Code, to do the following:\ +\ +2.1 Unmodified Code. You may use, reproduce, display, perform,\ +internally distribute within Your organization, and Externally Deploy\ +verbatim, unmodified copies of the Original Code, for commercial or\ +non-commercial purposes, provided that in each instance:\ +\ +(a) You must retain and reproduce in all copies of Original Code the\ +copyright and other proprietary notices and disclaimers of Apple as\ +they appear in the Original Code, and keep intact all notices in the\ +Original Code that refer to this License; and\ +\ +(b) You must include a copy of this License with every copy of Source\ +Code of Covered Code and documentation You distribute or Externally\ +Deploy, and You may not offer or impose any terms on such Source Code\ +that alter or restrict this License or the recipients' rights\ +hereunder, except as permitted under Section 6.\ +\ +2.2 Modified Code. You may modify Covered Code and use, reproduce,\ +display, perform, internally distribute within Your organization, and\ +Externally Deploy Your Modifications and Covered Code, for commercial\ +or non-commercial purposes, provided that in each instance You also\ +meet all of these conditions:\ +\ +(a) You must satisfy all the conditions of Section 2.1 with respect to\ +the Source Code of the Covered Code;\ +\ +(b) You must duplicate, to the extent it does not already exist, the\ +notice in Exhibit A in each file of the Source Code of all Your\ +Modifications, and cause the modified files to carry prominent notices\ +stating that You changed the files and the date of any change; and\ +\ +(c) If You Externally Deploy Your Modifications, You must make\ +Source Code of all Your Externally Deployed Modifications either\ +available to those to whom You have Externally Deployed Your\ +Modifications, or publicly available. Source Code of Your Externally\ +Deployed Modifications must be released under the terms set forth in\ +this License, including the license grants set forth in Section 3\ +below, for as long as you Externally Deploy the Covered Code or twelve\ +(12) months from the date of initial External Deployment, whichever is\ +longer. You should preferably distribute the Source Code of Your\ +Externally Deployed Modifications electronically (e.g. download from a\ +web site).\ +\ +2.3 Distribution of Executable Versions. In addition, if You\ +Externally Deploy Covered Code (Original Code and/or Modifications) in\ +object code, executable form only, You must include a prominent\ +notice, in the code itself as well as in related documentation,\ +stating that Source Code of the Covered Code is available under the\ +terms of this License with information on how and where to obtain such\ +Source Code.\ +\ +2.4 Third Party Rights. You expressly acknowledge and agree that\ +although Apple and each Contributor grants the licenses to their\ +respective portions of the Covered Code set forth herein, no\ +assurances are provided by Apple or any Contributor that the Covered\ +Code does not infringe the patent or other intellectual property\ +rights of any other entity. Apple and each Contributor disclaim any\ +liability to You for claims brought by any other entity based on\ +infringement of intellectual property rights or otherwise. As a\ +condition to exercising the rights and licenses granted hereunder, You\ +hereby assume sole responsibility to secure any other intellectual\ +property rights needed, if any. For example, if a third party patent\ +license is required to allow You to distribute the Covered Code, it is\ +Your responsibility to acquire that license before distributing the\ +Covered Code.\ +\ +3. Your Grants. In consideration of, and as a condition to, the\ +licenses granted to You under this License, You hereby grant to any\ +person or entity receiving or distributing Covered Code under this\ +License a non-exclusive, royalty-free, perpetual, irrevocable license,\ +under Your Applicable Patent Rights and other intellectual property\ +rights (other than patent) owned or controlled by You, to use,\ +reproduce, display, perform, modify, sublicense, distribute and\ +Externally Deploy Your Modifications of the same scope and extent as\ +Apple's licenses under Sections 2.1 and 2.2 above.\ +\ +4. Larger Works. You may create a Larger Work by combining Covered\ +Code with other code not governed by the terms of this License and\ +distribute the Larger Work as a single product. In each such instance,\ +You must make sure the requirements of this License are fulfilled for\ +the Covered Code or any portion thereof.\ +\ +5. Limitations on Patent License. Except as expressly stated in\ +Section 2, no other patent rights, express or implied, are granted by\ +Apple herein. Modifications and/or Larger Works may require additional\ +patent licenses from Apple which Apple may grant in its sole\ +discretion.\ +\ +6. Additional Terms. You may choose to offer, and to charge a fee for,\ +warranty, support, indemnity or liability obligations and/or other\ +rights consistent with the scope of the license granted herein\ +("Additional Terms") to one or more recipients of Covered Code.\ +However, You may do so only on Your own behalf and as Your sole\ +responsibility, and not on behalf of Apple or any Contributor. You\ +must obtain the recipient's agreement that any such Additional Terms\ +are offered by You alone, and You hereby agree to indemnify, defend\ +and hold Apple and every Contributor harmless for any liability\ +incurred by or claims asserted against Apple or such Contributor by\ +reason of any such Additional Terms.\ +\ +7. Versions of the License. Apple may publish revised and/or new\ +versions of this License from time to time. Each version will be given\ +a distinguishing version number. Once Original Code has been published\ +under a particular version of this License, You may continue to use it\ +under the terms of that version. You may also choose to use such\ +Original Code under the terms of any subsequent version of this\ +License published by Apple. No one other than Apple has the right to\ +modify the terms applicable to Covered Code created under this\ +License.\ +\ +8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in\ +part pre-release, untested, or not fully tested works. The Covered\ +Code may contain errors that could cause failures or loss of data, and\ +may be incomplete or contain inaccuracies. You expressly acknowledge\ +and agree that use of the Covered Code, or any portion thereof, is at\ +Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND\ +WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND\ +APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE\ +PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM\ +ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT\ +NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF\ +MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR\ +PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD\ +PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST\ +INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE\ +FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS,\ +THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR\ +ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO\ +ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE\ +AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY.\ +You acknowledge that the Covered Code is not intended for use in the\ +operation of nuclear facilities, aircraft navigation, communication\ +systems, or air traffic control machines in which case the failure of\ +the Covered Code could lead to death, personal injury, or severe\ +physical or environmental damage.\ +\ +9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO\ +EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL,\ +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING\ +TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR\ +ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY,\ +TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF\ +APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\ +DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY\ +REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF\ +INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY\ +TO YOU. In no event shall Apple's total liability to You for all\ +damages (other than as may be required by applicable law) under this\ +License exceed the amount of fifty dollars ($50.00).\ +\ +10. Trademarks. This License does not grant any rights to use the\ +trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS",\ +"QuickTime", "QuickTime Streaming Server" or any other trademarks,\ +service marks, logos or trade names belonging to Apple (collectively\ +"Apple Marks") or to any trademark, service mark, logo or trade name\ +belonging to any Contributor. You agree not to use any Apple Marks in\ +or as part of the name of products derived from the Original Code or\ +to endorse or promote products derived from the Original Code other\ +than as expressly permitted by and in strict compliance at all times\ +with Apple's third party trademark usage guidelines which are posted\ +at http://www.apple.com/legal/guidelinesfor3rdparties.html.\ +\ +11. Ownership. Subject to the licenses granted under this License,\ +each Contributor retains all rights, title and interest in and to any\ +Modifications made by such Contributor. Apple retains all rights,\ +title and interest in and to the Original Code and any Modifications\ +made by or on behalf of Apple ("Apple Modifications"), and such Apple\ +Modifications will not be automatically subject to this License. Apple\ +may, at its sole discretion, choose to license such Apple\ +Modifications under this License, or on different terms from those\ +contained in this License or may choose not to license them at all.\ +\ +12. Termination.\ +\ +12.1 Termination. This License and the rights granted hereunder will\ +terminate:\ +\ +(a) automatically without notice from Apple if You fail to comply with\ +any term(s) of this License and fail to cure such breach within 30\ +days of becoming aware of such breach;\ +\ +(b) immediately in the event of the circumstances described in Section\ +13.5(b); or\ +\ +(c) automatically without notice from Apple if You, at any time during\ +the term of this License, commence an action for patent infringement\ +against Apple; provided that Apple did not first commence\ +an action for patent infringement against You in that instance.\ +\ +12.2 Effect of Termination. Upon termination, You agree to immediately\ +stop any further use, reproduction, modification, sublicensing and\ +distribution of the Covered Code. All sublicenses to the Covered Code\ +which have been properly granted prior to termination shall survive\ +any termination of this License. Provisions which, by their nature,\ +should remain in effect beyond the termination of this License shall\ +survive, including but not limited to Sections 3, 5, 8, 9, 10, 11,\ +12.2 and 13. No party will be liable to any other for compensation,\ +indemnity or damages of any sort solely as a result of terminating\ +this License in accordance with its terms, and termination of this\ +License will be without prejudice to any other right or remedy of\ +any party.\ +\ +13. Miscellaneous.\ +\ +13.1 Government End Users. The Covered Code is a "commercial item" as\ +defined in FAR 2.101. Government software and technical data rights in\ +the Covered Code include only those rights customarily provided to the\ +public as defined in this License. This customary commercial license\ +in technical data and software is provided in accordance with FAR\ +12.211 (Technical Data) and 12.212 (Computer Software) and, for\ +Department of Defense purchases, DFAR 252.227-7015 (Technical Data --\ +Commercial Items) and 227.7202-3 (Rights in Commercial Computer\ +Software or Computer Software Documentation). Accordingly, all U.S.\ +Government End Users acquire Covered Code with only those rights set\ +forth herein.\ +\ +13.2 Relationship of Parties. This License will not be construed as\ +creating an agency, partnership, joint venture or any other form of\ +legal association between or among You, Apple or any Contributor, and\ +You will not represent to the contrary, whether expressly, by\ +implication, appearance or otherwise.\ +\ +13.3 Independent Development. Nothing in this License will impair\ +Apple's right to acquire, license, develop, have others develop for\ +it, market and/or distribute technology or products that perform the\ +same or similar functions as, or otherwise compete with,\ +Modifications, Larger Works, technology or products that You may\ +develop, produce, market or distribute.\ +\ +13.4 Waiver; Construction. Failure by Apple or any Contributor to\ +enforce any provision of this License will not be deemed a waiver of\ +future enforcement of that or any other provision. Any law or\ +regulation which provides that the language of a contract shall be\ +construed against the drafter will not apply to this License.\ +\ +13.5 Severability. (a) If for any reason a court of competent\ +jurisdiction finds any provision of this License, or portion thereof,\ +to be unenforceable, that provision of the License will be enforced to\ +the maximum extent permissible so as to effect the economic benefits\ +and intent of the parties, and the remainder of this License will\ +continue in full force and effect. (b) Notwithstanding the foregoing,\ +if applicable law prohibits or restricts You from fully and/or\ +specifically complying with Sections 2 and/or 3 or prevents the\ +enforceability of either of those Sections, this License will\ +immediately terminate and You must immediately discontinue any use of\ +the Covered Code and destroy all copies of it that are in your\ +possession or control.\ +\ +13.6 Dispute Resolution. Any litigation or other dispute resolution\ +between You and Apple relating to this License shall take place in the\ +Northern District of California, and You and Apple hereby consent to\ +the personal jurisdiction of, and venue in, the state and federal\ +courts within that District with respect to this License. The\ +application of the United Nations Convention on Contracts for the\ +International Sale of Goods is expressly excluded.\ +\ +13.7 Entire Agreement; Governing Law. This License constitutes the\ +entire agreement between the parties with respect to the subject\ +matter hereof. This License shall be governed by the laws of the\ +United States and the State of California, except that body of\ +California law concerning conflicts of law.\ +\ +Where You are located in the province of Quebec, Canada, the following\ +clause applies: The parties hereby confirm that they have requested\ +that this License and all related documents be drafted in English. Les\ +parties ont exige que le present contrat et tous les documents\ +connexes soient rediges en anglais.\ +\ +EXHIBIT A.\ +\ +"Portions Copyright (c) 1999-2003 Apple Computer, 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."\ +} \ No newline at end of file diff --git a/Documentation/MP3Broadcaster.1 b/Documentation/MP3Broadcaster.1 new file mode 100644 index 0000000..baeb90d --- /dev/null +++ b/Documentation/MP3Broadcaster.1 @@ -0,0 +1,196 @@ +.TH MP3BROADCASTER 1 "August 13, 2002" "Apple Computer, Inc." +.SH NAME +MP3Broadcaster \- MP3 file playlist broadcaster +.SH SYNOPSIS +.B MP3Broadcaster +[-v] [-d] [-x] [-X] [-a ipAddress] [-p portNum] [-l filename] [-w filename] [-e filename] -c filename +.SH DESCRIPTION +.I MP3Broadcaster +broadcasts a set of MP3 files listed in a special playlist file format. If the MP3 source files +referenced in the playlist contain meta-data suitable for display in clients then this too is +streamed to a server on a separate TCP connection. +.PP +.I MP3Broadcaster +is intended to be used with +.IR QuickTimeStreamingServer (1) +which handles the details of delivering one or more MP3 broadcast streams to MP3 clients. +.I MP3Broadcaster +cannot be used to deliver a MP3 broadcast stream directly to a MP3 client. A MP3 stream +reflector server such as +.IR QuickTimeStreamingServer (1) +or +.I Shoutcast +must be used to deliver the stream to clients. You may also use +.I MP3Broadcaster +with other 'Shoutcast-compatible' servers such as +.I Icecast. +.PP +.I MP3Broadcaster +requires a +.B \-c +command line option followed by a playlist config file path. All other command line options +are not required. If +.I MP3Broadcaster +is used with no options it just prints a usage string to the command line. +.PP +A command line option of +.B \-d +must be specified if you want +.I MP3Broadcaster +to run as a foreground shell process. (It defaults to running as a background +shell process otherwise.) +.PP +The playlist configuration file specified with a +.B \-c +command line option is a plain ASCII text file with configuration parameters +for the MP3 broadcast specified one per line. A configuration keyword must be +specified on each line followed by one or more spaces followed by the +value to be set associated with that keyword. If the value is a character +string containing spaces it must be surrounded by double quotes. +.PP +For example, a playlist configuration file might be given as: +.RS +.nf + +destination_ip_address 90.22.34.5 +destination_base_port 8000 +max_upcoming_list_size 1 +play_mode sequential_looped +recent_songs_list_size 1 +playlist_file /home/bozo/jazz.play +working_dir /home/bozo +logging enabled +show_current disabled +show_upcoming disabled +broadcast_name "Steve's Broadcaster" +broadcast_password hackme +broadcast_url http://myserver.nowhere.com:8000 +broadcast_mount_point /HotJazz +broadcast_genre Jazz + +.fi +.RE +which would cause +.I MP3Broadcaster +to start broadcasting the playlist named "jazz.play" in the directory +path "/usr/bozo" to the server located at IP address 90.22.34.5 on TCP +port number 8000. Assuming that the server at this IP address is +.IR QuickTimeStreamingServer (1) +and has been configured to accept MP3 streams on port 8000 with a MP3 broacast +password of "hackme" then the stream will be accepted by the server and be ready +to forward the stream to any client that requests the mountpoint of "/HotJazz". +.PP +The playlist file like the playlist configuration file is a plain text ASCII file +that list the MP3 files to be broadcast. The format of the file is a single line containing +string "*PLAY-LIST*" followed by a list of file paths to individual MP3 files with an optional +parameter of a number from 1 to 10 that designates the weight of the file. (This optional +parameter is used with the "play_mode" keyword associated with the value "weighted_random". +When this play mode is specified the files will be picked at random according to the +specfied weight.) +.PP +.PP +For example, a playlist file might be given as: +.RS +.nf + +*PLAY-LIST* +"/home/bozo/my_first_song.mp3" 6 +"/home/bozo/my_second_song.mp3" 7 +"/home/bozo/my_favorite_song.mp3" 10 +"/home/bozo/my_last_song.mp3" 3 + +.fi +.RE +which would cause each file in the playlist to be broadcast at random according to the +weight specified. (Assuming a play_mode of "weighted_random" is specified +in the configuration file.) If the optional weight parameter is not present +in the playlist file then a value of 10 is assumed. The weight parameter is +ignored if the play_mode is specfied as "sequential" or "sequential_looped". +.PP +Text lines in the playlist configuration file or the playlist file that begin +with the '#' character will be treated as comments and will not be read by +.I MP3Broadcaster. +.PP +The +.I MP3Broadcaster +may be run in "preflight" mode by using the +.B \-x +option which will cause the configuration file to be read +and the MP3 files to be scanned for potential errors. The +.I MP3Broadcaster +will not broadcast any files when run in this mode. You can +use this mode to check your configuration file for errors. +.SH OPTIONS +.PP +The +.I MP3Broadcaster +accepts the following command line options: +.TP +.B \-v +Displays the version and build information. +.TP +.B \-d +Runs +.I MP3Broadcaster +as a foreground shell process. (The default is to run as a background +shell process.) +.TP +.B \-x +Runs +.I MP3Broadcaster +in preflight mode. (See description above.) +.TP +.B \-X +Causes +.I MP3Broadcaster +to scan the MP3 files listed in the playlist file for errors. +.TP +.BI \-a " " +Is the broadcast IP address as given by the argument +.I . +The default if not specified in the playlist configuration or with +this option is the local loopback address. If this value is specfied +in the configuration file then using this option overrides that value. +.TP +.BI \-p " " +Is the TCP port number to be used in the broadcast as given by the argument +.I . +The default if not specified in the playlist configuration or with +this option is port number 8000. If this value is specified +in the configuration file then using this option overrides that value. +.TP +.BI \-c " filename" +Is the path to the playlist configuration file as given by the argument +.I filename. +The keyword values listed in +.I filename +must be one per line. Leading and trailing white space are not part of the +symbol name. Lines starting with # are ignored. +.TP +.BI \-l " filename" +Is the path to the playlist file as given by the argument +.I filename. +The MP3 file paths listed in +.I filename +must be one per line followed by an option weight value of between 1 to 10. +If the fle path names contains blanks then the path must be surrounded with +double quotes. The first line of +.I filename +must be the string "*PLAY-LIST*". Lines starting with # are ignored. Any value +specified with this option overrides a value given in the configuration file. +.TP +.BI \-w " path" +Is the path to the directory for temporary files. Any value specified with this +option overrides a value given in the configuration file. +.TP +.BI \-e " filename" +Is the path to an error log file as given by the argument +.I filename. +.SH "SEE ALSO" +QuickTimeStreamingServer(1), PlaylistBroadcaster(1) +.SH LIMITATIONS +The +.I MP3Broadcaster +does not perform down-sampling or re-encoding of the source MP3 files. If the source +MP3 files are encoded at a bit rate too high for reliable streaming then they must +be re-encoded by other means. diff --git a/Documentation/QTSSAPIDocs.pdf b/Documentation/QTSSAPIDocs.pdf new file mode 100644 index 0000000..88b2bde Binary files /dev/null and b/Documentation/QTSSAPIDocs.pdf differ diff --git a/Documentation/RTSP_Over_HTTP.pdf b/Documentation/RTSP_Over_HTTP.pdf new file mode 100644 index 0000000..5e4ede7 Binary files /dev/null and b/Documentation/RTSP_Over_HTTP.pdf differ diff --git a/Documentation/ReadMe.rtf b/Documentation/ReadMe.rtf new file mode 100644 index 0000000..936e3f7 --- /dev/null +++ b/Documentation/ReadMe.rtf @@ -0,0 +1,521 @@ +{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf270 +{\fonttbl\f0\froman\fcharset0 Times-Roman;\f1\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\vieww21200\viewh17740\viewkind0 +\pard\ql\qnatural + +\f0\b\fs36 \cf0 About Darwin Streaming Server \ +\ +\pard\ql\qnatural + +\fs24 \cf0 Contents\ +\pard\ql\qnatural + +\b0\fs28 \cf0 \ +Welcome to Darwin Streaming Server, Apple's open source version of the QuickTime Streaming Server technology allowing you to send streaming media across the Internet using the industry standard RTP and RTSP protocols. Based on the same code base as QuickTime Streaming Server, Darwin Streaming Server provides a high level of customizability and runs on a variety of platforms allowing you to manipulate the code to fit your needs. \ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 What's New with Darwin Streaming Server 6.0.3\ +\pard\ql\qnatural + +\b0\fs28 \cf0 - DSS now builds and runs 64-bit on MacOS X\ +\pard\pardeftab720\ql\qnatural +\cf0 - Updates for example modules:\ +\'a0\'a0- The example Authorization module has been updated.\ +\'a0\'a0 \'a0Source code is available in:\'a0APIModules/QTSSDemoAuthorizationModule.bproj\ +\'a0\'a0- The example Spam Defense module has been updated.\ +\'a0\'a0 \'a0Source code is available in:\'a0APIModules/QTSSSpamDefenseModule.bproj\ +\'a0\'a0- An example RTSP redirect module has been included\ +\'a0\'a0 \'a0Source code is available in:\'a0APIModules/QTSSDemoRedirectModule.bproj\ +\'a0\'a0Example modules are\'a0installed into:\ +\'a0\'a0 \'a0(MacOS X): /Library/QuickTimeStreamingServer/Modules.disabled\ +\'a0\'a0 \'a0(other Unix):\'a0/usr/local/sbin/StreamingServerModules\ +- OS X based user account support is added using Apple Open Directory services and includes LDAP and Active Directory user account authentication and authorization \ +- Separate thread pools are used for RTSP processing and RTP processing\ +- Performance is improved on OS X systems with 4 and 8 cores over previous releases.\ +- Supports hinted files greater than 4GB in size.\ +- The posted pre-built MacOS package runs on MacOS X 10.5 or later only\ +- No build support yet for non-Mac OS X systems (open source submissions are needed to build on other platforms)\ +- No longer posting pre-built install binaries for non-Mac OS X systems.\ + Version 5.5.5 for Linux and Windows can be downloaded from http://developer.apple.com/opensource/server/streaming/index.html\ +\ +\pard\ql\qnatural +\cf0 This release contains open source submissions for the following issues: +\b\fs24 \ + +\b0\fs28 - Fixed compilation problem on Solaris 10u3 (Stefan Parvu)\ +- Fixed access log c-bytes value (Amir Wolf)\ +- Fixed access log CPU utilization value\ +- Fixed compilation problem on FC6 linux PPC (Matthew McGillis)\ +- Fix to allow streaming of files with bad hint track references, allows compatibility with some popular encoders (Fredrik Widlund)\ +- Fix StreamingProxy compilation problems for some non-MacOSX platforms\ +- Added a HowTo for uninstalling DSS on a MacOS X system\ +- Misc. fixes for Debian linux (Ben Humpert)\ +- Fixed problem with video sync frame detection for MacOS X on Intel (Lorenzo Vicisano)\ +\pard\pardeftab720\ql\qnatural +\cf0 \ +\pard\ql\qnatural +\cf0 Please use http://dss.macosforge.org/ to submit your own Darwin Streaming Server modifications.\ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 What's New with Darwin Streaming Server 5.5.5b\ +\ +\pard\ql\qnatural + +\b0\fs28 \cf0 Darwin Streaming Server 5.5.5b is a beta release containing open source submissions for the following issues:\ +- Compilation problems using gcc 4 (Andreas Thienemann)\ +- Support for SDPs created by VLC and Mpeg4IP (David Moore)\ +- Fix date display in DSS Web Admin (Maksym Veremeyenko)\ +- Better support for streaming through NAT (Denis Ahrens)\ +- Better support for running DSS on a multi-homed system (Denis Ahrens)\ +- Relaying problems with VLC (Alessandro Falaschi, http://labtel.ing.uniroma1.it/opencdn/darwinp.html)\ +\ +Please use http://www.opensource.apple.com/projects/modifications.html to submit your own Darwin Streaming Server modifications.\ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 What's New with Darwin Streaming Server 5.5.4\ +\ +\pard\ql\qnatural + +\b0\fs28 \cf0 Darwin Streaming Server 5.5.4 includes the following enhancements to 5.5.3:\ +\ +- A fix to the unsigned character handling in the string parser resolves the following compiler generated issues:\ +-- Failure to stream to non-english QuickTime Players\ +-- Failure to stream live broadcast SDP files containing high-ascii characters\ +-- Failure to authenticate with users and passwords with high-ascii characters\ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 What's New with Darwin Streaming Server 5.5.3\ +\pard\ql\qnatural + +\b0\fs28 \cf0 \ +Darwin Streaming Server 5.5.3 includes the following enhancements to 5.5.1:\ +\ +- A security fix for DSS to prevent a crash when receiving an invalid RTSP request.\ +- A security fix for DSS to prevent a crash when reading an invalid movie file.\ +- An update to the Buildit script to build on Mac OS X intel systems.\ +\ +Darwin Streaming Server 5.5.1 includes the following enhancements to 5.5:\ +\pard\ql\qnatural + +\b\fs24 \cf0 \ +\pard\ql\qnatural + +\b0\fs28 \cf0 - A security fix for DSS Web Admin on Windows\ +\ +Darwin Streaming Server 5.5 includes the following enhancements to 5.0.1.1:\ +\ +- Latest security update changes\ +- Latest 3GPP release 5 client support\ +- High definition H.264 streaming \ +\ +\ +Darwin Streaming Server 5.0.1.1 includes the following enhancements to 5.0:\ +\ +- Latest security update changes\ +- Improved Safari compatibility\ +\ +Darwin Streaming Server 5.0\ +\ +- Enhanced multithread support \ +- Home directory streaming (UNIX-based platforms only)\ +- Broadcast directory streaming\ +- HTTP to RTSP url redirection using QuickTime HREF support.\ +- Improved security through non-root user execution (UNIX-based platforms only)\ +- 3GPP streaming enhancements - As we constantly improve our support for streaming the latest digital media standards, DSS 5 includes a number of enhancements for 3GPP streaming\ +\ +It can be ported to other platforms by modifying a handful of platform-specific source files. For more information about the source code and how to port to other platforms, see the files AboutTheSource.html and SourceFAQ.html provided with the Darwin Streaming Server source code.\ +\ +For more information about the Darwin Streaming Server project and to obtain the Darwin Streaming Server 5.5 source, see Apple's Open Source Web site at: .\ +\ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 System Requirements\ +\ +\pard\ql\qnatural + +\b0\fs28 \cf0 Darwin Streaming Server is currently available on the following platforms:\ +\ +\pard\li720\fi-720\ql\qnatural +\cf0 *Mac OS X (version 10.2.8 or later)\ +\pard\ql\qnatural +\cf0 *Linux (RedHat 8/9, Intel)\ +*Solaris 9 (SPARC)\ +*Windows 2000 Server/2003 Server\ +\ +Darwin Streaming Server is compatible with QuickTime 4 or later client software. Digest mode Authentication and Skip Protection (first introduced in QuickTime Streaming Server 3.0) require QuickTime 5 or later client software.\ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 Installing Darwin Streaming Server ( +\b0\fs28 Mac OS X +\b\fs24 )\ +\ +\pard\ql\qnatural + +\b0\fs28 \cf0 To install Darwin Streaming Server 5.5 software, follow these \ +steps:\ +\ +\pard\li90\fi-90\ql\qnatural +\cf0 1. After downloading Darwin Streaming Server, double-click the DarwinStreamingServer.dmg file. DarwinStreamingServer will mount a desktop image that contains DarwinStreamingServer.pkg. \ +\pard\li360\fi-360\ql\qnatural +\cf0 \ +2. Double-click the DarwinStreamingServer.pkg file. This will launch the installer.\ +\ +3. Click on the "lock" icon to make changes when prompted during installation. You will need to authenticate with the administrator username and password.\ +\ +4. Follow the onscreen instructions. After you have read and agreed to the license, you can proceed with the installation.\ +\ +5. If you are installing for the first time, after the install completes, you will be asked to create a user name and password for administering the server. You must complete this step to administer the server from a remote system using a web browser.\ + \ + If you are upgrading, you will be presented with a web browser login window.\ +\ +\pard\li360\fi-360\ql\qnatural + +\b\fs24 \cf0 Set Up ( +\b0\fs28 Mac OS X +\b\fs24 )\ +\pard\ql\qnatural +\cf0 \ +\pard\ql\qnatural + +\b0\fs28 \cf0 After creating an administrator user name and password, you can connect to the Darwin Streaming Server from your web browser.\ +\pard\li270\fi-270\ql\qnatural +\cf0 \ + Enter the URL for your Darwin Streaming Server:\ +\pard\li270\ql\qnatural +\cf0 http://myserver.com:1220\ +\ +Replace "myserver.com" with the name of your Darwin Streaming Server computer. \ +1220 is the port number.\ +\pard\ql\qnatural +\cf0 \ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 Installing Darwin Streaming Server (Linux, Solaris)\ +\ +\pard\ql\qnatural + +\b0\fs28 \cf0 To install Darwin Streaming Server 5.5 software, follow these steps on the server computer:\ +\ + Stop any Darwin Streaming Server related processes.\ +\ +\pard\tx0\ql\qnatural +\cf0 IMPORTANT: Installing Darwin Streaming Server will remove older version of Darwin Streaming Server. +\fs24 \ +\pard\ql\qnatural +\cf0 \ +\pard\tx360\li360\fi-360\ql\qnatural + +\fs28 \cf0 Expand the compressed (.gz) tar file and "cd" into one of the following directories, depending on the platform: +\fs24 \ +\pard\ql\qnatural + +\fs28 \cf0 \ +\pard\li720\ql\qnatural +\cf0 DarwinStreamingSrvr5.5-Linux \ + +\fs24 \ +\pard\ql\qnatural +\cf0 \ +\pard\tx360\li360\fi-360\ql\qnatural + +\fs28 \cf0 Then type: +\fs24 +\fs28 \ +\pard\ql\qnatural + +\fs24 \cf0 \ +\pard\li720\ql\qnatural +\cf0 ./Install\ +\pard\ql\qnatural + +\fs28 \cf0 \ +\pard\tx360\li360\fi-360\ql\qnatural +\cf0 During the install, the streamingadminserver.pl application will automatically launch. To avoid the need to manually relaunch streamingadminserver.pl following reboots, you may want to configure your server machine to launch it automatically at boot time.\ +\pard\ql\qnatural +\cf0 \ +\pard\li360\fi-360\ql\qnatural + +\b\fs24 \cf0 Set Up (Linux, Solaris)\ +\pard\li360\fi-360\ql\qnatural + +\b0\fs28 \cf0 During the install, you will be asked to create a user name and password for administering the server. You must complete this step to administer the server from a remote system using a web browser.\ +\pard\ql\qnatural + +\b\fs24 \cf0 \ +\pard\ql\qnatural + +\b0\fs28 \cf0 After creating an administrator user name and password, you can connect to the Darwin Streaming Server from your web browser.\ +\pard\li270\fi-270\ql\qnatural +\cf0 \ + Enter the URL for your Darwin Streaming Server:\ +\pard\li270\ql\qnatural +\cf0 http://myserver.com:1220\ +\ +Replace "myserver.com" with the name of your Darwin Streaming Server computer. \ +1220 is the port number.\ +\pard\ql\qnatural +\cf0 \ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 Installing Darwin Streaming Server (Windows 2000/2003 Server) +\b0\fs28 \ + +\b\fs24 \ +\pard\ql\qnatural + +\b0\fs28 \cf0 The Streaming Admin requires +\f1\fs24 ActivePerl 5.8 +\f0\fs28 (or later) to be running on the server machine. You must install a +\f1\fs24 Perl +\f0\fs28 interpreter in order to use the web-based administration software. \ +\ +\ +To install Darwin Streaming Server software, follow these steps on the server computer:\ +\ +Stop any Darwin Streaming Server related processes.\ +\ +When the Server package is unzipped, a folder with Darwin Streaming Server and associated files will be created. Inside this folder is an Install script, named "Install.bat". Double-click this file to install the server and its components on the server machine. The installer also starts up the Streaming Server Admin, so keep the command prompt window open.\ +\pard\ql\qnatural + +\fs24 \cf0 \'a0\ +\pard\ql\qnatural + +\fs28 \cf0 The Install script will create the following directory:\ +\ +\pard\li540\ql\qnatural +\cf0 c:\\Program Files\\Darwin Streaming Server\\\ +\pard\ql\qnatural +\cf0 \ +Inside this directory you will find:\ +\ +\pard\li540\ql\qnatural +\cf0 DarwinStreamingServer.exe +\i - Server executable\ + +\i0 PlaylistBroadcaster.exe +\i - PlaylistBroadcaster executable\ + +\i0 MP3Broadcaster.exe \'96 +\i MP3 Broadcaster executable +\i0 \ +qtpasswd.exe +\i - Command-line utility for generating password files for access control\ + +\i0 StreamingLoadTool.exe +\i - RTSP simulated client stress tool +\i0 \ +streamingadminserver.pl +\i - Admin Server that is used for administering the Streaming Server\ + +\i0 streamingserver.xml +\i - Default server configuration file\ + +\i0 relayconfig.xml-Sample +\i - Sample relay configuration file\ + +\i0 QTSSModules\\ +\i - Folder containing QTSS API modules\ + +\i0 Movies\\ +\i - Media folder\ + +\i0 Playlists\\ - +\i Folder containing Playlist configuration +\i0 \ +Logs\\ +\i - Folder containing access and error logs\ + +\i0 AdminHtml\\ +\i - Folder containing the CGIs and the HTMl files required by the Admin Server\ + +\i0 Documentation\\ +\i - Documentation folder\ +\pard\ql\qnatural +\cf0 \ +\pard\ql\qnatural + +\i0 \cf0 The Install script also installs Darwin Streaming Server as a service in the Service Manager. It is possible to start, stop, and check server status from the Service control panel.\ +\pard\li360\fi-360\ql\qnatural +\cf0 \ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural +\cf0 The Install script will attempt to launch the Admin Server. Make sure that the Perl interpreter installed on your machine is in the system PATH.\ +\ +The Admin Server can be launched from the command prompt by typing:\ +\ +C:\\> +\i perlpath +\i0 "C:\\Program Files\\Darwin Streaming Server\\streamingadminserver.pl"\ +\ +\pard\li360\fi-360\ql\qnatural +\cf0 where +\i perlpath +\i0 is the path to the Perl interpreter on your machine.\ +\ +\ + If you are installing for the first time, you will be asked to create a user name and password for administering the server. You must complete this step to administer the server from a remote system using a web browser.\ + \ +\ +\pard\li360\fi-360\ql\qnatural + +\b\fs24 \cf0 Set Up (Windows 2000/2003 Server)\ +\pard\ql\qnatural +\cf0 \ +\pard\ql\qnatural + +\b0\fs28 \cf0 After creating an administrator user name and password, you can connect to the Darwin Streaming Server from your web browser.\ +\pard\li270\fi-270\ql\qnatural +\cf0 \ + Enter the URL for your Darwin Streaming Server:\ +\pard\li270\ql\qnatural +\cf0 http://localhost:1220 on the same local system or\ +http://myserver.com:1220 from a remote system\ +\ +Replace "myserver.com" with the name of your Darwin Streaming Server computer. \ +1220 is the port number.\ +\pard\li270\fi-270\ql\qnatural +\cf0 For help on using Streaming Server Admin, setting up secure administration (SSL), and setting up your server to stream hinted media, refer to the online Help by selecting the Question Mark button from the Streaming Server Admin.\ +\pard\ql\qnatural +\cf0 \ +\ +\pard\ql\qnatural + +\b\fs24 \cf0 Troubleshooting +\b0 \ +\ +\ +\pard\ql\qnatural + +\b\fs28 \cf0 * File Locations +\fs24 \ +\pard\li1080\ql\qnatural + +\b0 \cf0 \ +\ +\pard\ql\qnatural + +\fs28 \cf0 \ul \ulc0 Darwin Streaming Server (Mac OS X)\ulnone \ +/usr/sbin/QuickTimeStreamingServer +\i - Streaming Server app +\i0 \ +/usr/sbin/streamingadminserver.pl +\i - QTSS Web Admin server +\i0 \ +/Library/QuickTimeStreaming/Modules/ +\i - QTSS plug-ins +\i0 \ +/usr/bin/PlaylistBroadcaster +\i - The PlaylistBroadcaster +\i0 \ +/usr//bin/MP3Broadcaster +\i - The MP3Broadcaster +\i0 \ +/usr/bin/qtpasswd +\i - Generates password files for access control\ + +\i0 /usr//bin/StreamingLoadTool +\i - RTSP simulated client stress tool +\i0 \ +/Library/QuickTimeStreaming/Config/ +\i - QTSS config files +\i0 \ +/Library/QuickTimeStreaming/Movies/ +\i - Media files +\i0 \ +/Library/QuickTimeStreaming/Docs/ +\i - readme.html & user manual.pdf files +\i0 \ +/Library/QuickTimeStreaming/logs/ +\i - Logs\ + +\i0 /Library/QuickTimeStreaming/playlists +\i - Web Admin Playlist files +\i0 \ +\pard\li1080\ql\qnatural + +\fs24 \cf0 \ +\pard\ql\qnatural + +\fs28 \cf0 \ul Darwin Streaming Server (Unix)\ulnone \ +/usr/local/sbin/DarwinStreamingServer +\i - Streaming Server app +\i0 \ +/usr/local/sbin/streamingadminserver.pl +\i - QTSS Web Admin server +\i0 \ +/usr/local/sbin/StreamingServerModules/ +\i - QTSS plug-ins +\i0 \ +/usr/local/bin/PlaylistBroadcaster +\i - The PlaylistBroadcaster +\i0 \ +/usr/local/bin/MP3Broadcaster +\i - The MP3Broadcaster +\i0 \ +/usr/local/bin/qtpasswd +\i - Generates password files for access control\ + +\i0 /usr/local/bin/StreamingLoadTool +\i - RTSP simulated client stress tool +\i0 \ +/etc/streaming/ +\i - QTSS config files +\i0 \ +/usr/local/movies/ +\i - Media files +\i0 \ +/var/streaming/ +\i - readme.html & user manual.pdf files +\i0 \ +/var/streaming/logs +\i - Logs\ + +\i0 /var/streaming/playlists +\i - Web Admin Playlist files +\i0 \ +\ +\ul Darwin Streaming Server (Windows) +\b \ulnone \ + +\b0 C:\\Program Files\\Darwin Streaming Server\\\ +C:\\Program Files\\Darwin Streaming Server\\Movies\ +C:\\Program Files\\Darwin Streaming Server\\Playlists\ +C:\\Program Files\\Darwin Streaming Server\\Logs\ +C:\\Program Files\\Darwin Streaming Server\\QTSSModules\ +C:\\Program Files\\Darwin Streaming Server\\AdminHtml +\fs24 \ + +\fs28 \ +\pard\ql\qnatural + +\b\fs24 \cf0 \ +Public Mailing Lists\ +\ +\pard\ql\qnatural + +\b0\fs28 \cf0 Through the Apple public mailing lists you can share experiences, questions, and comments with others who use the software. Apple employees may monitor the list, but Apple does not guarantee that questions sent to this list will be answered. For more information about joining the mailing lists, see the Apple mailing lists Web site at www.lists.apple.com.\ +\pard\ql\qnatural + +\b\fs24 \cf0 \ +\pard\ql\qnatural + +\b0\fs28 \cf0 For Darwin Streaming Server administration, join the Streaming Server mailing list, \'93streaming-server-users\'94. \ +\ +If you are interested in plug-in API or Open Source development, join the Streaming Server developer public mailing list, \'93streaming-server-developers\'94. \ +\ +The Darwin Streaming Server release is not supported by Apple Computer.\ +\ +\ +\pard\ql\qnatural + +\fs24 \cf0 \'a9 2008 Apple Computer, Inc. All rights reserved. Apple, the Apple logo, Mac, Macintosh, PowerBook, Power Macintosh, and QuickTime are trademarks of Apple Computer, Inc., registered in the United States and other countries. eMac, iBook, iMac, Power Mac and Xserve are trademarks of Apple Computer, Inc. All other product names are trademarks or registered trademarks of their respective holders.\ +\ +} \ No newline at end of file diff --git a/Documentation/ReliableRTP_WhitePaper.rtf b/Documentation/ReliableRTP_WhitePaper.rtf new file mode 100644 index 0000000..3eb4e98 --- /dev/null +++ b/Documentation/ReliableRTP_WhitePaper.rtf @@ -0,0 +1 @@ +{\rtf1\mac\ansicpg10000\uc1 \deff4\deflang1033\deflangfe1033{\upr{\fonttbl{\f0\fnil\fcharset256\fprq2{\*\panose 00020206030504050203}Times New Roman;}{\f2\fnil\fcharset256\fprq2{\*\panose 00020703090202050204}Courier New;} {\f4\fnil\fcharset256\fprq2{\*\panose 00020005000000000000}Times;}{\f5\fnil\fcharset256\fprq2{\*\panose 00020005000000000000}Helvetica;}}{\*\ud{\fonttbl{\f0\fnil\fcharset256\fprq2{\*\panose 00020206030504050203}Times New Roman;} {\f2\fnil\fcharset256\fprq2{\*\panose 00020703090202050204}Courier New;}{\f4\fnil\fcharset256\fprq2{\*\panose 00020005000000000000}Times;}{\f5\fnil\fcharset256\fprq2{\*\panose 00020005000000000000}Helvetica;}}}}{\colortbl;\red0\green0\blue0; \red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128; \red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f4\lang1033\cgrid \snext0 Normal;}{ \s1\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \b\f5\fs32\lang1033\kerning32\cgrid \sbasedon0 \snext0 heading 1;}{\s2\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \b\i\f5\fs28\lang1033\cgrid \sbasedon0 \snext0 heading 2;}{\s3\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \b\f5\fs26\lang1033\cgrid \sbasedon0 \snext0 heading 3;}{ \s4\keepn\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \b\f4\lang1033\cgrid \sbasedon0 \snext0 heading 4;}{\*\cs10 \additive Default Paragraph Font;}{\s15\widctlpar\tqc\tx4320\tqr\tx8640\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f4\lang1033\cgrid \sbasedon0 \snext15 header;}{\s16\widctlpar\tqc\tx4320\tqr\tx8640\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f4\lang1033\cgrid \sbasedon0 \snext16 footer;}{\*\cs17 \additive \sbasedon10 page number;}}{\info{\title 6/7/01} {\author Chris LeCroy}{\operator Chris LeCroy}{\creatim\yr2001\mo10\dy26\hr12\min50}{\revtim\yr2001\mo10\dy26\hr12\min53}{\printim\yr2001\mo6\dy8\hr9\min56}{\version3}{\edmins1}{\nofpages7}{\nofwords2094}{\nofchars11938}{\*\company Apple} {\nofcharsws14660}{\vern16409}}\margl1440\margr1440 \ftnbj\aenddoc\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\hyphcaps0\horzdoc\dgmargin\dghspace120\dgvspace120\dghorigin1440\dgvorigin1440\dghshow0\dgvshow3 \jcompress\viewkind1\viewscale100\nolnhtadjtbl \fet0\sectd \linex0\sectdefaultcl {\header \pard\plain \s15\widctlpar\tqc\tx4320\tqr\tx8640\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f4\lang1033\cgrid {\i Apple Computer, Inc.\tab Confidential \par }}{\footer \pard\plain \s16\widctlpar\tqc\tx4320\tqr\tx8640\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f4\lang1033\cgrid {\cs17 Page }{\field{\*\fldinst {\cs17 PAGE }}{\fldrslt {\cs17\lang1024 1}}}{\cs17 of }{\field{\*\fldinst {\cs17 NUMPAGES }}{\fldrslt {\cs17\lang1024 7}}}{\cs17 \tab Revision 1.0.1\tab }{\field{\*\fldinst {\cs17 DATE \\@ "M/d/yy" }}{\fldrslt {\cs17\lang1024 10/26/01}}}{ \par }}{\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}} {\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl8 \pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}\pard\plain \s15\nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par }\pard\plain \s1\qc\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0 \b\f5\fs32\lang1033\kerning32\cgrid {Reliable RTP \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par }\pard\plain \s2\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \b\i\f5\fs28\lang1033\cgrid {Abstract \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par Reliable RTP is a set of new features for the RTP (Real-time Transport Protocol) to improve its ability to present a good quality stream to the RTP client even in the event of network loss and network congestion. It also adds congestion control to RTP so s treams behave in a TCP-friendly fashion, without disturbing the real-time nature of the protocol. The algorithms used for retransmitting and congestion control are similar to those used in TCP so as to best interoperate with TCP traffic on the Internet. A dditionally, those algorithms are time tested to utilize available bandwidth in a near optimal fashion. \par \par The new features are as follows: \par \bullet ACK packets sent from the client to the server. \par \bullet Windowing and congestion control so the server does not exceed the currently available bandwidth. \par \bullet Retransmits sent from the server to client in the event of packet loss. \par \bullet Faster than real time streaming, also known as "over-buffering". \par \par The following sections detail the exact operation of each of these features, and t he reasons why this implementation leads to optimal streaming quality. Each of these features also involve negotiation of parameters, limits, etc. This negotiation primarily takes place out of band in RTSP, and is described in the final section. \par \par }\pard\plain \s2\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \b\i\f5\fs28\lang1033\cgrid {Section 1: Client to server ACK packets \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par The Reliable RTP server expects to receive an ACK for each RTP packet it sends. If it does not receive an ACK for a packet, it will retransmit it subject to constraints discussed in section 2. It is not necessary to send one ACK packet for each data packet received from the server\endash rather, ACKs for several packets may be coalesced and sent to the server in a single packet. \par \par The Reliable RTP ACK packet format is a type of RTCP APP packet (See RFC 1889 for details on the RTCP pr otocol) After the standard RTCP APP packet headers, the ACK payload consists of a RTP sequence number followed by a variable length bit mask. The sequence number identifies the first RTP packet that the client is acknowledging. Each additional RTP packet b eing acknowledged is represented by a bit set in the bitmask. The bit mask is an offset from the specified sequence number, where the high order bit of the first byte in the mask is one greater than the sequence number, second bit is two greater, and so o n. Bit masks must be sent in multiples of 4-octets. Setting a bit to 0 in the mask simply means that the client does not wish to acknowledge this sequence number right now, and does not imply a negative acknowledgment in any way. \par \par The Reliable RTP ACK packet format is shown below. \par \par }{\f2\fs20 0 1 2 3 \par 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par |V=2|P| subtype | PT=APP=204 | length | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | SSRC/CSRC | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | name (ASCII) = 'qtak' | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | SSRC/CSRC | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | Reserved | Seq num | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | Mask... | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+}{ \par \par }\pard\plain \s2\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \b\i\f5\fs28\lang1033\cgrid {Section 2: Windowing, congestion control, and retransmits \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par The Reliable RTP server attempts to fully utilize available bandwidth at all times without overstepping the constraints of the network. It does this using an algorithm similar to the TCP slow start and congestion control algorithms. Though a Reliable RTP client will interoperate with a Reliable RTP server that does not implement windowing, it is important for the server to follow these gu idelines closely so as not send Retransmits spuriously or send data to quickly for the network to handle. Deviating from this algorithm will result in sub-optimal use of network resources by the Reliable RTP server. \par \par }\pard\plain \s3\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0 \b\f5\fs26\lang1033\cgrid {Section A: Round-trip time estimation \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par T he Reliable RTP server uses Karn's algorithm to estimate the current packet round-trip time. The server marks the time at which it sends each RTP packet. When it receives the ACK for that packet, it marks the time and passes that sample round-trip time to Karn's algorithm to compute the estimate. \par \par If the computed round-trip estimate is below MIN_ROUNDTRIP_THRESHOLD, the server sets the estimate equal to that value. If it is above MAX_ROUNDTRIP_THRESHOLD, the server sets it equal to that value. The server in itializes the estimate to MIN_ROUNDTRIP_THRESHOLD. \par \par If the server has not received an ACK for a data packet within current estimated round-trip time, it retransmits the packet. The first time a packet gets resent, the round-trip time estimate is increased to 3/2 of the current round-trip time estimate. Bumping up the round-trip time in this manner prevents spurious retransmits in the event of network congestion or reaching the bandwidth ceiling. \par \par When the server receives an ACK for a packet that has already been retransmitted, it does not use that round-trip time sample as a part of its estimate. \par \par }\pard\plain \s3\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0 \b\f5\fs26\lang1033\cgrid {Section B: Windowing \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par The server's ability to send RTP packets is constrained by the size of the conge stion window. The congestion window is the maximum number of bytes the server can send without getting an ACK. When the server sends a RTP packet, its size gets added to the count of bytes in the congestion window. When an ACK packet is received for that R TP packet, the count of bytes in the congestion window is reduced by the RTP packet size. When the congestion window fills up, the server must wait for an ACK packet before sending any more data. Restricting the ability of the server to send RTP data with the congestion window prevents it from overstepping the constraints of a congested network and causing packet loss. \par \par Determining the optimal congestion window size is extremely important: if it is too small, network bandwidth will not be utilized optimally as the server will have to wait often for ACK packets to be received from the client. If too big, the server will be able to send out packets too fast and some will get dropped. \par \par The algorithm used is very similar to the one used by TCP. This method has decades of mileage on the public Internet to prove it works. Additionally, using a method similar to TCP makes Reliable RTP "TCP friendly". \par \par }\pard\plain \s3\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0 \b\f5\fs26\lang1033\cgrid {Section B2: The algorithm for sizing the congestion window \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par The congestion window can never be larger than the maximum window size (CLIENT_WINDOW) advertised by the Reliable RTP client. \par The server initializes the slow start threshold to 1/2 CLIENT_WINDOW. \par The server initializes the congestion window to 4x MAXIMUM_SEGMENT_SIZE. \par \par When the congestion window is below the slow start threshold, it is increased by 2x the size of the packet being ACKed for every ACK received. \par When above the slow start threshold, the congestion window increases by MAXIMUM_SEGMENT_SIZE for every full window of ACKs received. \par \par When the server sends a retransmit, the slow start threshold is reset to be half the current congestion window size, and the congestion window is set to be half of either the slow start threshold or the congestion window, whichever is smaller. \par \par }\pard\plain \s3\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0 \b\f5\fs26\lang1033\cgrid {Section B3: The effect of this algorithm \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par Because the congestion window starts small and grows with each ACK received, the server will not exceed the capacity of the network before it has enough data to determine what that capacity is. It increases the amount of data it can send until th ere is packet loss, and it needs to retransmit something. At that point, it has exceeded the capacity of the network, so clearly the congestion window needs to be scaled back. Eventually, the window reaches a steady state where it only fluctuates greatly if new congestion is introduced or old congestion is relieved. \par \par }\pard\plain \s3\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0 \b\f5\fs26\lang1033\cgrid {Section C: Packet expiration times. \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par Reliable RTP data is real-time. Therefore, there is a finite ti me after which a packet is too old to be used by the client. When a packet is sent, it is tagged with an expiration time. The expiration time is determined by the content and how far ahead of time it is being sent. If the Reliable RTP server has not recei ved an ACK for a packet by the expiration time, the packet will be marked as no longer available for retransmit. \par \par When a packet expires, the congestion window is resized as if it had just gotten an ACK for the packet. \par \par }\pard\plain \s2\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \b\i\f5\fs28\lang1033\cgrid {Section 3: Over-buffering \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par The windowi ng scheme described in section 2 gives the Reliable RTP server a good estimate of the available network bandwidth at any given time. Therefore, it is possible for the server to safely send stream data faster than real-time without unknowingly overstepping network capacity. This "over-buffering" reduces the chance that a temporary reduction of available bandwidth will impact stream quality, by giving the server more time to retransmit lost packets. Additionally, in the event of a significant change in avail able bandwidth, over-buffering gives the server more time to detect this and smoothly switch to a lower bit-rate stream. \par \par However, two other factors besides available bandwidth must constrain the amount of over-buffering that takes place. The first is the r ate at which the server over-buffers. If it sends data too fast, even if the network can handle it, over-buffering may disturb more important network traffic going to the client or from the server. For instance, a client may be receiving two RTP streams, o ne high-bit-rate and one low-bit-rate. Without constraining the over-buffer rate, the two streams will share network bandwidth equally. If the difference in normal stream bit-rate is great enough, the high bit-rate stream may not be able to play at all be cause the low bit-rate stream is using bandwidth for over-buffering. \par \par The second factor is client memory. Any data received early by the client must remain in the client's buffer until it can be played. If server over-buffering is not constrained and there is enough network bandwidth, the client may actually run out of memory because it has so much stream data in its buffer. In addition, the available memory on the client might change during streaming. Therefore, the server must be periodically notified of how much can be put in the client's buffer. \par \par Additionally, the server will currently constrain over-buffer speed to 2-times the bit-rate of the media stream. 2-times is an arbitrary number that may require some tuning or should be derived through calculation. \par \par Also, the current implementation will only over-buffer a maximum of 25 seconds worth of media. This is also an arbitrary number that might benefit from tuning or calculation. \par \par In Reliable RTP over-buffering, the server receives an RTCP APP packet con taining the size, in bytes, of the client's "over-buffer window". A packet is part of the over-buffer window if it is sent ahead of its normal transmit time. Packets fall out of the over-buffer window when the normal transmit time passes. A Reliable RTP s erver must not send any stream data if the over-buffer window is full. Instead, it must wait for some packets to fall out of the over-buffer window. \par \par The client must advertise the "over-buffer window" in every RTCP receiver report it sends to the server. In the absence of a non-zero window size, the server must assume the over-buffer window is zero. Though RTCP packets are specific to each stream, the value advertised applies to the window size for all streams in the current RTSP session. The server uses th e most recent value received as the value for the whole session, regardless of which stream it arrived on. \par \page \par \par The format of the RTCP over-buffer window size APP packet is shown below: \par \par }{\f2\fs20 0 1 2 3 \par 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par |V=2|P| subtype | PT=APP=204 | length | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | SSRC/CSRC | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | name (ASCII) = 'QTSS' | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | SSRC/CSRC | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | field name='ob | version=0 | length=4 | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \par | Over-buffer window size in bytes | \par +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+}{\f2 \par }{ \par The 'QT SS' APP packet can contain additional fields. Each field begins with a 2-octet field name, a 1-octet field version, and a 1-octet field length. There is no padding between individual fields. The other fields are documented in the QTSS APP packet documenta tion. \par \par }\pard\plain \s2\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \b\i\f5\fs28\lang1033\cgrid {Section 4: RTSP Negotiation \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par Whether or not to use Reliable RTP, and the parameters of the protocol, are negotiated out of band in RTSP (Real-Time Streaming Protocol, RFC 2326). \par \par }\pard\plain \s4\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0 \b\f4\lang1033\cgrid {Header 1: x-Retransmit header \par }\pard\plain \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 \f4\lang1033\cgrid { \par If a client would like to use Reliable R TP, it should append this header in its SETUP request. The body of the header contains the retransmit protocol name, followed by a semi-colon delimited list of arguments. The name for the Reliable RTP retransmit protocol is 'our-retransmit'. There is curr ently one argument that can be passed from client to server, 'window'. If included, this tells the Reliable RTP server the size of the client's window, in KBytes. See section 2B on how this is used to size the congestion window. \par \par Example: \par \par x-Retransmit: our-retransmit;window=128 \par \par The server must echo the header and all parameters. If the header is not in the SETUP response, the client must assume that Reliable RTP will not be used for this stream. If the parameter values have been changed, the client must use the new values. \par \par }\pard\plain \s4\keepn\widctlpar\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0 \b\f4\lang1033\cgrid {Header 2: x-Transport-Options header \par }\pard\plain \widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f4\lang1033\cgrid { \par }\pard \nowidctlpar\tx1440\tx2880\tx4320\tx5760\tx7200\faauto\rin0\lin0\itap0 {x-transport-options: late-tolerance=1.5 \par \par }} \ No newline at end of file diff --git a/Documentation/admin-protocol-README.txt b/Documentation/admin-protocol-README.txt new file mode 100644 index 0000000..89fc8ed --- /dev/null +++ b/Documentation/admin-protocol-README.txt @@ -0,0 +1,423 @@ +ADMIN PROTOCOL SPECIFICATION +DarwinStreamingServer 3.0 Beta Release +(This document is subject to change) + +SERVER DATA ORGANIZATION +The server's internals are mapped to a hierarchical tree of element arrays. Each element is a named type including a container type for retrieval of sub-node elements. + +PROTOCOL +The protocol relies upon the URI mechanism as defined by RFC2396 for specifying a container entity using a path and HTTP 1.0 RFC 1945 for specifying the Request and Response mechanisms. + +REQUEST METHODS +HTTP GET is the current request and response method. + +SESSION STATE +The session is closed at the end of each HTTP request response. + +SUPPORTED REQUEST HEADER FEATURES +Authorization + +SERVER DATA ACCESS +All data on the server is specified using a URI + +DEFINITION OF SERVER URI +The following URI references the top level of the Streaming Server's hierarchical data tree using a simple HTTP GET request. + +Example: +GET /modules/admin + +URI REQUESTS + +A valid request is an absolute reference (a URL beginning with "/") followed by the Server URI: +[absolute URL]?[parameters="value"(s)]+[command="value"]+ +["option"="value"] + +Example: +GET /modules/admin/server/qtssSvrClientSessions?parameters=rt+command=get + +Design Goals: + +Concept: +The server state machine and database can be accessed through a regular expression. The Admin protocol abstracts the QTSS module API to handle data access and in some cases to provide data access triggers for execution of server functions. + +Flexibility: +Four basic functions provide all of the administrative functions used by the server: add, set, del or get. + +Server Performance: +Server streaming threads are blocked during admin accesses to internal data. To minimize the blocking of the server's activities, the protocol allows scoped access to the server's data structures by allowing specific URL paths to any element. + +Query Functionality: +Queries can contain an array iterator, a name lookup, a recursive tree walk, and a filtered response. All functions can execute in a single URI query. + +Example of a query for the stream time scale and stream payload name from every stream in every session +GET /modules/admin/server/qtssSvrClientSessions/*/qtssCliSesStreamObjects?parameters=r+command=get+filter1=qtssRTPStrTimescale+filter2=qtssRTPStrPayloadName + +"*" = array iterator +"parameters=rt" = 'r' recursive walk and 't' show data types in result. +"filter1=qtssRTPStrTimescale" = return the stream time scale +"filter2=qtssRTPStrPayloadName" = return the stream payload + +Example of a query for all server module names and their descriptions +GET /modules/admin/server/qtssSvrModuleObjects?parameters=r+command=get+filter2=qtssModDesc+filter1=qtssModName + +URI RULES +/path = absolute reference +* = iterate each element in current URL location +path/* = is defined as all elements contained in the "path" container +. = not supported +.. = not supported +; = not supported +? query options follow ("+" delimited name="value" pairs) +spaces and tabs are stop characters. +"" are supported for values and required for values containing spaces and tabs. + +PATH DEFINITION +A path represents a server's virtual hierarchical data structure of containers and is expressed as a URL. + +DATA REFERENCES +All Elements are arrays. Single element arrays may be referenced by "path/element", "path/element/", "path/element/*", and"path/element/1" are evaluated as the same query. + + +QUERY OPTIONS +URIs without a '?' default to a get request. +URIs containing a '?' designator must contain a "command=[get|set|del|add] " query option. + +Query options are not case sensitive. +Query option values except for the command options are case sensitive. +Unknown query options are ignored. +Query options not required by a command are ignored. + +COMMAND OPTION: +command=[GET | SET | DEL | ADD] +Unknown commands are reported as an error. + +command=GET <- get data identified by URI +Command GET does not require other query options. +Example: GET /modules/admin/example_count + +command=SET <- set data identified by URI +Value checking is not performed. Conversion between the text value and the actual value is type specific. +Example: GET /modules/admin/example_count?command=SET+value=5 + +OPTIONAL QUERY OPTIONS +type= <- if defined then type checking of the server element type and the set type is performed. If a match of the stored type and the request type fails an error is returned and the command fails. +Example: GET /modules/admin/maxcount?command=SET+value=5+type=SInt32 + +command=DEL <- delete data identified by URI +The command deletes the element referenced by the URL. +Example: GET /modules/admin/maxcount?command=DEL + +command=ADD <- add data identified by URI +If the element at the end of the URL is an element then Add performs an add to the array of elements reference the element name. +required query options are +value= +type= +Example: GET /modules/admin/example_count ? command=ADD+value=6type=SInt16 + +If the element at the end of the URL is a QTSS_Object container then a "command=add" performs a named element add to the container. +required query options are +value= +type= +name= +Example: GET /modules/admin/?command=ADD+value=5+name=maxcount+type=SInt16 + + +"parameters=": +'r' = recurse -> walk downward in hierarchy starting at end of URL. Recursion should be avoided if "*" iterators or direct URL access to elements can be used. +'v' = verbose -> return full path in name +'a' = access -> return read/write access +'t' = type -> return date type of value +'d' = debug -> return debugging info with error +'c' = count -> return count of elements in path +Parameters are always single characters with no delimiters. +Parameter options follow the URL [URL]?parameters=[p][p] +example= path/path?parameters=rvat + +ACCESS TYPES +r = read +w = write +p = pre-emptive safe + +DATA TYPES +Data types can be any server allowed text value. New data types can be defined and returned by the server so data types are not limited to the basic set below. + + "UInt8" + "SInt8" + "UInt16" + "SInt16" + "UInt32" + "SInt32" + "UInt64", + "SInt64" + "Float32" + "Float64" + "Bool8" + "Bool16" + "CharArray" + "QTSS_Object" + "void_pointer" + +QTSS_Objects, pointers and unknown data types always converted to a host ordered string of hex values. Example of hex value result: unknown_pointer=DEADBEEF; type=void_pointer + + +URI POST FILTERS +Filters specify a subset of data to be returned on each request. +Multiple filters are evaluated in order with each result placed in the response. + + +RESPONSES + +Example: Unauthorized + +HTTP/1.1 401 Unauthorized +WWW-Authenticate: Basic realm="QTSS/modules/admin" +Server: QTSS +Connection: Close +Content-Type: text/plain + +Example: OK result + +HTTP/1.0 200 OK +Server: QTSS/3.0 [v252]-Linux +Connection: Close +Content-Type: text/plain + +Container="/" +admin/ +error:(0) + +RESPONSE END + +Each OK response ends with +error:(0) + + +RESPONSE DATA +All entity references follow the form [NAME=VALUE] ; [attribute="value"] , [attribute="value"]. +NAME=VALUE +NAME=VALUE;attribute="value" +NAME=VALUE;attribute="value",attribute="value" + +All container references follow the form [NAME/] ; [attribute="value"] , [attribute="value"]. +NAME/ +NAME/;attribute="value" +NAME;attribute="value",attribute="value" + +The order of appearance of container references and the containerÕs entity references is important. This is especially true when the response is a recursive walk of a container hierarchy. +The "Container=" reference must appear at the beginning of each new level in the hierarchy. Each Container list of elements must be a complete list of the contained elements and any containers. The appearance of a "Container=" reference indicates the end of a previous containerÕs contents and the start of a new container. + +The example below shows how each new container is identified with the unique path. + +Container="/level1/" +field1="value" +field2="value" +level2a/ +level2b/ +Container="/level1/level2a/" +field1="value" +level3a/ +level3b/ +Container="/level1/level2a/level3a" +field1="value" +Container="/level1/level2a/level3b" +Container="/level1/level2b/" +field1="value" +level3a/ +Container="/level1/level2b/level3a/" +field1="value" + +ARRAY VALUES +Arrays of elements are handled in the response by using a numerical value to represent the index. Arrays are containers. + +Container="/level1/" +field1="value" +field2="value" +array1/ +Container="/level1/array1/" +1=value +2=value + +Array elements may be containers. +Container="/level1/array1/" +1/ +2/ +3/ + +Container="/level1/array1/1/" +field1="value" +field2="value" +Container="/level1/array1/2/" +Container="/level1/array1/3/" +field1="value" + + +ROOT VALUE +/admin + +ERRORS IN RESPONSE +The Error state for the Request is always reported with each response at the end of the data. +Error:(0) <- no Error +Error:(404) <- data not found. + +The number appearing in the parenthesis is an HTTP error code followed by an error string when debugging is turned on using the "parameters=d" query option. +error:(404);reason="No data found" + +SETTING ENTITY VALUES + +When changing server values the entity names and their values are located in the request body. If a match is made on an entity name including the URL base at the current container level then the value is set in the server provided the read write attribute allows the set. + +base = /base/container +name = value +/base/container/name="value" + + + +EXAMPLES +================================ +This first example uses basic authentication and shows the HTTP response headers. Later examples will focus on the content of the response and URI of the request. Note a simple method for performing a request is to use a web browser using the URL + +http://17.221.45.238:554/modules/admin/?parameters=a+command=get + +----------------Request---------------- +GET /modules/admin?parameters=a+command=get +Authorization: Basic QWXtaW5pT3RXYXRvcjXkZWZhdWx0 +----------------Response---------------- +HTTP/1.0 200 OK +Server: QTSS/3.0 [v252]-Linux +Connection: Close +Content-Type: text/plain + +Container="/" +admin/;a=r +error:(0) + + +================================ +3 Examples Request + +----------------Request 1---------------- +GET /modules/admin?command=get+parameters=r <--recurse *get everything* + +----------------Request 2---------------- +GET /modules/admin?command=get+parameters=rat <- recurse, access, type + +----------------Request 3---------------- +GET /modules/admin/*<- request elements in admin command not required if no query options are present. + + +======================================== + +An admin client may wish to just monitor the session list like so + +----------------Request---------------- +GET /modules/admin/server/qtssSvrClientSessions/* +----------------Response----------------- +Container="/admin/server/qtssSvrClientSessions/" +12/ +2/ +4/ +8/ +error:(0) + +Response is a qtssSvrClientSessions list of unique session ids + +----------------Request---------------- +GET /modules/admin/server/qtssSvrClientSessions/*/qtssCliSesStreamObjects/* +----------------Response----------------- +Container="/admin/server/qtssSvrClientSessions/3/qtssCliSesStreamObjects/" +0/ +1/ +error:(0) + +qtssCliSesStreamObjects are an indexed array of streams +======================================== + +GET /modules/admin/server/qtssSvrClientSessions/3/qtssCliSesStreamObjects/0/* +----------------Response----------------- +qtssRTPStrTrackID="4" +qtssRTPStrSSRC="683618521" +qtssRTPStrPayloadName="X-QT/600" +qtssRTPStrPayloadType="1" +qtssRTPStrFirstSeqNumber="-7111" +qtssRTPStrFirstTimestamp="433634204" +qtssRTPStrTimescale="600" +qtssRTPStrQualityLevel="0" +qtssRTPStrNumQualityLevels="3" +qtssRTPStrBufferDelayInSecs="3.000000" +qtssRTPStrFractionLostPackets="0" +qtssRTPStrTotalLostPackets="52" +qtssRTPStrJitter="0" +qtssRTPStrRecvBitRate="1526072" +qtssRTPStrAvgLateMilliseconds="501" +qtssRTPStrPercentPacketsLost="0" +qtssRTPStrAvgBufDelayInMsec="30" +qtssRTPStrGettingBetter="0" +qtssRTPStrGettingWorse="0" +qtssRTPStrNumEyes="0" +qtssRTPStrNumEyesActive="0" +qtssRTPStrNumEyesPaused="0" +qtssRTPStrTotPacketsRecv="6763" +qtssRTPStrTotPacketsDropped="0" +qtssRTPStrTotPacketsLost="0" +qtssRTPStrClientBufFill="0" +qtssRTPStrFrameRate="0" +qtssRTPStrExpFrameRate="3903" +qtssRTPStrAudioDryCount="0" +qtssRTPStrIsTCP="false" +qtssRTPStrStreamRef="18861508" +qtssRTPStrCurrentPacketDelay="-2" +qtssRTPStrTransportType="0" +qtssRTPStrStalePacketsDropped="0" +qtssRTPStrTimeFlowControlLifted="974373815109" +qtssRTPStrCurrentAckTimeout="0" +qtssRTPStrCurPacketsLostInRTCPInterval="52" +qtssRTPStrPacketCountInRTCPInterval="689" +QTSSReflectorModuleStreamCookie=(null) +qtssNextSeqNum=(null) +qtssSeqNumOffset=(null) +QTSSSplitterModuleStreamCookie=(null) +QTSSFlowControlModuleLossAboveTol="0" +QTSSFlowControlModuleLossBelowTol="3" +QTSSFlowControlModuleGettingWorses="0" +error:(0) + + +======================================== + +Here is an example of monitoring just the IP addresses of connected clients. Only the IP addresses are polled and returned. + +----------------Request---------------- +modules/admin/server/qtssSvrClientSessions/*/qtssCliRTSPSessRemoteAddrStr +---------------Response---------------- +Container="/admin/server/qtssSvrClientSessions/5/"qtssCliRTSPSessRemoteAddrStr=17.221.40.1 +Container="/admin/server/qtssSvrClientSessions/6/"qtssCliRTSPSessRemoteAddrStr=17.221.40.2 +Container="/admin/server/qtssSvrClientSessions/8/"qtssCliRTSPSessRemoteAddrStr=17.221.40.3 +Container="/admin/server/qtssSvrClientSessions/14/"qtssCliRTSPSessRemoteAddrStr=17.221.40.4 +error:(0) +======================================== + + +SPECIAL PATHS + +PREFERENCES +Setting a server or module preference value causes the value to be flushed to the server's xml preference file and the new value will take affect immediately. + +/modules/admin/server/qtssSvrPreferences <-- server preferences +/modules/admin/server/qtssSvrModuleObjects/*/qtssModPrefs/ <-- module preferences + +The elements defined in qtssSvrPreferences are modify-only elements. +The elements in qtssModPrefs containers may be added, deleted and modified. Some deleted elements may be restored automatically by a module if the server or module requires them. The add, del, and set commands on a qtssModPrefs element will cause the streaming server.xml file to be rewritten. + +SERVER STATE +/modules/admin/server/qtssSvrState + +Controls the sever state. It can be modified as a UInt32 with the following values. + qtssStartingUpState = 0, + qtssRunningState = 1, + qtssRefusingConnectionsState = 2, + qtssFatalErrorState = 3,//a fatal error has occurred, not shutting down yet + qtssShuttingDownState = 4, + qtssIdleState = 5 // Like refusing connections state, but will also kill any currently connected clients + +See the QTSS.h API documentation for information about other documented server attributes. + diff --git a/Documentation/broadcasterctl.1 b/Documentation/broadcasterctl.1 new file mode 100644 index 0000000..6b8d18b --- /dev/null +++ b/Documentation/broadcasterctl.1 @@ -0,0 +1,125 @@ +.TH BROADCASTERCTL 1 "August 16, 2002" "Apple Computer, Inc." +.SH NAME +broadcasterctl \- controller for the QuickTime Broadcaster +.SH SYNOPSIS +.B broadcasterctl +[-b broadcaster-path] [-a audiopreset] [-v videopreset] [-n networkpreset] +[-t (audio|video|av)] [-r (record|norecord)] [-p recording-path] [-f settingsfile] +(config|status|presets|launch|start|stop|restart|quit) +.SH DESCRIPTION +.I broadcasterctl +is a command line tool for controlling the +.I QuickTime Broadcaster +application. Using +.I broadcasterctl +you can configure, launch, start, and stop a +.SM QuickTime +streaming broadcast generated by the +.SM MacOS X +.I QuickTime Broadcaster +application. +.PP +The +.I QuickTime Broadcaster +application must be accessable on the local host on which the terminal session is exectuting in order to +communicate with that application. (i.e. It will not control a network remote copy of the +application.) +.SH OPTIONS +.PP +The following command line options can be invoked with +.I broadcasterctl: +.TP +.B config +Sets the configuration of the +.I QuickTime Broadcaster +application to be that of the currently specified settings file. +.TP +.B status +Returns the current status +.I QuickTime Broadcaster +application. +.TP +.B presets +Sets the presets of the +.I QuickTime Broadcaster +application to be that of the currently specified preset values. +.TP +.B launch +Launches the +.I QuickTime Broadcaster +application. +.TP +.B start +Tells the +.I QuickTime Broadcaster +application to begin the broadcast. +.TP +.B stop +Tells the +.I QuickTime Broadcaster +application to stop the broadcast. +.TP +.B restart +Tells the +.I QuickTime Broadcaster +application to restart the broadcast. +.TP +.B quit +Causes the +.I QuickTime Broadcaster +application to quit. +.TP +.BI \-a " audiopreset" +Specifies the the current audio preset configuration to be the name given in +the +.I audiopreset +parameter. +.TP +.BI \-b " broadcaster-path" +Specifies the file path to the +.I QuickTime Broadcaster +application. +.TP +.BI \-f " settingsfile" +Specifies the file path to the broadcast settings file. +.TP +.BI \-n " networkpreset" +Specifies the the current network preset configuration to be the name given in +the +.I networkpreset +parameter. +.TP +.BI \-p " recording-path" +Specifies the file path to record the broadcast to. +.TP +.BI \-r " (record|norecord)" +Controls whether or not the +.I QuickTime Broadcaster +will record the broadcast to a file as it is broadcasting +the stream. Specify +.I record +to record the broadcast and +.I norecord +to do nothing. (The default is the +.I norecord +option.) +.TP +.BI \-t " (audio|video|av)" +Specifies the type of broadcast to be originated. Specifiy +.I audio +for an audio-only broadcast, +.I video +for an video-only broadcast, or +.I av +for a combination audio/video broadcast. +.TP +.BI \-v " videopreset" +Specifies the the current video preset configuration to be the name given in +the +.I videopreset +parameter. +.SH "SEE ALSO" +QuickTimeStreamingServer(1), ps(1), kill(1) +.SH LIMITATIONS +Only one audio, video, or audio/video session can be controlled per invokation of +.I broadcasterctl. \ No newline at end of file diff --git a/Documentation/draft-serenyi-avt-rtp-meta-00.txt b/Documentation/draft-serenyi-avt-rtp-meta-00.txt new file mode 100644 index 0000000..524b0d4 --- /dev/null +++ b/Documentation/draft-serenyi-avt-rtp-meta-00.txt @@ -0,0 +1,425 @@ + + + + + +AVT Working Group D. Serenyi +Internet Draft Apple Computer +Document: November 2001 +Category: Standards Track + + + + RTP Payload Format for Payload Meta-Information + + +Status of this Memo + +This document is an Internet-Draft and is in full conformance with all +provisions of Section 10 of RFC2026 [ ]. + +Internet-Drafts are working documents of the Internet Engineering Task +Force (IETF), its areas, and its working groups. Note that other +groups may also distribute working documents as Internet-Drafts. +Internet-Drafts are draft documents valid for a maximum of six months +and may be updated, replaced, or obsoleted by other documents at any +time. It is inappropriate to use Internet- Drafts as reference +material or to cite them other than as "work in progress." +The list of current Internet-Drafts can be accessed at +http://www.ietf.org/ietf/1id-abstracts.txt +The list of Internet-Draft Shadow Directories can be accessed at +http://www.ietf.org/shadow.html. + + +1. Abstract + +This memo describes the RTP payload format for payload meta- +information. A RTP-Meta-Info packet is a collection of individual +fields. Each field has its own format: the format of some fields are +documented here, other fields may be defined and documented elsewhere. + +This memo also specifies methods within SDP (Session Description +Protocol) and RTSP (Real-time Streaming Protocol) for negotiating the +contents of an RTP-Meta-Info stream. + +2. Conventions used in this document + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this +document are to be interpreted as described in RFC-2119 [ ]. + + + + + + + +Serenyi Standards Track 1 +INTERNET-DRAFT RTP Payload Meta-Information Nov 2001 + + +3. Introduction + +Certain RTP clients, such as RTP reflectors, relays, and caching +proxies, require per-packet meta information that goes beyond the +sequence number and timestamp already provided in the RTP header. For +instance, a caching proxy may want to provide stream thinning to its +clients in case those clients are bandwidth constrained. If that +stream thinning is based on the type of video frame being sent by the +origin server, there is no payload-independent way for the caching +proxy to determine the frame type. + +4. Format of the RTP-Meta-Info Payload Type + +This section documents the format of the RTP-Meta-Info payload as a +series of fields. The format of individual fields is described in +Section 6, or in other documents. + +The format of each field in a RTP-Meta-Info payload consists of a +field header and a field body. The field header can either have the +"standard" format or the "compressed" format. The first bit of each +field header indicates the type of field, so it is possible to mix +standard and compressed fields in the same packet. The bit should be 0 +for standard format fields, 1 for compressed format fields. + +The standard format consists of a 15 bit field name and a 2 octet +field length. The field name should be ASCII representations of +alphanumeric characters, such as "ft". Because the first character has +only 7 bits of space allocated, the names must use only 7-bit ASCII +characters. The field length should be the length of the entire field +body in octets. + +The following is an example of the standard RTP-Meta-Info payload +format: + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|C| field name | field len | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| .... | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|C| field name | field len | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| .... | + + + + + +Serenyi Standards Track 2 +INTERNET-DRAFT RTP Payload Meta-Information Nov 2001 + + +The compressed format requires out-of-band negotiation between client +and server. During the negotiation process, a 7-bit field ID is +negotiated for each field name. Instead of the field names being sent +in the RTP payload, only the field ID is sent. Negotiation can either +happen through RTSP, described in section 5, or through the SDP +description of the RTP-Meta-Info stream, described in section 6. + +In either case, the field description will be a text header consisting +of a semi-colon list of field names and field IDs. For example the +following could be a server to client RTSP header: + +x-RTP-Meta-Info: to=0;ft=1;ba=2;rb=3 + +Each semi-colon delimited section of the header maps a field name to a +unique ID. The field IDs must be between 0 and 127, because they are +represented by 7 bits in the compressed header. + +The format of the compressed header is the 1-bit header type +identifier, followed by the 7-bit field Id, followed by a 1-octet +field length. + +The following is an example of a compressed RTP-Meta-Info packet with +both compressed and standard fields: + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|C| field ID | field len | field data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| .... | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|C| field name | field len | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| .... | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|C| field ID | field len | field data | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| .... | + + + + +5. Field Negotiation Through RTSP + +Any payload can be re-encoded as an RTP-Meta-Info stream through the +x-RTP-Meta-Info RTSP header. + + + + + +Serenyi Standards Track 3 +INTERNET-DRAFT RTP Payload Meta-Information Nov 2001 + + +The "x- RTP-Meta-Info" header should be sent from a client to server +on a SETUP, and echoed by the server in its SETUP response. If the +header is not echoed, the client must assume that the server does not +support this header. + +The body of the x-RTP-Meta-Info header follows the format described in +section 3: the field format followed by a semi-colon delimited list of +field names. In the client request, this is the list of fields the +client would like to receive in the RTP stream specified by the SETUP. +In the server response, this is the list of fields the server will +provide in that RTP stream. The response may also contain field ID +mappings for some or all of the field names if the server supports the +compressed header format. The server may return a subset of the field +names in the event the server doesn't support all the requested +fields, or some fields don't apply to the RTP stream specified by the +SETUP. + +Example x-RTP-Meta-Info header in SETUP request: + +x-RTP-Meta-Info; to;bi;bo + +Example x-RTP-Meta-Info header in SETUP response: + +x-RTP-Meta-Info: to=0;bi;bo=1 +or: +x-RTP-Meta-Info: to;bi + + +6. Describing the RTP-Meta-Info Payload in SDP + +It is recommended that any source of RTP-Meta-Info payload packets +describe the contents of the payload as part of the SDP description of +the media. RTP-Meta-Info descriptions consist of two additional a= +headers. The a=x-embedded-rtpmap header tells the client the payload +type of the underlying RTP payload. The a=x-RTP-Meta-Info header is a +field description equivalent in format to the x-RTP-Meta-Info header +described in section 4. + +Example SDP description of the RTP-Meta-Info payload: + +m=other 5084 RTP/AVP 96 +a=rtpmap:96 x-RTP-Meta-Info +a=x-embedded-rtpmap:96 x-QT +a=x-RTP-Meta-Info: standard;to;bi;bo + + +7. Field Definitions + +The fields defined below are primarily meant to be used by RTSP / RTP +caching proxies and by broadcasters sending to a reflecting server. +More field definitions can be added as is necessary. + + +Serenyi Standards Track 4 +INTERNET-DRAFT RTP Payload Meta-Information Nov 2001 + + +7.1 transmit time field, 'tt' + +This field consists of a single 8-octet unsigned integer representing +the recommended transmission time of the RTP packet in milliseconds. +The transmit times are always offset from the start of the media +presentation. For example, if the SDP response for a URL includes a +range of 0-729.45, and the client makes a PLAY request with a Range of +100-729.45, then the first RTP packet from the server should have a tt +value of approximately 100,000 (it may not be exactly 100,000 because +the server is free to find a frame nearby the requested time). If the +SDP for a URL does not contain a range, then the client may at least +use these values as relative offsets. + +Example uncompressed transmit time field: + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field name, 'tt' | field len, 8 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| hi-order transmit time | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| lo-order transmit time | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +7.2 frame type field, 'ft' + +This field consists of a single 16-bit unsigned integer value with +several well-known values representing different frame types. The +well-known values are as follows: + +0 - unknown frame type +1 - key frame +2 - b-frame +3 - p-frame + +Future versions may add additional frame types. Any value greater than +3 should be considered to be an unknown frame type. + +This field is only valid for video RTP streams. If a client asks for +it when setting up another media type, the server may strip this sub- +extension from its "x-RTP-Extension" response, or it may provide the +field with a value of 0 in every packet. + +Example uncompressed frame type field: + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field name, 'ft' | field len, 2 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| frame type | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +Serenyi Standards Track 5 +INTERNET-DRAFT RTP Payload Meta-Information Nov 2001 + + +7.3 packet number field, 'pn' + +This field consists of a sinle 64-bit unsigned integer value. The +value is the packet number offset from the absolute start of the +stream. For example, if the SDP response for a URL includes a range of +0-729.45, and the client makes a PLAY request with a range of 0- +729.45, the pn value of the first packet will be 0, and increment by 1 +for each subsequent packet. If there are 1000 packets between in the +first 60 seconds of a stream, and a client makes a PLAY request of 60- +729.45, the pn value of the first packet will be 1001, and increment +by 1 for each subsequent packet. + +Example uncompressed packet number field: + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field name, 'pn' | field len, 8 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| hi-order packet number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| lo-order packet number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +7.4 packet position field, 'pp' + +This field consists of a single 64-bit unsigned integer value. The +value is the byte offset of this packet from the absolute start of the +stream. For example, if the SDP response for a URL includes a range of +0-729.45, and the client makes a PLAY request with a range of 100- +729.45, then the pp value of the first video RTP packet will be the +total number of bytes of the video RTP packets between 0 - 100. Only +the RTP packet payload bytes are used to compute each pp value. Some +RTP streams, because they are live or dynamic media, will not be able +to supply this identifier. In general, if the media SDP has a range +attribute, the server will be able to provide the pp extension. + +Example uncompressed packet position field: + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field name, 'pp' | field len, 8 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| hi-order packet position | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| lo-order packet position | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + + + +Serenyi Standards Track 6 +INTERNET-DRAFT RTP Payload Meta-Information Nov 2001 + + +7.5 media data field, 'md' + +This field contains media data for the underlying RTP payload. + +Example uncompressed media data field: + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field name, 'md' | field len | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ... | + +7.6 sequence number, 'sq' + +This field consists of a 2-octet RTP sequence number. This field is +useful for mapping RTP-Meta-Info fields to the underlying payload data +that they refer to, if that data is being sent out-of-band. + +Example uncompressed sequence number field: + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| field name, 'sq' | field len, 2 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| sequence number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +8. Security Considerations + +None. + + +9. References + + +[1] Schulzrinne, H., "Real Time Streaming Protocol (RTSP)", RFC 2326, +April 1998 + +[2] Schulzrinne, H., "RTP: A Transport Protocol for Real-Time +Applications", RFC 1889, January 1996 + +[3] Handley, M., "SDP: Session Description Protocol", RFC 2327, April +1998 + + + + + + + + +Serenyi Standards Track 7 +INTERNET-DRAFT RTP Payload Meta-Information Nov 2001 + + +10. Acknowledgments + + +11. Author's Addresses + +Denis Serenyi +Apple Computer +1 Infinite Loop +Cupertino, CA 95014 +Email: denis@apple.com + +Chris LeCroy +Apple Computer +1 Infinite Loop +Cupertino, CA 95014 +Email: lecroy@apple.com + + +12. Full Copyright Statement + +"Copyright (C) The Internet Society (date). All Rights Reserved. This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implmentation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works. However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English. The limited permissions granted above are perpetual and will not be revoked by the Internet Society or its successors or assigns. This document and the information contained herein is provided on an "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE." diff --git a/Documentation/man/qtss/MP3Broadcaster.1 b/Documentation/man/qtss/MP3Broadcaster.1 new file mode 100644 index 0000000..d003992 --- /dev/null +++ b/Documentation/man/qtss/MP3Broadcaster.1 @@ -0,0 +1,53 @@ +.Dd February 11, 2005 \" DATE +.Dt MP3Broadcaster 1 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm MP3Broadcaster +.Nd MP3 file playlist broadcaster +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Op Fl vdixX \" [-vdixX] +.Op Fl a Ar ipAddress \" [-a ipAddress] +.Op Fl p Ar portNum \" [-p portNum] +.Op Fl l Ar filename \" [-l filename] +.Op Fl w Ar filename \" [-w filename] +.Op Fl e Ar filename \" [-e filename] +-c fileName +.Sh DESCRIPTION \" Section Header - required - don't modify +.Nm +streams a playlist of MP3 files to an instance of QuickTimeStreamingServer. +.Pp +A list of flags and their descriptions: +.Bl -tag -width -indent \" Differs from above in -compact tag removed +.It Fl v +display version +.It Fl d +display version +.It Fl i +use 'icy-' protocol header prefix (default is 'x-audio-') +.It Fl x +preflight configuration +.It Fl X +check MP3 files +List running currently broadcasts +.It Fl a Ar ipaddr +broadcast to this address (default = local loopback) +.It Fl p Ar portNum +broadcast to this port (default = 8000) +.It Fl l Ar filename +path to playlist (overrides config file) +.It Fl w Ar filename +path to dir to create temp lists (overrides config file) +.It Fl e Ar filename +print output to error file +.El +.Sh FILES +.Bl -tag -width /Library/QuickTimeStreaming/Playlists -compact +.It Pa /Library/QuickTimeStreaming/Movies +.It Pa /Library/QuickTimeStreaming/Playlists +.El +.Sh SEE ALSO +.Xr QuickTimeStreamingServer 8 , +.Xr PlaylistBroadcaster 1 , +.\" .Sh BUGS +.\" .Sh HISTORY \ No newline at end of file diff --git a/Documentation/man/qtss/PlaylistBroadcaster.1 b/Documentation/man/qtss/PlaylistBroadcaster.1 new file mode 100644 index 0000000..f61eff3 --- /dev/null +++ b/Documentation/man/qtss/PlaylistBroadcaster.1 @@ -0,0 +1,54 @@ +.Dd February 11, 2005 \" DATE +.Dt PlaylistBroadcaster 1 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm PlaylistBroadcaster +.Nd hinted media playlist broadcaster +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Op Fl vhpcatfdl \" [-dvxIh] +.Op Fl i Ar destAddress \" [-i destAddress] +.Op Fl e Ar filename \" [-e filename] +.Op Fl s Ar broadcastnum \" [-s broadcastnum] +.Ar fileName +.Sh DESCRIPTION \" Section Header - required - don't modify +.Nm +streams a playlist of hinted files to an instance of QuickTimeStreamingServer. +.Pp +A list of flags and their descriptions: +.Bl -tag -width -indent \" Differs from above in -compact tag removed +.It Fl v +Prints version +.It Fl h +Prints usage +.It Fl p +Verify a broadcast description file and movie list +.It Fl c +Write the current movie to the log file +.It Fl a +ANNOUNCE the broadcast to the server +.It Fl t +Send the broadcast over TCP to the server +.It Fl f +Force a new SDP file to be created even if one already exists +.It Fl d +Run attached to the terminal +.It Fl l +List currently running broadcasts +.It Fl s Ar broadcastnum +Stop a running broadcast +.It Fl i Ar myconfigpath.conf +Specify the destination ip address. Overrides config file value. +.It Fl e Ar filename +Log errors to filename +.El +.Sh FILES +.Bl -tag -width /Library/QuickTimeStreaming/Playlists -compact +.It Pa /Library/QuickTimeStreaming/Movies +.It Pa /Library/QuickTimeStreaming/Playlists +.El +.Sh SEE ALSO +.Xr QuickTimeStreamingServer 8 , +.Xr MP3Broadcaster 1 , +.\" .Sh BUGS +.\" .Sh HISTORY \ No newline at end of file diff --git a/Documentation/man/qtss/QuickTimeStreamingServer.8 b/Documentation/man/qtss/QuickTimeStreamingServer.8 new file mode 100644 index 0000000..fd13761 --- /dev/null +++ b/Documentation/man/qtss/QuickTimeStreamingServer.8 @@ -0,0 +1,52 @@ +.Dd February 11, 2005 \" DATE +.Dt QuickTimeStreamingServer 8 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm QuickTimeStreamingServer +.Nd RTP/RTSP streaming server +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Op Fl hvdxID \" [-dvxIh] +.Op Fl c Ar configFile.xml \" [-c configFile.xml] +.Op Fl o Ar configFile.conf \" [-o configFile.conf] +.Op Fl S Ar seconds \" [-S seconds] +.Sh DESCRIPTION \" Section Header - required - don't modify +.Nm +is the Real Time Streaming Protocol (RTSP)/Realtime Trasport Protocol(RTP) server. +.Pp +A list of flags and their descriptions: +.Bl -tag -width -indent \" Differs from above in -compact tag removed +.It Fl h +Prints usage +.It Fl v +Prints usage +.It Fl d +Run in the foreground +.It Fl x +Force creation of a new .xml config file and exit +.It Fl I +Start the server in the idle state +.It Fl D +Display performance data +.It Fl c Ar myconfigpath.xml +Load configuration from xml config file +.It Fl o Ar myconfigpath.conf +Load configuration from old style config file +.It Fl S Ar Seconds +Output server statistics every n seconds +.El +.Sh FILES +.Bl -tag -width /Library/QuickTimeStreaming/Config/streamingserver.xml -compact +.It Pa /Library/QuickTimeStreaming/Config/streamingserver.xml +.It Pa /Library/QuickTimeStreaming/Modules +.It Pa /Library/QuickTimeStreaming/Movies +.It Pa /Library/QuickTimeStreaming/Playlists +.It Pa /Library/QuickTimeStreaming/Logs +.It Pa /Library/QuickTimeStreaming/Docs +.El +.Sh SEE ALSO +.Xr PlaylistBroadcaster 1 , +.Xr MP3Broadcaster 1 , +.Xr StreamingLoadTool 8 +.\" .Sh BUGS +.\" .Sh HISTORY \ No newline at end of file diff --git a/Documentation/man/qtss/StreamingLoadTool.8 b/Documentation/man/qtss/StreamingLoadTool.8 new file mode 100644 index 0000000..bf1d104 --- /dev/null +++ b/Documentation/man/qtss/StreamingLoadTool.8 @@ -0,0 +1,38 @@ +.Dd February 11, 2005 \" DATE +.Dt StreamingLoadTool 8 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm StreamingLoadTool +.Nd Load testing tool for QuickTimeStreamingServer +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Op Fl d \" [-d] +.Op Fl i Ar urlid \" [-i urlid] +.Op Fl f Ar path \" [-f configFile] +.Op Fl c Ar # \" [-c #] +-c fileName +.Sh DESCRIPTION \" Section Header - required - don't modify +.Nm +simulates multiple RTSP/RTP clients. The number of clients, URLs, and other +options can be configured via a +.Ar configFile . +.Pp +A list of flags and their descriptions: +.Bl -tag -width -indent \" Differs from above in -compact tag removed +.It Fl f Ar configFile +Config file to use. The default is /Library/QuickTimeStreaming/Config/streamingloadtool.conf +.It Fl c Ar httpCookie +HTTP cookie to use. Overrides what is in config file +.It Fl i Ar urlID +RTSP stream URL id (i.e. trackID or streamID etc.). Default is -i trackID +.It Fl d +Display debug messages +.El +.Sh FILES +.Bl -tag -width /Library/QuickTimeStreaming/Config/streamingloadtool.conf -compact +.It Pa /Library/QuickTimeStreaming/Config/streamingloadtool.conf +.El +.Sh SEE ALSO +.Xr QuickTimeStreamingServer 8 , +.\" .Sh BUGS +.\" .Sh HISTORY \ No newline at end of file diff --git a/Documentation/man/qtss/createuserstreamingdir.8 b/Documentation/man/qtss/createuserstreamingdir.8 new file mode 100644 index 0000000..291e57c --- /dev/null +++ b/Documentation/man/qtss/createuserstreamingdir.8 @@ -0,0 +1,25 @@ +.Dd February 11, 2005 \" DATE +.Dt createuserstreamingdir 8 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm createuserstreamingdir +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Ar username +.Sh DESCRIPTION \" Section Header - required - don't modify +.Nm +will create the directory ~user/Sites/Streaming/ +.br +.br +The created directory gives the QuickTimeStreamingServer access to user managed content +.Pp +A list of flags and their descriptions: +.Bl -tag -width -indent +.br +.Ar username +The user whose home directory should be updated +.El +.Sh SEE ALSO +.Xr QuickTimeStreamingServer 8 , +.\" .Sh BUGS +.\" .Sh HISTORY \ No newline at end of file diff --git a/Documentation/man/qtss/qtpasswd.1 b/Documentation/man/qtss/qtpasswd.1 new file mode 100644 index 0000000..87df156 --- /dev/null +++ b/Documentation/man/qtss/qtpasswd.1 @@ -0,0 +1,63 @@ +.Dd February 11, 2005 \" DATE +.Dt qtpasswd 1 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm qtpasswd +.Nd MP3 file playlist broadcaster +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Op Fl ?hvFd \" [-Fxd?v] +.Op Fl f Ar filename \" [-f filename] +.Op Fl g Ar groupsfilename \" [-g groupsfilename] +.Op Fl r Ar realm \" [-r realm] +.Op Fl p Ar password \" [-p password] +.Op Fl P Ar passwordfile \" [-P passwordfile] +.Op Fl A Ar group \" [-A group] +.Op Fl D Ar group \" [-D group] +.Op Fl C Ar group \" [-C group] +.Op Fl R Ar group \" [-R group] +.Sh DESCRIPTION \" Section Header - required - don't modify +.Nm +streams a playlist of MP3 files to an instance of QuickTimeStreamingServer. +.Pp +A list of flags and their descriptions: +.Bl -tag -width -indent \" Differs from above in -compact tag removed +.It Fl ? +display usage +.It Fl h +display usage +.It Fl v +display usage +.It Fl F +Don't ask for confirmation when deleting users or overwriting existing files. +.It Fl d +Delete the user. (Deletes the user from all groups) +.It Fl f Ar filename +Password file to manipulate (Default is "/Library/QuickTimeStreaming/Config/qtusers"). +.It Fl g Ar groupsfilename +Groups file to manipulate (Default is "/Library/QuickTimeStreaming/Config/qtgroups"). If not found, will create one when necessary. +.It Fl r Ar realm +The realm name to use when creating a new file via "-c" (Default is "Streaming Server"). +.It Fl p Ar password +Allows entry of password at command line rather than prompting for it. +.It Fl P Ar passwordfile +File to read the password from rather than prompting for it. +.It Fl A Ar group +Add user to group. Will create group automatically if group is not already present. +.It Fl D Ar group +Delete the user from the group. +.It Fl C Ar group +Create new group. Do not specify username with this option. +.It Fl R Ar group +Delete the group. Do not specify username with this option. +.El +.Sh FILES +.Bl -tag -width /Library/QuickTimeStreaming/Config/qtusers -compact +.It Pa /Library/QuickTimeStreaming/Config/qtusers +.It Pa /Library/QuickTimeStreaming/Config/qtgroups +.It Pa /Library/QuickTimeStreaming/Config/qtaccess +.El +.Sh SEE ALSO +.Xr QuickTimeStreamingServer 8 , +.\" .Sh BUGS +.\" .Sh HISTORY \ No newline at end of file diff --git a/Documentation/man/qtss/streamingadminserver.pl.8 b/Documentation/man/qtss/streamingadminserver.pl.8 new file mode 100644 index 0000000..1b9a41e --- /dev/null +++ b/Documentation/man/qtss/streamingadminserver.pl.8 @@ -0,0 +1,15 @@ +.Dd February 11, 2005 \" DATE +.Dt streamingadminserver.pl 8 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm streamingadminserver.pl +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Sh DESCRIPTION \" Section Header - required - don't modify +.Nm +is a web server that manages the html for QuickTimeStreamingServer web-based administration. +.Pp +.Sh SEE ALSO +.Xr QuickTimeStreamingServer 8 , +.\" .Sh BUGS +.\" .Sh HISTORY \ No newline at end of file diff --git a/Documentation/readme.txt b/Documentation/readme.txt new file mode 100644 index 0000000..2ff70aa Binary files /dev/null and b/Documentation/readme.txt differ diff --git a/MP3Broadcaster/BroadcasterMain.cpp b/MP3Broadcaster/BroadcasterMain.cpp new file mode 100755 index 0000000..006eefd --- /dev/null +++ b/MP3Broadcaster/BroadcasterMain.cpp @@ -0,0 +1,301 @@ +/* + * + * @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@ + * + */ + +#ifndef kVersionString +#include "../revision.h" +#endif +#include "MP3Broadcaster.h" +#include "OSHeaders.h" +#include "SocketUtils.h" +#include "OSThread.h" +#include "OS.h" +#include "OSMemory.h" +#include +#include "getopt.h" + +MP3Broadcaster* gBroadcaster = NULL; + +static void RegisterEventHandlers(); +static void SignalEventHandler( int signalID ); + +void version() +{ + /* + print PlaylistBroadcaster version and build info + + see revision.h + */ + + //RoadRunner/2.0.0-v24 Built on: Sep 17 1999 , 16:09:53 + //revision.h (project level file) -- include with -i option + qtss_printf("MP3Broadcaster/%s Built on: %s, %s\n", kVersionString, __DATE__, __TIME__ ); + +} + +void usage() +{ + /* + print MP3Broadcaster usage string + + */ + + qtss_printf("usage: MP3Broadcaster [-v] [-d] [-i] [-x] [-X] [-a ipAddress] [-p portNum] [-l filename] [-w filename] [-e filename] -c filename\n" ); + qtss_printf(" -v: display version\n" ); + qtss_printf(" -d: run in foreground\n" ); + qtss_printf(" -i: use 'icy-' protocol header prefix (default is 'x-audio-')\n" ); + qtss_printf(" -x: preflight configuration\n" ); + qtss_printf(" -X: check MP3 files\n" ); + qtss_printf(" -a : broadcast to this address (default = local loopback)\n" ); + qtss_printf(" -p : broadcast to this port (default = 8000)\n" ); + qtss_printf(" -c : path to config file\n" ); + qtss_printf(" -l : path to playlist (overrides config file)\n" ); + qtss_printf(" -w : path to dir to create temp lists (overrides config file)\n" ); + qtss_printf(" -e : print output to error file\n" ); +} + + +int main(int argc, char* argv[]) +{ + bool daemonize = true; + bool useICY = false; + char* ipaddr = NULL; + int port = 0; + char* config = NULL; + char* playList = NULL; + char* workingDir = NULL; + char ch; + bool preflight = false; + bool checkMP3s = false; + char* errorlog = NULL; +// extern int optind; + extern char* optarg; + +#ifdef __Win32__ + // + // Start Win32 DLLs + WORD wsVersion = MAKEWORD(1, 1); + WSADATA wsData; + (void)::WSAStartup(wsVersion, &wsData); +#endif + + OS::Initialize(); + OSThread::Initialize(); + OSMemory::SetMemoryError(ENOMEM); + Socket::Initialize(); + SocketUtils::Initialize(false); + + if (SocketUtils::GetNumIPAddrs() == 0) + { + qtss_printf("Network initialization failed. IP must be enabled to run MP3Broadcaster\n"); + ::exit(0); + } + + while ( (ch = getopt(argc,argv, "vdixXa:p:c:l:e:w:")) != EOF ) // opt: means requires option + { + switch(ch) + { + case 'v': + ::version(); + ::usage(); + return 0; + + case 'd': + daemonize = false; + break; + + case 'i': + useICY = true; + break; + + case 'a': + ipaddr = (char*)malloc(strlen(optarg)+1); + strcpy(ipaddr, optarg); + break; + + case 'p': + port = atoi(optarg); + break; + + case 'c': + config = (char*)malloc(strlen(optarg)+1); + strcpy(config, optarg); + break; + + case 'l': + playList = (char*)malloc(strlen(optarg)+1); + strcpy(playList, optarg); + break; + + case 'w': + workingDir = (char*)malloc(strlen(optarg)+1); + break; + + case 'x': + preflight = true; + daemonize = false; + break; + + case 'X': + preflight = true; + daemonize = false; + checkMP3s = true; + break; + + case 'e': + errorlog = (char*)malloc(strlen(optarg)+1); + strcpy(errorlog, optarg); + break; + + default: + ::usage(); + return 0; + } + } + + if (config == NULL) + { + qtss_printf("missing -c option\n"); + ::usage(); + return 0; + } + + if (errorlog != NULL) + { + if (preflight) + freopen(errorlog, "w", stdout); + else + freopen(errorlog, "a", stdout); + ::setvbuf(stdout, (char *)NULL, _IONBF, 0); + } + + gBroadcaster = new MP3Broadcaster(ipaddr, port, config, playList, workingDir, useICY); + + if (!gBroadcaster->IsValid()) + { + qtss_printf("Bad config--exiting\n"); + qtss_printf("Warnings: 0\nErrors: 1\n"); + return 0; + } + + RegisterEventHandlers(); + gBroadcaster->PreFlightOrBroadcast(preflight, daemonize, false, false, checkMP3s, errorlog); + + return 0; +} + +static void RegisterEventHandlers() +{ +#ifdef __Win32__ + SetConsoleCtrlHandler( (PHANDLER_ROUTINE) SignalEventHandler, true); + return; +#endif + +#ifndef __Win32__ + +struct sigaction act; + +#if defined(sun) || defined(i386) || defined(__MacOSX__) || defined(__sgi__) || defined(__osf__) || defined(__hpux__) || defined(__linux) || defined(__linuxppc__) + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = (void(*)(int))&SignalEventHandler; +#else + act.sa_mask = 0; + act.sa_flags = 0; + act.sa_handler = (void(*)(...))&SignalEventHandler; +#endif + + if ( ::signal(SIGTERM, SIG_IGN) != SIG_IGN) + { // from kill... + if ( ::sigaction(SIGTERM, &act, NULL) != 0 ) + { qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR ); + } + } + + if ( ::signal(SIGINT, SIG_IGN) != SIG_IGN) + { // ^C signal + if ( ::sigaction(SIGINT, &act, NULL) != 0 ) + { qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR ); + } + + } + + if ( ::signal(SIGPIPE, SIG_IGN) != SIG_IGN) + { // broken pipe probably from a failed RTSP session (the server went down?) + if ( ::sigaction(SIGPIPE, &act, NULL) != 0 ) + { qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR ); + } + + } + + if ( ::signal(SIGHUP, SIG_IGN) != SIG_IGN) + { // catch any SIGHUP + if ( ::sigaction(SIGHUP, &act, NULL) != 0) + { qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR ); + } + + } + + if ( ::signal(SIGALRM, SIG_IGN) != SIG_IGN) + { // catch any SIGALRM + if ( ::sigaction(SIGALRM, &act, NULL) != 0) + { qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR ); + } + + } + +#endif +} + +/* ======================================================================== + * Signal and error handler. + */ +static void SignalEventHandler( int signalID ) +{ + if (gBroadcaster) + { +#ifdef __Win32__ + if ( (signalID != SIGINT) && (signalID != SIGTERM) ) +#else + if ( (signalID == SIGALRM) || (signalID == SIGHUP) ) // unexpected SIGALRM || SIGHUP + + { + + qtss_printf( "- PlaylistBroadcaster: Unexpected signal type (%"_SPOINTERSIZEARG_").\n", signalID ); + + // just ignore it... + + return; + + } + + if (signalID == SIGPIPE) // broken pipe from server +#endif + + gBroadcaster->Cleanup(true); + else // kill or ^C + gBroadcaster->Cleanup(false); + } + ::exit(-1); +} diff --git a/MP3Broadcaster/MP3Broadcaster.cpp b/MP3Broadcaster/MP3Broadcaster.cpp new file mode 100755 index 0000000..089a092 --- /dev/null +++ b/MP3Broadcaster/MP3Broadcaster.cpp @@ -0,0 +1,1048 @@ +/* + * + * @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@ + * + */ + +#include "MP3Broadcaster.h" +#include "MP3MetaInfoUpdater.h" +#include "StringTranslator.h" + +#include "../defaultPaths.h" + +#include +#include +#include "SafeStdLib.h" +#include +#include +#ifndef __Win32__ + #include +#endif + + +#ifndef __Win32__ + #include + #if defined (__solaris__) || defined (__osf__) || defined (__hpux__) + #include "daemon.h" + #else + #ifndef __FreeBSD__ + #include + #endif + #endif +#endif + + +#include + +#include "OSHeaders.h" +#include "OS.h" +#include "OSMemory.h" +#include "Task.h" +#include "TimeoutTask.h" +#include "OSArrayObjectDeleter.h" +#include "ConfParser.h" +#include "MP3FileBroadcaster.h" +// must now inlcude this from the project level using the -i switch in the compiler +#ifndef __MacOSX__ + #include "../revision.h" +#endif + + +MP3Broadcaster* MP3Broadcaster::sBroadcaster = NULL; + +//#include "MyAssert.h" + +MP3Broadcaster::MP3Broadcaster(char* ipaddr, int port, char* config, char* playList, char* workingDir, Bool16 useICY) : + mValid(true), + mPort(8000), + mBitRate(0), + mFrequency(0), + mUpcomingSongsListSize(7), + mRecentSongsListSize(0), + mLogging(0), + mShowCurrent(true), + mShowUpcoming(true), + mNumErrors(0), + mNumWarnings(0), + mPreflight(false), + mCleanupDone(false), + mTempPicker(NULL), + mSocket(NULL, 0), + mLog(NULL), + mUseICY(useICY) +{ + sBroadcaster = this; + + strcpy(mIPAddr, "128.0.0.1"); + strcpy(mPlayListPath, DEFAULTPATHS_ETC_DIR "mp3playlist.ply"); + strcpy(mWorkingDirPath, DEFAULTPATHS_ETC_DIR); + strcpy(mPlayMode, "sequential"); + strcpy(mMountPoint, "/"); + strcpy(mGenre, "Pop"); + strcpy(mURL, ""); + strcpy(mPIDFile, ""); + + //see if there is a defaults File. + //if there is load it and over-ride the other defaults + int len = ::strlen(config) + 10; + char *defaultFileName = new char[len]; + qtss_snprintf(defaultFileName, len, "%s%s", config, ".def"); + (void) ::ParseConfigFile( false, defaultFileName, ConfigSetter, this ); //ignore if no defaults file + delete [] defaultFileName; + + int err = ::ParseConfigFile( false, config, ConfigSetter, this ); + if (err) + { + mValid = false; + return; + } + + if (ipaddr != NULL) + { + // override config file + // size limit for any IP addr is 255 + strncpy(mIPAddr, ipaddr, 255); + } + + if (port != 0) + { + // override config file + mPort = port; + } + + if (playList) + { + // override config file + // size limit for any playlist string is PATH_MAX - 1 + strncpy(mPlayListPath, playList, PATH_MAX-1); + } + + if (workingDir) + { + // override config file + // size limit for any working path is PATH_MAX - extension - 1 + strncpy(mWorkingDirPath, playList, PATH_MAX-12); + } + + CreateWorkingFilePath(".current", mCurrentFile); + CreateWorkingFilePath(".upcoming", mUpcomingFile); + CreateWorkingFilePath(".replacelist", mReplaceFile); + CreateWorkingFilePath(".stoplist", mStopFile); + CreateWorkingFilePath(".insertlist", mInsertFile); + CreateWorkingFilePath("mp3_broadcast.log", mLogFile); +} + +Bool16 MP3Broadcaster::ConfigSetter( const char* paramName, const char* paramValue[], void* userData ) +{ + // return true if set fails + MP3Broadcaster* thisPtr = (MP3Broadcaster*)userData; + + if (!::strcmp( "destination_ip_address", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mIPAddr)) + return true; + + strcpy(thisPtr->mIPAddr, paramValue[0]); + return false; + } + else if (!::strcmp( "destination_base_port", paramName) ) + { + thisPtr->mPort = atoi(paramValue[0]); + + return false; + } + else if (!::strcmp( "max_upcoming_list_size", paramName) ) + { + if ( ::atoi( paramValue[0] ) < 0 ) + return true; + + thisPtr->mUpcomingSongsListSize = ::atoi( paramValue[0] ); + return false; + } + else if (!::strcmp( "play_mode", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mPlayMode)) + return true; + + strcpy(thisPtr->mPlayMode, paramValue[0]); + return false; + } + else if (!::strcmp( "recent_songs_list_size", paramName) ) + { + if ( ::atoi( paramValue[0] ) < 0 ) + return true; + + thisPtr->mRecentSongsListSize = ::atoi( paramValue[0] ); + return false; + } + else if (!::strcmp( "playlist_file", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mPlayListPath)) + return true; + + strcpy(thisPtr->mPlayListPath, paramValue[0]); + return false; + } + else if (!::strcmp( "working_dir", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mWorkingDirPath)) + return true; + + strcpy(thisPtr->mWorkingDirPath, paramValue[0]); + return false; + } + else if (!::strcmp( "logging", paramName) ) + { + return SetEnabled(paramValue[0], &thisPtr->mLogging); + } + else if (!::strcmp( "show_current", paramName) ) + { + return SetEnabled(paramValue[0], &thisPtr->mShowCurrent); + } + else if (!::strcmp( "show_upcoming", paramName) ) + { + return SetEnabled(paramValue[0], &thisPtr->mShowUpcoming); + } + else if (!::strcmp( "use_icy", paramName) ) + { + return SetEnabled(paramValue[0], &thisPtr->mUseICY); + } + else if (!::strcmp( "broadcast_name", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mName)) + return true; + + strcpy(thisPtr->mName, paramValue[0]); + return false; + } + else if (!::strcmp( "broadcast_password", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mPassword)) + return true; + + strcpy(thisPtr->mPassword, paramValue[0]); + return false; + } + else if (!::strcmp( "broadcast_genre", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mGenre)) + return true; + + strcpy(thisPtr->mGenre, paramValue[0]); + return false; + } + else if (!::strcmp( "broadcast_url", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mURL)) + return true; + + strcpy(thisPtr->mURL, paramValue[0]); + return false; + } + else if (!::strcmp( "broadcast_bitrate", paramName) ) + { + if ( ::atoi( paramValue[0] ) < 0 ) + return true; + + thisPtr->mBitRate = ::atoi( paramValue[0] ); + return false; + } + else if (!::strcmp( "broadcast_sample_rate", paramName) ) + { + if ( ::atoi( paramValue[0] ) < -1 ) + return true; + + thisPtr->mFrequency = ::atoi( paramValue[0] ); + return false; + } + else if (!::strcmp( "broadcast_mount_point", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mMountPoint)) + return true; + // Make sure the mountpoint always begins with a '/' character. + // If its missing prepend it to the mountpoint name for them. + thisPtr->mMountPoint[0] = '\0'; + if (*paramValue[0] != '/') + strcpy(thisPtr->mMountPoint, "/"); + strcat(thisPtr->mMountPoint, paramValue[0]); + return false; + } + else if (!::strcmp( "pid_file", paramName) ) + { + if (strlen(paramValue[0]) >= sizeof(thisPtr->mPIDFile)) + return true; + + strcpy(thisPtr->mPIDFile, paramValue[0]); + return false; + } + else if (!::strcmp( "max_err_file_k_size", paramName) ) + { + if ( !paramValue[0] || !::strlen(paramValue[0]) ) + return true; + + UInt32 setvalue = kSInt32_Max; + int maxValue = ::atoi( paramValue[0] ); + if (maxValue >= 0) + setvalue = (UInt32) maxValue; + + qtss_setmaxprintfcharsinK( (UInt32) setvalue); + return false; + } + + return true; +} + +Bool16 MP3Broadcaster::SetEnabled( const char* value, Bool16* field) +{ + if ( ::strcmp( "enabled", value) && ::strcmp( "disabled", value) ) + return true; + + *field = !strcmp( "enabled", value ); + + return false; +} + +void MP3Broadcaster::CreateWorkingFilePath(char* extension, char* result) +{ + if (strlen(mWorkingDirPath) + strlen(extension) >= PATH_MAX) + result[0] = 0; + else + { + strcpy(result, mWorkingDirPath); + strcat(result, extension); + } +} + +void MP3Broadcaster::CreateCurrentAndUpcomingFiles() +{ + if (mShowCurrent) + { + if(FileCreateAndCheckAccess(mCurrentFile)) + { /* error */ + mLog->LogInfo( "MP3Broadcaster Error: Failed to create current broadcast file" ); + } + } + + + if (mShowUpcoming) + { + if(FileCreateAndCheckAccess(mUpcomingFile)) + { /* error */ + mLog->LogInfo( "MP3Broadcaster Error: Failed to create upcoming broadcast file" ); + } + } +} + +void MP3Broadcaster::UpdatePlaylistFiles(PlaylistPicker *picker,PlaylistPicker *insertPicker) +{ + if ( (NULL == picker) || (NULL == insertPicker) ) + return; + + /* if .stoplist file exists - prepare to stop broadcast */ + if(!access(mStopFile, R_OK)) + { + picker->CleanList(); + PopulatePickerFromFile(picker, mStopFile, "", NULL); + + mTempPicker->CleanList(); + + remove(mStopFile); + picker->mStopFlag = true; + } + + /* if .replacelist file exists - replace current playlist */ + if(!access(mReplaceFile, R_OK)) + { + picker->CleanList(); + PopulatePickerFromFile(picker, mReplaceFile, "", NULL); + + mTempPicker->CleanList(); + + remove(mReplaceFile); + picker->mStopFlag = false; + } + + /* if .insertlist file exists - insert into current playlist */ + if(!access(mInsertFile, R_OK)) + { + insertPicker->CleanList(); + mTempPicker->CleanList(); + + PopulatePickerFromFile(insertPicker, mInsertFile, "", NULL); + remove(mInsertFile); + picker->mStopFlag = false; + } + + + // write upcoming playlist to .upcoming file + if (mShowUpcoming) + { + FILE *upcomingFile = fopen(mUpcomingFile, "w"); + if(upcomingFile) + { + mElementCount = 0; + + if (!::strcmp(mPlayMode, "weighted_random")) + qtss_fprintf(upcomingFile,"#random play - upcoming list not supported\n"); + else + { qtss_fprintf(upcomingFile,"*PLAY-LIST*\n"); + ShowPlaylistElements(insertPicker,upcomingFile); + ShowPlaylistElements(picker,upcomingFile); + if ( picker->GetNumMovies() == 0 + && !picker->mStopFlag + && 0 != ::strcmp(mPlayMode, "sequential") + ) + { picker->CleanList(); + PopulatePickerFromFile(picker,mPlayListPath,"",NULL); + ShowPlaylistElements(picker,upcomingFile); + mTempPicker->CleanList(); + PopulatePickerFromFile(mTempPicker,mPlayListPath,"",NULL); + } + + if ( mElementCount <= mUpcomingSongsListSize + && 0 == ::strcmp(mPlayMode, "sequential_looped") + ) + { if (mTempPicker->GetNumMovies() == 0) + { mTempPicker->CleanList(); + PopulatePickerFromFile(mTempPicker,mPlayListPath,"",NULL); + } + //sElementCount can be zero if the playlist contains no paths to valid files + while ( (mElementCount != 0) && mElementCount <= mUpcomingSongsListSize ) + ShowPlaylistElements(mTempPicker,upcomingFile); + } + } + fclose(upcomingFile); + } + } + else + { + if ( picker->GetNumMovies() == 0 + && !picker->mStopFlag + && ::strcmp(mPlayMode, "sequential") + ) + { picker->CleanList(); + PopulatePickerFromFile(picker,mPlayListPath,"",NULL); + } + } +} + +void MP3Broadcaster::UpdateCurrentFile(char *thePick) +{ + if (NULL == thePick) + return; + + // save currently playing song to .current file + if (mShowCurrent) + { FILE *currentFile = fopen(mCurrentFile, "w"); + if(currentFile) + { + qtss_fprintf(currentFile,"u=%s\n",thePick); + + fclose(currentFile); + } + } +} + + +void MP3Broadcaster::PrintPlaylistElement(PLDoubleLinkedListNode *node,void *file) +{ + sBroadcaster->mElementCount ++; + if (sBroadcaster->mElementCount <= sBroadcaster->mUpcomingSongsListSize) + { + char* thePick = node->fElement->mElementName; + qtss_fprintf((FILE*)file,"%s\n", thePick); + } +} + +void MP3Broadcaster::ShowPlaylistElements(PlaylistPicker *picker,FILE *file) +{ + if (mElementCount > mUpcomingSongsListSize) + return; + + UInt32 x; + for (x= 0;xGetNumBuckets();x++) + { + picker->GetBucket(x)->ForEach(PrintPlaylistElement,file); + } +} + +void MP3Broadcaster::PreFlightOrBroadcast( bool preflight, bool daemonize, bool showMovieList, bool currentMovie, bool checkMP3s, const char* errorlog) +{ + PlaylistPicker* picker = NULL; + PlaylistPicker* insertPicker = NULL; + MP3FileBroadcaster fileBroadcaster(&mSocket, mBitRate, mFrequency); + MP3MetaInfoUpdater* metaInfoUpdater = NULL; + + SInt32 moviePlayCount; + char* thePick = NULL; + SInt32 numMovieErrors; + bool didAtLeastOneMoviePlay = false; + int err; + + mPreflight = preflight; + + if ( preflight ) + ShowSetupParams(); + + if (preflight) + picker = new PlaylistPicker(false); // make sequential picker, no looping + else + { + picker = MakePickerFromConfig(); // make picker according to parms + mTempPicker = new PlaylistPicker(false); + insertPicker = new PlaylistPicker(false); + insertPicker->mRemoveFlag = true; + } + + // initial call uses empty string for path, NULL for loop detection list + (void)PopulatePickerFromFile( picker, mPlayListPath, "", NULL ); + + if ( preflight ) + { + if ( picker->mNumToPickFrom == 1 ) + qtss_printf( "\nThere is one movie in the Playlist.\n\n" ); + else + qtss_printf( "\nThere are (%li) movies in the Playlist.\n\n", (SInt32) picker->mNumToPickFrom ); + } + + if ( picker->mNumToPickFrom == 0 ) + { + qtss_printf( "- MP3Broadcaster setup failed: There are no movies to play.\n" ); + mNumErrors++; + goto bail; + } + + + // check that we have enough movies to cover the recent movies list. + if ( preflight ) + { + if ( !strcmp( mPlayMode, "weighted_random" ) ) // this implies "random" play + { + if ( mRecentSongsListSize >= picker->mNumToPickFrom ) + { + mRecentSongsListSize = picker->mNumToPickFrom - 1; + qtss_printf("- PlaylistBroadcaster Warning:\n The recent_movies_list_size setting is greater than \n"); + qtss_printf(" or equal to the number of movies in the playlist.\n" ); + mNumWarnings++; + } + } + } + + // create the log file + mLog = new MP3BroadcasterLog( mWorkingDirPath, mLogFile, mLogging ); + +// if ( !PreflightTrackerFileAccess( R_OK | W_OK ) ) +// goto bail; + + if (!preflight) + { + err = ConnectToServer(); + if (err) + { + if (err == EACCES) + qtss_printf("- MP3Broadcaster: Disconnected from Server. Bad password or mount point\n Exiting.\n" ); + else + qtss_printf("- MP3Broadcaster: Couldn't connect to server\n Exiting.\n" ); + mNumErrors++; + goto bail; + } + } + + //Unless the Debug command line option is set, daemonize the process at this point + if (daemonize) + { +#ifndef __Win32__ + qtss_printf("- MP3Broadcaster: Started in background.\n"); + + // keep the same working directory.. +#ifdef __sgi__ + if (::_daemonize(_DF_NOCHDIR, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO) != 0) +#else + if (::daemon( 1, 0 ) != 0) +#endif + { + qtss_printf("- MP3Broadcaster: System error (%i).\n", errno); + mNumErrors++; + goto bail; + } + +#endif + } + + if (daemonize && (errorlog != NULL)) + { + freopen(errorlog, "a", stdout); + ::setvbuf(stdout, (char *)NULL, _IONBF, 0); + } + + if (!preflight) + { + metaInfoUpdater = new MP3MetaInfoUpdater(mPassword, mMountPoint, mSocket.GetRemoteAddr(), mPort); + metaInfoUpdater->Start(); + fileBroadcaster.SetInfoUpdater(metaInfoUpdater); + } + + // ^ daemon must be called before we Open the log and tracker since we'll + // get a new pid, our files close, ( does SIGTERM get sent? ) + + if (( mLog ) && ( mLogging )) + mLog->EnableLog( false ); // don't append ".log" to name for PLB + + if ( mLogging && !mLog->IsLogEnabled() ) + { + if ( mLog->LogDirName() && *mLog->LogDirName() ) + qtss_printf("- MP3Broadcaster: The log file failed to open.\n ( path: %s/%s )\n Exiting.\n", mLog->LogDirName(), mLog->LogFileName() ); + else + qtss_printf("- MP3Broadcaster: The log file failed to open.\n ( path: %s )\n Exiting.\n", mLog->LogFileName() ); + + mNumErrors++; + goto bail; + } + + + if (mPIDFile[0] != 0) + { + if(!FileCreateAndCheckAccess(mPIDFile)) + { + FILE *pidFile = fopen(mPIDFile, "w"); + if(pidFile) + { + qtss_fprintf(pidFile,"%d\n",getpid()); + fclose(pidFile); + } + } + } + +// AddOurPIDToTracker( bcastSetupFilePath ); // <-- exits on failure + + if ( !preflight ) + mLog->LogInfo( "MP3Broadcaster started." ); + else + mLog->LogInfo( "MP3Broadcaster preflight started." ); + + if(!preflight) + { + CreateCurrentAndUpcomingFiles(); + SendXAudioCastHeaders(); + } + + if (!preflight) + { + // check the frequency of the first song + fileBroadcaster.PlaySong( picker->GetFirstFile(), NULL, true, true ); + } + + moviePlayCount = 0; + numMovieErrors = 0; + didAtLeastOneMoviePlay = false; + + while (true) + { + + if (!showMovieList && !preflight) + UpdatePlaylistFiles(picker, insertPicker); + + if (NULL != insertPicker) + thePick = insertPicker->PickOne(); + + if (NULL == thePick && (NULL != picker)) + thePick = picker->PickOne(); + + if ( (thePick != NULL) && (!preflight && showMovieList) ) + { + // display the picks in debug mode, but not preflight + qtss_printf( "[%li] %s picked\n", moviePlayCount, thePick ); + } + + + if ( !showMovieList ) + { + int playError; + + if(!preflight) + { UpdateCurrentFile(thePick); + /* if playlist is about to run out repopulate it */ + if ( !::strcmp(mPlayMode, "sequential_looped") ) + { + if(NULL == thePick &&!picker->mStopFlag) + { + if (picker->GetNumMovies() == 0) + break; + else + continue; + } + + } + } + + if (thePick == NULL) + break; + + ++moviePlayCount; + + if(!preflight) + { + + SInt64 startTime = OS::Milliseconds(); + SInt64 endTime = 0; + playError = fileBroadcaster.PlaySong( thePick, mCurrentFile ); + endTime = OS::Milliseconds(); + + //were we able to actually play the movie? + didAtLeastOneMoviePlay = didAtLeastOneMoviePlay || (playError == 0); + + //ok, we've reached the end of the current playlist + if (picker->GetNumMovies() == 0) + { + //If we determine that every one of the movies resulted in an error, then bail + if (!didAtLeastOneMoviePlay) + { + qtss_printf("Quitting: Playlist contains no valid files.\n"); + mLog->LogInfo( "Quitting: Playlist contains no valid files.\n" ); + goto bail; + } + else + { + didAtLeastOneMoviePlay = false; + } + } + + mLog->LogMediaData(thePick, fileBroadcaster.GetTitle(), + fileBroadcaster.GetArtist(), + fileBroadcaster.GetAlbum(), + (UInt32) ((endTime - startTime)/1000L), + playError); + } + else + { + playError = fileBroadcaster.PlaySong( thePick, NULL, preflight, !checkMP3s ); + } + + if (playError == MP3FileBroadcaster::kConnectionError) + { + // do something + mNumErrors++; + goto bail; + } + + if ( !preflight && (playError != 0)) + { + qtss_printf("File %s : ", thePick); + mLog->LogMediaError( thePick, MapErrorToString(playError), NULL ); + } + else if (playError != 0) + { + qtss_printf("File %s : ", thePick); + MapErrorToString(playError); + numMovieErrors++; + mNumWarnings++; + } + } + + + delete [] thePick; + thePick = NULL; + } //while (true) + + remove(mCurrentFile); + remove(mUpcomingFile); + + if ( preflight ) + { + char str[256]; + qtss_printf( " - " ); + if (numMovieErrors == 1) + strcpy(str, "MP3Broadcaster found one problem MP3 file."); + else + qtss_sprintf( str, "MP3Broadcaster found %li problem MP3 files." , numMovieErrors ); + qtss_printf( "%s\n", str ); + if (mLog) mLog->LogInfo( str ); + + if (numMovieErrors == moviePlayCount) + { + qtss_printf("There are no valid MP3s to play\n"); + mNumErrors++; + } + } + +bail: + + delete picker; + + if (metaInfoUpdater) + delete metaInfoUpdater; + + Cleanup(); + +#ifndef __Win32__ +/* + if ( sgTrackingSucceeded ) + { + // remove ourselves from the tracker + // this is the "normal" remove, also signal handlers + // may remove us. + + BCasterTracker tracker( sgTrackerFilePath ); + + tracker.RemoveByProcessID( getpid() ); + tracker.Save(); + } +*/ +#endif +} + +PlaylistPicker* MP3Broadcaster::MakePickerFromConfig() +{ + // construct a PlaylistPicker object using options set + + PlaylistPicker *picker = NULL; + + if ( !::strcmp( mPlayMode, "weighted_random" ) ) + { + picker = new PlaylistPicker( 10, mRecentSongsListSize ); + + } + else if ( !::strcmp( mPlayMode, "sequential_looped" ) ) + { + picker = new PlaylistPicker(true); + picker->mRemoveFlag = true; + } + else if ( !::strcmp( mPlayMode, "sequential" ) ) + { + picker = new PlaylistPicker(false); + picker->mRemoveFlag = true; + } + + return picker; +} + +bool MP3Broadcaster::FileCreateAndCheckAccess(char *theFileName) +{ + FILE *theFile; + + +#ifndef __Win32__ + if(access(theFileName, F_OK)){ + /* file does not exist - create and set rights */ + theFile = ::fopen(theFileName, "w+"); + if(theFile){ + /* make sure everybody has r/w access to file */ + (void)::chmod(theFileName, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); + (void)::fclose(theFile); + }else return 1; + }else{ + /* file exists - check rights */ + if(access(theFileName, R_OK|W_OK))return 2; + } + +#else + + theFile = ::fopen(theFileName,"a"); + + if (theFile) ::fclose(theFile); + +#endif + + + return 0; +} + +int MP3Broadcaster::ConnectToServer() +{ + UInt32 addr; + addr = SocketUtils::ConvertStringToAddr(mIPAddr); + if (addr == INADDR_NONE) + { + struct hostent* theHostent = ::gethostbyname(mIPAddr); + if (theHostent != NULL) + addr = ntohl(*(UInt32*)(theHostent->h_addr_list[0])); + else + qtss_printf("Couldn't resolve address %s\n", mIPAddr); + } + + OS_Error err = mSocket.Open(); + err = mSocket.Connect(addr, mPort); + + if (err == 0) + { + char buffer1[512]; + char buffer2[512]; + UInt32 len; + + StringTranslator::EncodeURL(mMountPoint, strlen(mMountPoint) + 1, buffer1, sizeof(buffer1)); + if (strlen(buffer1) + strlen(mPassword) + 12 <= 512) + { + if (mUseICY) + { + // in the ICY protocol there is no mountpoint + // the reflector assumes a default mountpoint of "/" + qtss_sprintf(buffer2, "%s\n", mPassword); + } + else + { + // if the mountpoint does not start with a "/" we prepend one for the + // reflector before we send the broadcast request. + if (buffer1[0] == '/') + qtss_sprintf(buffer2, "SOURCE %s %s\n\n", mPassword, buffer1); + else + qtss_sprintf(buffer2, "SOURCE %s /%s\n\n", mPassword, buffer1); + } + mSocket.Send(buffer2, strlen(buffer2), &len); + } + else return -1; + + char buffer3[3]; + len = 0; + mSocket.Read(buffer3, 2, &len); + buffer3[2] = '\0'; + if (::strcmp(buffer3, "OK") != 0) + err = EACCES; + } + + return err; +} + +int MP3Broadcaster::SendXAudioCastHeaders() +{ + char buffer[1024]; + char temp[256]; + UInt32 len; + + if (mUseICY) + { + qtss_sprintf(buffer, "icy-name:%s\n", mName); + qtss_sprintf(temp, "icy-genre:%s\n", mGenre); + strcat(buffer, temp); + qtss_sprintf(temp, "icy-pub:%s\n", "0"); + strcat(buffer, temp); + qtss_sprintf(temp, "icy-url:%s\n", mURL); + strcat(buffer, temp); + } + else + { + qtss_sprintf(buffer, "x-audiocast-name:%s\n", mName); + qtss_sprintf(temp, "x-audiocast-genre:%s\n", mGenre); + strcat(buffer, temp); + qtss_sprintf(temp, "x-audiocast-public:%s\n", "0"); + strcat(buffer, temp); + qtss_sprintf(temp, "x-audiocast-description:%s\n", ""); + strcat(buffer, temp); + } + mSocket.Send(buffer, strlen(buffer), &len); + + return 0; +} + +void MP3Broadcaster::ShowSetupParams() +{ + qtss_printf( "\n" ); + qtss_printf( "Configuration Settings\n" ); + qtss_printf( "----------------------------\n" ); + qtss_printf( "Destination address %s:%d\n", mIPAddr, mPort ); + qtss_printf( "MP3 bitrate %d\n", mBitRate ); + qtss_printf( "play_mode %s\n", mPlayMode ); + qtss_printf( "recent_movies_list_size %d\n", mRecentSongsListSize ); + qtss_printf( "playlist_file %s\n", mPlayListPath ); + qtss_printf( "working_dir %s\n", mWorkingDirPath ); + qtss_printf( "logging %d\n", mLogging ); + qtss_printf( "log_file %s\n", mLogFile ); + qtss_printf( "max_upcoming_list_size %d\n", mUpcomingSongsListSize ); + qtss_printf( "show_current %d\n", mShowCurrent ); + qtss_printf( "show_upcoming %d\n", mShowUpcoming ); + qtss_printf( "use_icy %d\n", mUseICY ); + qtss_printf( "broadcast_name \"%s\"\n", mName); + qtss_printf( "broadcast_genre \"%s\"\n", mGenre); + qtss_printf( "broadcast_mount_point \"%s\"\n", mMountPoint); + qtss_printf( "broadcast_password \"XXXXX\"\n"); + qtss_printf( "max_err_file_k_size %"_U32BITARG_"\n", qtss_getmaxprintfcharsinK()); + qtss_printf( "\n" ); +} + +void MP3Broadcaster::RemoveFiles() +{ + if (mPIDFile[0] != 0) + { + remove(mPIDFile); + } + + remove(mStopFile); + remove(mReplaceFile); + remove(mInsertFile); + remove(mCurrentFile); + remove(mUpcomingFile); +} + +char* MP3Broadcaster::MapErrorToString(int error) +{ + char* result = NULL; + + if (error == MP3FileBroadcaster::kBadFileFormat) + result = "Bad file format."; + else if (error == MP3FileBroadcaster::kWrongFrequency) + result = "Encoded at wrong frequency."; + else if (error == MP3FileBroadcaster::kWrongBitRate) + result = "Doesn't use desired bit rate."; + else if (error == MP3FileBroadcaster::kCouldntOpenFile) + result = "Couldn't open file."; + + if (result != NULL) + qtss_printf("%s\n", result); + + return result; +} + +void MP3Broadcaster::Cleanup(bool signalHandler) +{ + if (mCleanupDone) + return; + + mCleanupDone = true; + + if (signalHandler) + { + mNumErrors++; + qtss_printf("- MP3Broadcaster: Disconnected from Server while playing. Exiting.\n"); + } + + if (mPreflight) + { + qtss_printf("Warnings: %"_S32BITARG_"\n", mNumWarnings); + qtss_printf("Errors: %"_S32BITARG_"\n", mNumErrors); + } + else + { + qtss_printf("Broadcast Warnings: %"_S32BITARG_"\n", mNumWarnings); + qtss_printf("Broadcast Errors: %"_S32BITARG_"\n", mNumErrors); + } + + RemoveFiles(); + + if (mLog) + { + if ( mPreflight ) + mLog->LogInfo( "MP3Broadcaster preflight finished." ); + else + mLog->LogInfo( "MP3Broadcaster finished." ); + } + + // mLog = NULL; // protect the interrupt handler and just let it die don't delete because it is a task thread +} diff --git a/MP3Broadcaster/MP3Broadcaster.h b/MP3Broadcaster/MP3Broadcaster.h new file mode 100755 index 0000000..710756f --- /dev/null +++ b/MP3Broadcaster/MP3Broadcaster.h @@ -0,0 +1,124 @@ +/* + * + * @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@ + * + */ + +#ifndef __MP3Broadcaster_H__ +#define __MP3Broadcaster_H__ + +#include "OSHeaders.h" +#include "TCPSocket.h" +#include "SocketUtils.h" +#include "PickerFromFile.h" +#include "MP3BroadcasterLog.h" + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +class MP3Broadcaster +{ +public: + MP3Broadcaster(char* ipaddr, int port, char* config, char* playList, char* workingDir, Bool16 useICY = false); + ~MP3Broadcaster(){} + + //void SetBroadcastAddr(char* addrString); + //void SetConfigPath(char* path); + //void SetBitRate(int bitRate); + //void SetPlayList(char* path); + + //Bool16 CheckConfig(); + int ConnectToServer(); + void PreFlightOrBroadcast( bool preflight, bool daemonize, bool showMovieList, bool currentMovie, bool checkMP3s, const char* errorlog); + + Bool16 IsValid() { return (Bool16) mValid; } + + char* GetPIDFilePath() { return mPIDFile; } + + void Cleanup(bool signalHandler = false); + +private: + static Bool16 ConfigSetter( const char* paramName, const char* paramValue[], void * userData ); + static Bool16 SetEnabled( const char* value, Bool16* field); + static void PrintPlaylistElement(PLDoubleLinkedListNode *node,void *file); + + void CreateWorkingFilePath(char* extension, char* result); + bool FileCreateAndCheckAccess(char *theFileName); + void CreateCurrentAndUpcomingFiles(); + void UpdatePlaylistFiles(PlaylistPicker *picker,PlaylistPicker *insertPicker); + void UpdateCurrentFile(char *thePick); + void ShowPlaylistElements(PlaylistPicker *picker,FILE *file); + //char* GetBroadcastDirPath(const char * setupFilePath); + PlaylistPicker* MakePickerFromConfig(); + int SendXAudioCastHeaders(); + void ShowSetupParams(); + void RemoveFiles(); + char* MapErrorToString(int error); + + Bool16 mValid; + + char mIPAddr[256]; + int mPort; + int mBitRate; + int mFrequency; + char mPlayListPath[PATH_MAX]; + char mWorkingDirPath[PATH_MAX]; + char mCurrentFile[PATH_MAX]; + char mUpcomingFile[PATH_MAX]; + char mReplaceFile[PATH_MAX]; + char mStopFile[PATH_MAX]; + char mInsertFile[PATH_MAX]; + char mLogFile[PATH_MAX]; + char mPIDFile[PATH_MAX]; + + char mPlayMode[256]; + int mUpcomingSongsListSize; + int mRecentSongsListSize; + char mName[256]; + char mGenre[256]; + char mPassword[256]; + char mURL[PATH_MAX]; + char mMountPoint[PATH_MAX]; + + Bool16 mLogging; + Bool16 mShowCurrent; + Bool16 mShowUpcoming; + + SInt32 mNumErrors; + SInt32 mNumWarnings; + bool mPreflight; + bool mCleanupDone; + + PlaylistPicker* mTempPicker; + int mElementCount; + + TCPSocket mSocket; + MP3BroadcasterLog* mLog; + Bool16 mUseICY; + + static MP3Broadcaster* sBroadcaster; + +}; + +#endif + diff --git a/MP3Broadcaster/MP3BroadcasterLog.cpp b/MP3Broadcaster/MP3BroadcasterLog.cpp new file mode 100644 index 0000000..d02be87 --- /dev/null +++ b/MP3Broadcaster/MP3BroadcasterLog.cpp @@ -0,0 +1,259 @@ + +/* + * + * @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@ + * + */ + + + +#include "MP3BroadcasterLog.h" + +#ifndef kVersionString +#include "../revision.h" +#endif + +static Bool16 sLogTimeInGMT = false; + +static char* sLogHeader = "#Software: %s\n" + "#Version: %s\n" //%s == version + "#Date: %s\n" //%s == date/time + "#Remark: All date values are in %s.\n" //%s == "GMT" or "local time" + "#Fields: date time filepath title artist album duration result\n"; + +MP3BroadcasterLog::MP3BroadcasterLog( char* defaultPath, char* logName, Bool16 enabled ) + : QTSSRollingLog() +{ + this->SetTaskName("MP3BroadcasterLog"); + *mDirPath = 0; + *mLogFileName = 0; + mWantsLogging = false; + + if (enabled) + { + mWantsLogging = true; + + // check if logName is a full path + ::strcpy( mDirPath, logName ); + char* nameBegins = ::strrchr( mDirPath, kPathDelimiterChar ); + if ( nameBegins ) + { + *nameBegins = 0; // terminate mDirPath at the last PathDelimeter + nameBegins++; + ::strcpy( mLogFileName, nameBegins ); + } + else + { // it was just a file name, no dir spec'd + ::strcpy( mDirPath, defaultPath ); + ::strcpy( mLogFileName, logName ); + } + + } + + this->SetLoggingEnabled(mWantsLogging); + +} + +time_t MP3BroadcasterLog::WriteLogHeader(FILE *inFile) +{ + // Write a W3C compatable log header + time_t calendarTime = ::time(NULL); + Assert(-1 != calendarTime); + if (-1 == calendarTime) + return -1; + + struct tm timeResult; + struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult); + Assert(NULL != theLocalTime); + if (NULL == theLocalTime) + return -1; + + char tempBuffer[1024] = { 0 }; + qtss_strftime(tempBuffer, sizeof(tempBuffer), "#Log File Created On: %m/%d/%Y %H:%M:%S\n", theLocalTime); + this->WriteToLog(tempBuffer, !kAllowLogToRoll); + tempBuffer[0] = '\0'; + + // format a date for the startup time + + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes] = { 0 }; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + if (result) + { + qtss_sprintf(tempBuffer, sLogHeader, "MP3Broadcaster" , kVersionString, + theDateBuffer, sLogTimeInGMT ? "GMT" : "local time"); + this->WriteToLog(tempBuffer, !kAllowLogToRoll); + } + + return calendarTime; +} + + +void MP3BroadcasterLog::LogInfo( const char* infoStr ) +{ + // log a generic comment + char strBuff[1024] = ""; + char dateBuff[80] = ""; + + if ( this->FormatDate( dateBuff, false ) ) + { + if ( (NULL != infoStr) + && ( ( strlen(infoStr) + strlen(strBuff) + strlen(dateBuff) ) < 800) + ) + { + qtss_sprintf(strBuff,"#Remark: %s %s\n",dateBuff, infoStr); + this->WriteToLog( strBuff, kAllowLogToRoll ); + } + else + { + ::strcat(strBuff,dateBuff); + ::strcat(strBuff," internal error in LogInfo\n"); + this->WriteToLog( strBuff, kAllowLogToRoll ); + } + + } + +} + + +void MP3BroadcasterLog::LogMediaError( const char* path, const char* errStr , const char* messageStr) +{ + // log movie play info + char strBuff[1024] = ""; + char dateBuff[80] = ""; + + if ( this->FormatDate( dateBuff, false ) ) + { + if ( (NULL != path) + && ( (strlen(path) + strlen(dateBuff) ) < 800) + ) + { + + qtss_sprintf(strBuff,"#Remark: %s %s ",dateBuff, path); + + if ( errStr ) + { if ( (strlen(strBuff) + strlen(errStr) ) < 1000 ) + { + ::strcat(strBuff,"Error:"); + ::strcat(strBuff,errStr); + } + } + else + if ( (NULL != messageStr) + && + ( (strlen(strBuff) + strlen(messageStr) ) < 1000 ) + ) + { ::strcat(strBuff,messageStr); + } + else + ::strcat(strBuff,"OK"); + + ::strcat(strBuff,"\n"); + this->WriteToLog(strBuff, kAllowLogToRoll ); + } + else + { + ::strcat(strBuff,dateBuff); + ::strcat(strBuff," internal error in LogMediaError\n"); + this->WriteToLog( strBuff, kAllowLogToRoll ); + } + + } + +} + +void MP3BroadcasterLog::LogMediaData( const char* song, const char* title, const char* artist, const char* album, + UInt32 duration, SInt16 result) +{ + // log movie play info + char strBuff[1024] = ""; + char dateBuff[80] = ""; + + if ( this->FormatDate( dateBuff, false ) ) + { + if ( (NULL != song) + && ( (strlen(song) + strlen(dateBuff) ) < 800) + ) + { + + qtss_sprintf(strBuff,"%s '%s'",dateBuff, song); + + if ( title || title[0] != 0) + { if ( (strlen(strBuff) + strlen(title) ) < 1000 ) + { + ::strcat(strBuff," '"); + ::strcat(strBuff,title); + ::strcat(strBuff,"'"); + } + } + else + { + ::strcat(strBuff," -"); + } + + if ( artist || artist[0] != 0) + { if ( (strlen(strBuff) + strlen(artist) ) < 1000 ) + { + ::strcat(strBuff," '"); + ::strcat(strBuff,artist); + ::strcat(strBuff,"'"); + } + } + else + { + ::strcat(strBuff," -"); + } + + if ( album || album[0] != 0) + { if ( (strlen(strBuff) + strlen(album) ) < 1000 ) + { + ::strcat(strBuff," '"); + ::strcat(strBuff,album); + ::strcat(strBuff,"'"); + } + } + else + { + ::strcat(strBuff," -"); + } + + // add the duration in seconds + qtss_sprintf(dateBuff, " %"_S32BITARG_" ", duration); + ::strcat(strBuff,dateBuff); + + // add the result code + qtss_sprintf(dateBuff, " %d\n", result); + ::strcat(strBuff,dateBuff); + + this->WriteToLog(strBuff, kAllowLogToRoll ); + } + else + { + ::strcat(strBuff,dateBuff); + ::strcat(strBuff," internal error in LogMediaData\n"); + this->WriteToLog( strBuff, kAllowLogToRoll ); + } + + } + +} + diff --git a/MP3Broadcaster/MP3BroadcasterLog.h b/MP3Broadcaster/MP3BroadcasterLog.h new file mode 100644 index 0000000..645359f --- /dev/null +++ b/MP3Broadcaster/MP3BroadcasterLog.h @@ -0,0 +1,84 @@ +/* + * + * @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@ + * + */ + +#ifndef __MP3BroadcasterLog_h__ +#define __MP3BroadcasterLog_h__ + +#include "QTSSRollingLog.h" +#include "StrPtrLen.h" +#include + + +class MP3BroadcasterLog : public QTSSRollingLog +{ + enum { eLogMaxBytes = 0, eLogMaxDays = 0 }; + + public: + MP3BroadcasterLog( char* defaultPath, char* logName, Bool16 enabled ); + virtual ~MP3BroadcasterLog() {} + + virtual char* GetLogName() + { // RTSPRollingLog wants to see a "new'd" copy of the file name + char* name = new char[strlen( mLogFileName ) + 1 ]; + + if ( name ) + ::strcpy( name, mLogFileName ); + + return name; + } + + virtual char* GetLogDir() + { // RTSPRollingLog wants to see a "new'd" copy of the file name + char *name = new char[strlen( mDirPath ) + 1 ]; + + if ( name ) + ::strcpy( name, mDirPath ); + + return name; + } + + virtual UInt32 GetRollIntervalInDays() { return eLogMaxDays; /* we dont' roll*/ } + + virtual UInt32 GetMaxLogBytes() { return eLogMaxBytes; /* we dont' roll*/ } + + void LogInfo( const char* infoStr ); + void LogMediaError( const char* path, const char* errStr, const char* messageStr); + void LogMediaData( const char* song, const char* title, const char* artist, const char* album, + UInt32 duration, SInt16 result); + + bool WantsLogging() { return mWantsLogging; } + const char* LogFileName() { return mLogFileName; } + const char* LogDirName() { return mDirPath; } + + virtual time_t WriteLogHeader(FILE *inFile); + + protected: + char mDirPath[256]; + char mLogFileName[256]; + bool mWantsLogging; + +}; + +#endif diff --git a/MP3Broadcaster/MP3FileBroadcaster.cpp b/MP3Broadcaster/MP3FileBroadcaster.cpp new file mode 100755 index 0000000..eed0fe0 --- /dev/null +++ b/MP3Broadcaster/MP3FileBroadcaster.cpp @@ -0,0 +1,593 @@ +/* + * + * @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@ + * + */ + +#include "MP3FileBroadcaster.h" +#include +//#include +#include + +#include "OS.h" +#include "OSThread.h" + +int gBitRateArray[] = { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }; +int gFrequencyArray[] = { 44100, 48000, 32000, 0 }; + +int gBitRateArrayv2[] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }; +int gFrequencyArrayv2[] = { 22050, 24000, 16000, 0 }; +int gFrequencyArrayv2_5[] = { 11025, 12000, 8000, 0 }; + +MP3FileBroadcaster::MP3FileBroadcaster(TCPSocket* socket, int bitrate, int frequency, int bufferSize) : + mSocket(socket), + mBitRate(bitrate), + mBufferSize(bufferSize), + mNumFramesSent(0), + mBroadcastStartTime(0), + mBuffer(NULL), + mUpdater(NULL), + mDesiredBitRate(bitrate), + mDesiredFrequency(frequency) +{ + mBuffer = new unsigned char[bufferSize]; + mTitle[0] = 0; + mArtist[0] = 0; + mSong[0] = 0; +} + +MP3FileBroadcaster::~MP3FileBroadcaster() +{ + delete [] mBuffer; +} + +int MP3FileBroadcaster::PlaySong(char *fileName, char *currentFile, bool preflight, bool fastpreflight) +{ + UInt32 length, lengthSent; + + if (mBuffer == NULL) + return -1; + + mFile.Set(fileName); + if (!mFile.IsValid()) + return kCouldntOpenFile; + + CheckForTags(); + + if (strlen(mTitle) == 0) + { + char* temp = fileName+strlen(fileName); + while ((temp > fileName) && (*(temp-1) != kPathDelimiterChar)) + temp--; + + ::strncpy(mTitle, temp,sizeof(mTitle) -1); + mTitle[sizeof(mTitle) -1] = 0; + } + + if (strlen(mArtist) != 0 && strlen(mAlbum) != 0) + qtss_sprintf(mSong, "%s - %s (%s)", mTitle, mArtist, mAlbum); + else if (strlen(mArtist) != 0) + qtss_sprintf(mSong, "%s - %s", mTitle, mArtist); + else + ::strcpy(mSong, mTitle); + + if (preflight) + qtss_printf("Preflighting %s\n", mSong); + + // skip all the padding at the beginning of the file + mFile.Seek(mStartByte); + bool done = false; + + OS_Error err = mFile.Read(mBuffer, mBufferSize, &length); + if (err != OS_NoErr) + return -1; + + for(UInt32 i = 0; iRequestMetaInfoUpdate(mSong); + + mFile.Seek(mStartByte); + + if (mBroadcastStartTime == 0) + mBroadcastStartTime = OS::Milliseconds(); + //unused UInt64 startTime = OS::Milliseconds(); + int totalBytes = 0; + //unused int numFrames = 0; // amount of play time this buffer represents + int leftOver = 0; // we may have a partial buffer left over from last read + SInt64 properElapsedTime; + while(true) + { + if (!preflight) + { + // the time length each frame represents depends on the frequency + int numSamplesPerFrame = mIsMPEG2 ? 576 : 1152; // these are MP3 standards + properElapsedTime = mNumFramesSent * numSamplesPerFrame * 1000 / mFrequency; // frequency is samples per second + SInt64 nextSendTime = mBroadcastStartTime + properElapsedTime; + + SInt64 currentTime = OS::Milliseconds(); + if (nextSendTime > currentTime) + OSThread::Sleep( (UInt32) (nextSendTime - currentTime)); + } + + length = 0; + err = mFile.Read(mBuffer + leftOver, mBufferSize - leftOver, &length); + if ((err != OS_NoErr) || (length == 0)) + break; + + length += leftOver; + mNumFramesSent += CountFrames(mBuffer, length, &leftOver); + + if (!preflight) + { + OS_Error err = mSocket->Send((char*)mBuffer, length - leftOver, &lengthSent); + if (err != 0) + { + mFile.Close(); + return kConnectionError; + } + } + + totalBytes += length; + if (leftOver > 0) + ::memcpy(mBuffer, mBuffer+length-leftOver, leftOver); + + if (preflight && fastpreflight && (totalBytes > 20 * 1024)) + break; // just check first 20K of file + } + +// UInt64 elapsed = OS::Milliseconds() - startTime; +// UInt64 rate = (UInt64)totalBytes * 8 * 1000 / elapsed; +// qtss_printf("Sent %d bytes in %qd milliseconds = %qd bits per second\n", totalBytes, elapsed, rate); + + if ((mDesiredFrequency == 0) || preflight) + { + // if we aren't fixing the frequency, then we need to time each song seperately + mBroadcastStartTime = OS::Milliseconds(); + mNumFramesSent = 0; + } + + mFile.Close(); + + return 0; +} + +void MP3FileBroadcaster::CheckForTags() +{ + mArtist[0] = 0; + mTitle[0] = 0; + mAlbum[0] = 0; + mStartByte = 0; + + if (ReadV2_3Tags()) + return; + + if (ReadV2_2Tags()) + return; + + if (ReadV1Tags()) + return; + + return; +} + +bool MP3FileBroadcaster::ReadV1Tags() +{ + char buffer[128] = ""; + UInt32 length = 0; + int i; + + mFile.Seek(mFile.GetLength()-128); + OS_Error err = mFile.Read(buffer, sizeof(buffer), &length); + if ((err != OS_NoErr) || (length != 128)) + return false; + + if (strncmp(buffer, "TAG", 3)) + return false; + + // Song Title + // stored as space padded 30 byte buffer (no null termination) + memcpy(mTitle, buffer+3, 30); + for (i = 29; i>=0; i--) + if (mTitle[i] != ' ') + { + mTitle[i+1] = 0; + break; + } + + // Artist Name + // stored as space padded 30 byte buffer (no null termination) + memcpy(mArtist, buffer+33, 30); + for (i = 29; i>=0; i--) + if (mArtist[i] != ' ') + { + mArtist[i+1] = 0; + break; + } + + // Album Title + // stored as space padded 30 byte buffer (no null termination) + memcpy(mAlbum, buffer+63, 30); + for (i = 29; i>=0; i--) + if (mAlbum[i] != ' ') + { + mAlbum[i+1] = 0; + break; + } + + return true; +} + +bool MP3FileBroadcaster::ReadV2_2Tags() +{ + char buffer[1024] = ""; + UInt32 length = 0; + + mFile.Seek(0); + OS_Error err = mFile.Read(buffer, sizeof(buffer), &length); + if (err) + return false; + + if (length < 4) // tag header size + return false; + + if (strncmp(buffer, "ID3", 3)) + return false; + + if (buffer[3] != 2) + return false; + + // we have a valid v2.2 tag header + char* ptr = buffer + 10; + + // the total length of tags is encoded in this strange way to avoid being + // interpreted as an MP3 "sync" flag (don't use the top bit of each byte). + int totalTagLen = buffer[6]*2097152+buffer[7]*16384+buffer[8]*128+buffer[9]; + mStartByte = totalTagLen; // skip tags when streaming + + // OK, I'm being lazy here, but if someone can't find a way to put the song + // title and artist in the first 1K of header then they're just being plain mean. + if (totalTagLen > 1024) totalTagLen = 1024; + + while (ptr-buffer < totalTagLen) + { + if (*ptr == 0) + break; + + // next three bytes are length, so go two bytes, copy 4 and mask off one + int fieldLen = ntohl(OS::GetUInt32FromMemory((UInt32*)(ptr+2))) & 0x00ffffff; + + if (!strncmp(ptr, "TP1", 3)) // Artist + { + int len = fieldLen; + if (len > 255) len = 255; + if (ptr[6] == 0) + { + ::memcpy(mArtist, ptr+7, len-1); // skip encoding byte + mArtist[len-1] = 0; + } + else + ConvertUTF16toASCII(ptr+7, len-1, mArtist, sizeof(mArtist)); + } + + if (!strncmp(ptr, "TT2", 3)) // Title + { + int len = fieldLen; + if (len > 255) len = 255; + if (ptr[6] == 0) + { + ::memcpy(mTitle, ptr+7, len-1); // skip encoding byte + mTitle[len-1] = 0; + } + else + ConvertUTF16toASCII(ptr+7, len-1, mTitle, sizeof(mTitle)); + } + + if (!strncmp(ptr, "TAL", 3)) // Album + { + int len = fieldLen; + if (len > 255) len = 255; + if (ptr[6] == 0) + { + ::memcpy(mAlbum, ptr+7, len-1); // skip encoding byte + mAlbum[len-1] = 0; + } + else + ConvertUTF16toASCII(ptr+7, len-1, mAlbum, sizeof(mAlbum)); + } + + if ((strlen(mTitle) > 0) && (strlen(mArtist) > 0) && (strlen(mAlbum) > 0)) + break; // we found the tags we need + + ptr += fieldLen + 6; // skip field and header + } + + return true; +} + +bool MP3FileBroadcaster::ReadV2_3Tags() +{ + char buffer[1024]; + UInt32 length; + + mFile.Seek(0); + OS_Error err = mFile.Read(buffer, sizeof(buffer), &length); + if (err) + return false; + + if (strncmp(buffer, "ID3", 3)) + return false; + + if (buffer[3] != 3) + return false; + + // we have a valid v2.3 tag header + char* ptr = buffer + 10; + + // skip extended header if it exists + if ((buffer[4] & 0x40) != 0) + ptr += 10; + + // if any other flags are set (like "unsychronization"), bail. + // these can be supported in the future. + if (((buffer[4] & 0xbf) != 0) || (buffer[5] != 0)) + return false; + + // the total length of tags is encoded in this strange way to avoid being + // interpreted as an MP3 "sync" flag (don't use the top bit of each byte). + int totalTagLen = buffer[6]*2097152+buffer[7]*16384+buffer[8]*128+buffer[9]; + mStartByte = totalTagLen; // skip tags when streaming + + // OK, I'm being lazy here, but if someone can't find a way to put the song + // title and artist in the first 1K of header then they're just being plain mean. + if (totalTagLen > 1024) totalTagLen = 1024; + + while (ptr-buffer < totalTagLen) + { + if (*ptr == 0) + break; + + int fieldLen = ntohl(OS::GetUInt32FromMemory((UInt32*)(ptr+4))); + + // should check compression and encryption flags for these fields, but I + // wouldn't really expect them to be set for title or artist + + if (!::strncmp(ptr, "TPE1", 4)) // Artist + { + int len = fieldLen; + if (len > 255) len = 255; + if (ptr[10] == 0) + { + ::memcpy(mArtist, ptr+11, len-1); // skip encoding byte + mArtist[len-1] = 0; + } + else + ConvertUTF16toASCII(ptr+11, len-1, mArtist, sizeof(mArtist)); + } + + if (!strncmp(ptr, "TIT2", 4)) // Title + { + int len = fieldLen; + if (len > 255) len = 255; + if (ptr[10] == 0) + { + ::memcpy(mTitle, ptr+11, len-1); // skip encoding byte + mTitle[len-1] = 0; + } + else + ConvertUTF16toASCII(ptr+11, len-1, mTitle, sizeof(mTitle)); + } + + if (!strncmp(ptr, "TALB", 4)) // Album + { + int len = fieldLen; + if (len > 255) len = 255; + if (ptr[10] == 0) + { + ::memcpy(mAlbum, ptr+11, len-1); // skip encoding byte + mAlbum[len-1] = 0; + } + else + ConvertUTF16toASCII(ptr+11, len-1, mAlbum, sizeof(mAlbum)); + } + + if ((::strlen(mTitle) > 0) && (::strlen(mArtist) > 0) && (::strlen(mAlbum) > 0)) + break; // we found the tags we need + + ptr += fieldLen + 10; // skip field and header + } + + return true; +} + +bool MP3FileBroadcaster::CheckHeaders(unsigned char * buffer) +{ + int bitRate, bitRate2; + int frequency, frequency2; + int recordSize; + + if (!ParseHeader(buffer, &bitRate, &frequency, &recordSize)) + return false; + + if (!ParseHeader(buffer + recordSize, &bitRate2, &frequency2, &recordSize)) + return false; + + if (frequency != frequency2) + return false; + + mBitRate = bitRate; + mFrequency = frequency; + + return true; +} + +bool MP3FileBroadcaster::ParseHeader(unsigned char* buffer, int* bitRate, int* frequency, int* recordSize) +{ + if ((buffer[0] != 0xff) || ((buffer[1] & 0xe6) != 0xe2)) + return false; // not a valid MP3 header (not valid or not layer 3) + + int version = (buffer[1] & 0x18) >> 3; + + mIsMPEG2 = (version != 3); + + if (mIsMPEG2) + *bitRate = gBitRateArrayv2[buffer[2] >> 4]; // MPEG2 + else + *bitRate = gBitRateArray[buffer[2] >> 4]; // MPEG 1 + + if (version == 3) + *frequency = gFrequencyArray[(buffer[2] & 0x0a) >> 2]; + else if (version == 2) + *frequency = gFrequencyArrayv2[(buffer[2] & 0x0a) >> 2]; + else + *frequency = gFrequencyArrayv2_5[(buffer[2] & 0x0a) >> 2]; + + if ((*bitRate == 0) || (*frequency == 0)) + return false; + + int pad = (buffer[2] & 0x02) >> 1; + + *recordSize = 144000 * *bitRate / *frequency; // standard MP3 calculation + + // MPEG 2 (and 2.5) frames encode half the number of samples + if (mIsMPEG2) + *recordSize /= 2; + + *recordSize += pad; + + return true; +} + +int MP3FileBroadcaster::CountFrames(unsigned char* buffer, UInt32 length, int* leftOver) +{ + int bitRate; + int frequency; + int recordSize; + + UInt32 offset = 0; + int numFrames = 0; + while ( offset < length) + { + if (length - offset < 4) + break; // we don't have a whole header left, so move on + + if (!ParseHeader(buffer + offset, &bitRate, &frequency, &recordSize)) + { + // Oops, we lost our stream, so advance byte by byte looking for a frame header + offset++; + continue; + } + + if ( ((UInt32) recordSize + offset) > length) + break; // we don't have the whole frame in this buffer. + + numFrames++; + + offset += recordSize; + } + + // usually there will be a partial frame left over. We leave this for next time. + *leftOver = length - offset; + + return numFrames; +} + +// We really should be converting from Unicode to Latin-1, but the conversion of the high byte characters isn't +// easy. If I find some code or tables to do this I can include it later. +bool MP3FileBroadcaster::ConvertUTF16toASCII(char* sourceStr,int sourceSize, char* dest, int destSize) +{ + unsigned short *sourceStart = (unsigned short *)sourceStr; + unsigned short *sourceEnd = (unsigned short *)(sourceStart + sourceSize); + unsigned char *targetStart = (unsigned char *)dest; + unsigned char *targetEnd = targetStart + destSize; + + bool result = true; + unsigned short *source = sourceStart; + unsigned char *target = targetStart; + unsigned short ch; + bool doSwap = false; + ch = *source++; + Assert((ch == 0xfffe) || (ch == 0xfeff)); + if (ch != 0xfeff) + doSwap = true; + + while ((source < sourceEnd) && (target <= targetEnd)) + { + ch = *source++; + if (doSwap) + { + unsigned short low = (ch & 0xff) << 8; + ch = (ch >> 8) | low; + } + + if (ch < 0x80) + { + *target = (UInt8) ch; + target++; + } + } + + if (target <= targetEnd) + *target = 0; + else + result = false; + + return result; +} diff --git a/MP3Broadcaster/MP3FileBroadcaster.h b/MP3Broadcaster/MP3FileBroadcaster.h new file mode 100755 index 0000000..e160361 --- /dev/null +++ b/MP3Broadcaster/MP3FileBroadcaster.h @@ -0,0 +1,94 @@ +/* + * + * @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@ + * + */ + +#ifndef __MP3FileBroadcaster_H__ +#define __MP3FileBroadcaster_H__ + +#include "TCPSocket.h" +#include "MP3MetaInfoUpdater.h" +#include "OSFileSource.h" + +class MP3FileBroadcaster +{ +public: + MP3FileBroadcaster(TCPSocket* socket, int bitrate, int frequency, int bufferSize = 7000); + ~MP3FileBroadcaster(); + + void SetInfoUpdater(MP3MetaInfoUpdater* updater) { mUpdater = updater; } + + int PlaySong(char *fileName, char *currentFile, bool preflight = false, bool fastpreflight = false); + + enum + { + kBadFileFormat = 1, + kWrongFrequency = 2, + kWrongBitRate = 3, + kConnectionError = 4, + kCouldntOpenFile = 5 + }; + + char* GetTitle() { return mTitle; } + char* GetArtist() { return mArtist; } + char* GetAlbum() { return mAlbum; } + char* GetSong() { return mSong; } + +private: + void CheckForTags(); + bool ReadV1Tags(); + bool ReadV2_2Tags(); + bool ReadV2_3Tags(); + + void UpdateMetaInfo(); + + bool CheckHeaders(unsigned char * buffer); + bool ParseHeader(unsigned char* buffer, int* bitRate, int* frequency, int* recordSize); + int CountFrames(unsigned char* buffer, UInt32 length, int* leftOver); + + bool ConvertUTF16toASCII(char* sourceStr,int sourceSize, char* dest, int destSize); + + TCPSocket* mSocket; + int mBitRate; + int mFrequency; + bool mIsMPEG2; + int mBufferSize; + int mDelay; + UInt64 mNumFramesSent; + UInt64 mBroadcastStartTime; + unsigned char* mBuffer; + MP3MetaInfoUpdater* mUpdater; + + int mDesiredBitRate; + int mDesiredFrequency; + + OSFileSource mFile; + int mStartByte; + + char mTitle[256]; + char mArtist[256]; + char mAlbum[256]; + char mSong[780]; +}; + +#endif diff --git a/MP3Broadcaster/MP3MetaInfoUpdater.cpp b/MP3Broadcaster/MP3MetaInfoUpdater.cpp new file mode 100644 index 0000000..307d932 --- /dev/null +++ b/MP3Broadcaster/MP3MetaInfoUpdater.cpp @@ -0,0 +1,95 @@ +/* + * + * @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@ + * + */ + +#include "MP3MetaInfoUpdater.h" +#include "StringTranslator.h" + +MP3MetaInfoUpdater::MP3MetaInfoUpdater(char* password, char* mountPoint, UInt32 addr, UInt16 port) + : mPassword(NULL), + mMountPoint(NULL), + mSocket(NULL, 0), + mAddr(addr), + mPort(port), + mFirstTime(true) +{ + mPassword = new char[strlen(password) + 1]; + strcpy(mPassword, password); + mMountPoint = new char[strlen(mountPoint) + 1]; + strcpy(mMountPoint, mountPoint); +} + +MP3MetaInfoUpdater::~MP3MetaInfoUpdater() +{ + delete [] mPassword; + delete [] mMountPoint; + SendStopRequest(); + mCond.Signal(); +} + +void MP3MetaInfoUpdater::Entry() +{ + while(!IsStopRequested()) + { + mMutex.Lock(); + mCond.Wait(&mMutex); + mMutex.Unlock(); + if (!IsStopRequested()) + { + if (mFirstTime) + { + Sleep(3000); // give the stream a chance to get established (icecast isn't happy otherwise) + mFirstTime = false; + } + DoUpdateMetaInfo(); + } + } +} + +void MP3MetaInfoUpdater::RequestMetaInfoUpdate(char* song) +{ + char temp[600]; + strcpy(temp, song); + StringTranslator::EncodeURL(temp, strlen(temp) + 1, mSong, sizeof(mSong)); + mCond.Signal(); +} + +void MP3MetaInfoUpdater::DoUpdateMetaInfo() +{ + mSocket.Open(); + int err = mSocket.Connect(mAddr, mPort); + + if (!err) + { + UInt32 len; + char* buffer = new char[100 + strlen(mSong) + strlen(mPassword) + strlen(mMountPoint)]; + qtss_sprintf(buffer, "GET /admin.cgi?mode=updinfo&pass=%s&mount=%s&song=%s HTTP/1.0\r\nUser-Agent: Darwin MP3Broadcaster\r\n\r\n", + mPassword, mMountPoint, mSong); + + mSocket.Send(buffer, strlen(buffer), &len); + delete [] buffer; + } + + mSocket.Cleanup(); +} diff --git a/MP3Broadcaster/MP3MetaInfoUpdater.h b/MP3Broadcaster/MP3MetaInfoUpdater.h new file mode 100644 index 0000000..8749e16 --- /dev/null +++ b/MP3Broadcaster/MP3MetaInfoUpdater.h @@ -0,0 +1,57 @@ +/* + * + * @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@ + * + */ + +#ifndef __MP3MetaInfoUpdater_H__ +#define __MP3MetaInfoUpdater_H__ + +#include "OSThread.h" +#include "OSCond.h" +#include "TCPSocket.h" + +class MP3MetaInfoUpdater : public OSThread +{ +public: + MP3MetaInfoUpdater(char* password, char* mountPoint, UInt32 addr, UInt16 port); + ~MP3MetaInfoUpdater(); + + void Entry(); + + void RequestMetaInfoUpdate(char* song); + +private: + void DoUpdateMetaInfo(); + + OSCond mCond; + OSMutex mMutex; + char mSong[600]; + char* mPassword; + char* mMountPoint; + TCPSocket mSocket; + UInt32 mAddr; + UInt16 mPort; + bool mFirstTime; +}; + +#endif diff --git a/OSMemoryLib/OSMemory.cpp b/OSMemoryLib/OSMemory.cpp new file mode 100644 index 0000000..f600676 --- /dev/null +++ b/OSMemoryLib/OSMemory.cpp @@ -0,0 +1,364 @@ +/* + * + * @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: OSMemory_Server.cpp + + Contains: Implementation of OSMemory stuff, including all new & delete + operators. + + +*/ + +#include +#include "OSMemory.h" + +#if MEMORY_DEBUGGING + +OSQueue OSMemory::sMemoryQueue; +OSQueue OSMemory::sTagQueue; +UInt32 OSMemory::sAllocatedBytes = 0; +OSMutex OSMemory::sMutex; + +#endif + +static SInt32 sMemoryErr = 0; + + +// +// OPERATORS + +#if MEMORY_DEBUGGING +void* operator new(size_t s, char* inFile, int inLine) +{ + return OSMemory::DebugNew(s, inFile, inLine, true); +} + +void* operator new[](size_t s, char* inFile, int inLine) +{ + return OSMemory::DebugNew(s, inFile, inLine, false); +} +#endif + +void* operator new (size_t s) +{ + return OSMemory::New(s); +} + +void* operator new[](size_t s) +{ + return OSMemory::New(s); +} + +void operator delete(void* mem) +{ + OSMemory::Delete(mem); +} + +void operator delete[](void* mem) +{ + OSMemory::Delete(mem); +} + + + + + +void OSMemory::SetMemoryError(SInt32 inErr) +{ + sMemoryErr = inErr; +} + +void* OSMemory::New(size_t inSize) +{ +#if MEMORY_DEBUGGING + return OSMemory::DebugNew(inSize, __FILE__, __LINE__, false); +#else + void *m = malloc(inSize); + if (m == NULL) + ::exit(sMemoryErr); + return m; +#endif +} + +void OSMemory::Delete(void* inMemory) +{ + if (inMemory == NULL) + return; +#if MEMORY_DEBUGGING + OSMemory::DebugDelete(inMemory); +#else + free(inMemory); +#endif +} + +#if MEMORY_DEBUGGING +void* OSMemory::DebugNew(size_t s, char* inFile, int inLine, Bool16 sizeCheck) +{ + //also allocate enough space for a Q elem and a long to store the length of this + //allocation block + OSMutexLocker locker(&sMutex); + ValidateMemoryQueue(); + UInt32 actualSize = s + sizeof(MemoryDebugging) + (2 * sizeof(inLine)); + char *m = (char *)malloc(actualSize); + if (m == NULL) + ::exit(sMemoryErr); + + char theFileName[kMaxFileNameSize]; + strncpy(theFileName, inFile, kMaxFileNameSize); + theFileName[kMaxFileNameSize] = '\0'; + + //mark the beginning and the end with the line number + memset(m, 0xfe, actualSize);//mark the block with an easily identifiable pattern + memcpy(m, &inLine, sizeof(inLine)); + memcpy((m + actualSize) - sizeof(inLine), &inLine, sizeof(inLine)); + + TagElem* theElem = NULL; + + //also update the tag queue + for (OSQueueIter iter(&sTagQueue); !iter.IsDone(); iter.Next()) + { + TagElem* elem = (TagElem*)iter.GetCurrent()->GetEnclosingObject(); + if ((::strcmp(elem->fileName, theFileName) == 0) && (elem->line == inLine)) + { + //verify that the size of this allocation is the same as all others + //(if requested... some tags are of variable size) + if (sizeCheck) + Assert(s == elem->tagSize); + elem->totMemory += s; + elem->numObjects++; + theElem = elem; + } + } + if (theElem == NULL) + { + //if we've gotten here, this tag doesn't exist, so let's add it. + theElem = (TagElem*)malloc(sizeof(TagElem)); + if (theElem == NULL) + ::exit(sMemoryErr); + memset(theElem, 0, sizeof(TagElem)); + theElem->elem.SetEnclosingObject(theElem); + ::strcpy(theElem->fileName, theFileName); + theElem->line = inLine; + theElem->tagSize = s; + theElem->totMemory = s; + theElem->numObjects = 1; + sTagQueue.EnQueue(&theElem->elem); + } + + //put this chunk on the global chunk queue + MemoryDebugging* header = (MemoryDebugging*)(m + sizeof(inLine)); + memset(header, 0, sizeof(MemoryDebugging)); + header->size = s; + header->tagElem = theElem; + header->elem.SetEnclosingObject(header); + sMemoryQueue.EnQueue(&header->elem); + sAllocatedBytes += s; + + return m + sizeof(inLine) + sizeof(MemoryDebugging); +} + +void OSMemory::DebugDelete(void *mem) +{ + OSMutexLocker locker(&sMutex); + ValidateMemoryQueue(); + char* memPtr = (char*)mem; + MemoryDebugging* memInfo = (MemoryDebugging*)mem; + memInfo--;//get a pointer to the MemoryDebugging structure + Assert(memInfo->elem.IsMemberOfAnyQueue());//must be on the memory Queue + //double check it's on the memory queue + Bool16 found = false; + for (OSQueueIter iter(&sMemoryQueue); !iter.IsDone(); iter.Next()) + { + MemoryDebugging* check = (MemoryDebugging*)iter.GetCurrent()->GetEnclosingObject(); + if (check == memInfo) + { + found = true; + break; + } + } + Assert(found == true); + sMemoryQueue.Remove(&memInfo->elem); + Assert(!memInfo->elem.IsMemberOfAnyQueue()); + sAllocatedBytes -= memInfo->size; + + //verify that the tags placed at the very beginning and very end of the + //block still exist + memPtr += memInfo->size; + int* linePtr = (int*)memPtr; + Assert(*linePtr == memInfo->tagElem->line); + memPtr -= sizeof(MemoryDebugging) + sizeof(int) + memInfo->size; + linePtr = (int*)memPtr; + Assert(*linePtr == memInfo->tagElem->line); + + //also update the tag queue + Assert(memInfo->tagElem->numObjects > 0); + memInfo->tagElem->numObjects--; + memInfo->tagElem->totMemory -= memInfo->size; + + if (memInfo->tagElem->numObjects == 0) + { + // If this tag has no elements, then delete the tag + Assert(memInfo->tagElem->totMemory == 0); + sTagQueue.Remove(&memInfo->tagElem->elem); + free(memInfo->tagElem); + } + + // delete our memory block + memset(mem, 0xfd,memInfo->size); + free(memPtr); +} + +void OSMemory::ValidateMemoryQueue() +{ + OSMutexLocker locker(&sMutex); + for(OSQueueIter iter(&sMemoryQueue); !iter.IsDone(); iter.Next()) + { + MemoryDebugging* elem = (MemoryDebugging*)iter.GetCurrent()->GetEnclosingObject(); + char* rawmem = (char*)elem; + rawmem -= sizeof(int); + int* tagPtr = (int*)rawmem; + Assert(*tagPtr == elem->tagElem->line); + rawmem += sizeof(int) + sizeof(MemoryDebugging) + elem->size; + tagPtr = (int*)rawmem; + Assert(*tagPtr == elem->tagElem->line); + } +} + +#if 0 +Bool16 OSMemory::MemoryDebuggingTest() +{ + static char* s20 = "this is 20 characte"; + static char* s30 = "this is 30 characters long, o"; + static char* s40 = "this is 40 characters long, okey dokeys"; + + void* victim = DebugNew(20, 'tsta', true); + strcpy((char*)victim, s20); + MemoryDebugging* victimInfo = (MemoryDebugging*)victim; + ValidateMemoryQueue(); + victimInfo--; + if (victimInfo->tag != 'tsta') + return false; + if (victimInfo->size != 20) + return false; + + void* victim2 = DebugNew(30, 'tstb', true); + strcpy((char*)victim2, s30); + ValidateMemoryQueue(); + void* victim3 = DebugNew(20, 'tsta', true); + strcpy((char*)victim3, s20); + ValidateMemoryQueue(); + void* victim4 = DebugNew(40, 'tstc', true); + strcpy((char*)victim4, s40); + ValidateMemoryQueue(); + void* victim5 = DebugNew(30, 'tstb', true); + strcpy((char*)victim5, s30); + ValidateMemoryQueue(); + + if (sTagQueue.GetLength() != 3) + return false; + for (OSQueueIter iter(&sTagQueue); !iter.IsDone(); iter.Next()) + { + TagElem* elem = (TagElem*)iter.GetCurrent()->GetEnclosingObject(); + if (*elem->tagPtr == 'tstb') + { + if (elem->tagSize != 30) + return false; + if (elem->numObjects != 2) + return false; + } + else if (*elem->tagPtr == 'tsta') + { + if (elem->tagSize != 20) + return false; + if (elem->numObjects != 2) + return false; + } + else if (*elem->tagPtr == 'tstc') + { + if (elem->tagSize != 40) + return false; + if (elem->numObjects != 1) + return false; + } + else + return false; + } + + DebugDelete(victim3); + ValidateMemoryQueue(); + DebugDelete(victim4); + ValidateMemoryQueue(); + + if (sTagQueue.GetLength() != 3) + return false; + for (OSQueueIter iter2(&sTagQueue); !iter2.IsDone(); iter2.Next()) + { + TagElem* elem = (TagElem*)iter2.GetCurrent()->GetEnclosingObject(); + if (*elem->tagPtr == 'tstb') + { + if (elem->tagSize != 30) + return false; + if (elem->numObjects != 2) + return false; + } + else if (*elem->tagPtr == 'tsta') + { + if (elem->tagSize != 20) + return false; + if (elem->numObjects != 1) + return false; + } + else if (*elem->tagPtr == 'tstc') + { + if (elem->tagSize != 40) + return false; + if (elem->numObjects != 0) + return false; + } + else + return false; + } + + if (sMemoryQueue.GetLength() != 3) + return false; + DebugDelete(victim); + ValidateMemoryQueue(); + if (sMemoryQueue.GetLength() != 2) + return false; + DebugDelete(victim5); + ValidateMemoryQueue(); + if (sMemoryQueue.GetLength() != 1) + return false; + DebugDelete(victim2); + ValidateMemoryQueue(); + if (sMemoryQueue.GetLength() != 0) + return false; + DebugDelete(victim4); + return true; +} +#endif //0 + +#endif // MEMORY_DEBUGGING + diff --git a/QTFileLib/QTAtom.cpp b/QTFileLib/QTAtom.cpp new file mode 100644 index 0000000..9b41e21 --- /dev/null +++ b/QTFileLib/QTAtom.cpp @@ -0,0 +1,269 @@ +/* + * + * @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@ + * + */ +// +// QTAtom: +// The base-class for atoms in a QuickTime file. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include + +#ifndef __Win32__ +#include +#include +#endif + +#include "QTFile.h" +#include "QTAtom.h" + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom::QTAtom(QTFile * File, QTFile::AtomTOCEntry * Atom, Bool16 Debug, Bool16 DeepDebug) + : fDebug(Debug), fDeepDebug(DeepDebug), + fFile(File) +{ + // + // Make a copy of the TOC entry. + memcpy(&fTOCEntry, Atom, sizeof(QTFile::AtomTOCEntry)); +} + +QTAtom::~QTAtom(void) +{ +} + + + +// ------------------------------------- +// Read functions +// +Bool16 QTAtom::ReadBytes(UInt64 Offset, char * Buffer, UInt32 Length) +{ + // + // Validate the arguments. + if( (Offset + Length) > fTOCEntry.AtomDataLength ) + return false; + + // + // Read and return this data. + return fFile->Read(fTOCEntry.AtomDataPos + Offset, Buffer, Length); +} + +char *QTAtom::MemMap(UInt64 Offset, UInt32 Length) +{ + // + // Validate the arguments. + if( (Offset + Length) > fTOCEntry.AtomDataLength ) + return NULL; + + // + // Read and return this data. + return fFile->MapFileToMem(fTOCEntry.AtomDataPos + Offset, Length); +} + + +Bool16 QTAtom::UnMap(char *memPtr, UInt32 Length) +{ + if (-1 == fFile->UnmapMem(memPtr, Length)) + return false; + + return true; +} + + +Bool16 QTAtom::ReadInt8(UInt64 Offset, UInt8 * Datum) +{ + // + // Read and return. + return ReadBytes(Offset, (char *)Datum, 1); +} + +Bool16 QTAtom::ReadInt16(UInt64 Offset, UInt16 * Datum) +{ + // General vars + UInt16 tempDatum; + + + // + // Read and flip. + if( !ReadBytes(Offset, (char *)&tempDatum, 2) ) + return false; + + *Datum = ntohs(tempDatum); + return true; +} + +Bool16 QTAtom::ReadInt32(UInt64 Offset, UInt32 * Datum) +{ + // General vars + UInt32 tempDatum; + + + // + // Read and flip. + if( !ReadBytes(Offset, (char *)&tempDatum, 4) ) + return false; + + *Datum = ntohl(tempDatum); + return true; +} + +Bool16 QTAtom::ReadInt32To64(UInt64 Offset, UInt64 * Datum) +{ + // General vars + UInt32 tempDatum; + + + // + // Read and flip. + if( !ReadBytes(Offset, (char *)&tempDatum, 4) ) + return false; + + tempDatum = ntohl(tempDatum); + *Datum = (UInt64) tempDatum; + return true; +} + +Bool16 QTAtom::ReadInt32To64Signed(UInt64 Offset, SInt64 * Datum) +{ + // General vars + UInt32 tempDatum; + + // + // Read and flip. + if( !ReadBytes(Offset, (char *)&tempDatum, 4) ) + return false; + + tempDatum = ntohl(tempDatum); + *Datum = (SInt64) (SInt32) tempDatum; + return true; +} + + + +Bool16 QTAtom::ReadInt64(UInt64 Offset, UInt64 * Datum) +{ + // General vars + UInt64 tempDatum; + + + // + // Read and flip. + if( !ReadBytes(Offset, (char *)&tempDatum, 8) ) + return false; + + *Datum = (UInt64) QTAtom::NTOH64(tempDatum); + return true; +} + + +Bool16 QTAtom::ReadSubAtomBytes(const char * AtomPath, char * Buffer, UInt32 Length) +{ + // General vars + QTFile::AtomTOCEntry *atomTOCEntry; + + + // + // Find the TOC entry for this sub-atom. + if( !fFile->FindTOCEntry(AtomPath, &atomTOCEntry, &fTOCEntry) ) + return false; + + // + // Validate the arguments. + if( (atomTOCEntry->AtomDataPos <= fTOCEntry.AtomDataPos) || ((atomTOCEntry->AtomDataPos + Length) > (fTOCEntry.AtomDataPos + fTOCEntry.AtomDataLength)) ) + return false; + + // + // Read and return this data. + return fFile->Read(atomTOCEntry->AtomDataPos, Buffer, Length); +} + +Bool16 QTAtom::ReadSubAtomInt8(const char * AtomPath, UInt8 * Datum) +{ + // + // Read and return. + return ReadSubAtomBytes(AtomPath, (char *)Datum, 1); +} + +Bool16 QTAtom::ReadSubAtomInt16(const char * AtomPath, UInt16 * Datum) +{ + // General vars + UInt16 tempDatum; + + + // + // Read and flip. + if( !ReadSubAtomBytes(AtomPath, (char *)&tempDatum, 2) ) + return false; + + *Datum = ntohs(tempDatum); + return true; +} + +Bool16 QTAtom::ReadSubAtomInt32(const char * AtomPath, UInt32 * Datum) +{ + // General vars + UInt32 tempDatum; + + + // + // Read and flip. + if( !ReadSubAtomBytes(AtomPath, (char *)&tempDatum, 4) ) + return false; + + *Datum = ntohl(tempDatum); + return true; +} + +Bool16 QTAtom::ReadSubAtomInt64(const char * AtomPath, UInt64 * Datum) +{ + // General vars + UInt64 tempDatum; + + + // + // Read and flip. + if( !ReadSubAtomBytes(AtomPath, (char *)&tempDatum, 8) ) + return false; + + *Datum = (UInt64) QTAtom::NTOH64(tempDatum); + return true; +} + diff --git a/QTFileLib/QTAtom.h b/QTFileLib/QTAtom.h new file mode 100644 index 0000000..d68d281 --- /dev/null +++ b/QTFileLib/QTAtom.h @@ -0,0 +1,100 @@ +/* + * + * @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@ + * + */ +// +// QTAtom: +// The base-class for atoms in a QuickTime file. + +#ifndef QTAtom_H +#define QTAtom_H + + +// +// Includes +#include "OSHeaders.h" + +#include "QTFile.h" + + +// +// QTAtom class +class QTAtom { + +public: + // + // Constructors and destructor. + QTAtom(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void) { return true; } + + static SInt64 NTOH64(SInt64 networkOrdered) + { + #if BIGENDIAN + return networkOrdered; + #else + return (SInt64) ( (UInt64) (networkOrdered << 56) | (UInt64) (((UInt64) 0x00ff0000 << 32) & (networkOrdered << 40)) + | (UInt64) ( ((UInt64) 0x0000ff00 << 32) & (networkOrdered << 24)) | (UInt64) (((UInt64) 0x000000ff << 32) & (networkOrdered << 8)) + | (UInt64) ( ((UInt64) 0x00ff0000 << 8) & (networkOrdered >> 8)) | (UInt64) ((UInt64) 0x00ff0000 & (networkOrdered >> 24)) + | (UInt64) ( (UInt64) 0x0000ff00 & (networkOrdered >> 40)) | (UInt64) ((UInt64) 0x00ff & (networkOrdered >> 56)) ); + #endif + } + + // + // Read functions. + Bool16 ReadBytes(UInt64 Offset, char * Buffer, UInt32 Length); + Bool16 ReadInt8(UInt64 Offset, UInt8 * Datum); + Bool16 ReadInt16(UInt64 Offset, UInt16 * Datum); + Bool16 ReadInt32(UInt64 Offset, UInt32 * Datum); + Bool16 ReadInt64(UInt64 Offset, UInt64 * Datum); + Bool16 ReadInt32To64(UInt64 Offset, UInt64 * Datum); + Bool16 ReadInt32To64Signed(UInt64 Offset, SInt64 * Datum); + + Bool16 ReadSubAtomBytes(const char * AtomPath, char * Buffer, UInt32 Length); + Bool16 ReadSubAtomInt8(const char * AtomPath, UInt8 * Datum); + Bool16 ReadSubAtomInt16(const char * AtomPath, UInt16 * Datum); + Bool16 ReadSubAtomInt32(const char * AtomPath, UInt32 * Datum); + Bool16 ReadSubAtomInt64(const char * AtomPath, UInt64 * Datum); + + char* MemMap(UInt64 Offset, UInt32 Length); + Bool16 UnMap(char *memPtr, UInt32 Length); + // + // Debugging functions. + virtual void DumpAtom(void) {} + + +protected: + // + // Protected member variables. + Bool16 fDebug, fDeepDebug; + QTFile *fFile; + + QTFile::AtomTOCEntry fTOCEntry; +}; + +#endif // QTAtom_H diff --git a/QTFileLib/QTAtom_dref.cpp b/QTFileLib/QTAtom_dref.cpp new file mode 100644 index 0000000..566743f --- /dev/null +++ b/QTFileLib/QTAtom_dref.cpp @@ -0,0 +1,381 @@ +/* + * + * @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@ + * + */ + +// +// QTAtom_dref: +// The 'dref' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include + +#ifndef __Win32__ +#include +#include +#endif + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_dref.h" +#include "OSMemory.h" + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constants +// +const int drefPos_VersionFlags = 0; +const int drefPos_NumRefs = 4; +const int drefPos_RefTable = 8; + +const int drefRefPos_Size = 0; +const int drefRefPos_Type = 4; +const int drefRefPos_VersionFlags = 8; +const int drefRefPos_Data = 12; + + + +// ------------------------------------- +// Mac alias constants and typedefs +// + +enum { + kEndMark =-1, /* -1 end of variable info */ + kAbsPath = 2, /* 2 absolute path name */ + kMaxMark = 10 /* End Marker */ +}; + +typedef struct +{ + short what; /* what kind of information */ + short len; /* length of variable data */ + char data[1]; /* actual data */ +} varInfo; + +typedef struct +{ + char private1[130]; + short nlvlFrom; /* # of levels from fromFile/toFile until a common */ + short nlvlTo; /* ancestor directory is found */ + char private2[16]; + varInfo vdata[1]; /* variable length info */ +} AliasRecordPriv; + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_dref::QTAtom_dref(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumRefs(0), fRefs(NULL) +{ +} + +QTAtom_dref::~QTAtom_dref(void) +{ + //+ 6.16.1999 rt fix memory leak + for( UInt32 curRef = 0; curRef < fNumRefs; curRef++ ) + { + delete [] fRefs[curRef].Data; + delete fRefs[curRef].FCB; + } + //-end + + // + // Free our variables. + delete[] fRefs; +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_dref::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + // General vars + UInt64 refPos; + + + // + // Parse this atom's fields. + ReadInt32(drefPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + ReadInt32(drefPos_NumRefs, &fNumRefs); + + // + // Read in all of the refs. + if( fNumRefs > 0 ) { + // + // Allocate our ref table. + fRefs = NEW DataRefEntry[fNumRefs]; + if( fRefs == NULL ) + return false; + + // + // Read them all in.. + refPos = drefPos_RefTable; + for( UInt32 CurRef = 0; CurRef < fNumRefs; CurRef++ ) { + // + // Set up the entry. + fRefs[CurRef].Flags = 0x0; + fRefs[CurRef].ReferenceType = FOUR_CHARS_TO_INT('N', 'U', 'L', 'L'); // NULL + fRefs[CurRef].DataLength = 0; + fRefs[CurRef].Data = NULL; + + fRefs[CurRef].IsEntryInitialized = false; + fRefs[CurRef].IsFileOpen = false; + fRefs[CurRef].FCB = NULL; + + + // + // Get the flags and type. + ReadInt32(refPos + drefRefPos_VersionFlags, &tempInt32); + fRefs[CurRef].Flags = tempInt32 & 0x00ffffff; + + ReadInt32(refPos + drefRefPos_Type, &tempInt32); + fRefs[CurRef].ReferenceType = tempInt32; + + // + // We're done if this is a self-referencing atom. + if( fRefs[CurRef].Flags & flagSelfRef ) { + fRefs[CurRef].IsEntryInitialized = true; + continue; + } + + + // + // Get all of the data. + ReadInt32(refPos + drefRefPos_Size, &tempInt32); + fRefs[CurRef].DataLength = tempInt32 - 12 /* skip the header */; + + fRefs[CurRef].Data = NEW char[fRefs[CurRef].DataLength]; + if( fRefs[CurRef].Data == NULL ) { + // + // De-initialize this entry. + fRefs[CurRef].DataLength = 0; + fRefs[CurRef].IsEntryInitialized = false; + } else { + // + // Read the entry data. + ReadBytes(refPos + drefRefPos_Data, fRefs[CurRef].Data, fRefs[CurRef].DataLength); + + // + // Configure the rest of the entry. + fRefs[CurRef].FCB = NEW QTFile_FileControlBlock(); + if( fRefs[CurRef].FCB == NULL ) + fRefs[CurRef].IsEntryInitialized = false; + else + fRefs[CurRef].IsEntryInitialized = true; + fRefs[CurRef].IsFileOpen = false; + } + + // + // Skip over this mini-atom. + refPos += fRefs[CurRef].DataLength + 12 /* account for the header */; + } + } + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Read functions. +// +Bool16 QTAtom_dref::Read(UInt32 RefID, UInt64 Offset, char * const Buffer, UInt32 Length, QTFile_FileControlBlock * FCB) +{ + // General vars + DataRefEntry *Entry; + + + // + // Validate that this ref exists. + if( (RefID == 0) || (RefID > fNumRefs) ) + return false; + +// qtss_printf("QTAtom_dref::Read Offset = %qd, Length = %"_S32BITARG_" \n", Offset, Length); + // + // If this data reference is in the movie file itself, then we can forward + // the request directly to QTFile. + if( IsRefInThisFile(RefID) ) + return fFile->Read(Offset, Buffer, Length, FCB); + + + // + // The ref is not in this file; see if we have already opened an FCB for + // the file and use that, otherwise we need to process the ref. + Entry = &fRefs[RefID - 1]; + + // + // Abort if the entry was not initialized. + if( !Entry->IsEntryInitialized ) + return false; + + // + // Open the file (after parsing the data reference) if it is not open + // already. + if( !Entry->IsFileOpen ) { + // General vars + char *AliasPath; + + + // + // We only support parsing alias types. + if( Entry->ReferenceType != FOUR_CHARS_TO_INT('a', 'l', 'i', 's') ) //alis + return false; + + // + // Parse the alias. + if( (AliasPath = ResolveAlias(Entry->Data, Entry->DataLength)) == NULL ) + return false; + + // + // Create a path which does *not* contain the current movie's name. + char * p; + char * MoviePath = fFile->GetMoviePath(); + char * NewPath = NEW char[strlen(MoviePath) + strlen(AliasPath) + 1]; + //char * NewPath = (char *)calloc(1, strlen(MoviePath) + strlen(AliasPath)); + if( (p = strrchr(MoviePath, QT_PATH_SEPARATOR)) == NULL ) { + memcpy(NewPath, AliasPath, strlen(AliasPath) + 1); + } else { + int MoviePathClippedLength = (p - MoviePath) + 1; + memcpy(NewPath, MoviePath, MoviePathClippedLength); + memcpy(NewPath + MoviePathClippedLength, AliasPath, strlen(AliasPath) + 1); + } + + + // + // Do the open. + Entry->FCB->Set(NewPath); + if(!Entry->FCB->IsValid()) { + delete [] NewPath; + //free(NewPath); + return false; + } + + Entry->IsFileOpen = true; + delete [] NewPath; + //free(NewPath); + } + + // + // Do the read. + return fFile->Read(Offset, Buffer, Length, Entry->FCB); +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_dref::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_dref::DumpAtom - Dumping atom.\n")); + for( UInt32 CurRef = 1; CurRef <= fNumRefs; CurRef++ ) + DEBUG_PRINT(("QTAtom_dref::DumpAtom - ..Ref #%"_U32BITARG_" is in file: %s\n", CurRef, IsRefInThisFile(CurRef) ? "yes" : "no")); +} + + + +// ------------------------------------- +// Protected member functions. +// +char * QTAtom_dref::ResolveAlias(char * const AliasData, UInt32 /* AliasDataLength */) +{ + // General vars + AliasRecordPriv *Alias = (AliasRecordPriv *)AliasData; + varInfo *AliasVarInfo; + + char *path, *pathStart; + int pathLength; + + + // + // Verify that we have a relative path. + if( (Alias->nlvlTo == -1) || (Alias->nlvlFrom == -1) ) + return NULL; + + // + // Search for the absolute pathname in this alias. + AliasVarInfo = Alias->vdata; + for( int loopCount = kMaxMark - 1; loopCount >= 0; loopCount--) { + // + // Break out of the loop if this is a match/the end of the alias. + if( ((short)ntohs(AliasVarInfo->what) == kAbsPath) || ((short)ntohs(AliasVarInfo->what) == kEndMark) ) + break; + + // + // Otherwise we need to move to the next data unit. + AliasVarInfo = (varInfo *)((char *)AliasVarInfo + ((ntohs(AliasVarInfo->len) + 1) & ~1) + 4 /* header size */); + } + + + // + // Now that we have the path, we need to strip off the absolute portions + // of it so that we can get at it from our current (relative) root. + AliasVarInfo->data[ntohs(AliasVarInfo->len)] = '\0'; + + pathStart = path = AliasVarInfo->data; + path += ntohs(AliasVarInfo->len); + int i = ntohs(Alias->nlvlTo); + pathLength = -1; + while( i && (path > pathStart) ) { + if( *path-- == ':' ) + i--; + pathLength++; + } + + if( i == 1 ) + pathLength += 2; // fell out of loop; we're relative to root + else + path += 2; // points past separator character + + + // + // Return the alias. + return path; +} diff --git a/QTFileLib/QTAtom_dref.h b/QTFileLib/QTAtom_dref.h new file mode 100644 index 0000000..4f2b704 --- /dev/null +++ b/QTFileLib/QTAtom_dref.h @@ -0,0 +1,113 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_dref: +// The 'dref' QTAtom class. + +#ifndef QTAtom_dref_H +#define QTAtom_dref_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" + + +// +// External classes +class QTFile_FileControlBlock; + + +// +// QTAtom class +class QTAtom_dref : public QTAtom { + // + // Class constants + enum { + flagSelfRef = 0x00000001 + }; + + + // + // Class typedefs. + struct DataRefEntry { + // Data ref information + UInt32 Flags; + OSType ReferenceType; + UInt32 DataLength; + char *Data; + + // Tracking information + Bool16 IsEntryInitialized, IsFileOpen; + QTFile_FileControlBlock *FCB; + }; + + +public: + // + // Constructors and destructor. + QTAtom_dref(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_dref(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + Bool16 IsRefInThisFile(UInt32 RefID) { if(RefID && (RefID<=fNumRefs)) \ + return ((fRefs[RefID-1].Flags & flagSelfRef) != 0); \ + return false; } + + // + // Read functions. + Bool16 Read(UInt32 RefID, UInt64 Offset, char * const Buffer, UInt32 Length, + QTFile_FileControlBlock * FCB = NULL); + + + // + // Debugging functions. + virtual void DumpAtom(void); + + +protected: + // + // Protected member functions. + char * ResolveAlias(char * const AliasData, UInt32 AliasDataLength); + + + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + + UInt32 fNumRefs; + DataRefEntry *fRefs; +}; + +#endif // QTAtom_dref_H diff --git a/QTFileLib/QTAtom_elst.cpp b/QTFileLib/QTAtom_elst.cpp new file mode 100644 index 0000000..be4bc63 --- /dev/null +++ b/QTFileLib/QTAtom_elst.cpp @@ -0,0 +1,174 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_elst: +// The 'elst' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_elst.h" +#include "OSMemory.h" + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constants +// +const int elstPos_VersionFlags = 0; +const int elstPos_NumEdits = 4; +const int elstPos_EditList = 8; + +const int elstEntryPos_Duration = 0; +const int elstEntryPos_MediaTime = 4; +const int elstEntryPos_MediaRate = 8; + +const int elstEntryPosV1_Duration = 0; +const int elstEntryPosV1_MediaTime = 8; +const int elstEntryPosV1_MediaRate = 16; + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_elst::QTAtom_elst(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumEdits(0), fEdits(NULL), + fFirstEditMovieTime(0) +{ +} + +QTAtom_elst::~QTAtom_elst(void) +{ + // + // Free our variables. + if( fEdits != NULL ) + delete[] fEdits; +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_elst::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + // + // Parse this atom's fields. + ReadInt32(elstPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + if (fVersion > 1) + { + DEEP_DEBUG_PRINT(("QTAtom_elst::Initialize failed. Version unsupported: %d\n",fVersion)); + return false; + } + + ReadInt32(elstPos_NumEdits, &fNumEdits); + + // + // Read in all of the edits. + if( fNumEdits > 0 ) { + DEEP_DEBUG_PRINT(("QTAtom_elst::Initialize ..%"_U32BITARG_" edits found.\n", fNumEdits)); + + // + // Allocate our ref table. + fEdits = NEW EditListEntry[fNumEdits]; + if( fEdits == NULL ) + return false; + + // + // Read them all in.. + for( UInt32 CurEdit = 0; CurEdit < fNumEdits; CurEdit++ ) + { + + if (0 == fVersion) + { + // + // Get all of the data in this edit list entry. + ReadInt32To64(elstPos_EditList + (CurEdit * 12) + elstEntryPos_Duration, &fEdits[CurEdit].EditDuration); + ReadInt32To64Signed(elstPos_EditList + (CurEdit * 12) + elstEntryPos_MediaTime, &fEdits[CurEdit].StartingMediaTime); + + ReadInt32(elstPos_EditList + (CurEdit * 12) + elstEntryPos_MediaRate, &fEdits[CurEdit].EditMediaRate); + + } + else if (1 == fVersion) + { + + // Get all of the data in this edit list entry. + ReadInt64(elstPos_EditList + (CurEdit * 20) + elstEntryPosV1_Duration, &fEdits[CurEdit].EditDuration); + ReadInt64(elstPos_EditList + (CurEdit * 20) + elstEntryPosV1_MediaTime, (UInt64*) &fEdits[CurEdit].StartingMediaTime ); + + ReadInt32(elstPos_EditList + (CurEdit * 20) + elstEntryPosV1_MediaRate, &fEdits[CurEdit].EditMediaRate); + + } + + DEEP_DEBUG_PRINT(("QTAtom_elst::Initialize ..Edit #%"_U32BITARG_": Duration=%"_64BITARG_"u; MediaTime=%"_64BITARG_"d\n", CurEdit, fEdits[CurEdit].EditDuration, fEdits[CurEdit].StartingMediaTime)); + + // + // Adjust our starting media time. + if( fEdits[CurEdit].StartingMediaTime == -1 ) + fFirstEditMovieTime = fEdits[CurEdit].EditDuration; + } + } + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_elst::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_elst::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_elst::DumpAtom - ..Version: %d.\n", (int) fVersion)); + DEBUG_PRINT(("QTAtom_elst::DumpAtom - ..Number of edits: %"_S32BITARG_"\n", fNumEdits)); +} diff --git a/QTFileLib/QTAtom_elst.h b/QTFileLib/QTAtom_elst.h new file mode 100644 index 0000000..94908a7 --- /dev/null +++ b/QTFileLib/QTAtom_elst.h @@ -0,0 +1,91 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_elst: +// The 'elst' QTAtom class. + +#ifndef QTAtom_elst_H +#define QTAtom_elst_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" + + +// +// External classes +class QTFile_FileControlBlock; + + +// +// QTAtom class +class QTAtom_elst : public QTAtom { + // + // Class typedefs. + struct EditListEntry { + // Edit information + UInt64 EditDuration; + SInt64 StartingMediaTime; + UInt32 EditMediaRate; + }; + + +public: + // + // Constructors and destructor. + QTAtom_elst(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_elst(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + inline UInt64 FirstEditMovieTime(void) { return fFirstEditMovieTime; } + + + // + // Debugging functions. + virtual void DumpAtom(void); + + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + + UInt32 fNumEdits; + EditListEntry *fEdits; + + UInt64 fFirstEditMovieTime; +}; + +#endif // QTAtom_elst_H diff --git a/QTFileLib/QTAtom_hinf.cpp b/QTFileLib/QTAtom_hinf.cpp new file mode 100644 index 0000000..b36f5c5 --- /dev/null +++ b/QTFileLib/QTAtom_hinf.cpp @@ -0,0 +1,212 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_hinf: +// The 'hinf' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_hinf.h" + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constants +// +const char * hinfAtom_TotalRTPBytes64 = ":trpy"; +const char * hinfAtom_TotalRTPBytes32 = ":totl"; +const char * hinfAtom_TotalRTPPackets64 = ":nump"; +const char * hinfAtom_TotalRTPPackets32 = ":npck"; +const char * hinfAtom_TotalPayloadBytes64 = ":tpyl"; +const char * hinfAtom_TotalPayloadBytes32 = ":tpay"; +const char * hinfAtom_MaxDataRate64 = ":maxr"; +const char * hinfAtom_TotalMediaBytes64 = ":dmed"; +const char * hinfAtom_TotalImmedBytes64 = ":dimm"; +const char * hinfAtom_TotalRepeatBytes64 = ":drep"; +const char * hinfAtom_MinTransTime32 = ":tmin"; +const char * hinfAtom_MaxTransTime32 = ":tmax"; +const char * hinfAtom_MaxPacketSize32 = ":pmax"; +const char * hinfAtom_MaxPacketDuration32 = ":dmax"; +const char * hinfAtom_PayloadType = ":payt"; +/* +'trpy' 8 bytes The total number of bytes that will be sent, +including 12-byte RTP headers, but not including +any network headers. +'totl' 4 bytes 4-byte version of 'trpy' +'nump' 8 bytes The total number of network packets that will be +sent (if the application knows there is a 28-byte +network header, it can multiply 28 by this number +and add it to the ÔtrpyÕ value to get the true +number of bytes sent. +'npck' 4 bytes 4-byte version of 'nump' +'tpyl' 8 bytes The total number of bytes that will be sent, not +including 12-byte RTP headers. +'tpay' 4 bytes 4-byte version of 'tpyl' +'maxr' 8 bytes The maximum data rate. This atom contains two +numbers: g, followed by m (both 32-bit values). g +is the granularity, in milliseconds. m is the +maximum data rate given that granularity. For +example, if g is 1 second, then m is the maximum +data rate over any 1 second. There may be +multiple 'maxr' atoms, with different values for g. +The maximum data rate calculation does not +include any network headers (but does include +12-byte RTP headers). +'dmed' 8 bytes The number of bytes from the media track to be +sent. +'dimm' 8 bytes The number of bytes of immediate data to be sent. +'drep' 8 bytes The number of bytes of repeated data to be sent. +'tmin' 4 bytes The smallest relative transmission time, in +milliseconds. +'tmax' 4 bytes The largest relative transmission time, in +milliseconds. + +'pmax' 4 bytes The largest packet, in bytes; includes 12-byte RTP +header. +'dmax' 4 bytes The largest packet duration, in milliseconds. +'payt' variable The payload type, which includes payload +number (32-bits) followed by rtpmap payload +string (Pascal string). +*/ + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_hinf::QTAtom_hinf(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fTotalRTPBytes32(0), //totl + fTotalRTPBytes64(0), //trpy + fTotalRTPPackets32(0), //nump + fTotalRTPPackets64(0), //npck + fTotalPayLoadBytes32(0), //tpay + fTotalPayLoadBytes64(0), //tpyl + fMaxDataRate64(0), //maxr + fTotalMediaBytes64(0), //dmed + fTotalImmediateBytes64(0), //dimm + fTotalRepeatBytes64(0), //drep + fMinTransTime32(0), //tmin + fMaxTransTime32(0), //tmax + fMaxPacketSizeBytes32(0), //pmax + fMaxPacketDuration32(0), //dmax + fPayloadID(0)//payt +{ + fPayloadStr[0] = 0;//payt +} + +QTAtom_hinf::~QTAtom_hinf(void) +{ +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_hinf::Initialize(void) +{ + // + // Parse this atom's sub-atoms. + ReadSubAtomInt32(hinfAtom_TotalRTPBytes32, &fTotalRTPBytes32); + ReadSubAtomInt32(hinfAtom_TotalRTPPackets32, &fTotalRTPPackets32); + ReadSubAtomInt32(hinfAtom_TotalPayloadBytes32, &fTotalPayLoadBytes32); + + + ReadSubAtomInt64(hinfAtom_TotalRTPBytes64, &fTotalRTPBytes64); + ReadSubAtomInt64(hinfAtom_TotalRTPPackets64, &fTotalRTPPackets64); + ReadSubAtomInt64(hinfAtom_TotalPayloadBytes64, &fTotalPayLoadBytes64); + + ReadSubAtomInt64(hinfAtom_MaxDataRate64, &fMaxDataRate64); + ReadSubAtomInt64(hinfAtom_TotalMediaBytes64, &fTotalMediaBytes64); + ReadSubAtomInt64(hinfAtom_TotalImmedBytes64, &fTotalImmediateBytes64); + ReadSubAtomInt64(hinfAtom_TotalRepeatBytes64, &fTotalRepeatBytes64); + + + ReadSubAtomInt32(hinfAtom_MinTransTime32, &fMinTransTime32); + ReadSubAtomInt32(hinfAtom_MaxTransTime32, &fMaxTransTime32); + ReadSubAtomInt32(hinfAtom_MaxPacketSize32, &fMaxPacketSizeBytes32); + ReadSubAtomInt32(hinfAtom_MaxPacketDuration32, &fMaxPacketDuration32); + + ReadSubAtomInt32(hinfAtom_PayloadType, &fPayloadID); + if (fPayloadID != 0) + { SInt8 len = 0; + ReadSubAtomBytes(hinfAtom_PayloadType, (char*)fPayloadStr, 5); + len = fPayloadStr[4]; + if (len > 0) + { ReadSubAtomBytes(hinfAtom_PayloadType, (char*)fPayloadStr, len+5); + ::memmove(fPayloadStr,&fPayloadStr[5],len); + fPayloadStr[len] = 0; + } + } + // + // This atom has been successfully read in. + return true; +} + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_hinf::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Total RTP bytes: %"_64BITARG_"u\n", this->GetTotalRTPBytes())); +#ifndef __Win32__ + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ....Average bitrate: %.2f Kbps\n", ((this->GetTotalRTPBytes() << 3) / fFile->GetDurationInSeconds()) / 1024)); +#endif + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Total RTP packets: %"_64BITARG_"u\n", this->GetTotalRTPPackets())); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ....Average packet size: %"_64BITARG_"u\n", this->GetTotalRTPBytes() / this->GetTotalRTPPackets())); + + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Total Payload bytes: %"_64BITARG_"u\n", this->GetTotalPayLoadBytes())); + + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Maximum Data Rate: %"_64BITARG_"u\n", this->GetMaxDataRate())); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Total Media Track bytes: %"_64BITARG_"u\n", this->GetTotalMediaBytes())); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Total Repeat Packet bytes: %"_64BITARG_"u\n", this->GetRepeatBytes())); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Total Immediate Bytes: %"_64BITARG_"u\n", this->GetTotalImmediateBytes())); + + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Minimum Transmission Time: %"_U32BITARG_"\n", this->GetMinTransTime())); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Maximum Transmission Time: %"_U32BITARG_"\n", this->GetMaxTransTime())); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Maximum Packet Size bytes: %"_U32BITARG_"\n", this->GetMaxPacketSizeBytes())); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Maximum Maximum Packet Duration: %"_U32BITARG_"\n", this->GetMaxPacketDuration())); + + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Payload ID: %"_U32BITARG_"\n", this->GetPayLoadID())); + DEBUG_PRINT(("QTAtom_hinf::DumpAtom - ..Payload string: %s\n", this->GetPayLoadStr())); + +} diff --git a/QTFileLib/QTAtom_hinf.h b/QTFileLib/QTAtom_hinf.h new file mode 100644 index 0000000..3f21e55 --- /dev/null +++ b/QTFileLib/QTAtom_hinf.h @@ -0,0 +1,103 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_hinf: +// The 'hinf' QTAtom class. + +#ifndef QTAtom_hinf_H +#define QTAtom_hinf_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" + + +// +// QTAtom class +class QTAtom_hinf : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_hinf(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_hinf(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + inline UInt64 GetTotalRTPBytes(void) { return fTotalRTPBytes32 ? (UInt64) fTotalRTPBytes32 : fTotalRTPBytes64 ; } + inline UInt64 GetTotalRTPPackets(void) { return fTotalRTPPackets32 ? (UInt64) fTotalRTPPackets32 : fTotalRTPPackets64; } + + inline UInt64 GetTotalPayLoadBytes(void) { return fTotalPayLoadBytes32 ? (UInt64) fTotalPayLoadBytes32 : fTotalPayLoadBytes64; } + + inline UInt64 GetMaxDataRate(void) { return fMaxDataRate64; } + inline UInt64 GetTotalMediaBytes(void) { return fTotalMediaBytes64; } + inline UInt64 GetTotalImmediateBytes(void) { return fTotalImmediateBytes64; } + inline UInt64 GetRepeatBytes(void) { return fTotalRepeatBytes64; } + + inline UInt32 GetMinTransTime(void) { return fMinTransTime32; } + inline UInt32 GetMaxTransTime(void) { return fMaxTransTime32; } + inline UInt32 GetMaxPacketSizeBytes(void) { return fMaxPacketSizeBytes32; } + inline UInt32 GetMaxPacketDuration(void) { return fMaxPacketDuration32; } + inline UInt32 GetPayLoadID(void) { return fPayloadID;} + inline char* GetPayLoadStr(void) { return (char*) fPayloadStr;} + + // + // Debugging functions. + virtual void DumpAtom(void); + + +protected: + // + // Protected member variables. + UInt32 fTotalRTPBytes32; //totl + UInt64 fTotalRTPBytes64; //trpy + + UInt32 fTotalRTPPackets32; //nump + UInt64 fTotalRTPPackets64; //npck + + UInt32 fTotalPayLoadBytes32; //tpay + UInt64 fTotalPayLoadBytes64; //tpyl + UInt64 fMaxDataRate64; //maxr + UInt64 fTotalMediaBytes64; //dmed + UInt64 fTotalImmediateBytes64; //dimm + UInt64 fTotalRepeatBytes64; //drep + + UInt32 fMinTransTime32; //tmin + UInt32 fMaxTransTime32; //tmax + UInt32 fMaxPacketSizeBytes32; //pmax + UInt32 fMaxPacketDuration32; //dmax + UInt32 fPayloadID;//payt + char fPayloadStr[262];//payt +}; + +#endif // QTAtom_hinf_H diff --git a/QTFileLib/QTAtom_mdhd.cpp b/QTFileLib/QTAtom_mdhd.cpp new file mode 100644 index 0000000..cf192ef --- /dev/null +++ b/QTFileLib/QTAtom_mdhd.cpp @@ -0,0 +1,161 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_mdhd: +// The 'mdhd' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_mdhd.h" + + + +// ------------------------------------- +// Constants +// +const int mdhdPos_VersionFlags = 0; +const int mdhdPos_CreationTime = 4; +const int mdhdPos_ModificationTime = 8; +const int mdhdPos_TimeScale = 12; +const int mdhdPos_Duration = 16; +const int mdhdPos_Language = 20; +const int mdhdPos_Quality = 22; + +const int mdhdPosV1_CreationTime = 4; +const int mdhdPosV1_ModificationTime = 12; +const int mdhdPosV1_TimeScale = 20; +const int mdhdPosV1_Duration = 24; +const int mdhdPosV1_Language = 20 + 12; +const int mdhdPosV1_Quality = 22 + 12; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_mdhd::QTAtom_mdhd(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug) +{ +} + +QTAtom_mdhd::~QTAtom_mdhd(void) +{ +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_mdhd::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + // + // Parse this atom's fields. + ReadInt32(mdhdPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + if (0 == fVersion) + { + // Verify that this atom is the correct length. + if( fTOCEntry.AtomDataLength != 24 ) + { + DEEP_DEBUG_PRINT(("QTAtom_mdhd::Initialize failed. Expected AtomDataLength == 24 version: %d AtomDataLength: %"_64BITARG_"u\n",fVersion, fTOCEntry.AtomDataLength)); + return false; + } + + ReadInt32To64(mdhdPos_CreationTime, &fCreationTime); + ReadInt32To64(mdhdPos_ModificationTime, &fModificationTime); + ReadInt32(mdhdPos_TimeScale, &fTimeScale); + ReadInt32To64(mdhdPos_Duration, &fDuration); + ReadInt16(mdhdPos_Language, &fLanguage); + ReadInt16(mdhdPos_Quality, &fQuality); + } + else if (1 == fVersion) + { + // Verify that this atom is the correct length. + if( fTOCEntry.AtomDataLength != 36 ) + { + DEEP_DEBUG_PRINT(("QTAtom_mdhd::Initialize failed. Expected AtomDataLength == 36 version: %d AtomDataLength: %"_64BITARG_"u\n",fVersion, fTOCEntry.AtomDataLength)); + return false; + } + + ReadInt64(mdhdPosV1_CreationTime, &fCreationTime); + ReadInt64(mdhdPosV1_ModificationTime, &fModificationTime); + ReadInt32(mdhdPosV1_TimeScale, &fTimeScale); + ReadInt64(mdhdPosV1_Duration, &fDuration); + ReadInt16(mdhdPosV1_Language, &fLanguage); + ReadInt16(mdhdPosV1_Quality, &fQuality); + } + else + { + DEEP_DEBUG_PRINT(("QTAtom_mdhd::Initialize failed. Version unsupported: %d\n",fVersion)); + return false; + } + + // + // Compute the reciprocal of the timescale. + fTimeScaleRecip = 1 / (Float64)fTimeScale; + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_mdhd::DumpAtom(void) +{ + // Temporary vars + time_t unixCreationTime = (time_t)fCreationTime + (time_t)QT_TIME_TO_LOCAL_TIME; + + char buffer[kTimeStrSize]; + struct tm timeResult; + DEBUG_PRINT(("QTAtom_mdhd::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_mdhd::DumpAtom - ..Version: %d.\n", (int) fVersion)); + DEBUG_PRINT(("QTAtom_mdhd::DumpAtom - ..Creation date: %s", qtss_asctime(qtss_gmtime(&unixCreationTime, &timeResult),buffer,sizeof(buffer)))); +} diff --git a/QTFileLib/QTAtom_mdhd.h b/QTFileLib/QTAtom_mdhd.h new file mode 100644 index 0000000..92694a6 --- /dev/null +++ b/QTFileLib/QTAtom_mdhd.h @@ -0,0 +1,83 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_mdhd: +// The 'mdhd' QTAtom class. + +#ifndef QTAtom_mdhd_H +#define QTAtom_mdhd_H + + +// +// Includes +#include "OSHeaders.h" + +#include "QTFile.h" +#include "QTAtom.h" + + +// +// QTAtom class +class QTAtom_mdhd : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_mdhd(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_mdhd(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + inline Float64 GetTimeScale(void) { return (Float64)fTimeScale; } + inline Float64 GetTimeScaleRecip(void) { return fTimeScaleRecip; } + + inline UInt64 GetDuration(void) { return fDuration; } + + // + // Debugging functions. + virtual void DumpAtom(void); + + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + UInt64 fCreationTime, fModificationTime; + UInt32 fTimeScale; + UInt64 fDuration; + UInt16 fLanguage; + UInt16 fQuality; + + Float64 fTimeScaleRecip; +}; + +#endif // QTAtom_mdhd_H diff --git a/QTFileLib/QTAtom_mvhd.cpp b/QTFileLib/QTAtom_mvhd.cpp new file mode 100644 index 0000000..af8c88f --- /dev/null +++ b/QTFileLib/QTAtom_mvhd.cpp @@ -0,0 +1,229 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_mvhd: +// The 'mvhd' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_mvhd.h" + + + +// ------------------------------------- +// Constants +// +const int mvhdPos_VersionFlags = 0; +const int mvhdPos_CreationTime = 4; +const int mvhdPos_ModificationTime = 8; +const int mvhdPos_TimeScale = 12; +const int mvhdPos_Duration = 16; +const int mvhdPos_PreferredRate = 20; +const int mvhdPos_PreferredVolume = 24; +const int mvhdPos_a = 36; +const int mvhdPos_b = 40; +const int mvhdPos_u = 44; +const int mvhdPos_c = 48; +const int mvhdPos_d = 52; +const int mvhdPos_v = 56; +const int mvhdPos_x = 60; +const int mvhdPos_y = 64; +const int mvhdPos_w = 68; +const int mvhdPos_PreviewTime = 72; +const int mvhdPos_PreviewDuration = 76; +const int mvhdPos_PosterTime = 80; +const int mvhdPos_SelectionTime = 84; +const int mvhdPos_SelectionDuration = 88; +const int mvhdPos_CurrentTime = 92; +const int mvhdPos_NextTrackID = 96; + + + +const int mvhdPosV1_CreationTime = 4; //+4 bytes +const int mvhdPosV1_ModificationTime = 12; //+4 bytes +const int mvhdPosV1_TimeScale = 20; +const int mvhdPosV1_Duration = 24; //+4 bytes + +const int mvhdPosV1_PreferredRate = 20 + 12; +const int mvhdPosV1_PreferredVolume = 24 + 12; +const int mvhdPosV1_a = 36 + 12; +const int mvhdPosV1_b = 40 + 12; +const int mvhdPosV1_u = 44 + 12; +const int mvhdPosV1_c = 48 + 12; +const int mvhdPosV1_d = 52 + 12; +const int mvhdPosV1_v = 56 + 12; +const int mvhdPosV1_x = 60 + 12; +const int mvhdPosV1_y = 64 + 12; +const int mvhdPosV1_w = 68 + 12; +const int mvhdPosV1_PreviewTime = 72 + 12; +const int mvhdPosV1_PreviewDuration = 76 + 12; +const int mvhdPosV1_PosterTime = 80 + 12; +const int mvhdPosV1_SelectionTime = 84 + 12; +const int mvhdPosV1_SelectionDuration = 88 + 12; +const int mvhdPosV1_CurrentTime = 92 + 12; +const int mvhdPosV1_NextTrackID = 96 + 12; + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_mvhd::QTAtom_mvhd(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug) +{ +} + +QTAtom_mvhd::~QTAtom_mvhd(void) +{ +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_mvhd::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + // + // Parse this atom's fields. + ReadInt32(mvhdPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + if (0 == fVersion) + { + // Verify that this atom is the correct length. + if( fTOCEntry.AtomDataLength != 100 ) + { + DEEP_DEBUG_PRINT(("QTAtom_mvhd::Initialize failed. Expected AtomDataLength == 100 version: %d AtomDataLength: %"_64BITARG_"u\n",fVersion, fTOCEntry.AtomDataLength)); + return false; + } + + ReadInt32To64(mvhdPos_CreationTime, &fCreationTime); + ReadInt32To64(mvhdPos_ModificationTime, &fModificationTime); + ReadInt32(mvhdPos_TimeScale, &fTimeScale); + ReadInt32To64(mvhdPos_Duration, &fDuration); + + ReadInt32(mvhdPos_PreferredRate, &fPreferredRate); + ReadInt16(mvhdPos_PreferredVolume, &fPreferredVolume); + + ReadInt32(mvhdPos_a, &fa); + ReadInt32(mvhdPos_b, &fb); + ReadInt32(mvhdPos_u, &fu); + ReadInt32(mvhdPos_c, &fc); + ReadInt32(mvhdPos_d, &fd); + ReadInt32(mvhdPos_v, &fv); + ReadInt32(mvhdPos_x, &fx); + ReadInt32(mvhdPos_y, &fy); + ReadInt32(mvhdPos_w, &fw); + + ReadInt32(mvhdPos_PreviewTime, &fPreviewTime); + ReadInt32(mvhdPos_PreviewDuration, &fPreviewDuration); + ReadInt32(mvhdPos_PosterTime, &fPosterTime); + ReadInt32(mvhdPos_SelectionTime, &fSelectionTime); + ReadInt32(mvhdPos_SelectionDuration, &fSelectionDuration); + ReadInt32(mvhdPos_CurrentTime, &fCurrentTime); + ReadInt32(mvhdPos_NextTrackID, &fNextTrackID); + } + else if (1 == fVersion) + { + // Verify that this atom is the correct length. + if( fTOCEntry.AtomDataLength != 112 ) + { + DEEP_DEBUG_PRINT(("QTAtom_mvhd::Initialize failed. Expected AtomDataLength = 112 version: %d AtomDataLength: %"_64BITARG_"u\n",fVersion, fTOCEntry.AtomDataLength)); + return false; + } + + ReadInt64(mvhdPosV1_CreationTime, &fCreationTime); + ReadInt64(mvhdPosV1_ModificationTime, &fModificationTime); + ReadInt32(mvhdPosV1_TimeScale, &fTimeScale); + ReadInt64(mvhdPosV1_Duration, &fDuration); + + ReadInt32(mvhdPosV1_PreferredRate, &fPreferredRate); + ReadInt16(mvhdPosV1_PreferredVolume, &fPreferredVolume); + + ReadInt32(mvhdPosV1_a, &fa); + ReadInt32(mvhdPosV1_b, &fb); + ReadInt32(mvhdPosV1_u, &fu); + ReadInt32(mvhdPosV1_c, &fc); + ReadInt32(mvhdPosV1_d, &fd); + ReadInt32(mvhdPosV1_v, &fv); + ReadInt32(mvhdPosV1_x, &fx); + ReadInt32(mvhdPosV1_y, &fy); + ReadInt32(mvhdPosV1_w, &fw); + + ReadInt32(mvhdPosV1_PreviewTime, &fPreviewTime); + ReadInt32(mvhdPosV1_PreviewDuration, &fPreviewDuration); + ReadInt32(mvhdPosV1_PosterTime, &fPosterTime); + ReadInt32(mvhdPosV1_SelectionTime, &fSelectionTime); + ReadInt32(mvhdPosV1_SelectionDuration, &fSelectionDuration); + ReadInt32(mvhdPosV1_CurrentTime, &fCurrentTime); + ReadInt32(mvhdPosV1_NextTrackID, &fNextTrackID); + } + else + { + DEEP_DEBUG_PRINT(("QTAtom_mvhd::Initialize failed. Version unsupported: %d\n",fVersion)); + return false; + } + + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_mvhd::DumpAtom(void) +{ + // Temporary vars + time_t unixCreationTime = (time_t)fCreationTime + (time_t)QT_TIME_TO_LOCAL_TIME; + char buffer[kTimeStrSize]; + struct tm timeResult; + DEBUG_PRINT(("QTAtom_mvhd::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_mvhd::DumpAtom - ..Version: %d.\n", (int) fVersion)); + DEBUG_PRINT(("QTAtom_mvhd::DumpAtom - ..Creation date: %s", qtss_asctime(qtss_gmtime(&unixCreationTime, &timeResult),buffer,sizeof(buffer)))); + DEBUG_PRINT(("QTAtom_mvhd::DumpAtom - ..Movie duration: %.2f seconds\n", GetDurationInSeconds())); +} diff --git a/QTFileLib/QTAtom_mvhd.h b/QTFileLib/QTAtom_mvhd.h new file mode 100644 index 0000000..f74f17f --- /dev/null +++ b/QTFileLib/QTAtom_mvhd.h @@ -0,0 +1,91 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_mvhd: +// The 'mvhd' QTAtom class. + +#ifndef QTAtom_mvhd_H +#define QTAtom_mvhd_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" + + +// +// QTAtom class +class QTAtom_mvhd : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_mvhd(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_mvhd(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + inline Float64 GetTimeScale(void) { return (Float64)fTimeScale; } +#if __Win32__ + + // Win compiler can't convert UInt64 to Float64. It does support SInt64 to Float64 though. + + inline Float64 GetDurationInSeconds(void) { if (fTimeScale != 0){return (Float64)((SInt64)fDuration) / (Float64) ((SInt64)fTimeScale); } else {return (Float64) 0.0;} } + +#else + + inline Float64 GetDurationInSeconds(void) { if (fTimeScale != 0){ return fDuration / (Float64)fTimeScale; } else {return (Float64) 0.0;} } +#endif + + // + // Debugging functions. + virtual void DumpAtom(void); + + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + UInt64 fCreationTime, fModificationTime; + UInt32 fTimeScale; + UInt64 fDuration; + UInt32 fPreferredRate; + UInt16 fPreferredVolume; + UInt32 fa, fb, fu, fc, fd, fv, fx, fy, fw; + UInt32 fPreviewTime, fPreviewDuration, fPosterTime; + UInt32 fSelectionTime, fSelectionDuration; + UInt32 fCurrentTime; + UInt32 fNextTrackID; +}; + +#endif // QTAtom_mvhd_H diff --git a/QTFileLib/QTAtom_stco.cpp b/QTFileLib/QTAtom_stco.cpp new file mode 100644 index 0000000..ee71e35 --- /dev/null +++ b/QTFileLib/QTAtom_stco.cpp @@ -0,0 +1,149 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_stco: +// The 'stco' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_stco.h" +#include "OSMemory.h" + + +// ------------------------------------- +// Constants +// +const int stcoPos_VersionFlags = 0; +const int stcoPos_NumEntries = 4; +const int stcoPos_SampleTable = 8; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_stco::QTAtom_stco(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, UInt16 offSetSize, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumEntries(0),fOffSetSize(offSetSize), fChunkOffsetTable(NULL), fTable(NULL) +{ +} + +QTAtom_stco::~QTAtom_stco(void) +{ + // + // Free our variables. + if( fChunkOffsetTable != NULL ) + delete[] fChunkOffsetTable; +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_stco::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + + // + // Parse this atom's fields. + ReadInt32(stcoPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + ReadInt32(stcoPos_NumEntries, &fNumEntries); + + // + // Validate the size of the sample table. + if( (UInt32)(fNumEntries * fOffSetSize) != (fTOCEntry.AtomDataLength - 8) ) + return false; + + // + // Read in the chunk offset table. + fChunkOffsetTable = NEW char[(fNumEntries * fOffSetSize) + 1]; + if( fChunkOffsetTable == NULL ) + return false; + + if( ((PointerSizedInt)fChunkOffsetTable & (PointerSizedInt)0x3) == 0) + fTable = (void *)fChunkOffsetTable; + else + fTable = (void *)(((PointerSizedInt)fChunkOffsetTable + 4) & ~((PointerSizedInt)0x3)); + + ReadBytes(stcoPos_SampleTable, (char *)fTable, fNumEntries * fOffSetSize); + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_stco::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_stco::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_stco::DumpAtom - ..Number of chunk offset entries: %"_S32BITARG_"\n", fNumEntries)); +} + +void QTAtom_stco::DumpTable(void) +{ + // + // Print out a header. + qtss_printf("-- Chunk Offset table ----------------------------------------------------------\n"); + qtss_printf("\n"); + qtss_printf(" Chunk Num. Offset\n"); + qtss_printf(" ---------- ----------\n"); + + // + // Print the table. + UInt64 offset = 0; + for( UInt32 CurEntry = 1; CurEntry <= fNumEntries; CurEntry++ ) + { + if (ChunkOffset(CurEntry, &offset)) + qtss_printf(" %10"_U32BITARG_": %"_64BITARG_"u\n", CurEntry, offset); + else + qtss_printf(" %10"_U32BITARG_": QTAtom_stco::DumpTable ChunkOffset error\n", CurEntry); + } +} diff --git a/QTFileLib/QTAtom_stco.h b/QTFileLib/QTAtom_stco.h new file mode 100644 index 0000000..4c785f0 --- /dev/null +++ b/QTFileLib/QTAtom_stco.h @@ -0,0 +1,95 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_stco: +// The 'stco' QTAtom class. + +#ifndef QTAtom_stco_H +#define QTAtom_stco_H + + +// +// Includes +#ifndef __Win32__ +#include +#endif +#include "QTFile.h" +#include "QTAtom.h" + + +// +// QTAtom class +class QTAtom_stco : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_stco(QTFile * File, QTFile::AtomTOCEntry * Atom, + UInt16 offSetSize = 4, Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_stco(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + + inline Bool16 ChunkOffset(UInt32 ChunkNumber, UInt64 *Offset = NULL) + { + if (Offset && ChunkNumber && (ChunkNumber<=fNumEntries)) + { + if (4 == fOffSetSize) + *Offset = (UInt64) ntohl( ( (UInt32 *) fTable)[ChunkNumber-1]); + else + *Offset = (UInt64) QTAtom::NTOH64( ( (UInt64 *) fTable)[ChunkNumber-1]); + + return true; + } + + return false; + } + + + // + // Debugging functions. + virtual void DumpAtom(void); + virtual void DumpTable(void); + + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + + UInt32 fNumEntries; + UInt16 fOffSetSize; + char *fChunkOffsetTable; + void *fTable; // longword-aligned version of the above +}; + +#endif // QTAtom_stco_H diff --git a/QTFileLib/QTAtom_stsc.cpp b/QTFileLib/QTAtom_stsc.cpp new file mode 100644 index 0000000..8081bc0 --- /dev/null +++ b/QTFileLib/QTAtom_stsc.cpp @@ -0,0 +1,619 @@ +/* + * + * @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@ + * + */ +// +// QTAtom_stsc: +// The 'stsc' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include + +#ifndef __Win32__ +#include +#include +#endif + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_stsc.h" +#include "OSMemory.h" + + +// ------------------------------------- +// Constants +// +const int stscPos_VersionFlags = 0; +const int stscPos_NumEntries = 4; +const int stscPos_SampleTable = 8; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Class state cookie +// +QTAtom_stsc_SampleTableControlBlock::QTAtom_stsc_SampleTableControlBlock(void) +{ + Reset(); +} + +QTAtom_stsc_SampleTableControlBlock::~QTAtom_stsc_SampleTableControlBlock(void) +{ +} + +void QTAtom_stsc_SampleTableControlBlock::Reset(void) +{ + fCurEntry = 0; + fCurSample = 1; + fLastFirstChunk = 1; + fLastSamplesPerChunk = 1; + fLastSampleDescription = 0; + + fLastFirstChunk_GetChunkFirstLastSample = 1; + fLastSamplesPerChunk_GetChunkFirstLastSample = 1; + fLastTotalSamples_GetChunkFirstLastSample = 0; + fCurEntry_GetChunkFirstLastSample = 0; + chunkNumber_GetChunkFirstLastSample = 0; + firstSample_GetChunkFirstLastSample = 0; + lastSample_GetChunkFirstLastSample = 0; + + + fCurEntry_SampleToChunkInfo = 0; + fCurSample_SampleToChunkInfo = 1; + fLastFirstChunk_SampleToChunkInfo = 1; + fLastSamplesPerChunk_SampleToChunkInfo = 1; + fLastSampleDescription_SampleToChunkInfo = 0; + + fFirstSampleNumber_SampleToChunkInfo = 0; + fFirstSamplesPerChunk_SampleToChunkInfo = 0; + fFirstChunkNumber_SampleToChunkInfo = 0; + fFirstSampleDescriptionIndex_SampleToChunkInfo = 0; + fFirstSampleOffsetInChunk_SampleToChunkInfo = 0; + + fGetSampleInfo_SampleNumber = 0; + fGetSampleInfo_Length = 0; + fGetSampleInfo_SampleDescriptionIndex = 0; + fGetSampleInfo_Offset = 0; + fGetSampleInfo_LastChunk = 0; + fGetSampleInfo_LastChunkOffset = 0; + + fGetSizeOfSamplesInChunk_chunkNumber = 0; + fGetSizeOfSamplesInChunk_firstSample = 0; + fGetSizeOfSamplesInChunk_lastSample = 0; + fGetSizeOfSamplesInChunk_size = 0; + + +} + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_stsc::QTAtom_stsc(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumEntries(0), fSampleToChunkTable(NULL), fTableSize(0) +{ +} + +QTAtom_stsc::~QTAtom_stsc(void) +{ + // + // Free our variables. +#if MMAP_TABLES + if( fSampleToChunkTable != NULL ) + this->UnMap(fSampleToChunkTable, fTableSize); +#else + if( fSampleToChunkTable != NULL ) + delete[] fSampleToChunkTable; +#endif + +} + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_stsc::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + + // + // Parse this atom's fields. + ReadInt32(stscPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + ReadInt32(stscPos_NumEntries, &fNumEntries); + + // + // Validate the size of the sample table. + if( (UInt32)(fNumEntries * 12) != (fTOCEntry.AtomDataLength - 8) ) + return false; + + // + // Read in the sample-to-chunk table. +#if MMAP_TABLES + fTableSize = fNumEntries * 12; + fSampleToChunkTable = this->MemMap(stscPos_SampleTable, fTableSize); + if( fSampleToChunkTable == NULL ) + return false; +#else + fSampleToChunkTable = NEW char[fNumEntries * 12]; + if( fSampleToChunkTable == NULL ) + return false; + ReadBytes(stscPos_SampleTable, fSampleToChunkTable, fNumEntries * 12); +#endif + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Accessors +// + +Bool16 QTAtom_stsc::GetChunkFirstLastSample(UInt32 chunkNumber, UInt32 *firstSample, UInt32 *lastSample, QTAtom_stsc_SampleTableControlBlock *STCB) +{ + // Temporary state var + QTAtom_stsc_SampleTableControlBlock *tempSTCB = NULL; + + + // General vars + UInt32 prevFirstChunk = 0, thisFirstChunk = 0; + UInt32 totalSamples = 0; + UInt32 numChunks = 0; + UInt32 numSamplesInChunks = 0; + UInt32 prevSamplesPerChunk = 0; + UInt32 samplesPerChunk = 0; + +// qtss_printf("GetChunkFirstLastSample chunk = %d STCB->chunkNumber_GetChunkFirstLastSample= %d \n",chunkNumber,STCB->chunkNumber_GetChunkFirstLastSample); + + + if( STCB == NULL ) + { +// qtss_printf(" QTAtom_stsc::GetChunkFirstLastSample (NULL == STCB) \n"); + tempSTCB = NEW QTAtom_stsc_SampleTableControlBlock; + STCB = tempSTCB; + + } + + if ( (STCB->chunkNumber_GetChunkFirstLastSample == chunkNumber) && (STCB->lastSample_GetChunkFirstLastSample > 0) ) + { +// qtss_printf("GetChunkFirstLastSample cache hit chunk = %d\n",chunkNumber); + if (firstSample) *firstSample = STCB->firstSample_GetChunkFirstLastSample; + if (lastSample) *lastSample = STCB->lastSample_GetChunkFirstLastSample; + goto GetChunkFirstLastSample_Done; + + } + + if (STCB->fCurEntry_GetChunkFirstLastSample > chunkNumber) + { +// qtss_printf(" QTAtom_stsc::GetChunkFirstLastSample missed Cache Loop \n"); + STCB->fLastFirstChunk_GetChunkFirstLastSample = 1; + STCB->fLastSamplesPerChunk_GetChunkFirstLastSample = 1; + STCB->fLastTotalSamples_GetChunkFirstLastSample = 0; + STCB->fCurEntry_GetChunkFirstLastSample = 0; + STCB->chunkNumber_GetChunkFirstLastSample = 0; + STCB->firstSample_GetChunkFirstLastSample = 0; + STCB->lastSample_GetChunkFirstLastSample = 0; + } + + if (STCB->fCurEntry_GetChunkFirstLastSample > 0) + { +// qtss_printf("GetChunkFirstLastSample cached loop chunk start = %d look for chunk = %"_S32BITARG_"\n",STCB->fLastFirstChunk_GetChunkFirstLastSample,chunkNumber); + samplesPerChunk = STCB->fLastSamplesPerChunk_GetChunkFirstLastSample; + totalSamples = STCB->fLastTotalSamples_GetChunkFirstLastSample; + thisFirstChunk = STCB->fLastFirstChunk_GetChunkFirstLastSample; + } + + + // + // Linearly search through the sample table until we find the chunk + // which contains the given sample. + + if (fNumEntries == 1) + { + memcpy(&samplesPerChunk, fSampleToChunkTable + (STCB->fCurEntry_GetChunkFirstLastSample * 12) + 4, 4); + samplesPerChunk = ntohl(samplesPerChunk); + + prevSamplesPerChunk = ((chunkNumber -1 ) * samplesPerChunk); + totalSamples = chunkNumber * samplesPerChunk; + } + else + for( ; STCB->fCurEntry_GetChunkFirstLastSample < fNumEntries; STCB->fCurEntry_GetChunkFirstLastSample++ ) + { + // Copy this entry's fields. + prevSamplesPerChunk = samplesPerChunk; + prevFirstChunk = thisFirstChunk; + + memcpy(&thisFirstChunk, fSampleToChunkTable + (STCB->fCurEntry_GetChunkFirstLastSample * 12) + 0, 4); + thisFirstChunk = ntohl(thisFirstChunk); + memcpy(&samplesPerChunk, fSampleToChunkTable + (STCB->fCurEntry_GetChunkFirstLastSample * 12) + 4, 4); + samplesPerChunk = ntohl(samplesPerChunk); + + if (prevSamplesPerChunk == 0) + prevSamplesPerChunk = samplesPerChunk; + + if ( chunkNumber < thisFirstChunk ) // found chunk in group + { +// qtss_printf("found chunk in group numEntries = %"_S32BITARG_" this chunk = %"_S32BITARG_" \n",fNumEntries, chunkNumber); + numSamplesInChunks = (chunkNumber - prevFirstChunk) * prevSamplesPerChunk; + totalSamples += numSamplesInChunks; + prevSamplesPerChunk = totalSamples - prevSamplesPerChunk; + break; + } + + if ( chunkNumber == thisFirstChunk ) // found chunk + { + numSamplesInChunks = samplesPerChunk; + totalSamples += numSamplesInChunks; + prevSamplesPerChunk = totalSamples - samplesPerChunk; + break; + } + + + numChunks = chunkNumber - prevFirstChunk; + numSamplesInChunks = numChunks * samplesPerChunk; + totalSamples += numSamplesInChunks; + + // + // We have yet to find the sample; update our CurSample counter + // and move on. + STCB->fLastFirstChunk_GetChunkFirstLastSample = thisFirstChunk; + STCB->fLastSamplesPerChunk_GetChunkFirstLastSample = samplesPerChunk; + STCB->fLastTotalSamples_GetChunkFirstLastSample = totalSamples; + + } + + if (firstSample) *firstSample = prevSamplesPerChunk + 1; + if (lastSample) *lastSample = totalSamples; + +// qtss_printf("Get Chunk %"_S32BITARG_" First %"_S32BITARG_" Last %"_S32BITARG_" prevSamplesPerChunk %"_S32BITARG_" samplesPerChunk %"_S32BITARG_"\n",chunkNumber,*firstSample,*lastSample,prevSamplesPerChunk,samplesPerChunk); + + STCB->chunkNumber_GetChunkFirstLastSample = chunkNumber; + STCB->firstSample_GetChunkFirstLastSample = prevSamplesPerChunk + 1; + STCB->lastSample_GetChunkFirstLastSample = totalSamples; + +GetChunkFirstLastSample_Done: + delete tempSTCB; + return true; +} + + +UInt32 QTAtom_stsc::GetChunkFirstSample(UInt32 chunkNumber) +{ + // Temporary state var + QTAtom_stsc_SampleTableControlBlock localSTCB; + QTAtom_stsc_SampleTableControlBlock *STCB = &localSTCB; + + // General vars + UInt32 prevFirstChunk = 0, thisFirstChunk = 0, sampleDescription = 0; + UInt32 totalSamples = 1; + UInt32 numChunks = 0; + UInt32 numSamplesInChunks = 0; + UInt32 thisChunk = 0; + UInt32 prevSamplesPerChunk = 0; + UInt32 samplesPerChunk = 0; + + + // + // Linearly search through the sample table until we find the chunk + // which contains the given sample. + for( ; STCB->fCurEntry < fNumEntries; STCB->fCurEntry++ ) + { + // Copy this entry's fields. + prevSamplesPerChunk = samplesPerChunk; + prevFirstChunk = thisFirstChunk; + + memcpy(&thisFirstChunk, fSampleToChunkTable + (STCB->fCurEntry * 12) + 0, 4); + thisFirstChunk = ntohl(thisFirstChunk); + memcpy(&samplesPerChunk, fSampleToChunkTable + (STCB->fCurEntry * 12) + 4, 4); + samplesPerChunk = ntohl(samplesPerChunk); + memcpy(&sampleDescription, fSampleToChunkTable + (STCB->fCurEntry * 12) + 8, 4); + sampleDescription = ntohl(sampleDescription); + + thisChunk = thisFirstChunk; + numChunks = thisFirstChunk - prevFirstChunk; + + if ( chunkNumber <= thisFirstChunk ) // found chunk in group + { + numChunks = chunkNumber - prevFirstChunk; + numSamplesInChunks = numChunks * prevSamplesPerChunk; + totalSamples += numSamplesInChunks; + break; + } + + + numChunks = thisFirstChunk - prevFirstChunk; + numSamplesInChunks = numChunks * prevSamplesPerChunk; + totalSamples += numSamplesInChunks; + + // + // We have yet to find the sample; update our CurSample counter + // and move on. + STCB->fLastFirstChunk = thisFirstChunk; + STCB->fLastSampleDescription = sampleDescription; + STCB->fLastSamplesPerChunk = samplesPerChunk; + } + + + return totalSamples; +} + + +//UInt32 gstscCacheCount = 0; +//UInt32 gstscCallCount = 0; + + +Bool16 QTAtom_stsc::SampleToChunkInfo(UInt32 SampleNumber, UInt32 *samplesPerChunk, UInt32 *ChunkNumber, UInt32 *SampleDescriptionIndex, UInt32 *SampleOffsetInChunk, QTAtom_stsc_SampleTableControlBlock * STCB) +{ + // Temporary state var + QTAtom_stsc_SampleTableControlBlock *tempSTCB = NULL; + + // General vars + UInt32 NewCurSample; + UInt32 FirstChunk = 0, SamplesPerChunk = 0, SampleDescription = 0; + + Bool16 missedCache = false; + + if (STCB == NULL ) + { +// qtss_printf(" QTAtom_stsc::SampleToChunkInfo (NULL == STCB) \n"); + tempSTCB = NEW QTAtom_stsc_SampleTableControlBlock; + STCB = tempSTCB; + } + + + UInt32 aChunkNumber = 0; + UInt32 aSampleDescriptionIndex = 0; + UInt32 aSampleOffsetInChunk = 0; + UInt32 aSamplesPerChunk = 0; + +/* + gstscCallCount ++; + if (gstscCallCount == 1000) + { + qtss_printf("QTAtom_stsc::SampleToChunkInfo #calls = %"_S32BITARG_", cache hits = %"_S32BITARG_" \n",gstscCallCount, gstscCacheCount); + gstscCacheCount = 0; + gstscCallCount = 0; + + } +*/ + + // + // Use a temporary STCB if we weren't given one. We cannot use a default + // STCB as we would have no way to know when we are seeking around in the + // movie. + +// qtss_printf("------ QTAtom_stsc::SampleToChunkInfo SampleNumber = %"_S32BITARG_"\n",SampleNumber); + + if (STCB->fFirstSampleNumber_SampleToChunkInfo == SampleNumber) + { +// qtss_printf("------ QTAtom_stsc::SampleToChunkInfo cache hit SampleNumber = %"_S32BITARG_"\n",SampleNumber); + +// gstscCacheCount ++; + aChunkNumber = STCB->fFirstChunkNumber_SampleToChunkInfo; + aSampleDescriptionIndex = STCB->fFirstSampleDescriptionIndex_SampleToChunkInfo; + aSamplesPerChunk = STCB->fFirstSamplesPerChunk_SampleToChunkInfo; + aSampleOffsetInChunk = STCB->fFirstSampleOffsetInChunk_SampleToChunkInfo; + + if( ChunkNumber != NULL ) + *ChunkNumber = aChunkNumber; + if( SampleDescriptionIndex != NULL ) + *SampleDescriptionIndex = aSampleDescriptionIndex; + if( SampleOffsetInChunk != NULL ) + *SampleOffsetInChunk = aSampleOffsetInChunk; + if (NULL != samplesPerChunk) + *samplesPerChunk = aSamplesPerChunk; + + goto done; + } +// qtss_printf("QTAtom_stsc::SampleToChunkInfo missed cache SampleNumber = %"_S32BITARG_"\n",SampleNumber); + + // + // Assume that this sample came out of the last chunk. + aChunkNumber = STCB->fLastFirstChunk_SampleToChunkInfo + ((SampleNumber - STCB->fCurSample_SampleToChunkInfo) / STCB->fLastSamplesPerChunk_SampleToChunkInfo) ; + aSampleDescriptionIndex = STCB->fLastSampleDescription_SampleToChunkInfo; + aSampleOffsetInChunk = SampleNumber - (STCB->fCurSample_SampleToChunkInfo + ((aChunkNumber - STCB->fLastFirstChunk_SampleToChunkInfo) * STCB->fLastSamplesPerChunk_SampleToChunkInfo)); + aSamplesPerChunk = STCB->fLastSamplesPerChunk_SampleToChunkInfo; + + if( ChunkNumber != NULL ) + *ChunkNumber = aChunkNumber; + if( SampleDescriptionIndex != NULL ) + *SampleDescriptionIndex = aSampleDescriptionIndex; + if( SampleOffsetInChunk != NULL ) + *SampleOffsetInChunk = aSampleOffsetInChunk; + if (NULL != samplesPerChunk) + *samplesPerChunk = aSamplesPerChunk; + + // + // Linear search through the sample table until we find the chunk + // which contains the given sample. + + if (STCB->fCurSample_SampleToChunkInfo > SampleNumber) // we missed the cache start over + { + missedCache = true; +// qtss_printf("missed loop Cache!! STCB = %"_S32BITARG_" STCB->fCurSample_SampleToChunkInfo = %"_S32BITARG_" > SampleNumber = %"_S32BITARG_" \n",STCB, STCB->fCurSample_SampleToChunkInfo, SampleNumber); + STCB->fCurEntry_SampleToChunkInfo = 0; + STCB->fCurSample_SampleToChunkInfo = 1; + STCB->fLastFirstChunk_SampleToChunkInfo = 1; + STCB->fLastSamplesPerChunk_SampleToChunkInfo = 1; + STCB->fLastSampleDescription_SampleToChunkInfo = 0; + } + + + + for(; STCB->fCurEntry_SampleToChunkInfo < fNumEntries; STCB->fCurEntry_SampleToChunkInfo++ ) { + // + // Copy this entry's fields. + memcpy(&FirstChunk, fSampleToChunkTable + (STCB->fCurEntry_SampleToChunkInfo * 12) + 0, 4); + FirstChunk = ntohl(FirstChunk); + memcpy(&SamplesPerChunk, fSampleToChunkTable + (STCB->fCurEntry_SampleToChunkInfo * 12) + 4, 4); + SamplesPerChunk = ntohl(SamplesPerChunk); + memcpy(&SampleDescription, fSampleToChunkTable + (STCB->fCurEntry_SampleToChunkInfo * 12) + 8, 4); + SampleDescription = ntohl(SampleDescription); + + // + // Check to see if the sample was actually in the last chunk and + // return if it was. + NewCurSample = STCB->fCurSample_SampleToChunkInfo + (FirstChunk - STCB->fLastFirstChunk_SampleToChunkInfo) * STCB->fLastSamplesPerChunk_SampleToChunkInfo ; + if( SampleNumber < NewCurSample ) + { + + if( ChunkNumber != NULL ) + *ChunkNumber = aChunkNumber; + if( SampleDescriptionIndex != NULL ) + *SampleDescriptionIndex = aSampleDescriptionIndex; + if( SampleOffsetInChunk != NULL ) + *SampleOffsetInChunk = aSampleOffsetInChunk; + if (NULL != samplesPerChunk) + *samplesPerChunk = aSamplesPerChunk; + + if (SampleNumber == 1 || missedCache) + { +// qtss_printf("QTAtom_stsc::SampleToChunkInfo (SampleNumber == %"_S32BITARG_") \n",SampleNumber); + STCB->fFirstChunkNumber_SampleToChunkInfo = aChunkNumber; + STCB->fFirstSampleDescriptionIndex_SampleToChunkInfo = aSampleDescriptionIndex; + STCB->fFirstSamplesPerChunk_SampleToChunkInfo = aSamplesPerChunk; + STCB->fFirstSampleOffsetInChunk_SampleToChunkInfo = aSampleOffsetInChunk; + STCB->fFirstSampleNumber_SampleToChunkInfo = SampleNumber; + } +// qtss_printf("GetSample Info in for loop returning offset %"_S32BITARG_" \n",aSampleOffsetInChunk); + goto done; + } + + STCB->fCurSample_SampleToChunkInfo = NewCurSample; + + // + // Assume that the sample will be in this chunk. + + + aChunkNumber = FirstChunk + ((SampleNumber - STCB->fCurSample_SampleToChunkInfo) / SamplesPerChunk) ; + aSampleDescriptionIndex = SampleDescription; + aSampleOffsetInChunk = SampleNumber - (STCB->fCurSample_SampleToChunkInfo + ((aChunkNumber - FirstChunk) * SamplesPerChunk)); + aSamplesPerChunk = SamplesPerChunk; + + // + // We have yet to find the sample; update our CurSample counter + // and move on. + + + STCB->fLastFirstChunk_SampleToChunkInfo = FirstChunk; + STCB->fLastSampleDescription_SampleToChunkInfo = SampleDescription; + STCB->fLastSamplesPerChunk_SampleToChunkInfo = SamplesPerChunk; + + + } + +// qtss_printf("GetSample Info fall out of loop returning offset%"_S32BITARG_" \n",aSampleOffsetInChunk); + // + // Falling out of the loop means that the sample is in the last chunk of + // the table. + + if( ChunkNumber != NULL ) + *ChunkNumber = aChunkNumber; + if( SampleDescriptionIndex != NULL ) + *SampleDescriptionIndex = aSampleDescriptionIndex; + if( SampleOffsetInChunk != NULL ) + *SampleOffsetInChunk = aSampleOffsetInChunk; + if (NULL != samplesPerChunk) + *samplesPerChunk = aSamplesPerChunk; + + if (SampleNumber == 1 || missedCache) + { + STCB->fFirstChunkNumber_SampleToChunkInfo = aChunkNumber; + STCB->fFirstSampleDescriptionIndex_SampleToChunkInfo = aSampleDescriptionIndex; + STCB->fFirstSamplesPerChunk_SampleToChunkInfo = aSamplesPerChunk; + STCB->fFirstSampleOffsetInChunk_SampleToChunkInfo = aSampleOffsetInChunk; + STCB->fFirstSampleNumber_SampleToChunkInfo = SampleNumber; + } + + +done: + delete tempSTCB; + return true; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_stsc::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_stsc::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_stsc::DumpAtom - ..Number of STC entries: %"_S32BITARG_"\n", fNumEntries)); +} + +void QTAtom_stsc::DumpTable(void) +{ + // General vars + UInt32 FirstChunk = 0, SamplesPerChunk = 0, SampleDescription = 0; + + + // + // Print out a header. + qtss_printf("-- Sample-to-Chunk table -------------------------------------------------------\n"); + qtss_printf("\n"); + qtss_printf(" Sample Num FirstChunk Samples/Chunk Sample Description\n"); + qtss_printf(" ---------- ---------- ------------- ------------------\n"); + + // + // Print the table. + for( UInt32 CurEntry = 0; CurEntry < fNumEntries; CurEntry++ ) { + // + // Copy this entry's fields. + memcpy(&FirstChunk, fSampleToChunkTable + (CurEntry * 12) + 0, 4); + FirstChunk = ntohl(FirstChunk); + memcpy(&SamplesPerChunk, fSampleToChunkTable + (CurEntry * 12) + 4, 4); + SamplesPerChunk = ntohl(SamplesPerChunk); + memcpy(&SampleDescription, fSampleToChunkTable + (CurEntry * 12) + 8, 4); + SampleDescription = ntohl(SampleDescription); + + // + // Print out a listing. + qtss_printf(" %10"_U32BITARG_" : %10"_U32BITARG_" %10"_U32BITARG_" %10"_U32BITARG_"\n", + CurEntry+1, FirstChunk, SamplesPerChunk, SampleDescription); + } +} diff --git a/QTFileLib/QTAtom_stsc.h b/QTFileLib/QTAtom_stsc.h new file mode 100644 index 0000000..58d269a --- /dev/null +++ b/QTFileLib/QTAtom_stsc.h @@ -0,0 +1,150 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stsc.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_stsc: +// The 'stsc' QTAtom class. + +#ifndef QTAtom_stsc_H +#define QTAtom_stsc_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" + + +// +// Class state cookie +class QTAtom_stsc_SampleTableControlBlock { + +public: + // + // Constructor and destructor. + QTAtom_stsc_SampleTableControlBlock(void); + virtual ~QTAtom_stsc_SampleTableControlBlock(void); + + // + // Reset function + void Reset(void); + + // + // Sample table cache + UInt32 fCurEntry; + UInt32 fCurSample; + UInt32 fLastFirstChunk, fLastSamplesPerChunk, fLastSampleDescription; + + + UInt32 fLastFirstChunk_GetChunkFirstLastSample; + UInt32 fLastSamplesPerChunk_GetChunkFirstLastSample; + UInt32 fLastTotalSamples_GetChunkFirstLastSample; + + UInt32 fCurEntry_GetChunkFirstLastSample; + UInt32 chunkNumber_GetChunkFirstLastSample; + UInt32 firstSample_GetChunkFirstLastSample; + UInt32 lastSample_GetChunkFirstLastSample; + + + UInt32 fFirstSampleNumber_SampleToChunkInfo; + UInt32 fFirstSamplesPerChunk_SampleToChunkInfo; + UInt32 fFirstChunkNumber_SampleToChunkInfo; + UInt32 fFirstSampleDescriptionIndex_SampleToChunkInfo; + UInt32 fFirstSampleOffsetInChunk_SampleToChunkInfo; + + UInt32 fCurEntry_SampleToChunkInfo; + UInt32 fCurSample_SampleToChunkInfo; + UInt32 fLastFirstChunk_SampleToChunkInfo; + UInt32 fLastSamplesPerChunk_SampleToChunkInfo; + UInt32 fLastSampleDescription_SampleToChunkInfo; + + UInt32 fGetSampleInfo_SampleNumber; + UInt32 fGetSampleInfo_Length; + UInt32 fGetSampleInfo_SampleDescriptionIndex; + UInt64 fGetSampleInfo_Offset; + UInt32 fGetSampleInfo_LastChunk; + UInt32 fGetSampleInfo_LastChunkOffset; + + UInt32 fGetSizeOfSamplesInChunk_chunkNumber; + UInt32 fGetSizeOfSamplesInChunk_firstSample; + UInt32 fGetSizeOfSamplesInChunk_lastSample; + UInt32 fGetSizeOfSamplesInChunk_size; + + +}; + + +// +// QTAtom class +class QTAtom_stsc : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_stsc(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_stsc(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + + Bool16 GetChunkFirstLastSample(UInt32 chunkNumber, UInt32 *firstSample, UInt32 *lastSample, QTAtom_stsc_SampleTableControlBlock *STCB); + + Bool16 SampleToChunkInfo(UInt32 SampleNumber, + UInt32 *samplesPerChunk = NULL, + UInt32 *ChunkNumber = NULL, + UInt32 *SampleDescriptionIndex = NULL, + UInt32 *SampleOffsetInChunk = NULL, + QTAtom_stsc_SampleTableControlBlock * STCB = NULL); + + + inline Bool16 SampleNumberToChunkNumber(UInt32 SampleNumber, UInt32 *ChunkNumber = NULL, UInt32 *SampleDescriptionIndex = NULL, UInt32 *SampleOffsetInChunk = NULL, + QTAtom_stsc_SampleTableControlBlock * STCB = NULL) + { return SampleToChunkInfo(SampleNumber,NULL /*samplesPerChunk*/, ChunkNumber, SampleDescriptionIndex, SampleOffsetInChunk, STCB); } + + UInt32 GetChunkFirstSample(UInt32 chunkNumber); + // + // Debugging functions. + virtual void DumpAtom(void); + virtual void DumpTable(void); + + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + + UInt32 fNumEntries; + char *fSampleToChunkTable; + UInt32 fTableSize; +}; + +#endif // QTAtom_stsc_H diff --git a/QTFileLib/QTAtom_stsd.cpp b/QTFileLib/QTAtom_stsd.cpp new file mode 100644 index 0000000..58d9129 --- /dev/null +++ b/QTFileLib/QTAtom_stsd.cpp @@ -0,0 +1,222 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stsd.cpp,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_stsd: +// The 'stsd' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include +#ifndef __Win32__ +#include +#include +#endif + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_stsd.h" +#include "OSMemory.h" + + +// ------------------------------------- +// Constants +// +const int stsdPos_VersionFlags = 0; +const int stsdPos_NumEntries = 4; +const int stsdPos_SampleTable = 8; + +const int stsdDescPos_Size = 0; +const int stsdDescPos_DataFormat = 4; +const int stsdDescPos_Index = 14; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_stsd::QTAtom_stsd(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumEntries(0), fSampleDescriptionTable(NULL), fTable(NULL) +{ +} + +QTAtom_stsd::~QTAtom_stsd(void) +{ + // + // Free our variables. + if( fSampleDescriptionTable != NULL ) + delete[] fSampleDescriptionTable; + if( fTable != NULL ) + delete[] fTable; +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_stsd::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + // General vars + char *pSampleDescriptionTable; + + + // + // Parse this atom's fields. + ReadInt32(stsdPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + ReadInt32(stsdPos_NumEntries, &fNumEntries); + + // + // Read in all of the sample descriptions. + if( fNumEntries > 0 ) { + // + // Allocate our description tables. + UInt64 tableSize = fTOCEntry.AtomDataLength - 8; + Assert(tableSize < kSInt32_Max); + fSampleDescriptionTable = NEW char[ (SInt32) tableSize]; + if( fSampleDescriptionTable == NULL ) + return false; + + fTable = NEW char *[fNumEntries]; + if( fTable == NULL ) + return false; + + // + // Read in the sample description table. + ReadBytes(stsdPos_SampleTable, fSampleDescriptionTable, (UInt32) tableSize); + + // + // Read them all in.. + pSampleDescriptionTable = fSampleDescriptionTable; + char *maxSampleDescriptionPtr = pSampleDescriptionTable + tableSize; + for( UInt32 CurDesc = 0; CurDesc < fNumEntries; CurDesc++ ) { + // + // Associate this entry in our Table with the actual location of + // this sample description. + fTable[CurDesc] = pSampleDescriptionTable; + + // + // Skip over this mini-atom. + memcpy(&tempInt32, pSampleDescriptionTable, 4); + pSampleDescriptionTable += ntohl(tempInt32); + if (pSampleDescriptionTable > maxSampleDescriptionPtr) + { return false; + } + } + } + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Accessors +// +Bool16 QTAtom_stsd::FindSampleDescription(OSType DataFormat, char ** Buffer, UInt32 * Length) +{ + // Temporary vars + UInt32 tempInt32; + + + // + // Go through all of the sample descriptions, looking for the requested + // entry. + for( UInt32 CurDesc = 0; CurDesc < fNumEntries; CurDesc++ ) { + // + // Get this entry's data format. + memcpy(&tempInt32, fTable[CurDesc] + stsdDescPos_DataFormat, 4); + tempInt32 = ntohl(tempInt32); + + // + // Skip this entry if it does not match. + if( DataFormat != tempInt32 ) + continue; + + // + // We found a match; return it. + *Buffer = fTable[CurDesc]; + + memcpy(&tempInt32, fTable[CurDesc] + stsdDescPos_Size, 4); + *Length = ntohl(tempInt32); + + return true; + } + + // + // No match was found. + return false; +} + +UInt16 QTAtom_stsd::SampleDescriptionToDataReference(UInt32 SampleDescriptionID) +{ + // Temporary vars + UInt16 tempInt16; + + + // + // Validate our arguments. + if( SampleDescriptionID > fNumEntries ) + return 1; + + // + // Find and return the given sample's data reference index. + memcpy(&tempInt16, fTable[SampleDescriptionID - 1] + stsdDescPos_Index, 2); + return ntohs(tempInt16); +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_stsd::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_stsd::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_stsd::DumpAtom - ..Number of sample description entries: %"_S32BITARG_"\n", fNumEntries)); +} diff --git a/QTFileLib/QTAtom_stsd.h b/QTFileLib/QTAtom_stsd.h new file mode 100644 index 0000000..96c1dfc --- /dev/null +++ b/QTFileLib/QTAtom_stsd.h @@ -0,0 +1,79 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stsd.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_stsd: +// The 'stsd' QTAtom class. + +#ifndef QTAtom_stsd_H +#define QTAtom_stsd_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" + + +// +// QTAtom class +class QTAtom_stsd : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_stsd(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_stsd(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + Bool16 FindSampleDescription(OSType DataFormat, char ** Buffer, UInt32 * Length); + UInt16 SampleDescriptionToDataReference(UInt32 SampleDescriptionID); + + + // + // Debugging functions. + virtual void DumpAtom(void); + + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + + UInt32 fNumEntries; + char *fSampleDescriptionTable; + char **fTable; // each entry points to the start of a sample + // description entry in the above memory area +}; + +#endif // QTAtom_stsd_H diff --git a/QTFileLib/QTAtom_stss.cpp b/QTFileLib/QTAtom_stss.cpp new file mode 100644 index 0000000..c303252 --- /dev/null +++ b/QTFileLib/QTAtom_stss.cpp @@ -0,0 +1,231 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stss.cpp,v 1.2 2006/03/29 00:47:00 murata Exp $ +// +// QTAtom_stss: +// The 'stss' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#ifndef __Win32__ +#include +#include +#endif + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_stss.h" +#include "OSMemory.h" + + +// ------------------------------------- +// Constants +// +const int stssPos_VersionFlags = 0; +const int stssPos_NumEntries = 4; +const int stssPos_SampleTable = 8; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_stss::QTAtom_stss(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumEntries(0), fSyncSampleTable(NULL), fTable(NULL), fTableSize(0) +{ +} + +QTAtom_stss::~QTAtom_stss(void) +{ + // + // Free our variables. +#if MMAP_TABLES + if( fSyncSampleTable != NULL ) + this->UnMap(fSyncSampleTable, fTableSize); +#else + if( fSyncSampleTable != NULL ) + delete[] fSyncSampleTable; +#endif +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_stss::Initialize(void) +{ + Bool16 initSucceeds = false; + UInt32 tempInt32; + + + // + // Parse this atom's fields. + initSucceeds = ReadInt32(stssPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + if ( initSucceeds ) + { + initSucceeds = ReadInt32(stssPos_NumEntries, &fNumEntries); + + // + // Validate the size of the sample table. + if( (UInt32)(fNumEntries * 4) != (fTOCEntry.AtomDataLength - 8) ) + return false; + + +#if 0// MMAP_TABLES needs fixing should be page aligned and maybe on a 64bit system the whole file should be mapped. + fTableSize = (fNumEntries * 4); + fSyncSampleTable = this->MemMap(stssPos_SampleTable, fTableSize); + fTable = (UInt32 *) fSyncSampleTable; + if (fSyncSampleTable == NULL) + return false; + +#else + // + // Read in the sync sample table. + fSyncSampleTable = NEW char[(fNumEntries * 4) + 1]; + if( fSyncSampleTable == NULL ) + return false; + + if( ((PointerSizedInt)fSyncSampleTable & (PointerSizedInt)0x3) == 0) + fTable = (UInt32 *)fSyncSampleTable; + else + fTable = (UInt32 *)(((PointerSizedInt)fSyncSampleTable + 4) & ~((PointerSizedInt)0x3)); + + initSucceeds = ReadBytes(stssPos_SampleTable, (char *)fTable, fNumEntries * 4); + +#endif + + if ( initSucceeds ) + { + // This atom has been successfully read in. + // sample offsets are in network byte order on disk, convert them to host order + UInt32 sampleIndex = 0; + + // convert each sample to host order + // NOTE - most other Atoms handle byte order conversions in + // the accessor function. For efficiency reasons it's converted + // to host order here for sync samples. + + for ( sampleIndex = 0; sampleIndex < fNumEntries; sampleIndex++ ) + { + fTable[sampleIndex] = ntohl( fTable[sampleIndex] ); + + } + + } + + + + } + + return initSucceeds; +} + + + +// ------------------------------------- +// Accessors +// +void QTAtom_stss::PreviousSyncSample(UInt32 SampleNumber, UInt32 *SyncSampleNumber) +{ + // + // We assume that we won't find an answer + *SyncSampleNumber = SampleNumber; + + // + // Scan the table until we find a sample number greater than our current + // sample number; then return that. + for( UInt32 CurEntry = 0; CurEntry < fNumEntries; CurEntry++ ) { + // + // Take this entry if it is before (or equal to) our current entry. + if( fTable[CurEntry] <= SampleNumber ) + *SyncSampleNumber = fTable[CurEntry]; + } +} + +void QTAtom_stss::NextSyncSample(UInt32 SampleNumber, UInt32 *SyncSampleNumber) +{ + // + // We assume that we won't find an answer + *SyncSampleNumber = SampleNumber + 1; + + // + // Scan the table until we find a sample number greater than our current + // sample number; then return that. + for( UInt32 CurEntry = 0; CurEntry < fNumEntries; CurEntry++ ) { + // + // Take this entry if it is greater than our current entry. + if( fTable[CurEntry] > SampleNumber ) { + *SyncSampleNumber = fTable[CurEntry]; + break; + } + } +} + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_stss::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_stss::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_stss::DumpAtom - ..Number of sync sample entries: %"_S32BITARG_"\n", fNumEntries)); +} + +void QTAtom_stss::DumpTable(void) +{ + // + // Print out a header. + qtss_printf("-- Sync Sample table -----------------------------------------------------------\n"); + qtss_printf("\n"); + qtss_printf(" Entry Num. Sample Num\n"); + qtss_printf(" ---------- ----------\n"); + + // + // Print the table. + for( UInt32 CurEntry = 1; CurEntry <= fNumEntries; CurEntry++ ) { + // + // Print out a listing. + qtss_printf(" %10"_U32BITARG_" : %10"_U32BITARG_"\n", CurEntry, fTable[CurEntry-1]); + } +} diff --git a/QTFileLib/QTAtom_stss.h b/QTFileLib/QTAtom_stss.h new file mode 100644 index 0000000..65f78c2 --- /dev/null +++ b/QTFileLib/QTAtom_stss.h @@ -0,0 +1,93 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stss.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_stss: +// The 'stss' QTAtom class. + +#ifndef QTAtom_stss_H +#define QTAtom_stss_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" +#include "MyAssert.h" + + +// +// QTAtom class +class QTAtom_stss : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_stss(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_stss(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + void PreviousSyncSample(UInt32 SampleNumber, UInt32 *SyncSampleNumber); + void NextSyncSample(UInt32 SampleNumber, UInt32 *SyncSampleNumber); + inline Bool16 IsSyncSample(UInt32 SampleNumber, UInt32 inCursor) + { + Assert(inCursor <= fNumEntries); + for (UInt32 curEntry = inCursor; curEntry < fNumEntries; curEntry++) + { + if (fTable[curEntry] == SampleNumber) + return true; + else if (fTable[curEntry] > SampleNumber) + return false; + } + return false; + } + + + // + // Debugging functions. + virtual void DumpAtom(void); + virtual void DumpTable(void); + + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + + UInt32 fNumEntries; + char *fSyncSampleTable; + UInt32 *fTable; // longword-aligned version of the above + UInt32 fTableSize; +}; + +#endif // QTAtom_stss_H diff --git a/QTFileLib/QTAtom_stsz.cpp b/QTFileLib/QTAtom_stsz.cpp new file mode 100644 index 0000000..4c82f08 --- /dev/null +++ b/QTFileLib/QTAtom_stsz.cpp @@ -0,0 +1,205 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stsz.cpp,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_stsz: +// The 'stsz' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#ifndef __Win32__ +#include +#include +#endif + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_stsz.h" +#include "OSMemory.h" + + +// ------------------------------------- +// Constants +// +const int stszPos_VersionFlags = 0; +const int stszPos_SampleSize = 4; +const int stszPos_NumEntries = 8; +const int stszPos_SampleTable = 12; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_stsz::QTAtom_stsz(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fCommonSampleSize(0), + fNumEntries(0), fSampleSizeTable(NULL), fTable(NULL) +{ +} + +QTAtom_stsz::~QTAtom_stsz(void) +{ + // + // Free our variables. + if( fSampleSizeTable != NULL ) + delete[] fSampleSizeTable; +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_stsz::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + + // + // Parse this atom's fields. + ReadInt32(stszPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + ReadInt32(stszPos_SampleSize, &fCommonSampleSize); + + // + // We don't need to read in the table (it doesn't exist anyway) if the + // SampleSize field is non-zero. + if( fCommonSampleSize != 0 ) + return true; + + + // + // Build the table.. + ReadInt32(stszPos_NumEntries, &fNumEntries); + + // + // Validate the size of the sample table. + if( (UInt32)(fNumEntries * 4) != (fTOCEntry.AtomDataLength - 12) ) + return false; + + // + // Read in the sample size table. + fSampleSizeTable = NEW char[(fNumEntries * 4) + 1]; + if( fSampleSizeTable == NULL ) + return false; + + if( ((PointerSizedInt)fSampleSizeTable & (PointerSizedInt)0x3) == 0) + fTable = (UInt32 *)fSampleSizeTable; + else + fTable = (UInt32 *)(((PointerSizedInt)fSampleSizeTable + 4) & ~((PointerSizedInt)0x3)); + + ReadBytes(stszPos_SampleTable, (char *)fTable, fNumEntries * 4); + + // + // This atom has been successfully read in. + return true; +} + +Bool16 QTAtom_stsz::SampleRangeSize(UInt32 firstSampleNumber, UInt32 lastSampleNumber, UInt32 *sizePtr) +{ + Bool16 result = false; + + + do + { + if (lastSampleNumber < firstSampleNumber) + { +// qtss_printf("QTAtom_stsz::SampleRangeSize (lastSampleNumber %"_S32BITARG_" < firstSampleNumber %"_S32BITARG_") \n",lastSampleNumber, firstSampleNumber); + break; + } + + if(fCommonSampleSize) + { +// qtss_printf("QTAtom_stsz::SampleRangeSize fCommonSampleSize %"_S32BITARG_" firstSampleNumber %"_S32BITARG_" lastSampleNumber %"_S32BITARG_" *sizePtr %"_S32BITARG_"\n",fCommonSampleSize,firstSampleNumber,lastSampleNumber,*sizePtr); + if( sizePtr != NULL ) + *sizePtr = fCommonSampleSize * (lastSampleNumber - firstSampleNumber + 1) ; + + result = true; + break; + } + + if(firstSampleNumber && lastSampleNumber && (lastSampleNumber<=fNumEntries) && (firstSampleNumber <= fNumEntries) ) + { + if( sizePtr != NULL ) + { *sizePtr = 0; + + for (UInt32 sampleNumber = firstSampleNumber; sampleNumber <= lastSampleNumber; sampleNumber++ ) + *sizePtr += ntohl(fTable[sampleNumber-1]); + + } + result = true; + break; + } + + } + while (false); + + return result; +} + +// ------------------------------------- +// Debugging functions +// +void QTAtom_stsz::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_stsz::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_stsz::DumpAtom - ..Number of sample size entries: %"_S32BITARG_"\n", fNumEntries)); +} + +void QTAtom_stsz::DumpTable(void) +{ + // + // Print out a header. + qtss_printf("-- Sample Size table -----------------------------------------------------------\n"); + qtss_printf("\n"); + qtss_printf(" Sample Num SampleSize\n"); + qtss_printf(" ---------- ----------\n"); + + // + // Print the table. + for( UInt32 CurEntry = 1; CurEntry <= fNumEntries; CurEntry++ ) { + // + // Print out a listing. + qtss_printf(" %10"_U32BITARG_" : %10"_U32BITARG_"\n", CurEntry, fTable[CurEntry-1]); + } +} diff --git a/QTFileLib/QTAtom_stsz.h b/QTFileLib/QTAtom_stsz.h new file mode 100644 index 0000000..a71ff64 --- /dev/null +++ b/QTFileLib/QTAtom_stsz.h @@ -0,0 +1,92 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stsz.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_stsz: +// The 'stsz' QTAtom class. + +#ifndef QTAtom_stsz_H +#define QTAtom_stsz_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" + + +// +// QTAtom class +class QTAtom_stsz : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_stsz(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_stsz(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + inline Bool16 SampleSize(UInt32 SampleNumber, UInt32 *Size = NULL) \ + { if(fCommonSampleSize) { \ + if( Size != NULL ) \ + *Size = fCommonSampleSize; \ + return true; \ + } else if(SampleNumber && (SampleNumber<=fNumEntries)) { \ + if( Size != NULL ) \ + *Size = ntohl(fTable[SampleNumber-1]); \ + return true; \ + } else \ + return false; \ + }; + + Bool16 SampleRangeSize(UInt32 firstSampleNumber, UInt32 lastSampleNumber, UInt32 *sizePtr); + + // + // Debugging functions. + virtual void DumpAtom(void); + virtual void DumpTable(void); + + inline UInt32 GetNumEntries() {return fNumEntries;} + inline UInt32 GetCommonSampleSize() {return fCommonSampleSize;} + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + UInt32 fCommonSampleSize; + UInt32 fNumEntries; + char *fSampleSizeTable; + UInt32 *fTable; // longword-aligned version of the above +}; + +#endif // QTAtom_stsz_H diff --git a/QTFileLib/QTAtom_stts.cpp b/QTFileLib/QTAtom_stts.cpp new file mode 100644 index 0000000..d343298 --- /dev/null +++ b/QTFileLib/QTAtom_stts.cpp @@ -0,0 +1,581 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stts.cpp,v 1.2 2006/03/29 00:47:00 murata Exp $ +// +// QTAtom_stts: +// The 'stts' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include +#ifndef __Win32__ +#include +#include +#endif + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_stts.h" +#include "OSMemory.h" + + +// ------------------------------------- +// Constants +// +const int sttsPos_VersionFlags = 0; +const int sttsPos_NumEntries = 4; +const int sttsPos_SampleTable = 8; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Class state cookie +// +QTAtom_stts_SampleTableControlBlock::QTAtom_stts_SampleTableControlBlock(void) +{ + Reset(); +} + +QTAtom_stts_SampleTableControlBlock::~QTAtom_stts_SampleTableControlBlock(void) +{ +} + +void QTAtom_stts_SampleTableControlBlock::Reset(void) +{ + fMTtSN_CurEntry = 0; + fMTtSN_CurMediaTime = 0; + fMTtSN_CurSample = 1; + + fSNtMT_CurEntry = 0; + fSNtMT_CurMediaTime = 0; + fSNtMT_CurSample = 1; + + fGetSampleMediaTime_SampleNumber = 0; + fGetSampleMediaTime_MediaTime = 0; + +} + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_stts::QTAtom_stts(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumEntries(0), fTimeToSampleTable(NULL), fTableSize(0) +{ +} + +QTAtom_stts::~QTAtom_stts(void) +{ + // + // Free our variables. +#if MMAP_TABLES + if( fTimeToSampleTable != NULL ) + this->UnMap(fTimeToSampleTable, fTableSize); +#else + if( fTimeToSampleTable != NULL ) + delete[] fTimeToSampleTable; +#endif + + +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_stts::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + + // + // Parse this atom's fields. + ReadInt32(sttsPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + ReadInt32(sttsPos_NumEntries, &fNumEntries); + + // + // Validate the size of the sample table. + if( (UInt32)(fNumEntries * 8) != (fTOCEntry.AtomDataLength - 8) ) + return false; + + // + // Read in the time-to-sample table. + +#if MMAP_TABLES + fTableSize = (fNumEntries * 8); + fTimeToSampleTable = this->MemMap(sttsPos_SampleTable, fTableSize); + if( fTimeToSampleTable == NULL ) + return false; +#else + fTimeToSampleTable = NEW char[fNumEntries * 8]; + if( fTimeToSampleTable == NULL ) + return false; + + ReadBytes(sttsPos_SampleTable, fTimeToSampleTable, fNumEntries * 8); +#endif + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Accessors +// +Bool16 QTAtom_stts::MediaTimeToSampleNumber(UInt32 MediaTime, UInt32 * SampleNumber, QTAtom_stts_SampleTableControlBlock * STCB) +{ + // General vars + UInt32 SampleCount, SampleDuration; + QTAtom_stts_SampleTableControlBlock *tempSTCB = NULL; + Bool16 result = false; + // + // Use the default STCB if one was not passed in to us. + if( STCB == NULL ) + { +// qtss_printf("QTAtom_stts::MediaTimeToSampleNumber ( STCB == NULL ) \n"); + tempSTCB = NEW QTAtom_stts_SampleTableControlBlock; + STCB = tempSTCB; + } + // + // Reconfigure the STCB if necessary. + if( MediaTime < STCB->fMTtSN_CurMediaTime ) + { +// qtss_printf(" QTAtom_stts::MediaTimeToSampleNumber RESET \n"); + STCB->Reset(); + } + // + // Linearly search through the sample table until we find the sample + // which fits inside the given media time. + for( ; STCB->fMTtSN_CurEntry < fNumEntries; STCB->fMTtSN_CurEntry++ ) { + // + // Copy this sample count and duration. + memcpy(&SampleCount, fTimeToSampleTable + (STCB->fMTtSN_CurEntry * 8), 4); + SampleCount = ntohl(SampleCount); + memcpy(&SampleDuration, fTimeToSampleTable + (STCB->fMTtSN_CurEntry * 8) + 4, 4); + SampleDuration = ntohl(SampleDuration); + + // + // Can we skip over this entry? + if( STCB->fMTtSN_CurMediaTime + (SampleCount * SampleDuration) < MediaTime ) { + STCB->fMTtSN_CurMediaTime += SampleCount * SampleDuration; + STCB->fMTtSN_CurSample += SampleCount; + continue; + } + + // + // Locate and return the sample which is/begins right before the + // given media time. + if( SampleNumber != NULL ) + { + *SampleNumber = STCB->fMTtSN_CurSample; + if (SampleDuration > 0) + *SampleNumber += (MediaTime - STCB->fMTtSN_CurMediaTime) / SampleDuration; + result = true; + break; + } + } + + delete tempSTCB; + + return result; +} + +Bool16 QTAtom_stts::SampleNumberToMediaTime(UInt32 SampleNumber, UInt32 * MediaTime, QTAtom_stts_SampleTableControlBlock * STCB) +{ + // General vars + UInt32 SampleCount, SampleDuration; + // + // Use the default STCB if one was not passed in to us. + Assert(STCB != NULL); + + if ( STCB->fGetSampleMediaTime_SampleNumber == SampleNumber) + { +// qtss_printf("QTTrack::GetSampleMediaTime cache hit SampleNumber %"_S32BITARG_" \n", SampleNumber); + *MediaTime = STCB->fGetSampleMediaTime_MediaTime; + return true; + } + + + // + // Reconfigure the STCB if necessary. + if( SampleNumber < STCB->fSNtMT_CurSample ) + { +// qtss_printf(" QTAtom_stts::SampleNumberToMediaTime reset \n"); + STCB->Reset(); + } + // + // Linearly search through the sample table until we find the sample + // which fits inside the given media time. + for( ; STCB->fSNtMT_CurEntry < fNumEntries; STCB->fSNtMT_CurEntry++ ) { + // + // Copy this sample count and duration. + memcpy(&SampleCount, fTimeToSampleTable + (STCB->fSNtMT_CurEntry * 8), 4); + SampleCount = ntohl(SampleCount); + memcpy(&SampleDuration, fTimeToSampleTable + (STCB->fSNtMT_CurEntry * 8) + 4, 4); + SampleDuration = ntohl(SampleDuration); + + // + // Can we skip over this entry? + if( STCB->fSNtMT_CurSample + SampleCount < SampleNumber ) { + STCB->fSNtMT_CurMediaTime += SampleCount * SampleDuration; + STCB->fSNtMT_CurSample += SampleCount; + continue; + } + + // + // Return the sample time at the beginning of this sample. + if( MediaTime != NULL ) + *MediaTime = STCB->fSNtMT_CurMediaTime + ((SampleNumber - STCB->fSNtMT_CurSample) * SampleDuration); + + STCB->fGetSampleMediaTime_SampleNumber = SampleNumber; + STCB->fGetSampleMediaTime_MediaTime = *MediaTime; + + return true; + } + + // + // No match; return false. + return false; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_stts::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_stts::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_stts::DumpAtom - ..Number of TTS entries: %"_S32BITARG_"\n", fNumEntries)); +} + +void QTAtom_stts::DumpTable(void) +{ + // + // Print out a header. + qtss_printf("-- Time To Sample table -----------------------------------------------------------\n"); + qtss_printf("\n"); + qtss_printf(" Entry Num. Sample Count Sample Duration\n"); + qtss_printf(" ---------- ------------ ---------------\n"); + + // + // Print the table. + UInt32 SampleCount = 0; + UInt32 SampleDuration = 0; + for( UInt32 CurEntry = 0; CurEntry < fNumEntries; CurEntry++ ) + { + // + // Copy this sample count and duration. + memcpy(&SampleCount, fTimeToSampleTable + (CurEntry * 8), 4); + SampleCount = ntohl(SampleCount); + memcpy(&SampleDuration, fTimeToSampleTable + (CurEntry* 8) + 4, 4); + SampleDuration = ntohl(SampleDuration); + + // Print out a listing. + qtss_printf(" %10"_U32BITARG_" : %10"_U32BITARG_" %10"_U32BITARG_"\n", CurEntry, SampleCount, SampleDuration); + } +} + +// ------------------------------------- +// Constants +// +const int cttsPos_VersionFlags = 0; +const int cttsPos_NumEntries = 4; +const int cttsPos_SampleTable = 8; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Class state cookie +// +QTAtom_ctts_SampleTableControlBlock::QTAtom_ctts_SampleTableControlBlock(void) +{ + Reset(); +} + +QTAtom_ctts_SampleTableControlBlock::~QTAtom_ctts_SampleTableControlBlock(void) +{ +} + +void QTAtom_ctts_SampleTableControlBlock::Reset(void) +{ + fMTtSN_CurEntry = 0; + fMTtSN_CurMediaTime = 0; + fMTtSN_CurSample = 1; + + fSNtMT_CurEntry = 0; + fSNtMT_CurMediaTime = 0; + fSNtMT_CurSample = 1; + + fGetSampleMediaTime_SampleNumber = 0; + fGetSampleMediaTime_MediaTime = 0; + +} + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_ctts::QTAtom_ctts(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumEntries(0), fTimeToSampleTable(NULL) +{ +} + +QTAtom_ctts::~QTAtom_ctts(void) +{ + // + // Free our variables. + if( fTimeToSampleTable != NULL ) + delete[] fTimeToSampleTable; +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_ctts::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + + // + // Parse this atom's fields. + ReadInt32(cttsPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + ReadInt32(cttsPos_NumEntries, &fNumEntries); + + // + // Validate the size of the sample table. + if( (UInt32)(fNumEntries * 8) != (fTOCEntry.AtomDataLength - 8) ) + return false; + + // + // Read in the time-to-sample table. + fTimeToSampleTable = NEW char[fNumEntries * 8]; + if( fTimeToSampleTable == NULL ) + return false; + + ReadBytes(cttsPos_SampleTable, fTimeToSampleTable, fNumEntries * 8); + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Accessors +// +Bool16 QTAtom_ctts::MediaTimeToSampleNumber(UInt32 MediaTime, UInt32 * SampleNumber, QTAtom_ctts_SampleTableControlBlock * STCB) +{ + // General vars + UInt32 SampleCount, SampleDuration; + QTAtom_ctts_SampleTableControlBlock *tempSTCB = NULL; + Bool16 result = false; + // + // Use the default STCB if one was not passed in to us. + if( STCB == NULL ) + { +// qtss_printf("QTAtom_ctts::MediaTimeToSampleNumber ( STCB == NULL ) \n"); + tempSTCB = NEW QTAtom_ctts_SampleTableControlBlock; + STCB = tempSTCB; + } + // + // Reconfigure the STCB if necessary. + if( MediaTime < STCB->fMTtSN_CurMediaTime ) + { +// qtss_printf(" QTAtom_ctts::MediaTimeToSampleNumber RESET \n"); + STCB->Reset(); + } + // + // Linearly search through the sample table until we find the sample + // which fits inside the given media time. + for( ; STCB->fMTtSN_CurEntry < fNumEntries; STCB->fMTtSN_CurEntry++ ) { + // + // Copy this sample count and duration. + memcpy(&SampleCount, fTimeToSampleTable + (STCB->fMTtSN_CurEntry * 8), 4); + SampleCount = ntohl(SampleCount); + memcpy(&SampleDuration, fTimeToSampleTable + (STCB->fMTtSN_CurEntry * 8) + 4, 4); + SampleDuration = ntohl(SampleDuration); + + // + // Can we skip over this entry? + if( STCB->fMTtSN_CurMediaTime + (SampleCount * SampleDuration) < MediaTime ) { + STCB->fMTtSN_CurMediaTime += SampleCount * SampleDuration; + STCB->fMTtSN_CurSample += SampleCount; + continue; + } + + // + // Locate and return the sample which is/begins right before the + // given media time. + if( SampleNumber != NULL ) + { + *SampleNumber = STCB->fMTtSN_CurSample; + if (SampleDuration > 0) + *SampleNumber += (MediaTime - STCB->fMTtSN_CurMediaTime) / SampleDuration; + result = true; + break; + } + } + + delete tempSTCB; + + return result; +} + +Bool16 QTAtom_ctts::SampleNumberToMediaTimeOffset(UInt32 SampleNumber, UInt32 * MediaTimeOffset, QTAtom_ctts_SampleTableControlBlock * STCB) +{ + // General vars + UInt32 SampleCount, SampleOffset; + // + // Use the default STCB if one was not passed in to us. + Assert(STCB != NULL); + + if ( STCB->fGetSampleMediaTime_SampleNumber == SampleNumber) + { +// qtss_printf("QTTrack::GetSampleMediaTime cache hit SampleNumber %"_S32BITARG_" \n", SampleNumber); + *MediaTimeOffset = STCB->fGetSampleMediaTime_MediaTime; + return true; + } + + + // + // Reconfigure the STCB if necessary. + if( SampleNumber < STCB->fSNtMT_CurSample ) + { +// qtss_printf(" QTAtom_ctts::SampleNumberToMediaTime reset \n"); + STCB->Reset(); + } + // + // Linearly search through the sample table until we find the sample + // which fits inside the given media time. + for( ; STCB->fSNtMT_CurEntry < fNumEntries; STCB->fSNtMT_CurEntry++ ) { + // + // Copy this sample count and duration. + memcpy(&SampleCount, fTimeToSampleTable + (STCB->fSNtMT_CurEntry * 8), 4); + SampleCount = ntohl(SampleCount); + memcpy(&SampleOffset, fTimeToSampleTable + (STCB->fSNtMT_CurEntry * 8) + 4, 4); + SampleOffset = ntohl(SampleOffset); + + // + // Can we skip over this entry? + if( STCB->fSNtMT_CurSample + SampleCount < SampleNumber ) { + STCB->fSNtMT_CurMediaTime += SampleCount * SampleOffset; + STCB->fSNtMT_CurSample += SampleCount; + continue; + } + + // + // Return the sample time at the beginning of this sample. + if( MediaTimeOffset != NULL ) + *MediaTimeOffset = SampleOffset; + + STCB->fGetSampleMediaTime_SampleNumber = SampleNumber; + STCB->fGetSampleMediaTime_MediaTime = *MediaTimeOffset; + + return true; + } + + // + // No match; return false. + return false; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_ctts::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_ctts::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_ctts::DumpAtom - ..Number of CTTS entries: %"_S32BITARG_"\n", fNumEntries)); +} + +void QTAtom_ctts::DumpTable(void) +{ + // + // Print out a header. + qtss_printf("-- Composition Time To Sample table ----------------------------------------------\n"); + qtss_printf("\n"); + qtss_printf(" Entry Num. Sample Count Sample Offset\n"); + qtss_printf(" ---------- ------------ ---------------\n"); + + // + // Print the table. + UInt32 SampleCount = 0; + UInt32 SampleOffset = 0; + for( UInt32 CurEntry = 0; CurEntry < fNumEntries; CurEntry++ ) + { + // + // Copy this sample count and duration. + memcpy(&SampleCount, fTimeToSampleTable + (CurEntry * 8), 4); + SampleCount = ntohl(SampleCount); + memcpy(&SampleOffset, fTimeToSampleTable + (CurEntry* 8) + 4, 4); + SampleOffset = ntohl(SampleOffset); + + // Print out a listing. + qtss_printf(" %10"_U32BITARG_" : %10"_U32BITARG_" %10"_U32BITARG_"\n", CurEntry, SampleCount, SampleOffset); + } +} diff --git a/QTFileLib/QTAtom_stts.h b/QTFileLib/QTAtom_stts.h new file mode 100644 index 0000000..f16d66b --- /dev/null +++ b/QTFileLib/QTAtom_stts.h @@ -0,0 +1,181 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_stts.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_stts: +// The 'stts' QTAtom class. + +#ifndef QTAtom_stts_H +#define QTAtom_stts_H + + +// +// Includes +#include "QTFile.h" +#include "QTAtom.h" + + +// +// Class state cookie +class QTAtom_stts_SampleTableControlBlock { + +public: + // + // Constructor and destructor. + QTAtom_stts_SampleTableControlBlock(void); + virtual ~QTAtom_stts_SampleTableControlBlock(void); + + // + // Reset function + void Reset(void); + + // + // MT->SN Sample table cache + UInt32 fMTtSN_CurEntry; + UInt32 fMTtSN_CurMediaTime, fMTtSN_CurSample; + + // + /// SN->MT Sample table cache + UInt32 fSNtMT_CurEntry; + UInt32 fSNtMT_CurMediaTime, fSNtMT_CurSample; + + UInt32 fGetSampleMediaTime_SampleNumber; + UInt32 fGetSampleMediaTime_MediaTime; + +}; + + +// +// QTAtom class +class QTAtom_stts : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_stts(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_stts(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + Bool16 MediaTimeToSampleNumber(UInt32 MediaTime, UInt32 * SampleNumber, + QTAtom_stts_SampleTableControlBlock * STCB); + Bool16 SampleNumberToMediaTime(UInt32 SampleNumber, UInt32 * MediaTime, + QTAtom_stts_SampleTableControlBlock * STCB); + + + // + // Debugging functions. + virtual void DumpAtom(void); + virtual void DumpTable(void); + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + + UInt32 fNumEntries; + char *fTimeToSampleTable; + UInt32 fTableSize; + +}; + +// +// Class state cookie +class QTAtom_ctts_SampleTableControlBlock { + +public: + // + // Constructor and destructor. + QTAtom_ctts_SampleTableControlBlock(void); + virtual ~QTAtom_ctts_SampleTableControlBlock(void); + + // + // Reset function + void Reset(void); + + // + // MT->SN Sample table cache + UInt32 fMTtSN_CurEntry; + UInt32 fMTtSN_CurMediaTime, fMTtSN_CurSample; + + // + /// SN->MT Sample table cache + UInt32 fSNtMT_CurEntry; + UInt32 fSNtMT_CurMediaTime, fSNtMT_CurSample; + + UInt32 fGetSampleMediaTime_SampleNumber; + UInt32 fGetSampleMediaTime_MediaTime; + +}; + + +// +// QTAtom class +class QTAtom_ctts : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_ctts(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_ctts(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + Bool16 MediaTimeToSampleNumber(UInt32 MediaTime, UInt32 * SampleNumber, + QTAtom_ctts_SampleTableControlBlock * STCB); + Bool16 SampleNumberToMediaTimeOffset(UInt32 SampleNumber, UInt32 * MediaTimeOffset, + QTAtom_ctts_SampleTableControlBlock * STCB); + + + // + // Debugging functions. + virtual void DumpAtom(void); + virtual void DumpTable(void); + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + + UInt32 fNumEntries; + char *fTimeToSampleTable; + +}; + +#endif // QTAtom_stts_H diff --git a/QTFileLib/QTAtom_tkhd.cpp b/QTFileLib/QTAtom_tkhd.cpp new file mode 100644 index 0000000..13e2c24 --- /dev/null +++ b/QTFileLib/QTAtom_tkhd.cpp @@ -0,0 +1,210 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_tkhd.cpp,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_tkhd: +// The 'tkhd' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_tkhd.h" + + + +// ------------------------------------- +// Constants +// +const int tkhdPos_VersionFlags = 0; +const int tkhdPos_CreationTime = 4; +const int tkhdPos_ModificationTime = 8; +const int tkhdPos_TrackID = 12; +const int tkhdPos_Duration = 20; +const int tkhdPos_Layer = 32; +const int tkhdPos_AlternateGroup = 34; +const int tkhdPos_Volume = 36; +const int tkhdPos_a = 40; +const int tkhdPos_b = 44; +const int tkhdPos_u = 48; +const int tkhdPos_c = 52; +const int tkhdPos_d = 56; +const int tkhdPos_v = 60; +const int tkhdPos_x = 64; +const int tkhdPos_y = 68; +const int tkhdPos_w = 72; +const int tkhdPos_TrackWidth = 76; +const int tkhdPos_TrackHeight = 80; + +const int tkhdPosV1_CreationTime = 4; +const int tkhdPosV1_ModificationTime = 12; +const int tkhdPosV1_TrackID = 20; +const int tkhdPosV1_Duration = 28; +const int tkhdPosV1_Layer = 44; +const int tkhdPosV1_AlternateGroup = 34 + 12; +const int tkhdPosV1_Volume = 36 + 12; +const int tkhdPosV1_a = 40 + 12; +const int tkhdPosV1_b = 44 + 12; +const int tkhdPosV1_u = 48 + 12; +const int tkhdPosV1_c = 52 + 12; +const int tkhdPosV1_d = 56 + 12; +const int tkhdPosV1_v = 60 + 12; +const int tkhdPosV1_x = 64 + 12; +const int tkhdPosV1_y = 68 + 12; +const int tkhdPosV1_w = 72 + 12; +const int tkhdPosV1_TrackWidth = 76 + 12; +const int tkhdPosV1_TrackHeight = 80 + 12; + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_tkhd::QTAtom_tkhd(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug) +{ +} + +QTAtom_tkhd::~QTAtom_tkhd(void) +{ +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_tkhd::Initialize(void) +{ + // Temporary vars + UInt32 tempInt32; + + // + // Parse this atom's fields. + ReadInt32(tkhdPos_VersionFlags, &tempInt32); + fVersion = (UInt8)((tempInt32 >> 24) & 0x000000ff); + fFlags = tempInt32 & 0x00ffffff; + + if (0 == fVersion) + { + // Verify that this atom is the correct length. + if( fTOCEntry.AtomDataLength != 84 ) + { + DEEP_DEBUG_PRINT(("QTAtom_tkhd::Initialize failed. Expected AtomDataLength == 84 version: %d AtomDataLength: %"_64BITARG_"u\n",fVersion, fTOCEntry.AtomDataLength)); + return false; + } + + ReadInt32To64(tkhdPos_CreationTime, &fCreationTime); + ReadInt32To64(tkhdPos_ModificationTime, &fModificationTime); + ReadInt32(tkhdPos_TrackID, &fTrackID); + ReadInt32To64(tkhdPos_Duration, &fDuration); + ReadInt16(tkhdPos_AlternateGroup, &fAlternateGroup); + ReadInt16(tkhdPos_Volume, &fVolume); + + ReadInt32(tkhdPos_a, &fa); + ReadInt32(tkhdPos_b, &fb); + ReadInt32(tkhdPos_u, &fu); + ReadInt32(tkhdPos_c, &fc); + ReadInt32(tkhdPos_d, &fd); + ReadInt32(tkhdPos_v, &fv); + ReadInt32(tkhdPos_x, &fx); + ReadInt32(tkhdPos_y, &fy); + ReadInt32(tkhdPos_w, &fw); + + ReadInt32(tkhdPos_TrackWidth, &fTrackWidth); + ReadInt32(tkhdPos_TrackHeight, &fTrackHeight); + } + else if (1 == fVersion) + { + // Verify that this atom is the correct length. + if (fTOCEntry.AtomDataLength != 96) + { + DEEP_DEBUG_PRINT(("QTAtom_tkhd::Initialize failed. Expected AtomDataLength == 96 version: %d AtomDataLength: %"_64BITARG_"u\n",fVersion, fTOCEntry.AtomDataLength)); + return false; + } + + ReadInt64(tkhdPosV1_CreationTime, &fCreationTime); + ReadInt64(tkhdPosV1_ModificationTime, &fModificationTime); + ReadInt32(tkhdPosV1_TrackID, &fTrackID); + ReadInt64(tkhdPosV1_Duration, &fDuration); + ReadInt16(tkhdPosV1_AlternateGroup, &fAlternateGroup); + ReadInt16(tkhdPosV1_Volume, &fVolume); + + ReadInt32(tkhdPosV1_a, &fa); + ReadInt32(tkhdPosV1_b, &fb); + ReadInt32(tkhdPosV1_u, &fu); + ReadInt32(tkhdPosV1_c, &fc); + ReadInt32(tkhdPosV1_d, &fd); + ReadInt32(tkhdPosV1_v, &fv); + ReadInt32(tkhdPosV1_x, &fx); + ReadInt32(tkhdPosV1_y, &fy); + ReadInt32(tkhdPosV1_w, &fw); + + ReadInt32(tkhdPosV1_TrackWidth, &fTrackWidth); + ReadInt32(tkhdPosV1_TrackHeight, &fTrackHeight); + } + else + { + DEEP_DEBUG_PRINT(("QTAtom_tkhd::Initialize failed. Version unsupported: %d",fVersion)); + return false; + } + + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_tkhd::DumpAtom(void) +{ + // Temporary vars + time_t unixCreationTime = (time_t)fCreationTime + (time_t)QT_TIME_TO_LOCAL_TIME; + char buffer[kTimeStrSize]; + struct tm timeResult; + + DEBUG_PRINT(("QTAtom_tkhd::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_tkhd::DumpAtom - ..Version: %d.\n", (int) fVersion)); + DEBUG_PRINT(("QTAtom_tkhd::DumpAtom - ..Track ID: %"_S32BITARG_"\n", fTrackID)); + DEBUG_PRINT(("QTAtom_tkhd::DumpAtom - ..Flags:%s%s%s%s\n", (fFlags & flagEnabled) ? " Enabled" : "", (fFlags & flagInMovie) ? " InMovie" : "", (fFlags & flagInPreview) ? " InPreview" : "", (fFlags & flagInPoster) ? " InPoster" : "")); + DEBUG_PRINT(("QTAtom_tkhd::DumpAtom - ..Creation date: %s", qtss_asctime(qtss_gmtime(&unixCreationTime, &timeResult),buffer,sizeof(buffer)))); +} diff --git a/QTFileLib/QTAtom_tkhd.h b/QTFileLib/QTAtom_tkhd.h new file mode 100644 index 0000000..d860278 --- /dev/null +++ b/QTFileLib/QTAtom_tkhd.h @@ -0,0 +1,98 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_tkhd.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_tkhd: +// The 'tkhd' QTAtom class. + +#ifndef QTAtom_tkhd_H +#define QTAtom_tkhd_H + + +// +// Includes +#include "OSHeaders.h" + +#include "QTFile.h" +#include "QTAtom.h" + + +// +// QTAtom class +class QTAtom_tkhd : public QTAtom { + // + // Class constants + enum { + flagEnabled = 0x00000001, + flagInMovie = 0x00000002, + flagInPreview = 0x00000004, + flagInPoster = 0x00000008 + }; + + +public: + // + // Constructors and destructor. + QTAtom_tkhd(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_tkhd(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + inline UInt32 GetTrackID(void) { return fTrackID; } + inline UInt32 GetFlags(void) { return fFlags; } + inline UInt64 GetCreationTime(void) { return fCreationTime; } + inline UInt64 GetModificationTime(void) { return fModificationTime; } + inline UInt64 GetDuration(void) { return fDuration; } + + + // + // Debugging functions. + virtual void DumpAtom(void); + + +protected: + // + // Protected member variables. + UInt8 fVersion; + UInt32 fFlags; // 24 bits in the low 3 bytes + UInt64 fCreationTime, fModificationTime; + UInt32 fTrackID; + UInt32 freserved1; + UInt64 fDuration; + UInt32 freserved2, freserved3; + UInt16 fLayer, fAlternateGroup; + UInt16 fVolume; + UInt16 freserved4; + UInt32 fa, fb, fu, fc, fd, fv, fx, fy, fw; + UInt32 fTrackWidth, fTrackHeight; +}; + +#endif // QTAtom_tkhd_H diff --git a/QTFileLib/QTAtom_tref.cpp b/QTFileLib/QTAtom_tref.cpp new file mode 100644 index 0000000..0cc0317 --- /dev/null +++ b/QTFileLib/QTAtom_tref.cpp @@ -0,0 +1,114 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_tref.cpp,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_tref: +// The 'tref' QTAtom class. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_tref.h" +#include "OSMemory.h" + + +// ------------------------------------- +// Constants +// +const int trefPos_SampleTable = 0; + + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTAtom_tref::QTAtom_tref(QTFile * File, QTFile::AtomTOCEntry * TOCEntry, Bool16 Debug, Bool16 DeepDebug) + : QTAtom(File, TOCEntry, Debug, DeepDebug), + fNumEntries(0), fTrackReferenceTable(NULL), fTable(NULL) +{ +} + +QTAtom_tref::~QTAtom_tref(void) +{ + // + // Free our variables. + if( fTrackReferenceTable != NULL ) + delete[] fTrackReferenceTable; +} + + + +// ------------------------------------- +// Initialization functions +// +Bool16 QTAtom_tref::Initialize(void) +{ + // + // Compute the size of the sample table. + fNumEntries = fTOCEntry.AtomDataLength / 4; + + // + // Read in the track reference table. + fTrackReferenceTable = NEW char[ (SInt32) ((fNumEntries * 4) + 1)]; + if( fTrackReferenceTable == NULL ) + return false; + + if( ((PointerSizedInt)fTrackReferenceTable & (PointerSizedInt)0x3) == 0) + fTable = (UInt32 *)fTrackReferenceTable; + else + fTable = (UInt32 *)(((PointerSizedInt)fTrackReferenceTable + 4) & ~((PointerSizedInt)0x3)); + + ReadBytes(trefPos_SampleTable, (char *)fTable, (UInt32) (fNumEntries * 4) ); + + // + // This atom has been successfully read in. + return true; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTAtom_tref::DumpAtom(void) +{ + DEBUG_PRINT(("QTAtom_tref::DumpAtom - Dumping atom.\n")); + DEBUG_PRINT(("QTAtom_tref::DumpAtom - ..Number of track reference entries: %"_U32BITARG_"\n", (UInt32) fNumEntries)); +} diff --git a/QTFileLib/QTAtom_tref.h b/QTFileLib/QTAtom_tref.h new file mode 100644 index 0000000..fff9650 --- /dev/null +++ b/QTFileLib/QTAtom_tref.h @@ -0,0 +1,85 @@ +/* + * + * @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@ + * + */ +// $Id: QTAtom_tref.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTAtom_tref: +// The 'tref' QTAtom class. + +#ifndef QTAtom_tref_H +#define QTAtom_tref_H + +// +// Includes +#ifndef __Win32__ +#include +#endif + +#include "QTFile.h" +#include "QTAtom.h" + + +// +// QTAtom class +class QTAtom_tref : public QTAtom { + +public: + // + // Constructors and destructor. + QTAtom_tref(QTFile * File, QTFile::AtomTOCEntry * Atom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTAtom_tref(void); + + + // + // Initialization functions. + virtual Bool16 Initialize(void); + + // + // Accessors. + inline UInt32 GetNumReferences(void) { return (UInt32) fNumEntries; } + inline Bool16 TrackReferenceToTrackID(UInt32 TrackReference, UInt32 * TrackID = NULL) \ + { if(TrackReference < fNumEntries) { \ + if( TrackID != NULL ) \ + *TrackID = ntohl(fTable[TrackReference]); \ + return true; \ + } else \ + return false; \ + } + + + // + // Debugging functions. + virtual void DumpAtom(void); + + +protected: + // + // Protected member variables. + UInt64 fNumEntries; + char *fTrackReferenceTable; + UInt32 *fTable; // longword-aligned version of the above +}; + +#endif // QTAtom_tref_H diff --git a/QTFileLib/QTFile.cpp b/QTFileLib/QTFile.cpp new file mode 100644 index 0000000..15617a2 --- /dev/null +++ b/QTFileLib/QTFile.cpp @@ -0,0 +1,988 @@ +/* + * + * @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@ + * + */ + +// +// QTFile: +// The central point of control for a file in the QuickTime File Format. + + +// ------------------------------------- +// Includes +// + +#include + +#include +#include +#include "SafeStdLib.h" +#include + +#ifndef __Win32__ +#include +#endif + +#include "OSMutex.h" + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_mvhd.h" +#include "QTAtom_tkhd.h" + +#include "QTTrack.h" +#include "QTHintTrack.h" +#include "OSMemory.h" +#if MMAP_TABLES +#include +#endif + +#if DSS_USE_API_CALLBACKS +#include "QTSS.h" // When inside the server, we need to use the QTSS API file system callbacks +#endif + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + + +// ------------------------------------- +// Constructors and destructors +// +QTFile::QTFile(Bool16 Debug, Bool16 DeepDebug) + : fDebug(Debug), fDeepDebug(DeepDebug), + fNextTOCID(1), +#if DSS_USE_API_CALLBACKS + fMovieFD(NULL), + fOSFileSourceFD(NULL), +#endif + fCacheBuffersSet(false), + fTOC(NULL), fTOCOrdHead(NULL), fTOCOrdTail(NULL), + fNumTracks(0), + fFirstTrack(NULL), fLastTrack(NULL), + fMovieHeaderAtom(NULL), + fFile(-1) +{ +} + +QTFile::~QTFile(void) +{ + // + // Free our track list (and the associated tracks) + TrackListEntry *TrackEntry = fFirstTrack, + *NextTrackEntry = TrackEntry ? TrackEntry->NextTrack : NULL; + while( TrackEntry != NULL ) { + // + // Delete this track entry and move to the next one. + if( TrackEntry->Track != NULL ) + delete TrackEntry->Track; + delete TrackEntry; + + TrackEntry = NextTrackEntry; + if( TrackEntry != NULL ) + NextTrackEntry = TrackEntry->NextTrack; + } + + // + // Free our variables. + if( fMovieHeaderAtom != NULL ) + delete fMovieHeaderAtom; + + // + // Free our table of contents + AtomTOCEntry *TOCEntry = fTOCOrdHead, + *NextTOCEntry = TOCEntry ? TOCEntry->NextOrdAtom : NULL; + while( TOCEntry != NULL ) { + // + // Delete this track entry and move to the next one. + delete TOCEntry; + + TOCEntry = NextTOCEntry; + if( TOCEntry != NULL ) + NextTOCEntry = TOCEntry->NextOrdAtom; + } + + // + // Delete our mutexen. + if( fReadMutex != NULL ) + delete fReadMutex; + + // + // Free our path. + if( fMoviePath != NULL ) + //free(fMoviePath); + delete [] fMoviePath; + +#if DSS_USE_API_CALLBACKS + (void)QTSS_CloseFileObject(fMovieFD); +#endif +#if MMAP_TABLES + ::close(fFile); +#endif +} + + + +// ------------------------------------- +// Public functions +// + +// +// Open a movie file and generate the atom table of contents. +QTFile::ErrorCode QTFile::Open(const char * MoviePath) +{ + // General vars + AtomTOCEntry *TOCEntry; + + + // + // Create our mutexen. + fReadMutex = NEW OSMutex(); + if( fReadMutex == NULL ) + return errInternalError; + + + // + // Attempt to open the movie file. + DEBUG_PRINT(("QTFile::Open - Opening movie.\n")); + + fMoviePath = NEW char[strlen(MoviePath) + 1]; + ::strcpy(fMoviePath, MoviePath); + +#if MMAP_TABLES + fFile = open(fMoviePath, O_RDONLY); +#endif + + +#if DSS_USE_API_CALLBACKS + QTSS_Error theErr = QTSS_OpenFileObject(fMoviePath, qtssOpenFileReadAhead, &fMovieFD); + if (theErr != QTSS_NoErr) + return errFileNotFound; + + QTSS_AttrInfoObject attrInfoObject; + QTSS_Error error = QTSS_GetAttrInfoByName(fMovieFD, "QTSSPosixFileSysModuleOSFileSource", &attrInfoObject); + if (QTSS_NoErr == error) + { + QTSS_AttributeID fdID; + UInt32 len = sizeof(fdID); + error = QTSS_GetValue(attrInfoObject, qtssAttrID, 0, &fdID, &len); + + if (theErr == QTSS_NoErr && len > 0) + { len = sizeof(fOSFileSourceFD); + error = QTSS_GetValue(fMovieFD, fdID, 0, &fOSFileSourceFD, &len); + if (theErr != QTSS_NoErr || len == 0) + fOSFileSourceFD = NULL; + } + } + +#else + fMovieFD.Set(MoviePath); + if( !fMovieFD.IsValid() ) + return errFileNotFound; +#endif + + // + // We have a file, generate the mod date str + fModDateBuffer.Update(this->GetModDate()); + + // + // Generate the table of contents for this movie. + DEBUG_PRINT(("QTFile::Open - Generating Atom TOC.\n")); + if( !GenerateAtomTOC() ) + return errInvalidQuickTimeFile; + + + // + // Find the Movie Header atom and read it in. + DEBUG_PRINT(("QTFile::Open - Reading movie header.\n")); + if( !FindTOCEntry("moov:mvhd", &TOCEntry) ) + return errInvalidQuickTimeFile; + + fMovieHeaderAtom = NEW QTAtom_mvhd(this, TOCEntry, fDebug, fDeepDebug); + if( fMovieHeaderAtom == NULL ) + return errInternalError; + if( !fMovieHeaderAtom->Initialize() ) + return errInvalidQuickTimeFile; + + if(fDeepDebug) fMovieHeaderAtom->DumpAtom(); + + + // + // Create QTTrack objects for all of the tracks in this movie. (Although + // this does incur some extra resource usage where tracks are not used, + // they can always A) be disposed of later, or B) be ignored as their use + // of system resources is exceptionally minimal.) + // NOTE that the tracks are *not* initialized here. That is done when they + // are actually used; either directly or by a QTHintTrack. + DEBUG_PRINT(("QTFile::Open - Loading tracks.\n")); + TOCEntry = NULL; + while( FindTOCEntry("moov:trak", &TOCEntry, TOCEntry) ) { + // General vars + TrackListEntry *ListEntry; + + + // + // Allocate space for this list entry. + ListEntry = NEW TrackListEntry(); + if( ListEntry == NULL ) + return errInternalError; + + // + // Make a hint track if that's what this is. + if( FindTOCEntry(":tref:hint", NULL, TOCEntry) ) { + ListEntry->Track = NEW QTHintTrack(this, TOCEntry, fDebug, fDeepDebug); + ListEntry->IsHintTrack = true; + } else { + ListEntry->Track = NEW QTTrack(this, TOCEntry, fDebug, fDeepDebug); + ListEntry->IsHintTrack = false; + } + if( ListEntry->Track == NULL ) { + delete ListEntry; + return errInternalError; + } + + QTTrack* theTrack = NULL; + if (FindTrack(ListEntry->Track->GetTrackID(), &theTrack)) + { + // + // A track with this track ID already exists. Ignore other tracks with + // identical track IDs. + delete ListEntry->Track; + delete ListEntry; + continue; + } + + // + // Add this track object to our track list. + ListEntry->TrackID = ListEntry->Track->GetTrackID(); + + ListEntry->NextTrack = NULL; + + if( fFirstTrack == NULL ) { + fFirstTrack = fLastTrack = ListEntry; + } else { + fLastTrack->NextTrack = ListEntry; + fLastTrack = ListEntry; + } + + // + // One more track.. + fNumTracks++; + } + + + // + // The file has been successfully opened. + DEBUG_PRINT(("QTFile::Open - Finished loading.\n")); + return errNoError; +} + +char *QTFile::GetModDateStr() +{ + return fModDateBuffer.GetDateBuffer(); +} + +void QTFile::AllocateBuffers(UInt32 inUnitSizeInK, UInt32 inBufferInc, UInt32 inBufferSizeUnits, UInt32 inMaxBitRateBuffSizeInBlocks, UInt32 inBitrate) +{ + +#if DSS_USE_API_CALLBACKS + if (fOSFileSourceFD != NULL) + { + if (!fCacheBuffersSet) + { + fCacheBuffersSet = true; + fOSFileSourceFD->AllocateFileCache(inUnitSizeInK, inBufferSizeUnits, inBufferInc,inMaxBitRateBuffSizeInBlocks, inBitrate); + fOSFileSourceFD->EnableFileCache(fCacheBuffersSet); + } + else + { + fOSFileSourceFD->IncMaxBuffers(); + } + } + +#else + if (!fCacheBuffersSet) + { + fCacheBuffersSet = true; + fMovieFD.AllocateFileCache(inUnitSizeInK, inBufferSizeUnits, inBufferInc,inMaxBitRateBuffSizeInBlocks, inBitrate); + fMovieFD.EnableFileCache(fCacheBuffersSet); + } + else + { + fMovieFD.IncMaxBuffers(); + } + +#endif + +} + + +// +// Table of Contents functions. +Bool16 QTFile::FindTOCEntry(const char * AtomPath, AtomTOCEntry **TOCEntry, AtomTOCEntry *LastFoundTOCEntry) +{ + // General vars + AtomTOCEntry *Atom, *CurParent; + const char *pCurAtomType = AtomPath; + + UInt32 RootTOCID = 0; + + + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - Searching for \"%s\".\n", AtomPath)); + + // + // If we were given a LastFoundTOCEntry to start from, then we need to + // find that before we can do the real search. + if( LastFoundTOCEntry != NULL ) { + for( Atom = fTOCOrdHead; ; Atom = Atom->NextOrdAtom ) { + // + // If it's NULL, then something is seriously wrong. + if( Atom == NULL ) + return false; + + // + // Check for matches. + if( Atom->TOCID == LastFoundTOCEntry->TOCID ) + break; + } + + // + // Is this a root search or a rooted search? + if( *pCurAtomType == ':' ) { + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - ..Rooting search at [%03"_U32BITARG_"].\n", LastFoundTOCEntry->TOCID)); + + RootTOCID = LastFoundTOCEntry->TOCID; + pCurAtomType++; + Atom = Atom->FirstChild; + } else { + // + // "Wind up" the list to get our new search path. + for( CurParent = Atom->Parent; CurParent != NULL; CurParent = CurParent->Parent ) + pCurAtomType += (4 + 1); + + // + // Move to the next atom. + Atom = Atom->NextAtom; + + if( Atom != NULL ) { + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - ..Starting search at [%03"_U32BITARG_"] '%c%c%c%c'. Search path is \"%s\"\n", + Atom->TOCID, + (char)((Atom->AtomType & 0xff000000) >> 24), + (char)((Atom->AtomType & 0x00ff0000) >> 16), + (char)((Atom->AtomType & 0x0000ff00) >> 8), + (char)((Atom->AtomType & 0x000000ff)), + pCurAtomType)); + } + } + } else { + // + // Start at the head.. + Atom = fTOC; + } + + // + // Recurse through our table of contents until we find this path. + while( Atom != NULL ) { // already initialized by the above + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - ..Comparing against [%03"_U32BITARG_" '%c%c%c%c'\n", + Atom->TOCID, + (char)((Atom->AtomType & 0xff000000) >> 24), + (char)((Atom->AtomType & 0x00ff0000) >> 16), + (char)((Atom->AtomType & 0x0000ff00) >> 8), + (char)((Atom->AtomType & 0x000000ff)))); + + // + // Is this a match? + if( memcmp(&Atom->beAtomType, pCurAtomType, 4) == 0 ) { + // + // Skip to the delimiter. + pCurAtomType += 4; + + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - ....Found match for '%c%c%c%c'; search path is \"%s\"\n", + (char)((Atom->AtomType & 0xff000000) >> 24), + (char)((Atom->AtomType & 0x00ff0000) >> 16), + (char)((Atom->AtomType & 0x0000ff00) >> 8), + (char)((Atom->AtomType & 0x000000ff)), + pCurAtomType)); + + // + // Did we finish matching? + if( *pCurAtomType == '\0' ) { + // + // Here's the atom. + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - ..Matched atom path.\n")); + if( TOCEntry != NULL ) + *TOCEntry = Atom; + + return true; + } + + // + // Not done yet; descend. + pCurAtomType++; + Atom = Atom->FirstChild; + continue; + } + + // + // If there is no next atom, but we have a parent, then move up and + // continue the search. (this is necessary if A) the file is wacky, + // or B) we were given a LastFoundTOCEntry) Do not, however, leave + // the realm of the RootTOCID (if we have one). + while( (Atom->NextAtom == NULL) && (Atom->Parent != NULL) ) { + // + // Do not leave the realm of the RootTOCID (if we have one). + if( RootTOCID && (RootTOCID == Atom->Parent->TOCID) ) { + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - ....Hit RootTOCID; aborting ascension.\n")); + break; + } + + // + // Move up. + pCurAtomType -= (4 + 1); + Atom = Atom->Parent; + + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - ....Failed match; ascending to parent. Search path is \"%s\".\n", pCurAtomType)); + } + + // + // No match; keep going. + Atom = Atom->NextAtom; + } + + // + // Couldn't find a match.. + DEEP_DEBUG_PRINT(("QTFile::FindTOCEntry - ..Match failed.\n")); + return false; +} + + +// +// Track List functions. +Bool16 QTFile::NextTrack(QTTrack **Track, QTTrack *LastFoundTrack) +{ + // General vars + TrackListEntry *ListEntry; + + + // + // Return the first track if requested. + if( LastFoundTrack == NULL ) { + if( fFirstTrack != NULL ) { + *Track = fFirstTrack->Track; + return true; + } else { + return false; + } + } + + // + // Find LastTrack and return the one after it. + for( ListEntry = fFirstTrack; ListEntry != NULL; ListEntry = ListEntry->NextTrack ) { + // + // Check for matches. + if( ListEntry->TrackID == LastFoundTrack->GetTrackID() ) { + // + // Is there a next track? + if( ListEntry->NextTrack != NULL ) { + *Track = ListEntry->NextTrack->Track; + return true; + } else { + return false; + } + } + } + + // + // This should never happen, but.. + return false; +} + +Bool16 QTFile::FindTrack(UInt32 TrackID, QTTrack **Track) +{ + // General vars + TrackListEntry *ListEntry; + + + // + // Find the specified track. + for( ListEntry = fFirstTrack; ListEntry != NULL; ListEntry = ListEntry->NextTrack ) { + // + // Check for matches. + if( ListEntry->TrackID == TrackID ) { + *Track = ListEntry->Track; + return true; + } + } + + // + // The search failed. + return false; +} + +Bool16 QTFile::IsHintTrack(QTTrack *Track) +{ + // General vars + TrackListEntry *ListEntry; + + + // + // Find the specified track. + for( ListEntry = fFirstTrack; ListEntry != NULL; ListEntry = ListEntry->NextTrack ) { + // + // Check for matches. + if( ListEntry->Track == Track ) + return ListEntry->IsHintTrack; + } + + // + // The search failed. Can this actually happen? + return false; +} + + + +// +// Accessors +Float64 QTFile::GetTimeScale(void) +{ + if (fMovieHeaderAtom == NULL) + return 0.0; + + return fMovieHeaderAtom->GetTimeScale(); +} + +Float64 QTFile::GetDurationInSeconds(void) +{ + if (fMovieHeaderAtom == NULL) + return 0.0; + + return fMovieHeaderAtom->GetDurationInSeconds(); +} + +SInt64 QTFile::GetModDate() +{ +#if DSS_USE_API_CALLBACKS + SInt64 theTime = 0; + UInt32 theLen = sizeof(SInt64); + (void)QTSS_GetValue(fMovieFD, qtssFlObjModDate, 0, (void*)&theTime, &theLen); + return theTime; +#else + time_t theTime = fMovieFD.GetModDate(); + return (SInt64)theTime * 1000; +#endif +} + +// +// Read functions. +Bool16 QTFile::Read(UInt64 Offset, char * const Buffer, UInt32 Length, QTFile_FileControlBlock * FCB) +{ + // General vars + OSMutexLocker ReadMutex(fReadMutex); + Bool16 rv = false; + UInt32 gotlen = 0; + + if( FCB ) + rv = FCB->Read(&fMovieFD,Offset,Buffer,Length); + else + { +#if DSS_USE_API_CALLBACKS + QTSS_Error theErr = QTSS_Seek(fMovieFD, Offset); + if (theErr == QTSS_NoErr) + theErr = QTSS_Read(fMovieFD, Buffer, Length, &gotlen); + if ((theErr == QTSS_NoErr) && (gotlen == Length)) + rv = true; +#else + if ((fMovieFD.Read(Offset,Buffer,Length,&gotlen) == OS_NoErr) && + (gotlen == Length)) + rv = true; +#endif + } + return rv; +} + + + + +// ------------------------------------- +// Protected functions +// +Bool16 QTFile::GenerateAtomTOC(void) +{ + // General vars + OSType AtomType; + UInt32 atomLength; + UInt64 BigAtomLength; + + UInt64 CurPos; + UInt32 CurAtomHeaderSize; + + AtomTOCEntry *NewTOCEntry = NULL, + *CurParent = NULL, *LastTOCEntry = NULL; + Bool16 hasMoovAtom = false; + Bool16 hasBigAtom = false; + + + // + // Scan through all of the atoms in this movie, generating a TOC entry + // for each one. + CurPos = 0; + while( Read(CurPos, (char *)&atomLength, 4) ) { + + // + // Swap the AtomLength for little-endian machines. + CurPos += 4; + atomLength = ntohl(atomLength); + BigAtomLength = (UInt64) atomLength; + hasBigAtom = false; + + // + // Is AtomLength zero? If so, and we're in a 'udta' atom, then all + // is well (this is the end of a 'udta' atom). Leave this level of + // siblings as the 'udta' atom is obviously over. + if( (BigAtomLength == 0) && CurParent && (CurParent->AtomType == FOUR_CHARS_TO_INT('u', 'd', 't', 'a')) ) { + // + // Do no harm.. + DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - Found End-of-udta marker.\n")); + LastTOCEntry = CurParent; + CurParent = CurParent->Parent; + + // + // Keep moving up. + goto lbl_SkipAtom; + + // + // Is the AtomLength zero? If so, this is a "QT atom" which needs + // some additional work before it can be processed. + } + else if( BigAtomLength == 0 ) + { + // + // This is a QT atom; skip the (rest of the) reserved field and + // the lock count field. + CurPos += 22; + + // + // Read the size and the type of this atom. + if( !Read(CurPos, (char *)&atomLength, 4) ) + return false; + CurPos += 4; + BigAtomLength = (UInt64) ntohl(atomLength); + + if( !Read(CurPos, (char *)&AtomType, 4) ) + return false; + CurPos += 4; + AtomType = ntohl(AtomType); + + // + // Skip over the rest of the fields. + CurPos += 12; + + // + // Set the header size to that of a QT atom. + CurAtomHeaderSize = 10 + 16 + 4 + 4 + 4 + 2 + 2 + 4; + + // + // This is a normal atom; get the atom type. + } + else // This is a normal atom; get the atom type. + { + if( !Read(CurPos, (char *)&AtomType, 4) ) + break; + + CurPos += 4; + CurAtomHeaderSize = 4 + 4; // AtomLength + AtomType + AtomType = ntohl(AtomType); + + if ( atomLength == 1 ) //large size atom + { + if( !Read(CurPos, (char *)&BigAtomLength, 8) ) + break; + BigAtomLength = QTAtom::NTOH64(BigAtomLength); + CurPos += 8; + CurAtomHeaderSize += 8; // AtomLength + AtomType + big atom length + hasBigAtom = true; + } + + if (AtomType == FOUR_CHARS_TO_INT('u', 'u', 'i', 'd')) + { + static const int sExtendedTypeSize = 16; + UInt8 usertype[sExtendedTypeSize + 1]; //sExtendedTypeSize for the type + 1 for 0 terminator. + usertype[sExtendedTypeSize] = 0; + if( !Read(CurPos, (char *)usertype, 16) ) // read and just throw it away we don't need to store + return false; + + DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - Found 'uuid' extended type name= %s.\n",usertype)); + CurPos += sExtendedTypeSize; + CurAtomHeaderSize += sExtendedTypeSize; + } + + } + + + if (0 == BigAtomLength) + { + DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - Bail atom is bad. type= '%c%c%c%c'; pos=%"_64BITARG_"u; length=%"_64BITARG_"u; header=%"_U32BITARG_".\n", + (char)((AtomType & 0xff000000) >> 24), + (char)((AtomType & 0x00ff0000) >> 16), + (char)((AtomType & 0x0000ff00) >> 8), + (char)((AtomType & 0x000000ff)), + CurPos - CurAtomHeaderSize, BigAtomLength, CurAtomHeaderSize)); + + return false; + } + + if (hasBigAtom) + { DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - Found 64 bit atom '%c%c%c%c'; pos=%"_64BITARG_"u; length=%"_64BITARG_"u; header=%"_U32BITARG_".\n", + (char)((AtomType & 0xff000000) >> 24), + (char)((AtomType & 0x00ff0000) >> 16), + (char)((AtomType & 0x0000ff00) >> 8), + (char)((AtomType & 0x000000ff)), + CurPos - CurAtomHeaderSize, BigAtomLength, CurAtomHeaderSize)); + } + else + { DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - Found 32 bit atom '%c%c%c%c'; pos=%"_64BITARG_"u; length=%"_64BITARG_"u; header=%"_U32BITARG_".\n", + (char)((AtomType & 0xff000000) >> 24), + (char)((AtomType & 0x00ff0000) >> 16), + (char)((AtomType & 0x0000ff00) >> 8), + (char)((AtomType & 0x000000ff)), + CurPos - CurAtomHeaderSize, BigAtomLength, CurAtomHeaderSize)); + } + + if ((AtomType == FOUR_CHARS_TO_INT('m', 'o', 'o', 'v')) && (hasMoovAtom)) + { + // + // Skip over any additional 'moov' atoms once we find one. + CurPos += BigAtomLength - CurAtomHeaderSize; + continue; + } + else if (AtomType == FOUR_CHARS_TO_INT('m', 'o', 'o', 'v')) + { + hasMoovAtom = true; + } + else if (!hasMoovAtom) + { + CurPos += BigAtomLength - CurAtomHeaderSize; + continue; + } + + // + // Create a TOC entry for this atom. + NewTOCEntry = NEW AtomTOCEntry(); + if( NewTOCEntry == NULL ) + return false; + + NewTOCEntry->TOCID = fNextTOCID++; + + NewTOCEntry->AtomType = AtomType; + NewTOCEntry->beAtomType = htonl(AtomType); + + NewTOCEntry->AtomDataPos = CurPos; + NewTOCEntry->AtomDataLength = BigAtomLength - CurAtomHeaderSize; + NewTOCEntry->AtomHeaderSize = CurAtomHeaderSize; + + NewTOCEntry->NextOrdAtom = NULL; + + NewTOCEntry->PrevAtom = LastTOCEntry; + NewTOCEntry->NextAtom = NULL; + + if( NewTOCEntry->PrevAtom ) + NewTOCEntry->PrevAtom->NextAtom = NewTOCEntry; + + NewTOCEntry->Parent = CurParent; + NewTOCEntry->FirstChild = NULL; + + LastTOCEntry = NewTOCEntry; + + // + // Make this entry the head of the TOC list if necessary. + if( fTOC == NULL ) { + DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - Placing this atom at the head of the TOC.\n")); + fTOC = NewTOCEntry; + fTOCOrdHead = fTOCOrdTail = NewTOCEntry; + } else { + fTOCOrdTail->NextOrdAtom = NewTOCEntry; + fTOCOrdTail = NewTOCEntry; + } + + // + // Make this the first child if we have one. + if( CurParent && (CurParent->FirstChild == NULL) ) { + DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - ..This atom is the first child of our new parent.\n")); + CurParent->FirstChild = NewTOCEntry; + } + + + // + // Figure out if we have to descend into this entry and do so. + switch( NewTOCEntry->AtomType ) + { + case FOUR_CHARS_TO_INT('m', 'o', 'o', 'v'): //moov + case FOUR_CHARS_TO_INT('c', 'l', 'i', 'p'): //clip + case FOUR_CHARS_TO_INT('t', 'r', 'a', 'k'): //trak + case FOUR_CHARS_TO_INT('m', 'a', 't', 't'): //matt + case FOUR_CHARS_TO_INT('e', 'd', 't', 's'): //edts + case FOUR_CHARS_TO_INT('t', 'r', 'e', 'f'): //tref + case FOUR_CHARS_TO_INT('m', 'd', 'i', 'a'): //mdia + case FOUR_CHARS_TO_INT('m', 'i', 'n', 'f'): //minf + case FOUR_CHARS_TO_INT('d', 'i', 'n', 'f'): //dinf + case FOUR_CHARS_TO_INT('s', 't', 'b', 'l'): //stbl + case FOUR_CHARS_TO_INT('u', 'd', 't', 'a'): /* can appear anywhere */ //udta + case FOUR_CHARS_TO_INT('h', 'n', 't', 'i'): //hnti + case FOUR_CHARS_TO_INT('h', 'i', 'n', 'f'): //hinf + { + // + // All of the above atoms need to be descended into. Set up + // our variables to descend into this atom. + + if (NewTOCEntry->AtomDataLength > 0) // maybe it should be greater than some number such as header size? + { + DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - ..Creating a new parent.\n")); + CurParent = NewTOCEntry; + LastTOCEntry = NULL; + continue; // skip the level checks below + } + else + { + DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - Empty atom.\n")); + } + + } + break; + } + + + // + // Skip over this atom's data. + CurPos += NewTOCEntry->AtomDataLength; + + // + // Would continuing to the next atom cause us to leave this level? + // If so, move up a level and move on. Keep doing this until we find + // a level we can continue on. +lbl_SkipAtom: + while( CurParent && ((LastTOCEntry->AtomDataPos - LastTOCEntry->AtomHeaderSize) + (LastTOCEntry->AtomDataLength + LastTOCEntry->AtomHeaderSize)) >= ((CurParent->AtomDataPos - CurParent->AtomHeaderSize) + (CurParent->AtomDataLength + CurParent->AtomHeaderSize)) ) { + DEEP_DEBUG_PRINT(("QTFile::GenerateAtomTOC - End of this parent's ('%c%c%c%c') children.\n", + (char)((CurParent->AtomType & 0xff000000) >> 24), + (char)((CurParent->AtomType & 0x00ff0000) >> 16), + (char)((CurParent->AtomType & 0x0000ff00) >> 8), + (char)((CurParent->AtomType & 0x000000ff)))); + LastTOCEntry = CurParent; + CurParent = CurParent->Parent; + } + } + + + if (!this->ValidTOC()) // make sure we were able to read all the atoms. + return false; + + + + // + // The TOC has been successfully read in. + return true; +} + + +char *QTFile::MapFileToMem(UInt64 offset, UInt32 length) +{ +#if MMAP_TABLES + char* mappedMem = (char *) mmap( NULL, + (size_t) length, + PROT_READ, + 0, + fFile, + (off_t) offset); + + if(mappedMem == MAP_FAILED ) + { //printf("MAP_FAILED\n"); + mappedMem = NULL; + } + return mappedMem; +#else + return NULL; +#endif + +} + +int QTFile::UnmapMem(char* memPtr, UInt32 length) +{ +#if MMAP_TABLES + return munmap( (caddr_t) memPtr, (size_t) length); +#else + return 0; +#endif +} + + +// ------------------------------------- +// Debugging functions. +void QTFile::DumpAtomTOC(void) +{ + // General vars + AtomTOCEntry *Atom; + char Indent[128] = { '\0' }; + + + // Display all of the atoms. + DEBUG_PRINT(("QTFile::DumpAtomTOC - Dumping TOC.\n")); + for( Atom = fTOC; Atom != NULL; ) + { + + // Print out this atom. + DEBUG_PRINT(("%s[%03"_U32BITARG_"] AtomType=%c%c%c%c; AtomDataPos=%"_64BITARG_"u; AtomDataLength=%"_64BITARG_"u\n", + Indent, + Atom->TOCID, + (char)((Atom->AtomType & 0xff000000) >> 24), + (char)((Atom->AtomType & 0x00ff0000) >> 16), + (char)((Atom->AtomType & 0x0000ff00) >> 8), + (char)((Atom->AtomType & 0x000000ff)), + Atom->AtomDataPos, Atom->AtomDataLength)); + + + // Descend into this atom's children if it has any. + if( Atom->FirstChild != NULL ) { + Atom = Atom->FirstChild; + strcat(Indent, " "); + continue; + } + + + // Are we at the end of a sibling list? If so, move up a level. Keep + // moving up until we find a non-NULL atom. + while( Atom && (Atom->NextAtom == NULL) ) + { + Atom = Atom->Parent; + Indent[strlen(Indent) - 2] = '\0'; + } + + + // Next atom.. + if( Atom != NULL ) // could be NULL if we just moved up to a NULL parent + Atom = Atom->NextAtom; + } +} diff --git a/QTFileLib/QTFile.h b/QTFileLib/QTFile.h new file mode 100644 index 0000000..8ff65b3 --- /dev/null +++ b/QTFileLib/QTFile.h @@ -0,0 +1,215 @@ +/* + * + * @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@ + * + */ +// $Id: QTFile.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTFile: +// The central point of control for a file in the QuickTime File Format. + +#ifndef QTFile_H +#define QTFile_H + + +// +// Includes +#include "OSHeaders.h" +#include "OSFileSource.h" +#include "QTFile_FileControlBlock.h" +#include "DateTranslator.h" + +// +// External classes +class OSMutex; + +class QTAtom_mvhd; +class QTTrack; + + +// +// QTFile class +class QTFile { + +public: + // + // Class constants + + + // + // Class error codes + enum ErrorCode { + errNoError = 0, + errFileNotFound = 1, + errInvalidQuickTimeFile = 2, + errInternalError = 100 + }; + + + // + // Class typedefs. + struct AtomTOCEntry { + // TOC id (used to compare TOCs) + UInt32 TOCID; + + // Atom information + OSType AtomType, beAtomType; // be = Big Endian + + UInt64 AtomDataPos; + UInt64 AtomDataLength; + UInt32 AtomHeaderSize; + + // TOC pointers + AtomTOCEntry *NextOrdAtom; + + AtomTOCEntry *PrevAtom, *NextAtom; + AtomTOCEntry *Parent, *FirstChild; + }; + + struct TrackListEntry { + // Track information + UInt32 TrackID; + QTTrack *Track; + Bool16 IsHintTrack; + + // List pointers + TrackListEntry *NextTrack; + }; + + +public: + // + // Constructors and destructor. + QTFile(Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTFile(void); + + + // + // Open a movie file and generate the atom table of contents. + ErrorCode Open(const char * MoviePath); + + OSMutex* GetMutex() { return fReadMutex; } + + // + // Table of Contents functions. + Bool16 FindTOCEntry(const char * AtomPath, + AtomTOCEntry **TOCEntry, + AtomTOCEntry *LastFoundTOCEntry = NULL); + + // + // Track List functions + inline UInt32 GetNumTracks(void) { return fNumTracks; } + Bool16 NextTrack(QTTrack **Track, QTTrack *LastFoundTrack = NULL); + Bool16 FindTrack(UInt32 TrackID, QTTrack **Track); + Bool16 IsHintTrack(QTTrack *Track); + + // + // Accessors + inline char * GetMoviePath(void) { return fMoviePath; } + Float64 GetTimeScale(void); + Float64 GetDurationInSeconds(void); + SInt64 GetModDate(); + // Returns the mod date as a RFC 1123 formatted string + char* GetModDateStr(); + // + // Read functions. + Bool16 Read(UInt64 Offset, char * const Buffer, UInt32 Length, QTFile_FileControlBlock * FCB = NULL); + + + void AllocateBuffers(UInt32 inUnitSizeInK, UInt32 inBufferInc, UInt32 inBufferSize, UInt32 inMaxBitRateBuffSizeInBlocks, UInt32 inBitrate); +#if DSS_USE_API_CALLBACKS + void IncBufferUserCount() {if (fOSFileSourceFD != NULL) fOSFileSourceFD->IncMaxBuffers();} + void DecBufferUserCount() {if (fOSFileSourceFD != NULL) fOSFileSourceFD->DecMaxBuffers();} +#else + void IncBufferUserCount() {fMovieFD.IncMaxBuffers();} + void DecBufferUserCount() {fMovieFD.DecMaxBuffers();} +#endif + + inline Bool16 ValidTOC(); + + + char* MapFileToMem(UInt64 offset, UInt32 length); + + int UnmapMem(char *memPtr, UInt32 length); + + // + // Debugging functions. + void DumpAtomTOC(void); + +protected: + // + // Protected member functions. + Bool16 GenerateAtomTOC(void); + + // + // Protected member variables. + Bool16 fDebug, fDeepDebug; + + UInt32 fNextTOCID; +#if DSS_USE_API_CALLBACKS + QTSS_Object fMovieFD; + OSFileSource *fOSFileSourceFD; +#else + OSFileSource fMovieFD; +#endif + Bool16 fCacheBuffersSet; + + DateBuffer fModDateBuffer; + char *fMoviePath; + + AtomTOCEntry *fTOC, *fTOCOrdHead, *fTOCOrdTail; + + UInt32 fNumTracks; + TrackListEntry *fFirstTrack, *fLastTrack; + + QTAtom_mvhd *fMovieHeaderAtom; + + OSMutex *fReadMutex; + int fFile; + +}; + +Bool16 QTFile::ValidTOC() +{ + UInt64 theLength = 0; + UInt64 thePos = 0; + +#if DSS_USE_API_CALLBACKS + UInt32 theDataLen = sizeof(UInt64); + (void)QTSS_GetValue(fMovieFD, qtssFlObjLength, 0, (void*)&theLength, &theDataLen); + (void)QTSS_GetValue(fMovieFD, qtssFlObjPosition, 0, (void*)&thePos, &theDataLen); +// qtss_printf("GenerateAtomTOC failed CurPos=%"_64BITARG_"u < Length=%"_64BITARG_"u\n", CurPos, theLength); +#else + theLength = fMovieFD.GetLength(); + thePos = fMovieFD.GetCurOffset(); +#endif + + if (thePos < theLength) // failure pos not at end of file + { +// qtss_printf("GenerateAtomTOC failed CurPos=%"_64BITARG_"u < Length=%"_64BITARG_"u\n", CurPos, theLength); + return false; + } + + return true; +} + +#endif // QTFile_H diff --git a/QTFileLib/QTFile_FileControlBlock.cpp b/QTFileLib/QTFile_FileControlBlock.cpp new file mode 100644 index 0000000..303bd7d --- /dev/null +++ b/QTFileLib/QTFile_FileControlBlock.cpp @@ -0,0 +1,322 @@ +/* + * + * @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@ + * + */ + +// +// QTFile: +// The central point of control for a file in the QuickTime File Format. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include + +#include "OSMutex.h" +#include "OSMemory.h" + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_mvhd.h" +#include "QTAtom_tkhd.h" + +#include "QTTrack.h" +#include "QTHintTrack.h" + +#include + + + +// ------------------------------------- +// Macros +// +//#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +//#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + +// ------------------------------------- +// Class state cookie +// + +QTFile_FileControlBlock::QTFile_FileControlBlock(void) + : fDataFD(NULL), fDataBufferPool(NULL), + fDataBufferSize(0), fDataBufferPosStart(0), fDataBufferPosEnd(0), + fCurrentDataBuffer(NULL), fPreviousDataBuffer(NULL), + fCurrentDataBufferLength(0), fPreviousDataBufferLength(0), + fNumBlocksPerBuff(1),fNumBuffs(1), + fCacheEnabled(false) + +{ +} + +QTFile_FileControlBlock::~QTFile_FileControlBlock(void) +{ + if( fDataBufferPool != NULL ) + delete[] fDataBufferPool; +#if DSS_USE_API_CALLBACKS + (void)QTSS_CloseFileObject(fDataFD); +#endif + +} + + +void QTFile_FileControlBlock::Set( char * DataPath) +{ +#if DSS_USE_API_CALLBACKS + (void)QTSS_OpenFileObject(DataPath, qtssOpenFileReadAhead, &fDataFD); +#else + fDataFD.Set(DataPath); +#endif + +} + +Bool16 QTFile_FileControlBlock::ReadInternal(FILE_SOURCE *dataFD, UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32 *inReadLenPtr) +{ + UInt32 readLen = 0; + if (NULL != inReadLenPtr) + *inReadLenPtr = 0; + +#if DSS_USE_API_CALLBACKS + QTSS_Error theErr = QTSS_Seek(*dataFD, inPosition); + if (theErr == QTSS_NoErr) + theErr = QTSS_Read(*dataFD, inBuffer, inLength, &readLen); + if (theErr != QTSS_NoErr) + return false; +#else + if( dataFD->Read(inPosition, inBuffer, inLength, &readLen) != OS_NoErr ) + return false; +#endif + if (NULL != inReadLenPtr) + *inReadLenPtr = readLen; + + if(inReadLenPtr == NULL && readLen != inLength) //external reads expect false if it fails to read all the requested data. + return false; + + return true; +} + + +Bool16 QTFile_FileControlBlock::Read(FILE_SOURCE *dflt, UInt64 inPosition, void* inBuffer, UInt32 inLength) +{ + // Temporary vars + UInt32 rcSize; + + // General vars + FILE_SOURCE *dataFD; + + // success or failure + Bool16 result = false; + + // Get the file descriptor. If the FCB is NULL, or the descriptor in + // the FCB is -1, then we need to use the class' descriptor. + if (this->IsValid()) + dataFD = &fDataFD; + else + dataFD = dflt; + + if ( + ( !fCacheEnabled) || // file control block caching disabled + ( inLength > fDataBufferSize ) || // too big for this cache + ( inPosition < fDataBufferPosStart) // backing up + ) + { + //if ( !fCacheEnabled) qtss_printf("QTFile_FileControlBlock::Read cache not enabled\n"); + //if ( inLength > fDataBufferSize) qtss_printf("QTFile_FileControlBlock::Read read too big for cache len=%"_U32BITARG_" max%"_U32BITARG_"\n",inLength,fDataBufferSize); + //if ( inPosition < fDataBufferPosStart) qtss_printf("QTFile_FileControlBlock::Read backing up skipping cache missed by =%"_U32BITARG_" bytes\n", fDataBufferPosStart - inPosition); + result = this->ReadInternal(dataFD, inPosition, inBuffer, inLength); + goto done; + } + + + // Is the requested block of data in our data buffer? If not, read in the + // section of the file where this piece of data is. + if( (inPosition < fDataBufferPosStart) || ((inPosition + inLength) > fDataBufferPosEnd) ) + { + // If this is a forward-moving, contiguous read, then we can keep the + // current buffer around. + + if ( (fCurrentDataBufferLength != 0) + && ((fDataBufferPosEnd - fCurrentDataBufferLength) <= inPosition) + && ((fDataBufferPosEnd + fDataBufferSize) >= (inPosition + inLength)) + ) + { + // Temporary vars + char *TempDataBuffer; + + //qtss_printf("QTFile_FileControlBlock::Read forward read inPosition=%"_64BITARG_"u fPreviousDataBuffer=%"_64BITARG_"u start=%"_64BITARG_"u\n",inPosition,fDataBufferPosStart,fDataBufferPosEnd); + + // First, demote the current buffer. + fDataBufferPosStart += fPreviousDataBufferLength; + TempDataBuffer = fPreviousDataBuffer; + + fPreviousDataBuffer = fCurrentDataBuffer; + fPreviousDataBufferLength = fCurrentDataBufferLength; + + fCurrentDataBuffer = TempDataBuffer; + fCurrentDataBufferLength = 0; + + // + // Then, fill the now-current buffer with data. + if (!this->ReadInternal(dataFD, fDataBufferPosEnd, fCurrentDataBuffer, fDataBufferSize, &rcSize) ) + goto done; + + //Assert(rcSize == fDataBufferSize); + fCurrentDataBufferLength = (UInt32)rcSize; + fDataBufferPosEnd += fCurrentDataBufferLength; + } + else + { + //qtss_printf("QTFile_FileControlBlock::Read not a contiguous forward read inPosition=%"_64BITARG_"u fPreviousDataBuffer=%"_64BITARG_"u missed=%"_64BITARG_"d\n ",inPosition,fDataBufferPosStart,(fDataBufferPosStart > inPosition) ? fDataBufferPosStart-inPosition: inPosition - fDataBufferPosStart); + + // We need to play with our current and previous data buffers in + // order to skip around while reading. + fCurrentDataBuffer = fDataBufferPool; + fCurrentDataBufferLength = 0; + + fPreviousDataBuffer = (char *)fDataBufferPool + fDataBufferSize; + fPreviousDataBufferLength = 0; + + fDataBufferPosStart = inPosition; + + if (! this->ReadInternal(dataFD, fDataBufferPosStart, fCurrentDataBuffer, fDataBufferSize, &rcSize) ) + goto done; + + //Assert(rcSize == fDataBufferSize); + fCurrentDataBufferLength = (UInt32)rcSize; + fDataBufferPosEnd = fDataBufferPosStart + fCurrentDataBufferLength; + } + + } + + // + // Copy the data out of our buffer(s). + { + // General vars + UInt64 ReadLength = inLength; + UInt64 ReadOffset = inPosition - fDataBufferPosStart; + + // + // Figure out if doing a continuous copy would cause us to cross a + // buffer boundary. + if ( (inPosition < (fDataBufferPosStart + fPreviousDataBufferLength)) + && ((ReadOffset + ReadLength) > fPreviousDataBufferLength ) + ) + { + // Temporary vars + char *pBuffer = (char *)inBuffer; + + // + // Read the first part of the block. + ReadLength = fDataBufferSize - ReadOffset; + if (ReadLength <= (fPreviousDataBufferLength - ReadOffset) ) + ::memcpy(pBuffer, fPreviousDataBuffer + ReadOffset, (UInt32) ReadLength); + else + goto done; + + pBuffer += ReadLength; + + // + // Read the last part of the block. + ReadLength = inLength - ReadLength; + if (ReadLength <= fCurrentDataBufferLength ) + { ::memcpy(pBuffer, fCurrentDataBuffer, (UInt32) ReadLength); + result = true; + } + // + // Or maybe this is a continuous copy out of the old buffer. + } + else if ( inPosition < (fDataBufferPosEnd - fCurrentDataBufferLength) ) + { + if (ReadLength <= (fPreviousDataBufferLength - ReadOffset) ) + { ::memcpy(inBuffer, fPreviousDataBuffer + ReadOffset, (UInt32)ReadLength); + + result = true; + } + } + else + { + ReadOffset -= fPreviousDataBufferLength; + if (ReadLength <= (fCurrentDataBufferLength - ReadOffset) ) + { ::memcpy(inBuffer, fCurrentDataBuffer + ReadOffset, (UInt32) ReadLength); + result = true; + } + + } + } + +done: + // We're done. + return result; +} + + +void QTFile_FileControlBlock::AdjustDataBufferBitRate(UInt32 inUnitSizeInK, UInt32 inFileBitRate, UInt32 inNumBuffSizeUnits, UInt32 inMaxBitRateBuffSizeInBlocks) +{ + if (!fCacheEnabled) + return; + + // General vars + UInt32 newDataBufferSizeInUnits = inNumBuffSizeUnits; + UInt32 newDataBufferSize = 0; + UInt32 newUnitSizeBytes = 0; + + if (inUnitSizeInK < 1) + inUnitSizeInK = 32; + + newUnitSizeBytes = inUnitSizeInK * 1024; + + if (inMaxBitRateBuffSizeInBlocks < 1) + inMaxBitRateBuffSizeInBlocks = kMaxDefaultBlocks; + + if (inFileBitRate == 0) // set the maximum + inFileBitRate = inMaxBitRateBuffSizeInBlocks * newUnitSizeBytes; + + if (inNumBuffSizeUnits < 1) // calculate if not set to a given number + { + newDataBufferSizeInUnits = (inFileBitRate + newUnitSizeBytes) / newUnitSizeBytes; // 32k bytes of buffer for every 32k bits in rate + if( newDataBufferSizeInUnits > inMaxBitRateBuffSizeInBlocks ) // Limit the buffer size value to a reasonable maximum. should be pref + newDataBufferSizeInUnits = inMaxBitRateBuffSizeInBlocks; + } + + newDataBufferSize = newDataBufferSizeInUnits * kBlockByteSize; + + //qtss_printf("QTFile_FileControlBlock::AdjustDataBuffer private buffers NewDataBufferSizeInUnits =%"_U32BITARG_" NewDataBufferSize = %"_U32BITARG_"\n",newDataBufferSizeInUnits,newDataBufferSize); + + // Free the old buffer. + delete[] fDataBufferPool; + fDataBufferSize = newDataBufferSize; + fDataBufferPool = NEW char[2 * newDataBufferSize]; // 2 contiguous buffers + fCurrentDataBuffer = fDataBufferPool; + fPreviousDataBuffer = (char *)fDataBufferPool + newDataBufferSize; + + fDataBufferPosStart = 0; + fDataBufferPosEnd = 0; + fCurrentDataBufferLength = 0; + fPreviousDataBuffer = 0; + +} diff --git a/QTFileLib/QTFile_FileControlBlock.h b/QTFileLib/QTFile_FileControlBlock.h new file mode 100644 index 0000000..92ea087 --- /dev/null +++ b/QTFileLib/QTFile_FileControlBlock.h @@ -0,0 +1,113 @@ +/* + * + * @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@ + * + */ +// +// QTFile_FileControlBlock: +// All the per-client stuff for QTFile. + + +#ifndef _QTFILE_FILECONTROLBLOCK_H_ +#define _QTFILE_FILECONTROLBLOCK_H_ + +// +// Includes +#include "OSHeaders.h" +#include "OSFileSource.h" + +#if DSS_USE_API_CALLBACKS +#include "QTSS.h" +#endif + +#if DSS_USE_API_CALLBACKS + #define FILE_SOURCE QTSS_Object +#else + #define FILE_SOURCE OSFileSource +#endif + +// +// Class state cookie +class QTFile_FileControlBlock { + + public: + // + // Constructor and destructor. + QTFile_FileControlBlock(void); + virtual ~QTFile_FileControlBlock(void); + + //Sets this object to reference this file + void Set(char *inPath); + + //Advise: this advises the OS that we are going to be reading soon from the + //following position in the file + // void Advise(OSFileSource *dflt, UInt64 advisePos, UInt32 adviseAmt); + + Bool16 Read(FILE_SOURCE *dflt, UInt64 inPosition, void* inBuffer, UInt32 inLength); + + Bool16 ReadInternal(FILE_SOURCE *dataFD, UInt64 inPosition, void* inBuffer, UInt32 inLength, UInt32 *inReadLenPtr = NULL); + + // + // Buffer management functions + void AdjustDataBufferBitRate(UInt32 inUnitSizeInK = 32, UInt32 inFileBitRate = 32768, UInt32 inNumBuffSizeUnits = 0, UInt32 inMaxBitRateBuffSizeInBlocks = 8); + void AdjustDataBuffers(UInt32 inBlockSizeKBits = 32, UInt32 inBlockCountPerBuff = 1); + void EnableCacheBuffers(Bool16 enabled) {fCacheEnabled = enabled;} + + // QTSS_ErrorCode Close(); + + Bool16 IsValid() + { +#if DSS_USE_API_CALLBACKS + return fDataFD != NULL; +#else + return fDataFD.IsValid(); +#endif + } + + +private: + // + // File descriptor for this control block + FILE_SOURCE fDataFD; + + enum + { + kMaxDefaultBlocks = 8, + kDataBufferUnitSizeExp = 15, // 32Kbytes + kBlockByteSize = ( 1 << kDataBufferUnitSizeExp) + }; + // + // Data buffer cache + char *fDataBufferPool; + + UInt32 fDataBufferSize; + UInt64 fDataBufferPosStart, fDataBufferPosEnd; + + char *fCurrentDataBuffer, *fPreviousDataBuffer; + UInt32 fCurrentDataBufferLength, fPreviousDataBufferLength; + + UInt32 fNumBlocksPerBuff; + UInt32 fNumBuffs; + Bool16 fCacheEnabled; +}; + +#endif //_QTFILE_FILECONTROLBLOCK_H_ diff --git a/QTFileLib/QTHintTrack.cpp b/QTFileLib/QTHintTrack.cpp new file mode 100644 index 0000000..180793b --- /dev/null +++ b/QTFileLib/QTHintTrack.cpp @@ -0,0 +1,1516 @@ +/* + * + * @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@ + * + */ +// +// QTHintTrack: +// The central point of control for a track in a QTFile. + +/* + + 9.21.99 rtucker - CVS is down, so I might forget this by the time tis back up + + + added: + + changed GetPacket to be more efficient in get RTP packets from the hint track + + The FastCopyMacros replace calls to memcpy for moving chars, shorts, longs etc. + + fixed a bug in B-frame thinning. would previously discard all packets in any sample + containing one or more b-frame packets. seems unlikely this would ever be seen in + the real world. + +*/ + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_hinf.h" +#include "QTAtom_tref.h" + +#include "QTHintTrack.h" +#include "OSMutex.h" +#include "FastCopyMacros.h" +#include "MyAssert.h" +#include "OSMemory.h" +#include "OS.h" + +// ------------------------------------- +// Macros +// + +#define TEMP_PRINT_ON 0 + +#if TEMP_PRINT_ON + +#define TEMP_PRINT(s) qtss_printf( s ) +#define TEMP_PRINT_ONE( s, p1 ) qtss_printf( s, p1 ) + #define TEMP_PRINT_TWO( s, p1, p2 ) qtss_printf( s, p1, p2 ) + +#else + + #define TEMP_PRINT(s) + #define TEMP_PRINT_ONE( s, p1 ) + #define TEMP_PRINT_TWO( s, p1, p2 ) + +#endif + +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + +#define ENABLE_REPEAT_DROPPING 1 + +#define TESTTIME 0 + +// ------------------------------------- +#if TESTTIME +#include "OS.h" +#include + + +static Bool16 timerStarted = false; +static Bool16 doneMediaCount = false; +static Bool16 doneHintCount = false; +static SInt64 totalMediaSampleReadTime = 0; +static SInt64 totalHintSampleReadTime = 0; +enum {eMicro = 1000000, eMilli =1000}; +#define kMaxPacketCount 10000 +static SInt32 mediaPacketCount = 0; +static SInt32 hintPacketCount = 0; +static SInt32 totalMediaLength = 0; +static SInt32 totalHintLength = 0; +static SInt64 totalMediaReadTime = 0; +static SInt64 totalHintReadTime = 0; + + +SInt64 GetMicroseconds() +{ + return OS::Milliseconds(); +} +#endif + +// ------------------------------------- +// Class state cookie +// +QTHintTrack_HintTrackControlBlock::QTHintTrack_HintTrackControlBlock(QTFile_FileControlBlock * FCB) + : fFCB(FCB), + + fCachedSampleNumber(0), + fCachedSample(NULL), + fCachedSampleSize(0), fCachedSampleLength(0), + + fCachedHintTrackSampleNumber(0), fCachedHintTrackSampleOffset(0), + fCachedHintTrackSample(NULL), + fCachedHintTrackSampleLength(0), + fLastPacketNumberFetched(0xFFFF), + fPointerToNextPacket(NULL), + + fRTPMetaInfoFieldArray(NULL), + fSyncSampleCursor(0), + + fCurrentPacketNumber(0), + fCurrentPacketPosition(0) +{ + fMediaTrackSTSC_STCB = NULL; + fMediaTrackRefIndex = -2; +} + +QTHintTrack_HintTrackControlBlock::~QTHintTrack_HintTrackControlBlock(void) +{ + delete fMediaTrackSTSC_STCB; + delete []fCachedSample; + delete []fCachedHintTrackSample; + + delete [] fRTPMetaInfoFieldArray; +} + +void QTHintTrack_HintTrackControlBlock::Reset() +{ + fSyncSampleCursor = 0; + fCurrentPacketNumber = 0; + fCurrentPacketPosition = 0; +} + + +// ------------------------------------- +// Constructors and destructors +// +QTHintTrack::QTHintTrack(QTFile * File, QTFile::AtomTOCEntry * Atom, Bool16 Debug, Bool16 DeepDebug) + : QTTrack(File, Atom, Debug, DeepDebug), + fHintInfoAtom(NULL), fHintTrackReferenceAtom(NULL), + fTrackRefs(NULL), + fMaxPacketSize(65536), + fRTPTimescale(0), + fFirstRTPTimestamp(0), + fTimestampRandomOffset(0), + fSequenceNumberRandomOffset(0), + fHintTrackInitialized(false), + fHintType(QTHintTrack::kUnknown), + fFirstTransmitTime(0.0), + fAllowInvalidHintRefs(false) +{ +#if TESTTIME + qtss_printf(" QTHintTrack initialized \n"); + mediaPacketCount = 0; + hintPacketCount = 0; + totalMediaSampleReadTime = 0; + totalHintSampleReadTime = 0; + totalMediaReadTime = 0; + totalHintReadTime = 0; +#endif + +} + +QTHintTrack::~QTHintTrack(void) +{ + + // + // Free our variables + if( fHintInfoAtom != NULL ) + delete fHintInfoAtom; + if( fHintTrackReferenceAtom != NULL ) + delete fHintTrackReferenceAtom; + + if( fTrackRefs != NULL ) + delete[] fTrackRefs; +} + + + +// ------------------------------------- +// Initialization functions +// +QTTrack::ErrorCode QTHintTrack::Initialize(void) +{ + // General vars + char *sampleDescription, *pSampleDescription; + UInt32 sampleDescriptionLength; + + QTFile::AtomTOCEntry *hinfTOCEntry; + + // + // Don't initialize more than once. + if( IsHintTrackInitialized() ) + return errNoError; + + // + // Initialize the QTTrack class. + if( QTTrack::Initialize() != errNoError ) + return errInvalidQuickTimeFile; + + + // + // Get the sample description table for this track and verify that it is an + // RTP track. + if( !fSampleDescriptionAtom->FindSampleDescription(FOUR_CHARS_TO_INT('r', 't', 'p', ' '), &sampleDescription, &sampleDescriptionLength) ) + return errInvalidQuickTimeFile; + + ::memcpy(&fMaxPacketSize, sampleDescription + 20, 4); + fMaxPacketSize = ntohl(fMaxPacketSize); + + for( pSampleDescription = (sampleDescription + 24); + pSampleDescription < (sampleDescription + sampleDescriptionLength); + ) { + // General vars + UInt32 entryLength, dataType; + + + // + // Get the entry length and data type of this entry. + ::memcpy( &entryLength, pSampleDescription + 0, 4); + entryLength = ntohl(entryLength); + + ::memcpy( &dataType, pSampleDescription + 4, 4); + dataType = ntohl(dataType); + + // + // Process this data type. + switch( dataType ) { + case FOUR_CHARS_TO_INT('t', 'i', 'm', 's'): // tims RTP timescale + ::memcpy(&fRTPTimescale, pSampleDescription + 8, 4); + fRTPTimescale = ntohl(fRTPTimescale); + break; + + case FOUR_CHARS_TO_INT('t', 's', 'r', 'o'): // tsro Timestamp random offset + ::memcpy(&fTimestampRandomOffset, pSampleDescription + 8, 4); + fTimestampRandomOffset = ntohl(fTimestampRandomOffset); + break; + + case FOUR_CHARS_TO_INT('s', 'n', 'r', 'o'): // snro Sequence number random offset + ::memcpy(&fSequenceNumberRandomOffset, pSampleDescription + 8, 2); + fSequenceNumberRandomOffset = ntohs(fSequenceNumberRandomOffset); + break; + } + + // + // Next entry.. + pSampleDescription += entryLength; + } + + + + // + // Load in the hint info atom for this track. + if( fFile->FindTOCEntry(":udta:hinf", &hinfTOCEntry, &fTOCEntry) ) + { + fHintInfoAtom = NEW QTAtom_hinf(fFile, hinfTOCEntry, fDebug, fDeepDebug); + + if( fHintInfoAtom == NULL ) + return errInternalError; + + if( !fHintInfoAtom->Initialize() ) + return errInvalidQuickTimeFile; + } + + // + // Load in the hint track reference atom for this track. + if( !fFile->FindTOCEntry(":tref:hint", &hinfTOCEntry, &fTOCEntry) ) + return errInvalidQuickTimeFile; + + fHintTrackReferenceAtom = NEW QTAtom_tref(fFile, hinfTOCEntry, fDebug, fDeepDebug); + if( fHintTrackReferenceAtom == NULL ) + return errInternalError; + if( !fHintTrackReferenceAtom->Initialize() ) + return errInvalidQuickTimeFile; + + + // + // Allocate space for our track reference table. + UInt32 numTrackRefs = fHintTrackReferenceAtom->GetNumReferences(); + if (numTrackRefs > QTHintTrack::kMaxHintTrackRefs) + return errInvalidQuickTimeFile; + + fTrackRefs = NEW QTTrack *[numTrackRefs]; + if( fTrackRefs == NULL ) + return errInternalError; + + ::memset( (void *) fTrackRefs, 0, sizeof(QTTrack*) * numTrackRefs); //initialize ptr array with 0. + + // + // Locate all of the tracks that we use, but don't initialize them until we + // actually try to access them. + for( UInt32 CurRef = 0; CurRef < numTrackRefs; CurRef++ ) + { + // General vars + UInt32 trackID = 0; + + // + // Get the reference and make sure it's not empty. + if( !fHintTrackReferenceAtom->TrackReferenceToTrackID(CurRef, &trackID) ) + return errInvalidQuickTimeFile; + + // + // Store away a reference to this track. + if( !fFile->FindTrack( trackID, &fTrackRefs[CurRef]) ) + if ( !fAllowInvalidHintRefs ) + return errInvalidQuickTimeFile; + } + + + // + // Calculate the first RTP timestamp for this track. + if( GetFirstEditMovieTime() > 0 ) + { + UInt64 trackTime = GetFirstEditMovieTime(); + + trackTime *= fRTPTimescale; + + if( fFile->GetTimeScale() > 0.0 ) + trackTime /= (UInt64)fFile->GetTimeScale(); + + fFirstRTPTimestamp = (UInt32)(trackTime & 0xffffffff); + + } + else + { + fFirstRTPTimestamp = 0; + } + + // + // This track has been successfully initialiazed. + fHintTrackInitialized = true; + + return errNoError; +} + + + +// ------------------------------------- +// Accessors. +// +QTTrack::ErrorCode QTHintTrack::GetSDPFileLength(int * length) +{ + QTFile::AtomTOCEntry* sdpTOCEntry; + + + // + // Locate the 'sdp ' atom for this track. + if( !fFile->FindTOCEntry(":udta:hnti:sdp ", &sdpTOCEntry, &fTOCEntry) ) + return errInvalidQuickTimeFile; + + // + // Return the length. + *length = (int) sdpTOCEntry->AtomDataLength; + + + return errNoError; +} + +char * QTHintTrack::GetSDPFile(int * length) +{ + QTFile::AtomTOCEntry* sdpTOCEntry; + QTAtom* sdpAtom; + char* sdpBuffer; + + + // + // Locate and read in the 'sdp ' atom for this track. + if( !fFile->FindTOCEntry(":udta:hnti:sdp ", &sdpTOCEntry, &fTOCEntry) ) + return NULL; + + sdpAtom = NEW QTAtom(fFile, sdpTOCEntry, fDebug, fDeepDebug); + if( sdpAtom == NULL ) + return NULL; + if( !sdpAtom->Initialize() ) + return NULL; + + // + // Allocate a buffer to hold the SDP file. + *length = (int) sdpTOCEntry->AtomDataLength; + //sdpBuffer = (char *)malloc(*Length); + sdpBuffer = NEW char[*length]; + if( sdpBuffer != NULL ) + sdpAtom->ReadBytes(0, sdpBuffer, *length); + + // + // Free the atom and return the buffer. + delete sdpAtom; + return sdpBuffer; +} + + + +// ------------------------------------- +// Sample functions +// + + +void QTHintTrack::GetSamplePacketHeaderVars( char *samplePacketPtr,char *maxBuffPtr, QTHintTrackRTPHeaderData &hdrData ) +{ + // Read in this (packetNumber) packet's header. + // need to acquire the header info on every pass + + MOVE_WORD( hdrData.hintFlags, samplePacketPtr + 8); + hdrData.hintFlags = ntohs( hdrData.hintFlags ); + + MOVE_WORD( hdrData.dataEntryCount, samplePacketPtr + 10); + hdrData.dataEntryCount = ntohs(hdrData.dataEntryCount); + + hdrData.tlvTimestampOffset = 0; + + Bool16 tlvOK = false; // reset tlvSize to 0 if the size value or the tlv flag is invalid + if( hdrData.hintFlags & 0x4 ) do // Extra Information TLV is present + { + hdrData.tlvSize = ntohl( *(UInt32*) (samplePacketPtr + 12) ); + char* tlvParser = samplePacketPtr + 16; // start of tlv data + char* tlvEnd = tlvParser + hdrData.tlvSize;// 1 past the end of tlv data + + if (tlvEnd >= maxBuffPtr || tlvParser >= maxBuffPtr || tlvEnd < samplePacketPtr || tlvParser < samplePacketPtr) + { + WarnV(tlvParser < maxBuffPtr, "incorrect tlv data: ignoring"); // error tlv-start past buffer + WarnV(tlvEnd < maxBuffPtr, "incorrect tlv data: ignoring"); // error tlv-end past buffer + WarnV(tlvEnd >= samplePacketPtr, "incorrect tlv data: ignoring"); // error tlv-end before start of sample in buffer + WarnV(tlvParser >= samplePacketPtr, "incorrect tlv data: ignoring"); // error tlv-start before start of sample in buffer + break; + } + + tlvOK = true; // at this point hdrData.tlvSize is ok. The size is used to calculate the next packet so it better be correct. + + // if there is a TLV, parse out the 1 field we currently know about, the 'rtpo' field + while (tlvParser + 12 < tlvEnd) // test for the minimum tlv size (size+type+smallest data size) + { + UInt32 fieldSize = ntohl( *(UInt32*) tlvParser ); + UInt32 theType = ntohl( *(UInt32*) (tlvParser + 4) ); + UInt32 theData = ntohl( *(UInt32*) (tlvParser + 8) ); //data can't be smaller 4 and we only know about rtpo data + + if (theType == FOUR_CHARS_TO_INT( 'r', 't', 'p', 'o' )) //'rtpo' + { + // This is the RTP timestamp TLV. Record it + hdrData.tlvTimestampOffset = theData; + } + + tlvParser += fieldSize;// add the field size to current for the next TLV entry + } + + } while (false); + + if (!tlvOK) + { hdrData.tlvSize = 0; + } + + MOVE_LONG_WORD( hdrData.relativePacketTransmissionTime, samplePacketPtr ); + hdrData.relativePacketTransmissionTime = ntohl(hdrData.relativePacketTransmissionTime); + + MOVE_WORD( hdrData.rtpHeaderBits, samplePacketPtr + 4); + MOVE_WORD( hdrData.rtpSequenceNumber, samplePacketPtr + 6); + hdrData.rtpSequenceNumber = ntohs(hdrData.rtpSequenceNumber); + +} + + +QTTrack::ErrorCode QTHintTrack::GetSamplePacketPtr( char ** samplePacketPtr, UInt32 sampleNumber, UInt16 packetNumber + , QTHintTrackRTPHeaderData &hdrData, QTHintTrack_HintTrackControlBlock& htcb) +{ + // get a pointer to the packetNumber # in sampleNumber #, from the QTHintTrack_HintTrackControlBlock htcb + // return a pointer to the data in samplePacketPtr ( past the header info we read into QTHintTrackRTPHeaderData ) + // and fill in the QTHintTrackRTPHeaderData fields + + // you must insure that the sampleNumber you want is already cached + // use GetSamplePtr to do this + + // on exit fLastPacketNumberFetched = packetNumber + // fPointerToNextPacket points to 1 past the end of the current packet + + + Assert( sampleNumber == htcb.fCachedSampleNumber ); + + if( htcb.fLastPacketNumberFetched != 0xFFFF && packetNumber == htcb.fLastPacketNumberFetched + 1 ) + { + // we're still looking at the same cached sample, and we just want the next packet in that sample + Assert( htcb.fPointerToNextPacket >= htcb.fCachedSample ); + + char* maxBuffPtr = (char*)(htcb.fCachedSample + htcb.fCachedSampleLength); + Assert( htcb.fPointerToNextPacket < maxBuffPtr ); + + this->GetSamplePacketHeaderVars( htcb.fPointerToNextPacket,maxBuffPtr, hdrData ); + + // adjust fPointerToNextPacket past current header data + // return this in samplePacketPtr, it points to the hint data table now. + htcb.fPointerToNextPacket += (4 + 2 + 2 + 2 + 2) + hdrData.tlvSize; + *samplePacketPtr = htcb.fPointerToNextPacket; + + // now adjust fPointerToNextPacket to point at the header of the next packet + htcb.fPointerToNextPacket += hdrData.dataEntryCount * 16; + + // validate the buffer + htcb.fLastPacketNumberFetched++; + Assert( htcb.fPointerToNextPacket <= (char*)( htcb.fCachedSample + htcb.fCachedSampleLength ) ); + + if( htcb.fPointerToNextPacket > ( htcb.fCachedSample + htcb.fCachedSampleLength ) ) + return errInvalidQuickTimeFile; + + } + else + { + Assert( sampleNumber == htcb.fCachedSampleNumber ); + + char* pSampleBuffer = htcb.fCachedSample; + char* pSampleBufferEnd = (char*) ( pSampleBuffer + htcb.fCachedSampleLength ); + pSampleBuffer += 4; + + // Loop through the sample until we find the packet that we want. + for( UInt16 curPacket = 0; curPacket != packetNumber; curPacket++ ) + { + + this->GetSamplePacketHeaderVars( pSampleBuffer,pSampleBufferEnd, hdrData ); + + // Adjust (and check) pSampleBuffer) + pSampleBuffer += (4 + 2 + 2 + 2 + 2) + hdrData.tlvSize; + if( pSampleBuffer > pSampleBufferEnd ) + return errInvalidQuickTimeFile; + + // Skip over the data table entries if this is *not* the packet that + // we want. + if( (curPacket + 1) != packetNumber ) + { + pSampleBuffer += hdrData.dataEntryCount * 16; + if( pSampleBuffer > pSampleBufferEnd ) + return errInvalidQuickTimeFile; + } + } + + + htcb.fPointerToNextPacket = pSampleBuffer + (hdrData.dataEntryCount * 16); + htcb.fLastPacketNumberFetched = packetNumber; + + #if DEBUG + // validate that sample fits within the buffer provided.. + Assert ( htcb.fPointerToNextPacket <= pSampleBufferEnd ); + #endif + + if ( htcb.fPointerToNextPacket > pSampleBufferEnd ) + return errInvalidQuickTimeFile; + + *samplePacketPtr = pSampleBuffer; + } + + return errNoError; +} + + +Bool16 QTHintTrack::GetSamplePtr(UInt32 sampleNumber, char ** samplePtr, UInt32 * length, QTHintTrack_HintTrackControlBlock * htcb) +{ + // General vars + UInt32 newSampleLength; + Assert(htcb != NULL); + + // See if this sample is in our cache, returning it out of the cache if it + // is, fetching and caching it if it is not. + if( sampleNumber == htcb->fCachedSampleNumber ) + { + *samplePtr = htcb->fCachedSample; + *length = htcb->fCachedSampleLength; + return true; + } + + //TEMP_PRINT( "QTHintTrack::GetSamplePtr; sample not cached\n" ); + + htcb->fLastPacketNumberFetched = 0xFFFF; // mark to invalid + htcb->fPointerToNextPacket = NULL; + + // + // Get the length of the new sample. + UInt32 sampleDescriptionIndex; + UInt64 sampleOffset; + + if( !this->GetSampleInfo(sampleNumber, &newSampleLength, &sampleOffset, &sampleDescriptionIndex, &htcb->fstscSTCB) ) + return false; + + // + // Create a new (bigger) cache samplePtr if the sample wouldn't fit in the + // old one. + if( (htcb->fCachedSample == NULL) || (htcb->fCachedSampleSize < newSampleLength) ) + { + // + // Free the old cache entry if we had one. + if( htcb->fCachedSample != NULL ) + { + htcb->fCachedSampleNumber = 0; + htcb->fCachedSampleSize = 0; + delete[] htcb->fCachedSample; + } + + // + // Create a new cache entry. + htcb->fCachedSampleLength = htcb->fCachedSampleSize = newSampleLength; + htcb->fCachedSample = NEW char[htcb->fCachedSampleSize]; + if( htcb->fCachedSample == NULL ) + return false; + } + + + // + // Read in the new sample. + htcb->fCachedSampleLength = newSampleLength; + + //- this did another GetSampleInfo and we already have that data... + //if( !this->GetSample(sampleNumber, htcb->fCachedSample, &htcb->fCachedSampleLength, htcb->fFCB, htcb->fstscSTCB) ) + // return false; + + // + // Read in the sample + if( !fDataReferenceAtom->Read( sampleDescriptionIndex, sampleOffset, htcb->fCachedSample, htcb->fCachedSampleLength, htcb->fFCB ) ) + return false; + // + // Return the cached sample. + htcb->fCachedSampleNumber = sampleNumber; + *samplePtr = htcb->fCachedSample; + *length = htcb->fCachedSampleLength; + + + return true; +} + + + +// ------------------------------------- +// Packet functions +// +QTTrack::ErrorCode QTHintTrack::GetNumPackets(UInt32 sampleNumber, UInt16 * numPackets, QTHintTrack_HintTrackControlBlock * htcb) +{ + char *buf; + UInt32 bufLen; + UInt16 entryCount; + + + // + // Read this sample and figure out how many packets are in it. + if( !this->GetSamplePtr(sampleNumber, &buf, &bufLen, htcb) ) + return errInvalidQuickTimeFile; + + MOVE_WORD( entryCount, buf ); + //::memcpy(&entryCount, buf, 2); + + *numPackets = ntohs(entryCount); + + return errNoError; +} + + +QTTrack::ErrorCode QTHintTrack::GetSampleData( QTHintTrack_HintTrackControlBlock * htcb, char **buffPtr, char **ppPacketBufOut, UInt32 sampleNumber, UInt16 packetNumber, UInt32 buffOutLen ) +{ + +// qtss_printf("GetSampleData sampleNumber = %"_U32BITARG_" packetNumber = %"_U32BITARG_" buffOutLen = %"_U32BITARG_" \n",sampleNumber, packetNumber, buffOutLen); + // General vars + SInt8 trackRefIndex = 0; + UInt16 readLength = 0; + UInt32 mediaSampleNumber = 0; + UInt32 readOffset = 0; + UInt16 bytesPerCompressionBlock = 0; + UInt16 samplesPerCompressionBlock= 0; // inititialization eliminates a stupid compiler warning :( + UInt32 sampleDescriptionIndex; + UInt64 dataOffset; + char* pBuf = NULL; + char* maxBuffPtr = NULL; + char* buffOutPtr = NULL; + QTTrack *track = NULL; + UInt32 samplesPerChunk = 0; + UInt32 chunkNumber = 0; + UInt64 chunkOffset = 0; + UInt32 sampleOffsetInChunk = 0; + UInt32 sampleLength = 0; + UInt64 cacheHintSampleLen = 0; + SInt32 hintMaxRead = 0; + SInt64 sizeOfSamplesInChunk =0; + SInt64 endOfSampleInChunk = 0; + SInt64 sampleFirstPartLength = 0; + SInt64 remainingLength = 0; + Bool16 isOneForOne = false; + Bool16 isCompressed = false; + + QTAtom_stsc_SampleTableControlBlock * mediaTrackSTSC_STCBPtr = NULL; + +#if TESTTIME + Bool16 isMediaSample = false; + Bool16 isHintSample = false; + SInt64 startTime = GetMicroseconds(); + SInt64 readStart = 0; +#endif + + if (NULL == ppPacketBufOut || NULL == *ppPacketBufOut) return errInternalError; + if (NULL == buffPtr || NULL == *buffPtr) return errInternalError; + if (buffOutLen <= 0) return errInternalError; + pBuf = *buffPtr; + maxBuffPtr = *ppPacketBufOut + buffOutLen -1; + buffOutPtr = *ppPacketBufOut; + cacheHintSampleLen = buffOutLen; + // + // Get the information about this sample + trackRefIndex = (SInt8)*(pBuf + 1); + + MOVE_WORD( readLength, pBuf + 2); + cacheHintSampleLen = hintMaxRead = readLength = ntohs(readLength); + + MOVE_LONG_WORD( mediaSampleNumber, pBuf + 4); + mediaSampleNumber = ntohl(mediaSampleNumber); + + MOVE_LONG_WORD( readOffset, pBuf + 8); + readOffset = ntohl(readOffset); + + MOVE_WORD( bytesPerCompressionBlock, pBuf + 12); + bytesPerCompressionBlock = ntohs(bytesPerCompressionBlock); + if( bytesPerCompressionBlock == 0 ) + bytesPerCompressionBlock = 1; + + MOVE_WORD( samplesPerCompressionBlock, pBuf + 14); + samplesPerCompressionBlock = ntohs(samplesPerCompressionBlock); + if( samplesPerCompressionBlock == 0 ) + samplesPerCompressionBlock = 1; + + DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ....Sample entry found (sample#=%"_U32BITARG_"; offset=%"_U32BITARG_"; length=%u; BPCB=%u; SPCB=%u)\n", mediaSampleNumber, readOffset, readLength, bytesPerCompressionBlock, samplesPerCompressionBlock)); + + if( trackRefIndex == -1 ) + { + if (kUnknown == fHintType) + fHintType = kOptimized; +// qtss_printf("hint track sample = %"_S32BITARG_" \n", mediaSampleNumber); + // + // We're getting data out of the hint track.. + #if TESTTIME + isHintSample = true; + totalHintLength += readLength; + #endif + + track = this; + + // + // ..this might be the Sample Description that is stored in the first + // few samples of this track. See if we have it cached and use that + // if so. If we do not have this block of data cached, then continue + // on; we'll cache it later. + if ( (mediaSampleNumber == htcb->fCachedHintTrackSampleNumber) + && (readOffset == htcb->fCachedHintTrackSampleOffset) + && (readLength == htcb->fCachedHintTrackSampleLength) + ) + { +// qtss_printf("found cached hint sample = %"_S32BITARG_" \n", mediaSampleNumber); + if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr ) + { return errInvalidQuickTimeFile; + } + + ::memcpy( *ppPacketBufOut, htcb->fCachedHintTrackSample, readLength); + *ppPacketBufOut += readLength; + + #if TESTTIME + if (hintPacketCount < kMaxPacketCount) + { totalHintSampleReadTime += (GetMicroseconds() - startTime); + hintPacketCount ++; + } + if (hintPacketCount >= kMaxPacketCount) + { +// qtss_printf("hintPacketCount = %"_S32BITARG_" hint get info time = %f hint bytes read = %d read time = %f\n", hintPacketCount, (float) (totalHintSampleReadTime - totalHintReadTime) / eMilli,totalHintLength, (float) totalHintReadTime/ eMilli); + hintPacketCount = 0; + totalHintSampleReadTime = 0; + totalHintLength = 0; + totalHintReadTime = 0; + } + #endif + + return errNoError; + } + + // + // ..or this might be in our currently-cached data sample. + + if ( (mediaSampleNumber == htcb->fCachedSampleNumber) + && ((readOffset + readLength) <= htcb->fCachedSampleLength) + ) + { +// qtss_printf("found currently cached sample = %"_S32BITARG_" \n", mediaSampleNumber); + if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr) + { return errInvalidQuickTimeFile; + } + + ::memcpy( *ppPacketBufOut, htcb->fCachedSample + readOffset, readLength); + *ppPacketBufOut += readLength; + + #if TESTTIME + if (hintPacketCount < kMaxPacketCount) + { totalHintSampleReadTime += (GetMicroseconds() - startTime); + hintPacketCount ++; + } + + if (hintPacketCount >= kMaxPacketCount) + { +// qtss_printf("hintPacketCount = %"_S32BITARG_" hint get info time = %f hint bytes read = %d read time = %f\n", hintPacketCount, (float) (totalHintSampleReadTime - totalHintReadTime) / eMilli,totalHintLength, (float) totalHintReadTime/ eMilli); + hintPacketCount = 0; + totalHintSampleReadTime = 0; + totalHintLength = 0; + totalHintReadTime = 0; + } + #endif + + return errNoError; + } + + mediaTrackSTSC_STCBPtr = &htcb->fstscSTCB; + if( !track->GetSampleInfo(mediaSampleNumber,&sampleLength, &dataOffset, &sampleDescriptionIndex, mediaTrackSTSC_STCBPtr ) ) + return errInvalidQuickTimeFile; + + dataOffset += readOffset; + + if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr) + { return errInvalidQuickTimeFile; + } + + if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) ) + return (errInvalidQuickTimeFile); + *ppPacketBufOut += readLength; // point to remainder of buffer; + + + } // trackRefIndex == -1 + else + { // trackRefIndex != -1 + + // We're getting data out of a media track.. + if (kUnknown == fHintType || kOptimized == fHintType) // if uninitialized or self-referencing then reset + fHintType = kUnoptimized; + +// qtss_printf("media track sample = %"_S32BITARG_" \n", mediaSampleNumber); + + #if TESTTIME + isMediaSample = true; + totalMediaLength += readLength; + #endif + + track = fTrackRefs[trackRefIndex]; + if( 0 == track ) + return errInvalidQuickTimeFile; + + + // Initialize this track if we haven't done so yet. + if( !track->IsInitialized() ) + { TEMP_PRINT_ONE( "QTHintTrack::GetPacket trackRefIndex %li, not initialized,\n", (SInt32)trackRefIndex ); + OSMutexLocker theLocker(fFile->GetMutex()); + + TEMP_PRINT("Initializing media track\n"); + if( track->Initialize() != QTTrack::errNoError ) + return errInvalidQuickTimeFile; + } + + if (htcb->fMediaTrackRefIndex == -2) // initial value : -1 is hint track and 0 is first index so use -2 as uninitialized + { + htcb->fMediaTrackSTSC_STCB = NEW QTAtom_stsc_SampleTableControlBlock(); + htcb->fMediaTrackRefIndex = trackRefIndex; + } + + if(htcb->fMediaTrackRefIndex == trackRefIndex) + { + mediaTrackSTSC_STCBPtr = htcb->fMediaTrackSTSC_STCB; + } + + if( !track->GetSampleInfo(mediaSampleNumber,&sampleLength, &dataOffset, &sampleDescriptionIndex, mediaTrackSTSC_STCBPtr ) ) + return errInvalidQuickTimeFile; + + if ( (1 == samplesPerCompressionBlock) && (1 == bytesPerCompressionBlock) ) + isOneForOne = true; + + + if ( (isOneForOne && sampleLength == 1) // special case (isOneForOne && sampleLength == 1) // the data is compressed and the sample's byte offset is really a byte offset into a chunk + || (!isOneForOne) // compressed data is normally defined by bytesPerCompressionBlock or samplesPerCompressionBlock + ) + { +// qtss_printf("track = %"_S32BITARG_" sample = %"_S32BITARG_" is compressed \n",this, mediaSampleNumber); +// qtss_printf("is compressed bytesPerCompressionBlock = %"_S32BITARG_" samplesPerCompressionBlock = %d sampleLength = %"_S32BITARG_"\n", bytesPerCompressionBlock, samplesPerCompressionBlock, sampleLength); + isCompressed = true; + } + + + // + // Get the information about this sample and compute an offset. If the BPCB + // and SPCB are 1, then we use the standard sample routines to get the location + // of this sample, otherwise we have to compute it ourselves. + + if (isCompressed) + { // Media track sample compressed + + UInt32 compressionBlocksToSkip = (UInt32) ( (Float64) readOffset / (Float64) bytesPerCompressionBlock); + mediaSampleNumber += compressionBlocksToSkip * samplesPerCompressionBlock; + readOffset -= compressionBlocksToSkip * bytesPerCompressionBlock; // readoffset should always be 0 after this + // start gathering chunk info to check sample length against chunk length + if( !track->SampleToChunkInfo(mediaSampleNumber, &samplesPerChunk, &chunkNumber, NULL, &sampleOffsetInChunk,mediaTrackSTSC_STCBPtr) ) + return (errInvalidQuickTimeFile); + + if( !track->ChunkOffset(chunkNumber, &chunkOffset) ) + return (errInvalidQuickTimeFile); + + dataOffset = (UInt64) chunkOffset + (UInt64) ((Float64) sampleOffsetInChunk * ((Float64)bytesPerCompressionBlock / (Float64)samplesPerCompressionBlock)); + + if( !track->GetSizeOfSamplesInChunk(chunkNumber, (UInt32 *) &sizeOfSamplesInChunk , NULL , NULL , mediaTrackSTSC_STCBPtr) ) + return (errInvalidQuickTimeFile); + + sizeOfSamplesInChunk = (UInt32) ( (Float64) sizeOfSamplesInChunk * ((Float64) bytesPerCompressionBlock / (Float64)samplesPerCompressionBlock) ); + + endOfSampleInChunk = sizeOfSamplesInChunk + chunkOffset; + sampleFirstPartLength = endOfSampleInChunk - dataOffset; // the first piece length = maxlen - start + remainingLength = readLength - sampleFirstPartLength; // the read - first piece is either <= 0 or the remaining amount. + + if ( (remainingLength > 0) && (sampleFirstPartLength > 0) ) // this packet is split across chunks + { +// qtss_printf("mediaSampleNumber = %"_S32BITARG_" is compressed and split across chunks first part = %"_S32BITARG_" remaining = %"_S32BITARG_"\n",mediaSampleNumber, readLength, remainingLength); + readLength = (UInt16) sampleFirstPartLength; + } + else + { // this is still needed. For some movies the compressed split packet calculation doesn't match the simple dataOffset calc below --a problem with sampleOffsetInChunk +// qtss_printf("compressed but not split across chunks \n"); +// qtss_printf("chunkOffset = %"_S32BITARG_" ",chunkOffset); +// qtss_printf("old dataOffset = %qd ",dataOffset); + remainingLength = 0; + dataOffset = chunkOffset + readOffset + (UInt64)(sampleOffsetInChunk * ((Float64)bytesPerCompressionBlock / (Float64)samplesPerCompressionBlock)); +// qtss_printf("new dataOffset = %qd \n", dataOffset); + } + + hintMaxRead -= readLength; + if (hintMaxRead < 0) + { return (errInvalidQuickTimeFile); + } + + #if TESTTIME +// qtss_printf("Read mediaSampleNumber = %"_S32BITARG_" dataOffset = %qd readLength = %"_S32BITARG_" remaining = %"_S32BITARG_"\n",mediaSampleNumber, dataOffset, readLength, remainingLength); + readStart = GetMicroseconds(); + + if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr) + { return errInvalidQuickTimeFile; + } + + if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) ) + return (errInvalidQuickTimeFile); + totalMediaReadTime += GetMicroseconds() - readStart; + #else +// qtss_printf("Read mediaSampleNumber = %"_S32BITARG_" dataOffset = %qd readLength = %"_S32BITARG_" remaining = %"_S32BITARG_"\n",mediaSampleNumber, dataOffset, readLength, remainingLength); + + if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr) + { return errInvalidQuickTimeFile; + } + + if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) ) + return (errInvalidQuickTimeFile); + + #endif + + + *ppPacketBufOut += readLength; // point to remainder of buffer; + + + + while (remainingLength > 0) // loop if packet is split across more than just two chunks + { +// qtss_printf("mediaSampleNumber = %"_S32BITARG_" remaining read = %"_S32BITARG_"\n",mediaSampleNumber, remainingLength); + + readLength = (UInt16) remainingLength; // set the read to what is left + chunkNumber++; // The rest of the sample is in the next N chunks + if( !track->ChunkOffset(chunkNumber, &chunkOffset) ) // Get the Next chunk location + { + // It seems some movies have sample lengths that overstep the end of the last chunk. + // if that happens, truncate? + // Below is a commented out work around for bad offsets. It is commented out because if it happens we should report something is wrong with the file. + // remainingLength = 0; + // break; + return (errInvalidQuickTimeFile); + } + + dataOffset = chunkOffset; // the location of the data starting at the beginning of the chunk + if( !track->GetSizeOfSamplesInChunk(chunkNumber, (UInt32 *) &sizeOfSamplesInChunk , NULL , NULL , mediaTrackSTSC_STCBPtr) ) + return (errInvalidQuickTimeFile); + + sizeOfSamplesInChunk = (UInt32) ( (Float64) sizeOfSamplesInChunk * ((Float64) bytesPerCompressionBlock / (Float64)samplesPerCompressionBlock) ); + + if (sizeOfSamplesInChunk < remainingLength) // read in the whole chunk and keep going + { + remainingLength -= sizeOfSamplesInChunk; + readLength = (UInt16) sizeOfSamplesInChunk; + } + else + { + remainingLength = 0; // done reading this packet + } + + hintMaxRead -= readLength; + if (hintMaxRead < 0) + { return (errInvalidQuickTimeFile); + } + + #if TESTTIME +// qtss_printf("Read next parts mediaSampleNumber = %"_S32BITARG_" dataOffset = %qd readLength = %"_S32BITARG_" remaining = %"_S32BITARG_"\n",mediaSampleNumber, dataOffset, readLength, remainingLength); + readStart = GetMicroseconds(); + + if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr) + { return errInvalidQuickTimeFile; + } + + if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) ) + return errInvalidQuickTimeFile; + totalMediaReadTime += GetMicroseconds() - readStart; + #else + if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr) + { return errInvalidQuickTimeFile; + } + + if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) ) + { return errInvalidQuickTimeFile; + } + #endif + + *ppPacketBufOut += readLength; // point to remainder of buffer; + } + + + } + else + { // Media track sample not compressed + dataOffset += readOffset; + + if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr) + { return errInvalidQuickTimeFile; + } + + + if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) ) + return (errInvalidQuickTimeFile); + + *ppPacketBufOut += readLength; // point to remainder of buffer; + } + + + *buffPtr = pBuf; + + } + + // If this chunk of data came out of our hint track; then we should cache + // it, just in case we want it later. + if( (trackRefIndex == -1) && (mediaSampleNumber < sampleNumber) ) + { + + if( htcb->fCachedHintTrackSample == NULL ) + { +// qtss_printf("create a cache buffer for %"_S32BITARG_" of size %"_S32BITARG_"\n",mediaSampleNumber,cacheHintSampleLen); + htcb->fCachedHintTrackSample = NEW char[ (SInt32) cacheHintSampleLen]; + htcb->fCachedHintTrackBufferLength = (SInt32) cacheHintSampleLen; + + } + else if (htcb->fCachedHintTrackBufferLength < cacheHintSampleLen ) + { +// qtss_printf("reallocate a cache buffer for %"_S32BITARG_" of size %"_S32BITARG_"\n",mediaSampleNumber,cacheHintSampleLen); + htcb->fCachedHintTrackSampleNumber = 0; + htcb->fCachedHintTrackSampleOffset = 0; + delete[] htcb->fCachedHintTrackSample; + + htcb->fCachedHintTrackSample = NEW char[ (SInt32) cacheHintSampleLen]; + htcb->fCachedHintTrackBufferLength = (SInt32) cacheHintSampleLen; + } + + if (htcb->fCachedHintTrackSampleNumber != mediaSampleNumber) + { + + if( htcb->fCachedHintTrackSample != NULL ) + { +// qtss_printf("cache a hint sample sampleNumber %"_S32BITARG_" readLength = %"_S32BITARG_"\n",mediaSampleNumber,cacheHintSampleLen); + ::memcpy(htcb->fCachedHintTrackSample, buffOutPtr, (UInt32) cacheHintSampleLen); + htcb->fCachedHintTrackSampleNumber = mediaSampleNumber; + htcb->fCachedHintTrackSampleOffset = readOffset; + htcb->fCachedHintTrackSampleLength = (UInt32) cacheHintSampleLen; + } + } + } + + #if TESTTIME + if (mediaPacketCount < kMaxPacketCount) + { totalMediaSampleReadTime += (GetMicroseconds() - startTime); + mediaPacketCount ++; + } + if (mediaPacketCount >= kMaxPacketCount) + { +// qtss_printf("mediaPacketCount = %"_S32BITARG_" media get info time = %f media bytes read = %d read time = %f\n", mediaPacketCount, (float) (totalMediaSampleReadTime - totalMediaReadTime) / eMilli, totalMediaLength, (float) totalMediaReadTime/ eMilli); + totalMediaSampleReadTime = 0; + mediaPacketCount = 0; + totalMediaLength = 0; + totalMediaReadTime = 0; + } + + if (isHintSample && (hintPacketCount < kMaxPacketCount)) + { totalHintSampleReadTime += (GetMicroseconds() - startTime); + hintPacketCount ++; + } + else if (isHintSample && (hintPacketCount >= kMaxPacketCount) ) + { +// qtss_printf("hintPacketCount = %"_S32BITARG_" hint get info time = %f hint bytes read = %d read time = %f\n", hintPacketCount, (float) (totalHintSampleReadTime - totalHintReadTime) / eMilli,totalHintLength, (float) totalHintReadTime/ eMilli); + hintPacketCount = 0; + totalHintSampleReadTime = 0; + totalHintLength = 0; + totalHintReadTime = 0; + } + #endif + + + return errNoError; + + +} + + +QTTrack::ErrorCode QTHintTrack::GetPacket(UInt32 sampleNumber, UInt16 packetNumber, char * buffer, UInt32 * length + , Float64 * transmitTime, Bool16 dropBFrames, Bool16 dropRepeatPackets, UInt32 ssrc, QTHintTrack_HintTrackControlBlock * htcb) +{ + // Temporary vars + UInt16 tempInt16; + UInt32 tempInt32; + + UInt16 curEntry; + + // General vars + UInt32 mediaTime; + + char* buf; + UInt32 bufLen; + + char* pSampleBuffer; + char *pDataTableStart; + + UInt16 entryCount; + UInt32 rtpTimestamp; + + QTHintTrackRTPHeaderData hdrData; + char* pPacketOutBuf; + UInt32 packetSize; + QTTrack::ErrorCode err = errNoError; + + Float64 timeScale = 1.0; + + Assert(htcb != NULL); + + DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - Building packet #%u in sample %"_U32BITARG_".\n", packetNumber, sampleNumber)); + + // + // Get the RTP timestamp for this sample. + if( !this->GetSampleMediaTime(sampleNumber, &mediaTime, &htcb->fsttsSTCB) ) + return errInvalidQuickTimeFile; + + if( fRTPTimescale != this->GetTimeScale() ) + timeScale = (Float64) fRTPTimescale * (Float64) GetTimeScaleRecip(); + + rtpTimestamp = (UInt32) ((Float64) mediaTime * timeScale); + rtpTimestamp += fFirstRTPTimestamp; + + // + // Add the first edit's media time. + // What about other edits? + mediaTime += this->GetFirstEditMediaTime(); + + // + // Read this sample and generate the n'th packet. + if( !this->GetSamplePtr(sampleNumber, &buf, &bufLen, htcb) ) + return errInvalidQuickTimeFile; + + MOVE_WORD( entryCount, (char *)buf + 0); + entryCount = ntohs(entryCount); + if( (packetNumber-1) > entryCount ) + return errInvalidQuickTimeFile; + + err = this->GetSamplePacketPtr( &pSampleBuffer, sampleNumber, packetNumber, hdrData, *htcb); + + if ( err != errNoError ) + { return err; + } + +#if 0 // ignore relativePacketTransmissionTime offsets + *transmitTime = ( mediaTime * fMediaHeaderAtom->GetTimeScaleRecip() ); + +#else //keep relativePacketTransmissionTime offsets. time 0 base the streams independently. All streams start together regardless of their transmission time. + + *transmitTime = ( mediaTime * fMediaHeaderAtom->GetTimeScaleRecip() ) + + ( hdrData.relativePacketTransmissionTime * fMediaHeaderAtom->GetTimeScaleRecip() ); + +#endif + + // remove negative start times so each track's transmit time is now 0 based + if ( hdrData.relativePacketTransmissionTime != 0 && (fFirstTransmitTime == 0.0) ) + { fFirstTransmitTime = *transmitTime * -1; + } + *transmitTime += fFirstTransmitTime; // prevent any negative transmission times by making all the streams 0 based. + + + if ( hdrData.hintFlags ) + { //qtss_printf( "QTHintTrack::GetPacket hintFlags %lx\n", (SInt32)hdrData.hintFlags ); + } + + if ( hdrData.hintFlags & kRepeatPacketMask && (dropRepeatPackets)) + { //qtss_printf( "QTHintTrack::GetPacket repeat packet dropped.\n" ); + return QTTrack::errIsSkippedPacket; + } + + + if (( hdrData.hintFlags & kBFrameBitMask) && (dropBFrames)) + { TEMP_PRINT( "QTHintTrack::GetPacket bframe packet dropped.\n" ); + return QTTrack::errIsSkippedPacket; + } + + + + DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ..rtpTimestamp=%"_U32BITARG_"; rtpSequenceNumber=%u; transmitTime=%.2f\n", rtpTimestamp, hdrData.rtpSequenceNumber, *transmitTime)); + + // + // We found this packet and the start of our data table. + pDataTableStart = pSampleBuffer; + + // + // Our first pass through the data table is done to compute the size of the + // RTP packet that we will be generating and to validate the contents of + // the data table. + + // and now only one pass!! the 2 loops are merged now + + // + // Now we go through the data table again, but this time we build the + // packet. + + pPacketOutBuf = buffer; + + // + // Add in the RTP header. + tempInt16 = hdrData.rtpHeaderBits | ntohs(0x8000) /* v2 RTP header */; + COPY_WORD(pPacketOutBuf, &tempInt16); + + //TEMP_PRINT_ONE( "QTHintTrack::GetPacket rtpHeaderBits %li.\n", (SInt32)rtpHeaderBits ); + pPacketOutBuf += 2; + + tempInt16 = htons(hdrData.rtpSequenceNumber); + COPY_WORD(pPacketOutBuf, &tempInt16); + pPacketOutBuf += 2; + + tempInt32 = rtpTimestamp; + tempInt32 += hdrData.tlvTimestampOffset; // rtpo tlv offset + + tempInt32 = htonl(tempInt32); + COPY_LONG_WORD(pPacketOutBuf, &tempInt32); + pPacketOutBuf += 4; + + tempInt32 = htonl(ssrc); + COPY_LONG_WORD(pPacketOutBuf, &tempInt32); + pPacketOutBuf += 4; + + // + // Go through each possible field. For each one, see if caller + // wants the field appended. If so, append the field + for ( UInt32 fieldCount = 0; fieldCount < RTPMetaInfoPacket::kNumFields; fieldCount++) + { + // + // If there is no field array, don't generate a packet + if (htcb->fRTPMetaInfoFieldArray == NULL) + break; + + // + // Check if field should be appended + if (htcb->fRTPMetaInfoFieldArray[fieldCount] == RTPMetaInfoPacket::kFieldNotUsed) + continue; + + switch (fieldCount) + { + case RTPMetaInfoPacket::kPacketPosField: + { + SInt64 curPacketPos = OS::HostToNetworkSInt64(htcb->fCurrentPacketPosition); + this->WriteMetaInfoField(RTPMetaInfoPacket::kPacketPosField, htcb->fRTPMetaInfoFieldArray[fieldCount], &curPacketPos, sizeof(curPacketPos), &pPacketOutBuf); + break; + } + case RTPMetaInfoPacket::kTransTimeField: + { + SInt64 transmitTimeInMsec = OS::HostToNetworkSInt64((SInt64)(*transmitTime * 1000)); + this->WriteMetaInfoField(RTPMetaInfoPacket::kTransTimeField, htcb->fRTPMetaInfoFieldArray[fieldCount], &transmitTimeInMsec, sizeof(transmitTimeInMsec), &pPacketOutBuf); + break; + } + + case RTPMetaInfoPacket::kFrameTypeField: + { + UInt16 theFrameType = RTPMetaInfoPacket::kUnknownFrameType; + + if (!htcb->fIsVideo) + theFrameType = RTPMetaInfoPacket::kUnknownFrameType; + else if (hdrData.hintFlags & kBFrameBitMask) + theFrameType = RTPMetaInfoPacket::kBFrameType; + else if (this->IsSyncSample(sampleNumber, htcb->fSyncSampleCursor)) + theFrameType = RTPMetaInfoPacket::kKeyFrameType; + else + theFrameType = RTPMetaInfoPacket::kPFrameType; + + theFrameType = htons(theFrameType); + this->WriteMetaInfoField(RTPMetaInfoPacket::kFrameTypeField, htcb->fRTPMetaInfoFieldArray[fieldCount], &theFrameType, sizeof(theFrameType), &pPacketOutBuf); + break; + } + case RTPMetaInfoPacket::kPacketNumField: + { + SInt64 curPacketNum = OS::HostToNetworkSInt64(htcb->fCurrentPacketNumber); + this->WriteMetaInfoField(RTPMetaInfoPacket::kPacketNumField, htcb->fRTPMetaInfoFieldArray[fieldCount], &curPacketNum, sizeof(curPacketNum), &pPacketOutBuf); + break; + } + case RTPMetaInfoPacket::kSeqNumField: + { + tempInt16 = htons(hdrData.rtpSequenceNumber); + this->WriteMetaInfoField(RTPMetaInfoPacket::kSeqNumField, htcb->fRTPMetaInfoFieldArray[fieldCount], &tempInt16, sizeof(tempInt16), &pPacketOutBuf); + break; + } + case RTPMetaInfoPacket::kMediaDataField: + { + // + // This field cannot be compressed + Assert(htcb->fRTPMetaInfoFieldArray[fieldCount] == RTPMetaInfoPacket::kUncompressed); + + // + // We don't have the data yet, so just write in the header + this->WriteMetaInfoField(RTPMetaInfoPacket::kMediaDataField, htcb->fRTPMetaInfoFieldArray[fieldCount], NULL, 0, &pPacketOutBuf); + break; + } + } + } + + char* endOfMetaInfo = pPacketOutBuf; + packetSize = endOfMetaInfo - buffer; + + DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ..Building packet.\n")); + TEMP_PRINT_TWO( "QTHintTrack::GetPacket Building packet %li ; hdrData.dataEntryCount %li .\n", (SInt32)packetNumber ,(SInt32)hdrData.dataEntryCount ); + + for( curEntry = 0; curEntry < hdrData.dataEntryCount; curEntry++ ) + { + // + // Get the size out of this entry. + if ( *pSampleBuffer == 0x02 ) + { + // Sample Mode + MOVE_WORD( tempInt16, pSampleBuffer + 2); + tempInt16 = ntohs(tempInt16); + + DEEP_DEBUG_PRINT (( "QTHintTrack::GetPacket - ....Sample entry found (size=%u)\n", tempInt16 ) ); + packetSize += tempInt16; + + if( *length < packetSize ) + return errParamError; + + err = this->GetSampleData( htcb, &pSampleBuffer, &pPacketOutBuf, sampleNumber, packetNumber, *length); + if ( err != errNoError ) + return err; + + // GetSampleData increments our out pointer + } + else if ( *pSampleBuffer == 0x01 ) + { + // Immediate Data Mode + DEEP_DEBUG_PRINT (( "QTHintTrack::GetPacket - ....Immediate entry found (size=%u)\n", (UInt16)*(pSampleBuffer + 1) ) ); + packetSize += *(pSampleBuffer + 1); + + if ( *length < packetSize ) + return errParamError; + + // + // Copy the data straight into the packet. + // ( it's data <= 16 bytes, padded out to a full 16 byte of header ) + ::memcpy(pPacketOutBuf, pSampleBuffer + 2, *(pSampleBuffer + 1)); + + // increment our out pointer + pPacketOutBuf += *(pSampleBuffer + 1); + + } + else if ( *pSampleBuffer == 0x03 ) + { + + // Sample Description Data Mode + MOVE_WORD( tempInt16, pSampleBuffer + 2); + tempInt16 = ntohs(tempInt16); + + DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ....Sample Description entry found (size=%u)\n", tempInt16)); + packetSize += tempInt16; + + if( *length < packetSize ) + return errParamError; + // guess we don't handle these?? + DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ....Sample Description entry found (size=%u)\n", tempInt16)); + Assert(0); + } + else if ( *pSampleBuffer == 0x00 ) + { + // No-Op Data Mode + DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ....No-Op entry found\n")); + } + else + { +// qtss_printf("Found unknown RTP data table type!\n"); + Assert(0); + } + + // + // Move to the next entry. + pSampleBuffer += 16; + } + + DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ..Packet length is %"_U32BITARG_" bytes.\n", packetSize)); + + *length = packetSize; + + // + // Always track packet number and packet position. + UInt16 thePacketDataLen = pPacketOutBuf - endOfMetaInfo; + htcb->fCurrentPacketNumber++; + htcb->fCurrentPacketPosition += thePacketDataLen; + + // + // If this is RTP-Meta-Info, well then update the fields we haven't updated yet!!!!!!! + if (htcb->fRTPMetaInfoFieldArray != NULL) + { + // + // If this is an RTP-Meta-Info packet, and there is no 'md' field, we shouldn't + // send the media data in the packet, so strip it off. + if (htcb->fRTPMetaInfoFieldArray[RTPMetaInfoPacket::kMediaDataField] == RTPMetaInfoPacket::kFieldNotUsed) + *length -= thePacketDataLen; + else + { + // If we do have md, make sure to put the right length in the right place + thePacketDataLen = htons(thePacketDataLen); + COPY_WORD(endOfMetaInfo - 2, &thePacketDataLen); + } + } + + // + // The packet has been generated. + return err; +} + +void QTHintTrack::WriteMetaInfoField( RTPMetaInfoPacket::FieldIndex inFieldIndex, + RTPMetaInfoPacket::FieldID inFieldID, + void* inFieldData, UInt32 inFieldLen, char** ioBuffer) +{ + if (inFieldID == RTPMetaInfoPacket::kUncompressed) + { + // + // Write an uncompressed field + RTPMetaInfoPacket::FieldName theName = htons(RTPMetaInfoPacket::GetFieldNameForIndex(inFieldIndex)); + COPY_WORD(*ioBuffer, &theName); + (*ioBuffer)+=2; + UInt16 theLen = htons((UInt16)inFieldLen); + COPY_WORD(*ioBuffer, &theLen); + (*ioBuffer)+=2; + } + else + { + // + // Write a compressed field + UInt8 theID = (UInt8)inFieldID; + theID |= 0x80; + COPY_BYTE(*ioBuffer, &theID); + (*ioBuffer)+=1; + UInt8 theLenByte = (UInt8)inFieldLen; + COPY_BYTE(*ioBuffer, &theLenByte); + (*ioBuffer)+=1; + } + + if (inFieldData != NULL) + { + ::memcpy(*ioBuffer, inFieldData, inFieldLen); + (*ioBuffer)+=inFieldLen; + } +} + + + + + +// ------------------------------------- +// Debugging functions +// +void QTHintTrack::DumpTrack(void) +{ + // + // Dump the QTTrack class. + QTTrack::DumpTrack(); + + // + // Dump the sub-atoms of this track. + if( fHintInfoAtom != NULL ) + fHintInfoAtom->DumpAtom(); +} diff --git a/QTFileLib/QTHintTrack.h b/QTFileLib/QTHintTrack.h new file mode 100644 index 0000000..14161f5 --- /dev/null +++ b/QTFileLib/QTHintTrack.h @@ -0,0 +1,243 @@ +/* + * + * @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@ + * + */ +// +// QTHintTrack: +// The central point of control for a hint track in a QTFile. + +#ifndef QTHintTrack_H +#define QTHintTrack_H + + +// +// Includes +#include "QTTrack.h" +#include "QTAtom_hinf.h" +#include "QTAtom_tref.h" +#include "RTPMetaInfoPacket.h" +#include "MyAssert.h" + + +// +// External classes +class QTFile; +class QTAtom_stsc_SampleTableControlBlock; +class QTAtom_stts_SampleTableControlBlock; + + +class QTHintTrackRTPHeaderData { + + public: + UInt16 rtpHeaderBits; + UInt16 rtpSequenceNumber; + SInt32 relativePacketTransmissionTime; + UInt16 hintFlags; + UInt16 dataEntryCount; + UInt32 tlvSize; + SInt32 tlvTimestampOffset; //'rtpo' TLV which is the timestamp offset for this packet +}; + +// +// Class state cookie +class QTHintTrack_HintTrackControlBlock { + +public: + // + // Constructor and destructor. + QTHintTrack_HintTrackControlBlock(QTFile_FileControlBlock * FCB = NULL); + virtual ~QTHintTrack_HintTrackControlBlock(void); + + // + // If you are moving around randomly (seeking), you should call this to reset + // caches + void Reset(); + + // + // If you want this HTCB to build RTP Meta Info packets, + // tell it which fields to add, and also which IDs to assign, by passing + // in an array of RTPMetaInfoPacket::kNumFields size, with all the right info + void SetupRTPMetaInfo(RTPMetaInfoPacket::FieldID* inFieldArray, Bool16 isVideo) + { Assert(fRTPMetaInfoFieldArray == NULL); fRTPMetaInfoFieldArray = inFieldArray; + fIsVideo = isVideo; + } + + // + // File control block + QTFile_FileControlBlock *fFCB; + + // + // Sample Table control blocks + QTAtom_stsc_SampleTableControlBlock fstscSTCB; + QTAtom_stts_SampleTableControlBlock fsttsSTCB; + + // + // Sample cache + UInt32 fCachedSampleNumber; + char * fCachedSample; + UInt32 fCachedSampleSize, fCachedSampleLength; + + // + // Sample (description) cache + UInt32 fCachedHintTrackSampleNumber, fCachedHintTrackSampleOffset; + char * fCachedHintTrackSample; + UInt32 fCachedHintTrackSampleLength; + UInt32 fCachedHintTrackBufferLength; + + UInt16 fLastPacketNumberFetched; // for optimizing Getting a packet from a cached sample + char* fPointerToNextPacket; // after we get one, we point the next at this... + + // + // To support RTP-Meta-Info payload + RTPMetaInfoPacket::FieldID* fRTPMetaInfoFieldArray; + UInt32 fSyncSampleCursor; // Where are we in the sync sample table? + Bool16 fIsVideo; // so that we know what to do with the frame type field + UInt64 fCurrentPacketNumber; + UInt64 fCurrentPacketPosition; + + SInt32 fMediaTrackRefIndex; + QTAtom_stsc_SampleTableControlBlock * fMediaTrackSTSC_STCB; + +}; + + +// +// QTHintTrack class +class QTHintTrack : public QTTrack { + +public: + // + // Constructors and destructor. + QTHintTrack(QTFile * File, QTFile::AtomTOCEntry * trakAtom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTHintTrack(void); + + + // + // Initialization functions. + virtual ErrorCode Initialize(void); + + Bool16 IsHintTrackInitialized() { return fHintTrackInitialized; } + + // + // Accessors. + ErrorCode GetSDPFileLength(int * Length); + char * GetSDPFile(int * Length); + + inline UInt64 GetTotalRTPBytes(void) { return fHintInfoAtom ? fHintInfoAtom->GetTotalRTPBytes() : 0; } + inline UInt64 GetTotalRTPPackets(void) { return fHintInfoAtom ? fHintInfoAtom->GetTotalRTPPackets() : 0; } + + inline UInt32 GetFirstRTPTimestamp(void) { return fFirstRTPTimestamp; } + inline void SetAllowInvalidHintRefs(Bool16 inAllowInvalidHintRefs) { fAllowInvalidHintRefs = inAllowInvalidHintRefs; } + + // + // Sample functions + Bool16 GetSamplePtr(UInt32 SampleNumber, char ** Buffer, UInt32 * Length, + QTHintTrack_HintTrackControlBlock * HTCB); + + // + // Packet functions + inline UInt32 GetRTPTimescale(void) { return fRTPTimescale; } + + inline UInt32 GetRTPTimestampRandomOffset(void) { return fTimestampRandomOffset; } + + inline UInt16 GetRTPSequenceNumberRandomOffset(void) { return fSequenceNumberRandomOffset; } + + ErrorCode GetNumPackets(UInt32 SampleNumber, UInt16 * NumPackets, + QTHintTrack_HintTrackControlBlock * HTCB = NULL); + + // + // This function will build an RTP-Meta-Info packet if the last argument + // is non-NULL. Some caveats apply to maximize performance of this operation: + // + // 1. If the "md" (media data) field is desired, please put it at the end. + // + // 2. If you want to use compressed fields, pass in the field ID in the first + // byte of the TwoCharConst. Also set the high bit to indicate that this + // is a compressed field ID. + // + // Supported fields: tt, md, ft, pp, pn, sq + ErrorCode GetPacket(UInt32 SampleNumber, UInt16 PacketNumber, + char * Buffer, UInt32 * Length, + Float64 * TransmitTime, + Bool16 dropBFrames, + Bool16 dropRepeatPackets = false, + UInt32 SSRC = 0, + QTHintTrack_HintTrackControlBlock * HTCB = NULL); + + inline ErrorCode GetSampleData( QTHintTrack_HintTrackControlBlock * htcb, char **buffPtr, char **ppPacketBufOut, UInt32 sampleNumber, UInt16 packetNumber, UInt32 buffOutLen ); + + // + // Debugging functions. + virtual void DumpTrack(void); + + // only reliable after all of the packets have been played + // any hint packet may reference another media track and we don't know until all have been played. + inline SInt16 GetHintTrackType(void) { return fHintType; } + +protected: + + enum + { + kRepeatPacketMask = 0x0001, + kBFrameBitMask = 0x0002 + }; + + enum + { + kUnknown = 0, + kOptimized = -1, + kUnoptimized = 1 + }; + + enum + { + kMaxHintTrackRefs = 1024 + }; + + // + // Protected member variables. + QTAtom_hinf *fHintInfoAtom; + QTAtom_tref *fHintTrackReferenceAtom; + + QTTrack **fTrackRefs; + + UInt32 fMaxPacketSize; + UInt32 fRTPTimescale, fFirstRTPTimestamp; + UInt32 fTimestampRandomOffset; + UInt16 fSequenceNumberRandomOffset; + Bool16 fHintTrackInitialized; + SInt16 fHintType; + Float64 fFirstTransmitTime; + Bool16 fAllowInvalidHintRefs; + // + // Used by GetPacket for RTP-Meta-Info payload stuff + void WriteMetaInfoField( RTPMetaInfoPacket::FieldIndex inFieldIndex, + RTPMetaInfoPacket::FieldID inFieldID, + void* inFieldData, UInt32 inFieldLen, char** ioBuffer); + + inline QTTrack::ErrorCode GetSamplePacketPtr( char ** samplePacketPtr, UInt32 sampleNumber, UInt16 packetNumber, QTHintTrackRTPHeaderData &hdrData, QTHintTrack_HintTrackControlBlock & htcb); + inline void GetSamplePacketHeaderVars( char *samplePacketPtr,char *maxBuffPtr, QTHintTrackRTPHeaderData &hdrData ); +}; + +#endif // QTHintTrack_H diff --git a/QTFileLib/QTRTPFile.cpp b/QTFileLib/QTRTPFile.cpp new file mode 100644 index 0000000..ec6ee95 --- /dev/null +++ b/QTFileLib/QTRTPFile.cpp @@ -0,0 +1,1544 @@ +/* + * + * @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@ + * + */ +// +// QTRTPFile: +// An interface to QTFile for TimeShare. +// +// htons and friends are macros and should not include the global specifier :: + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include + +#include "OSMutex.h" + +#include "QTFile.h" + +#include "QTTrack.h" +#include "QTHintTrack.h" + +#include "QTRTPFile.h" +#include "OSMemory.h" + + +#define QT_PROFILE 0 + + +#if QT_PROFILE +#include "StopWatch.h" +#endif + + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + + +// +// This is a declaration of what RTP-Meta-Info fields are supported by QTFileLib. +// The actual support goes into QTHintTrack.h / .cpp +// +// Current fields are: kPacketPosField, kTransTimeField, kFrameTypeField, kPacketNumField, kSeqNumField, kMediaDataField +// See RTPMetaInfoPacket.h for details. +const RTPMetaInfoPacket::FieldID QTRTPFile::kMetaInfoFields[] = + { RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed }; + + +// ------------------------------------- +// Protected cache functions and variables. +// +OSMutex *QTRTPFile::gFileCacheMutex, + *QTRTPFile::gFileCacheAddMutex; +QTRTPFile::RTPFileCacheEntry *QTRTPFile::gFirstFileCacheEntry = NULL; + +void QTRTPFile::Initialize(void) +{ + QTRTPFile::gFileCacheMutex = NEW OSMutex(); + QTRTPFile::gFileCacheAddMutex = NEW OSMutex(); +} + + +QTRTPFile::ErrorCode QTRTPFile::new_QTFile(const char * filePath, QTFile ** theQTFile, Bool16 debugFlag, Bool16 deepDebugFlag) +{ + // Temporary vars + QTFile::ErrorCode rcFile; + + // General vars + OSMutexLocker fileCacheAddMutex(QTRTPFile::gFileCacheAddMutex); + + QTRTPFile::RTPFileCacheEntry *fileCacheEntry; + + + // + // Find and return the QTFile object out of our cache, if it exists. + if( QTRTPFile::FindAndRefcountFileCacheEntry(filePath, &fileCacheEntry) ) + { + fileCacheAddMutex.Unlock(); + + fileCacheEntry->InitMutex->Lock(); // Guaranteed to block as the mutex + // is acquired before it is added to + // the list. + fileCacheEntry->InitMutex->Unlock();// Because we don't actually need it. + + *theQTFile = fileCacheEntry->File; + Assert(*theQTFile); + + return errNoError; + } + + + // + // Construct our file object. + *theQTFile = NEW QTFile(debugFlag, deepDebugFlag); + if( *theQTFile == NULL ) + return errInternalError; + + // + // Open the specified movie. + if( (rcFile = (*theQTFile)->Open(filePath)) != QTFile::errNoError ) + { + delete *theQTFile; + + switch( rcFile ) + { + case errFileNotFound: + return errFileNotFound; + + case errInvalidQuickTimeFile: + return errInvalidQuickTimeFile; + + default: + return errInternalError; + } + } + + + // + // Add this file to our cache and release the global add mutex. + QTRTPFile::AddFileToCache(filePath, &fileCacheEntry); // Grabs InitMutex. + fileCacheAddMutex.Unlock(); + + + // + // Finish setting up the fileCacheEntry. + if( fileCacheEntry != NULL ) + { // it may not have been cached.. + fileCacheEntry->File = *theQTFile; + fileCacheEntry->InitMutex->Unlock(); + } + + // + // Return the file object. + return errNoError; +} + + +void QTRTPFile::delete_QTFile(QTFile * theQTFile) +{ + + // General vars + OSMutexLocker fileCacheMutex(QTRTPFile::gFileCacheMutex); + QTRTPFile::RTPFileCacheEntry *listEntry; + + + // + // Find the specified cache entry. + for ( listEntry = QTRTPFile::gFirstFileCacheEntry; listEntry != NULL; listEntry = listEntry->NextEntry ) + { + // + // Check for matches. + if( listEntry->File != theQTFile ) + continue; + + theQTFile->DecBufferUserCount(); + // + // Delete the object if the reference count has dropped to zero. + if ( --listEntry->ReferenceCount == 0 ) + { + // + // Delete the file. + if( theQTFile != NULL ) + { delete theQTFile; + } + + // + // Free our other vars. + if( listEntry->InitMutex != NULL ) + delete listEntry->InitMutex; + + if( listEntry->fFilename != NULL ) + delete [] listEntry->fFilename; + + // + // Remove this entry from the list. + if( listEntry->PrevEntry != NULL ) + listEntry->PrevEntry->NextEntry = listEntry->NextEntry; + + if( listEntry->NextEntry != NULL ) + listEntry->NextEntry->PrevEntry = listEntry->PrevEntry; + + if( QTRTPFile::gFirstFileCacheEntry == listEntry ) + QTRTPFile::gFirstFileCacheEntry = listEntry->NextEntry; + + delete listEntry; + } + + // + // Return. + return; + } + + // + // The object was not in the cache. Delete it + if( theQTFile != NULL ) + { delete theQTFile; + } +} + + +void QTRTPFile::AddFileToCache(const char *inFilename, QTRTPFile::RTPFileCacheEntry ** newListEntry) +{ + // General vars + OSMutexLocker fileCacheMutex(QTRTPFile::gFileCacheMutex); + QTRTPFile::RTPFileCacheEntry* listEntry; + QTRTPFile::RTPFileCacheEntry* lastListEntry; + + + // + // Add this track object to our track list. + (*newListEntry) = NEW QTRTPFile::RTPFileCacheEntry(); + if( (*newListEntry) == NULL ) + return; + + (*newListEntry)->InitMutex = NEW OSMutex(); + if( (*newListEntry)->InitMutex == NULL ) { + delete (*newListEntry); + *newListEntry = NULL; + return; + } + (*newListEntry)->InitMutex->Lock(); + + (*newListEntry)->fFilename = NEW char[(::strlen(inFilename) + 2)]; + ::strcpy((*newListEntry)->fFilename, inFilename); + (*newListEntry)->File = NULL; + + (*newListEntry)->ReferenceCount = 1; + + (*newListEntry)->PrevEntry = NULL; + (*newListEntry)->NextEntry = NULL; + + // + // Make this the first entry if there are no entries, otherwise we need to + // find out where this file fits in the list and insert it there. + if( QTRTPFile::gFirstFileCacheEntry == NULL ) + { + QTRTPFile::gFirstFileCacheEntry = (*newListEntry); + } + else + { + // + // Go through the cache list until we find an inode number greater than + // the one that we have now. Insert it in the list when we find this. + for( listEntry = lastListEntry = QTRTPFile::gFirstFileCacheEntry; listEntry != NULL; listEntry = listEntry->NextEntry ) { + // + // This is the last list entry that we saw (useful for later). + lastListEntry = listEntry; + + // + // Skip this entry if this inode number is smaller than the one + // for our new entry. + if( strcmp(listEntry->fFilename,inFilename) < 0 ) + continue; + + // + // We've found a larger inode; insert this one in the list. + if( listEntry->PrevEntry == NULL ) + QTRTPFile::gFirstFileCacheEntry = (*newListEntry); + else + listEntry->PrevEntry->NextEntry = (*newListEntry); + + (*newListEntry)->PrevEntry = listEntry->PrevEntry; + + listEntry->PrevEntry = (*newListEntry); + + (*newListEntry)->NextEntry = listEntry; + + return; + } + + // + // We fell out of our loop; this means that we are the largest inode + // in the list; add ourselves to the end of the list. + if( lastListEntry == NULL ) { // this can't happen, but.. + QTRTPFile::gFirstFileCacheEntry = (*newListEntry); + } + else + { + lastListEntry->NextEntry = (*newListEntry); + (*newListEntry)->PrevEntry = lastListEntry; + } + } +} + +Bool16 QTRTPFile::FindAndRefcountFileCacheEntry(const char *inFilename, QTRTPFile::RTPFileCacheEntry **cacheEntry) +{ + // General vars + OSMutexLocker fileCacheMutex(QTRTPFile::gFileCacheMutex); + QTRTPFile::RTPFileCacheEntry *listEntry; + + + // + // Find the specified cache entry. + for( listEntry = QTRTPFile::gFirstFileCacheEntry; listEntry != NULL; listEntry = listEntry->NextEntry ) + { + // + // Check for matches. + if( ::strcmp(listEntry->fFilename, inFilename) != 0 ) + continue; + + // + // Update the reference count and set the return value. + listEntry->ReferenceCount++; + + *cacheEntry = listEntry; + + // + // Return. + return true; + } + + // + // The search failed. + return false; +} + + + +// ------------------------------------- +// Constructors and destructors +// +QTRTPFile::QTRTPFile(Bool16 debugFlag, Bool16 deepDebugFlag) + : fDebug(debugFlag) + , fDeepDebug(deepDebugFlag) + , fFile(NULL) + , fFCB(NULL) + , fNumHintTracks(0) + , fFirstTrack(NULL) + , fLastTrack(NULL) + , fCurSeekTrack(NULL) + , fSDPFile(NULL) + , fSDPFileLength(0) + , fNumSkippedSamples(0) + , fRequestedSeekTime(0.0) + , fSeekTime(0.0) + , fLastPacketTrack(NULL) + , fBytesPerSecond(0) + , fHasRTPMetaInfoFieldArray(false) + , fWasLastSeekASeekToPacketNumber(false) + , fDropRepeatPackets(false) + , fErr(errNoError) +{ + fFCB = NEW QTFile_FileControlBlock(); +} + + +QTRTPFile::~QTRTPFile(void) +{ + // + // Free our track list (and the associated tracks) + RTPTrackListEntry* trackEntry = fFirstTrack; + RTPTrackListEntry* nextTrackEntry; + + + while( trackEntry != NULL ) + { + nextTrackEntry = trackEntry->NextTrack; + + // + // Delete this track entry and move to the next one. + if( trackEntry->HTCB != NULL ) + delete trackEntry->HTCB; + + delete trackEntry; + + trackEntry = nextTrackEntry; + + } + + // + // Free our variables + if( fSDPFile != NULL ) + delete[] fSDPFile; + + this->delete_QTFile(fFile); + + if( fFCB != NULL ) + delete fFCB; +} + + + +// ------------------------------------- +// Initialization functions. +// +QTRTPFile::ErrorCode QTRTPFile::Initialize(const char * filePath) +{ + // Temporary vars + QTRTPFile::ErrorCode rc; + + // General vars + QTTrack *track; + + + // + // Create our file object. + rc = this->new_QTFile(filePath, &fFile, fDebug, fDeepDebug); + if ( rc != errNoError ) + { + fFile = NULL; + return rc; + } + + + // + // Iterate through all of the tracks, adding hint tracks to our list. + for ( track = NULL; fFile->NextTrack(&track, track); ) + { + // General vars + QTHintTrack *hintTrack; + RTPTrackListEntry *listEntry; + + // + // Skip over anything that's *not* a hint track. + if( !fFile->IsHintTrack(track) ) + continue; + + hintTrack = (QTHintTrack *)track; + + // + // Add this track object to our track list. + listEntry = NEW RTPTrackListEntry(); + if( listEntry == NULL ) + return fErr = errNoHintTracks; + + listEntry->TrackID = track->GetTrackID(); + listEntry->HintTrack = hintTrack; + + listEntry->HTCB = NEW QTHintTrack_HintTrackControlBlock(fFCB); + listEntry->IsTrackActive = false; + listEntry->IsPacketAvailable = false; + listEntry->QualityLevel = kAllPackets; + + listEntry->Cookie1 = NULL; + listEntry->Cookie2 = 0; + listEntry->SSRC = 0; + + listEntry->BaseSequenceNumberRandomOffset = 0; + listEntry->FileSequenceNumberRandomOffset = 0; + listEntry->LastSequenceNumber = 0; + listEntry->SequenceNumberAdditive = 0; + + listEntry->BaseTimestampRandomOffset = 0; + listEntry->FileTimestampRandomOffset = 0; + + listEntry->CurSampleNumber = 0; + listEntry->ConsecutivePFramesSent = 0; + listEntry->TargetPercentage = 0; + listEntry->SampleToSeekTo = 0; + listEntry->LastSyncSampleNumber = 0; + listEntry->NextSyncSampleNumber = 0; + listEntry->NumPacketsInThisSample = 0; + listEntry->CurPacketNumber = 0; + + listEntry->CurPacketTime = 0.0; + listEntry->CurPacketLength = 0; + + listEntry->NextTrack = NULL; + + if( fFirstTrack == NULL ) { + fFirstTrack = fLastTrack = listEntry; + } else { + fLastTrack->NextTrack = listEntry; + fLastTrack = listEntry; + } + + // One more track.. + fNumHintTracks++; + } + + // If there aren't any hint tracks, there's no way we can stream this movie, + // so notify the caller + if (fNumHintTracks == 0) + return fErr = errNoHintTracks; + + + + // The RTP file has been initialized. + return errNoError; +} + + + +// ------------------------------------- +// Accessors +// +Float64 QTRTPFile::GetMovieDuration(void) +{ + return fFile->GetDurationInSeconds(); +} + +UInt64 QTRTPFile::GetAddedTracksRTPBytes(void) +{ + // Temporary vars + RTPTrackListEntry *curEntry; + + // General vars + UInt64 totalRTPBytes = 0; + + + // + // Go through all of the tracks, adding up the size of the RTP bytes + // for all active tracks. + for( curEntry = fFirstTrack; + curEntry != NULL; + curEntry = curEntry->NextTrack + ) + { + // + // We only want active tracks. + if( !curEntry->IsTrackActive ) + continue; + + // + // Add this length to our count. + totalRTPBytes += curEntry->HintTrack->GetTotalRTPBytes(); + } + + // + // Return the size. + return totalRTPBytes; +} + +char * QTRTPFile::GetSDPFile(int * sdpFileLength) +{ + // Temporary vars + RTPTrackListEntry* curEntry; + UInt32 tempAtomType; + + // General vars + QTFile::AtomTOCEntry* globalSDPTOCEntry; + Bool16 haveGlobalSDPAtom = false; + char sdpRangeLine[256]; + char* pSDPFile; + + + // + // Return our cached SDP file if we have one. + if ( fSDPFile != NULL) + { + *sdpFileLength = fSDPFileLength; + return fSDPFile; + } + + // + // Build our range header. + qtss_sprintf(sdpRangeLine, "a=range:npt=0-%10.5f\r\n", this->GetMovieDuration()); + + + // + // Figure out how long the SDP file is going to be. + fSDPFileLength = ::strlen(sdpRangeLine); + + for ( curEntry = fFirstTrack; + curEntry != NULL; + curEntry = curEntry->NextTrack + ) + { + // Temporary vars + int trackSDPLength; + + + // + // Get the length of this track's SDP file. + if( curEntry->HintTrack->GetSDPFileLength(&trackSDPLength) != QTTrack::errNoError ) + continue; + + // + // Add it to our count. + fSDPFileLength += trackSDPLength; + } + + // + // See if this movie has a global SDP atom. + if( fFile->FindTOCEntry("moov:udta:hnti:rtp ", &globalSDPTOCEntry, NULL) ) + { + // + // Verify that this is an SDP atom. + fFile->Read(globalSDPTOCEntry->AtomDataPos, (char *)&tempAtomType, 4); + + if ( ntohl(tempAtomType) == FOUR_CHARS_TO_INT('s', 'd', 'p', ' ') ) + { + haveGlobalSDPAtom = true; + fSDPFileLength += (UInt32) (globalSDPTOCEntry->AtomDataLength - 4); + } + } + + // + // Build the SDP file. + fSDPFile = pSDPFile = NEW char[fSDPFileLength + 1]; + if( fSDPFile == NULL ) + { fErr = errInternalError; + return NULL; + } + + if( haveGlobalSDPAtom ) + { + fFile->Read(globalSDPTOCEntry->AtomDataPos + 4, pSDPFile, (UInt32) (globalSDPTOCEntry->AtomDataLength - 4) ); + pSDPFile += globalSDPTOCEntry->AtomDataLength - 4; + } + + ::memcpy(pSDPFile, sdpRangeLine, ::strlen(sdpRangeLine)); + + pSDPFile += ::strlen(sdpRangeLine); + + for ( curEntry = fFirstTrack; + curEntry != NULL; + curEntry = curEntry->NextTrack + ) + { + // Temporary vars + char *trackSDP; + int trackSDPLength; + + + // + // Get this track's SDP file and add it to our buffer. + trackSDP = curEntry->HintTrack->GetSDPFile(&trackSDPLength); + if( trackSDP == NULL ) + continue; + + ::memcpy(pSDPFile, trackSDP, trackSDPLength); + delete [] trackSDP;//ARGH! GetSDPFile allocates the pointer that is being returned. + pSDPFile += trackSDPLength; + } + + + // + // Return the (cached) SDP file. + *sdpFileLength = fSDPFileLength; + fSDPFile[fSDPFileLength] = 0; + + return fSDPFile; +} + +char* QTRTPFile::GetMoviePath() +{ + Assert( fFile ); + if (!fFile) + { fErr = errInternalError; + return NULL; + } + else + return fFile->GetMoviePath(); +} + + +// ------------------------------------- +// track functions +// +QTRTPFile::ErrorCode QTRTPFile::AddTrack(UInt32 trackID, Bool16 useRandomOffset) +{ + // General vars + RTPTrackListEntry *trackEntry = NULL; + + + // + // Find this track. + if( !this->FindTrackEntry(trackID, &trackEntry) ) + return fErr = errTrackIDNotFound; + + { + OSMutexLocker locker(fFile->GetMutex()); + // + // Initialize this track. + trackEntry->HintTrack->SetAllowInvalidHintRefs(fAllowInvalidHintRefs); + if( trackEntry->HintTrack->Initialize() != QTTrack::errNoError ) + return fErr = errInternalError; + } + + // + // Set up the sequence number and timestamp offsets. + if( useRandomOffset ) { + trackEntry->BaseSequenceNumberRandomOffset = (UInt16)rand(); + trackEntry->BaseTimestampRandomOffset = (UInt32)rand(); + } else { + trackEntry->BaseSequenceNumberRandomOffset = 0; + trackEntry->BaseTimestampRandomOffset = 0; + } + + trackEntry->FileSequenceNumberRandomOffset = trackEntry->HintTrack->GetRTPSequenceNumberRandomOffset(); + trackEntry->FileTimestampRandomOffset = trackEntry->HintTrack->GetRTPTimestampRandomOffset(); + + trackEntry->LastSequenceNumber = 0; + trackEntry->SequenceNumberAdditive = 0; + + // + // Setup RTP-Meta-Info stuff for this track. + + // + // This track is now active. + trackEntry->IsTrackActive = true; + + // + // The track has been added. + return errNoError; +} + +Float64 QTRTPFile::GetTrackDuration(UInt32 trackID) +{ + // General vars + RTPTrackListEntry *trackEntry = NULL; + + + // + // Find this track. + if( !this->FindTrackEntry(trackID, &trackEntry) ) + return 0; + + // + // Return the duration. + return trackEntry->HintTrack->GetDurationInSeconds(); +} + +UInt32 QTRTPFile::GetTrackTimeScale(UInt32 trackID) +{ + // General vars + RTPTrackListEntry *trackEntry = NULL; + + + // + // Find this track. + if( !this->FindTrackEntry(trackID, &trackEntry) ) + return 0; + + // + // Return the duration. + return (UInt32)trackEntry->HintTrack->GetTimeScale(); +} + +void QTRTPFile::SetTrackSSRC(UInt32 trackID, UInt32 SSRC) +{ + // General vars + RTPTrackListEntry *trackEntry = NULL; + + + // + // Find this track. + if( !this->FindTrackEntry(trackID, &trackEntry) ) + return; + + // + // Set the SSRC. + trackEntry->SSRC = SSRC; +} + +void QTRTPFile::SetTrackCookies(UInt32 TrackID, void * Cookie1, UInt32 Cookie2) +{ + // General vars + RTPTrackListEntry *trackEntry = NULL; + + + // + // Find this track. + if( !this->FindTrackEntry(TrackID, &trackEntry) ) + return; + + // + // Set the cookie. + trackEntry->Cookie1 = Cookie1; + trackEntry->Cookie2 = Cookie2; +} + +void QTRTPFile::SetTrackQualityLevel(RTPTrackListEntry* inEntry, UInt32 inNewQualityLevel) +{ + if (inNewQualityLevel < kAllPackets) + inNewQualityLevel = kAllPackets; + if (inNewQualityLevel > kKeyFramesPlusOneP) + inNewQualityLevel = kKeyFramesPlusOneP; + + if (inNewQualityLevel != inEntry->QualityLevel) + { + inEntry->QualityLevel = inNewQualityLevel; + + if (inEntry->QualityLevel == k75PercentPFrames) + inEntry->TargetPercentage = 75; + else if (inEntry->QualityLevel == k50PercentPFrames) + inEntry->TargetPercentage = 50; + else if (inEntry->QualityLevel == k25PercentPFrames) + inEntry->TargetPercentage = 25; + } +} + +void QTRTPFile::SetTrackRTPMetaInfo(UInt32 TrackID, RTPMetaInfoPacket::FieldID* inFieldArray, Bool16 isVideo) +{ + // General vars + RTPTrackListEntry *trackEntry = NULL; + + + // + // Find this track. + if( !this->FindTrackEntry(TrackID, &trackEntry) ) + return; + + // + // Set the cookie. + trackEntry->HTCB->SetupRTPMetaInfo(inFieldArray, isVideo); + fHasRTPMetaInfoFieldArray = true; + fDropRepeatPackets = false; //force repeat packets on meta info connections. +} + +// ------------------------------------- +// BytesPerSecond +UInt32 QTRTPFile::GetBytesPerSecond(void) +{ + if (NULL == fFile) + return 0; + // Must be a SInt64 bc Win32 doesn't implement UInt64 -> Float64 + SInt64 totalBytes = (SInt64)QTRTPFile::GetAddedTracksRTPBytes(); + +#ifdef USE_RTP_TRACK_DURATION + Float64 duration = 0; + RTPTrackListEntry* curEntry=NULL; + Float64 maxDuration=0; + + // + // Go through all of the tracks, getting duration + for( curEntry = fFirstTrack; + curEntry != NULL; + curEntry = curEntry->NextTrack + ) + { + // + // We only want active tracks. + if( !curEntry->IsTrackActive ) + continue; + + duration = curEntry->HintTrack->GetDurationInSeconds(); + + if (duration > maxDuration) + maxDuration = duration; + } + + duration= maxDuration; +#else + Float64 duration = fFile->GetDurationInSeconds(); +#endif + + UInt32 bytesPerSecond = 0; + if (duration > 0) + bytesPerSecond = (UInt32) ( (Float64) totalBytes / (Float64) duration); + + return bytesPerSecond; +} + +// ------------------------------------- +void QTRTPFile::AllocatePrivateBuffers(UInt32 inUnitSizeInK, UInt32 inNumBuffSizeUnits, UInt32 inMaxBitRateBuffSizeInBlocks) +{ + + fFCB->EnableCacheBuffers(true); + UInt32 bytesPerSecond = this->GetBytesPerSecond(); + UInt32 bitRate = bytesPerSecond * 8; + fFCB->AdjustDataBufferBitRate(inUnitSizeInK, bitRate, inNumBuffSizeUnits, inMaxBitRateBuffSizeInBlocks); + +} + +// ------------------------------------- +// Packet functions +// +QTRTPFile::ErrorCode QTRTPFile::Seek(Float64 seekToTime, Float64 maxBackupTime) +{ + //fHasRTPMetaInfoFieldArray = true; + // General vars + RTPTrackListEntry *listEntry = NULL; + Float64 syncToTime = seekToTime; + + if (fErr == errCallAgain) + { + fErr = this->ScanToCorrectSample(); + return fErr; + } + + // + // This is Seek, not SeekToPacketNumber. + fWasLastSeekASeekToPacketNumber = false; + + + // + // Find the earliest sync sample and sync all of the tracks to that + // sample. + for ( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack ) + { + // General vars + SInt32 mediaTime; + UInt32 newSampleNumber; + UInt32 newSyncSampleNumber; + UInt32 newSampleMediaTime; + Float64 newSampleTime; + + + // + // Only consider active tracks. + if( !listEntry->IsTrackActive ) + continue; + + // + // Compute the media time and get the sample at that time. + mediaTime = (SInt32)(seekToTime * listEntry->HintTrack->GetTimeScale()); + mediaTime -= listEntry->HintTrack->GetFirstEditMediaTime(); + if ( mediaTime < 0 ) + mediaTime = 0; + + if ( !listEntry->HintTrack->GetSampleNumberFromMediaTime(mediaTime, &newSampleNumber, &listEntry->HTCB->fsttsSTCB) ) + continue; // This track is probably done playing. + + // + // Find the nearest (moving backwards in time) keyframe. + listEntry->HintTrack->GetPreviousSyncSample(newSampleNumber, &newSyncSampleNumber); + if ( newSampleNumber == newSyncSampleNumber ) + continue; + + // + // Figure out what time this sample is at. + if( !listEntry->HintTrack->GetSampleMediaTime(newSyncSampleNumber, &newSampleMediaTime, &listEntry->HTCB->fsttsSTCB) ) + return errInvalidQuickTimeFile; + + newSampleMediaTime += listEntry->HintTrack->GetFirstEditMediaTime(); + newSampleTime = (Float64)newSampleMediaTime * listEntry->HintTrack->GetTimeScaleRecip(); + + // + // Figure out if this is the time that we need to sync to. + if( newSampleTime < syncToTime ) + syncToTime = newSampleTime; + + } + + if (0 == seekToTime) // optimize the first read to align to the first chunk + { + UInt64 firstChunkOffset = ~ (UInt64)0;// max UInt64 + UInt64 trackChunkOffset = 0; + QTTrack *track = NULL; + for (; fFile->NextTrack(&track, track) ; ) + { + if (track == NULL) + continue; + + if ( !track->IsInitialized() && track->Initialize() != QTTrack::errNoError) + continue; + + track->ChunkOffset(1, &trackChunkOffset); + if ( (UInt64)trackChunkOffset < firstChunkOffset) + firstChunkOffset = (UInt64) trackChunkOffset; + } + + if (~(UInt64)0 != firstChunkOffset) + { + char junk[4]; + fFile->Read(firstChunkOffset, junk, sizeof(junk),fFCB); + } + } + + // + // Evaluate/Store the seek time + fRequestedSeekTime = seekToTime; + if( (seekToTime - syncToTime) <= maxBackupTime ) + fSeekTime = syncToTime; + else + fSeekTime = seekToTime; + + for ( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack ) + { + if( !listEntry->IsTrackActive ) + continue; + + listEntry->IsPacketAvailable = false; + + // + // Reset the sample table caches. + listEntry->HTCB->fstscSTCB.Reset(); + listEntry->HTCB->Reset(); + + // + // Compute the media time and get the sample at that time. + SInt32 mediaTime = (SInt32)(fSeekTime * listEntry->HintTrack->GetTimeScale()); + mediaTime -= listEntry->HintTrack->GetFirstEditMediaTime(); + if( mediaTime < 0 ) + mediaTime = 0; + + listEntry->SampleToSeekTo = 0; + if (!listEntry->HintTrack->GetSampleNumberFromMediaTime(mediaTime, &listEntry->SampleToSeekTo, &listEntry->HTCB->fsttsSTCB)) + continue; + + // + // If we are building meta-info packets, we have to build all the packets up until the destination + // point in the movie, we can't just skip there. So, start by jumping to the beginning of the movie, + // then PrefetchPacketForAllTracks will move to the right point in the movie + if (fHasRTPMetaInfoFieldArray) + { + mediaTime = 0; + mediaTime -= listEntry->HintTrack->GetFirstEditMediaTime(); + if( mediaTime < 0 ) + mediaTime = 0; + + listEntry->CurSampleNumber = 0; + if (!listEntry->HintTrack->GetSampleNumberFromMediaTime(mediaTime, &listEntry->CurSampleNumber, &listEntry->HTCB->fsttsSTCB)) + continue; + } + else + listEntry->CurSampleNumber = listEntry->SampleToSeekTo; + + // + // Clear our current packet information. + listEntry->NumPacketsInThisSample = 0; + listEntry->CurPacketNumber = 0; + + if (this->PrefetchNextPacket(listEntry, true)) + listEntry->IsPacketAvailable = true; + } + + // + // 'Forget' that we had a previous packet. + fLastPacketTrack = NULL; + + if (fHasRTPMetaInfoFieldArray) + { + fCurSeekTrack = fFirstTrack; + fErr = this->ScanToCorrectSample(); + return fErr; + } + + return errNoError; +} + +QTRTPFile::ErrorCode QTRTPFile::ScanToCorrectSample() +{ + UInt32 packetCount = 0; + // + // Prefetch a packet for all active tracks. + for( ; fCurSeekTrack != NULL; fCurSeekTrack = fCurSeekTrack->NextTrack ) + { + // + // Only fetch packets on active tracks. + if( !fCurSeekTrack->IsTrackActive ) + continue; + + // + // Scan through all the packets in this track until we get to the sample we want + while (fCurSeekTrack->CurSampleNumber < fCurSeekTrack->SampleToSeekTo) + { + if (packetCount > 100) + return errCallAgain; + else + packetCount++; + + if (!this->PrefetchNextPacket(fCurSeekTrack)) + { + fCurSeekTrack->IsPacketAvailable = false; + break; + } + } + + } + return errNoError; +} + +QTRTPFile::ErrorCode QTRTPFile::SeekToPacketNumber(UInt32 inTrackID, UInt64 inPacketNumber) +{ + if (fErr == errCallAgain) + { + fErr = this->ScanToCorrectPacketNumber(inTrackID, inPacketNumber); + return fErr; + } + + // + // We need to track this so that we don't use the sync sample table if we start thinnning + fWasLastSeekASeekToPacketNumber = true; + + for (RTPTrackListEntry *listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack ) + { + if( !listEntry->IsTrackActive ) + continue; + + listEntry->IsPacketAvailable = false; + + // + // Reset the sample table caches. + listEntry->HTCB->fstscSTCB.Reset(); + listEntry->HTCB->Reset(); + + // + // Jump to the beginning of each track + SInt32 mediaTime = 0; + mediaTime -= listEntry->HintTrack->GetFirstEditMediaTime(); + if( mediaTime < 0 ) + mediaTime = 0; + + listEntry->CurSampleNumber = 0; + if (!listEntry->HintTrack->GetSampleNumberFromMediaTime(mediaTime, &listEntry->CurSampleNumber, &listEntry->HTCB->fsttsSTCB)) + continue; + + // + // Clear our current packet information. + listEntry->NumPacketsInThisSample = 0; + listEntry->CurPacketNumber = 0; + + if (this->PrefetchNextPacket(listEntry, true)) + listEntry->IsPacketAvailable = true; + } + + if (inPacketNumber == 0) + return errNoError; + + fErr = this->ScanToCorrectPacketNumber(inTrackID, inPacketNumber); + return fErr; +} + +QTRTPFile::ErrorCode QTRTPFile::ScanToCorrectPacketNumber(UInt32 inTrackID, UInt64 inPacketNumber) +{ + int theLen = 0; + for (UInt32 packetCount = 0; packetCount < 100; packetCount++) + { + if ((fLastPacketTrack != NULL) && (fLastPacketTrack->TrackID == inTrackID) && (fLastPacketTrack->HTCB->fCurrentPacketNumber == inPacketNumber)) + { + UInt32 newSampleMediaTime; + // + // Figure out what time this sample is at. + if( !fLastPacketTrack->HintTrack->GetSampleMediaTime(fLastPacketTrack->CurSampleNumber, &newSampleMediaTime, &fLastPacketTrack->HTCB->fsttsSTCB) ) + return errInvalidQuickTimeFile; + + newSampleMediaTime += fLastPacketTrack->HintTrack->GetFirstEditMediaTime(); + fRequestedSeekTime = (Float64)newSampleMediaTime * fLastPacketTrack->HintTrack->GetTimeScaleRecip(); + //fLastPacketTrack = NULL; // So that when we next call GetNextPacket, we actually get the same packet + return errNoError; + } + + char* thePacket = NULL; + (void)this->GetNextPacket(&thePacket, &theLen); + + if (thePacket == NULL) + return errInvalidQuickTimeFile; + } + + return errCallAgain; +} + +UInt32 QTRTPFile::GetSeekTimestamp(UInt32 trackID) +{ + // General vars + RTPTrackListEntry *trackEntry = NULL; + UInt32 mediaTime; + UInt32 rtpTimestamp; + UInt32* timeStampP; + + + DEEP_DEBUG_PRINT(("Calculating RTP timestamp for track #%"_U32BITARG_" at time %.2f.\n", trackID, fRequestedSeekTime)); + + // + // Find this track. + if( !FindTrackEntry(trackID, &trackEntry) ) + return 0; + + if (trackEntry->CurPacket) // we have the packet + { + timeStampP = (UInt32 *)((char *)trackEntry->CurPacket + 4); + rtpTimestamp = ntohl(*timeStampP); + } + else + { + // + // Calculate the timestamp at this seek time. + mediaTime = (UInt32)(fRequestedSeekTime * trackEntry->HintTrack->GetTimeScale()); + if( trackEntry->HintTrack->GetRTPTimescale() == trackEntry->HintTrack->GetTimeScale() ) + rtpTimestamp = mediaTime; + else + rtpTimestamp = (UInt32)(mediaTime * (trackEntry->HintTrack->GetRTPTimescale() * trackEntry->HintTrack->GetTimeScaleRecip()) ); + + // + // Add the appropriate offsets. + rtpTimestamp += trackEntry->BaseTimestampRandomOffset + trackEntry->FileTimestampRandomOffset; + } + + // + // Return the RTP timestamp. + DEEP_DEBUG_PRINT(("..timestamp=%"_U32BITARG_"\n", rtpTimestamp)); + + return rtpTimestamp; +} + +Float64 QTRTPFile::GetFirstPacketTransmitTime() +{ + RTPTrackListEntry *listEntry = NULL; + Bool16 haveFirstPacketTime = false; + Float64 firstPacketTime = 0; + + // + // Figure out which track is going to produce the next packet. + for( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack ) + { + // + // Only look at active tracks that have packets. + if( !listEntry->IsTrackActive || !listEntry->IsPacketAvailable) + continue; + + // + // See if this track has a time earlier than our initial time. + if( (listEntry->CurPacketTime <= firstPacketTime) || !haveFirstPacketTime ) + { + haveFirstPacketTime = true; + firstPacketTime = listEntry->CurPacketTime; + } + } + return firstPacketTime; +} + +UInt16 QTRTPFile::GetNextTrackSequenceNumber(UInt32 trackID) +{ + // General vars + RTPTrackListEntry *trackEntry = NULL; + UInt16 rtpSequenceNumber; + + + // + // Find this track. + if( !this->FindTrackEntry(trackID, &trackEntry) ) + return 0; + + // + // Read the sequence number right out of the packet. + ::memcpy(&rtpSequenceNumber, (char *)trackEntry->CurPacket + 2, 2); + + return ntohs(rtpSequenceNumber); +} + +Float64 QTRTPFile::GetNextPacket(char ** outPacket, int * outPacketLength) +{ + // General vars + RTPTrackListEntry *listEntry = NULL; + + Bool16 haveFirstPacketTime = false; + Float64 firstPacketTime = 0.0; + RTPTrackListEntry *firstPacket = NULL; + + + // + // Clear the input. + *outPacket = NULL; + *outPacketLength = 0; + + // + // Prefetch the next packet of the track that the *last* packet came from. + if( fLastPacketTrack != NULL ) + { + if ( !this->PrefetchNextPacket(fLastPacketTrack) ) + fLastPacketTrack->IsPacketAvailable = false; + } + + // + // Figure out which track is going to produce the next packet. + for( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack ) + { + // + // Only look at active tracks that have packets. + if( !listEntry->IsTrackActive || !listEntry->IsPacketAvailable) + continue; + + // + // See if this track has a time earlier than our initial time. + if( (listEntry->CurPacketTime <= firstPacketTime) || !haveFirstPacketTime ) + { + haveFirstPacketTime = true; + firstPacketTime = listEntry->CurPacketTime; + firstPacket = listEntry; + } + } + + // + // Abort if we didn't find a packet. Either the movie is over, or there + // weren't any packets to begin with. + if( firstPacket == NULL ) + return 0.0; + + // + // Remember the sequence number of this packet. + firstPacket->LastSequenceNumber = ntohs(*(UInt16 *)((char *)firstPacket->CurPacket + 2)); + firstPacket->LastSequenceNumber -= (UInt16) (firstPacket->BaseSequenceNumberRandomOffset + firstPacket->FileSequenceNumberRandomOffset + firstPacket->SequenceNumberAdditive); + + // + // Return this packet. + fLastPacketTrack = firstPacket; + + *outPacket = firstPacket->CurPacket; + *outPacketLength = firstPacket->CurPacketLength; + + return firstPacket->CurPacketTime; +} + + + +// ------------------------------------- +// Protected member functions +// +Bool16 QTRTPFile::FindTrackEntry(UInt32 trackID, RTPTrackListEntry **trackEntry) +{ + // General vars + RTPTrackListEntry *listEntry; + + + // + // Find the specified track. + for( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack ) + { + // + // Check for matches. + if( listEntry->TrackID == trackID ) + { + *trackEntry = listEntry; + return true; + } + } + + // + // The search failed. + return false; +} + +SInt32 QTRTPFile::GetMovieHintType() +{ + SInt32 movieHintType= 0; + SInt32 trackHintType= 0; + for( RTPTrackListEntry *listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack ) + { + // + // Check for matches. + trackHintType = listEntry->HintTrack->GetHintTrackType(); + if (trackHintType > 0) // always set movie hint type to unoptimized if a track is unoptimized + { movieHintType = trackHintType; + + } + else if ( (movieHintType == 0) && trackHintType < 0) // only set optimized if movie type is uninitialized + { movieHintType = trackHintType; + } + } + + + return movieHintType; + +} + + +Bool16 QTRTPFile::PrefetchNextPacket(RTPTrackListEntry * trackEntry, Bool16 doSeek) +{ + // General vars + UInt16 *pSequenceNumber; + UInt32 *pTimestamp; + Bool16 skipThisSample = false; + + // Temporary vars + QTTrack::ErrorCode getPacketErr = QTTrack::errIsSkippedPacket; + + // If we are dropping b-frames or repeat packets, QTHintTrack::GetPacket will return the errIsSkippedPacket error to us. + // If we get that error, we should fetch another packet. So, we have this loop here. + while (getPacketErr == QTTrack::errIsSkippedPacket) + { + // + // Next packet (or first packet, since CurPacketNumber starts at 0 above) + trackEntry->CurPacketNumber++; + + // + // Do we need to move to the next sample? + if ( (trackEntry->CurPacketNumber > trackEntry->NumPacketsInThisSample) + && (trackEntry->NumPacketsInThisSample != 0) + ) + { + if (trackEntry->HintTrack->IsSyncSample(trackEntry->CurSampleNumber, 0)) + { + trackEntry->LastSyncSampleNumber = trackEntry->CurSampleNumber; + if (trackEntry->NextSyncSampleNumber != trackEntry->CurSampleNumber) + trackEntry->NextSyncSampleNumber = 0; // this must have been obsolete + } + + // + // If we're only reading sync samples, then we need to find the next + // one to send out, otherwise just increment the sample number and + // move on. + if( trackEntry->QualityLevel == kKeyFramesOnly ) + { + trackEntry->HintTrack->GetNextSyncSample(trackEntry->CurSampleNumber, &trackEntry->NextSyncSampleNumber); + if (!fHasRTPMetaInfoFieldArray && !fWasLastSeekASeekToPacketNumber) + { + fNumSkippedSamples += trackEntry->NextSyncSampleNumber - trackEntry->CurSampleNumber; + trackEntry->CurSampleNumber = trackEntry->NextSyncSampleNumber; + } + else + { + // + // If we are generating RTPMetaInfo packets, we need to track + // the total number of packets we are generating. The only way + // to do that is to actually generate all the packets. So do so. + trackEntry->CurSampleNumber++; + if (trackEntry->CurSampleNumber != trackEntry->NextSyncSampleNumber) + { + fNumSkippedSamples++; + skipThisSample = true; + } + else + skipThisSample = false; + } + } + else if (trackEntry->QualityLevel >= k75PercentPFrames) + { + // + // If we are skipping a certain % of p-frames, figure out + // whether we need to skip this p-frame sample + trackEntry->CurSampleNumber++; + skipThisSample = false; + + // + // Only skip this sample if it is not a sync sample + if (!trackEntry->HintTrack->IsSyncSample(trackEntry->CurSampleNumber, 0)) + { + // + // figure out where the next sync sample is + if (trackEntry->CurSampleNumber >= trackEntry->NextSyncSampleNumber) + { + trackEntry->HintTrack->GetNextSyncSample(trackEntry->CurSampleNumber, &trackEntry->NextSyncSampleNumber); + } + + // this shouldn't ever be false, but I'm worried about when people skip backwards in movies + // in that case, just don't thin. + if (trackEntry->CurSampleNumber > trackEntry->LastSyncSampleNumber) + { + Assert(trackEntry->NextSyncSampleNumber != trackEntry->LastSyncSampleNumber); + + UInt32 percentageIn = (trackEntry->CurSampleNumber - trackEntry->LastSyncSampleNumber) * 100 / + (trackEntry->NextSyncSampleNumber - trackEntry->LastSyncSampleNumber); + + if (trackEntry->QualityLevel == kKeyFramesPlusOneP) + skipThisSample = trackEntry->CurSampleNumber - trackEntry->LastSyncSampleNumber > 1; + else if (percentageIn > trackEntry->TargetPercentage) + skipThisSample = true; + /* + if (trackEntry->QualityLevel == k50PercentPFrames) + skipThisSample = trackEntry->CurSampleNumber % 2 == 0; + else if (trackEntry->QualityLevel == k75PercentPFrames) + skipThisSample = trackEntry->CurSampleNumber % 4 == 0; + else if (trackEntry->QualityLevel == k25PercentPFrames) + skipThisSample = trackEntry->CurSampleNumber % 4 != 0; + */ + } + } + } + else + { + trackEntry->CurSampleNumber++; + skipThisSample = false; + } + + // + // We'll need to recompute the number of samples in this packet. + trackEntry->NumPacketsInThisSample = 0; + } + + // + // Do we know how many packets are in this sample? If not, figure it out. + while ( trackEntry->NumPacketsInThisSample == 0 ) + { + if ( trackEntry->HintTrack->GetNumPackets(trackEntry->CurSampleNumber, &trackEntry->NumPacketsInThisSample, trackEntry->HTCB) != QTTrack::errNoError ) + return false; + + if ( trackEntry->NumPacketsInThisSample == 0 ) + trackEntry->CurSampleNumber++; + + trackEntry->CurPacketNumber = 1; + } + + // + // Fetch this packet. + trackEntry->CurPacketLength = QTRTPFILE_MAX_PACKET_LENGTH; + + + #if QT_PROFILE + MicroSecondStopWatch packetTimer; + packetTimer.Start(); + #endif + getPacketErr = trackEntry->HintTrack->GetPacket(trackEntry->CurSampleNumber, trackEntry->CurPacketNumber, + trackEntry->CurPacket, &trackEntry->CurPacketLength, + &trackEntry->CurPacketTime, + (trackEntry->QualityLevel >= kNoBFrames), + fDropRepeatPackets, + trackEntry->SSRC, + trackEntry->HTCB); + + #if QT_PROFILE + packetTimer.Stop(); + qtss_printf( "GetPacket sample time %li NumPackets: %li Packet fetched %li. len: %li\n", + (SInt32)packetTimer.Duration(), (SInt32)trackEntry->NumPacketsInThisSample, (SInt32)trackEntry->CurPacketNumber, (SInt32)trackEntry->CurPacketLength ); + #endif + + // + // If we are doing keyframes only and this is not a sync sample, don't return this packet. + // This will happen if we are generating RTPMetaInfo packets and thinning. + if (skipThisSample) + getPacketErr = QTTrack::errIsSkippedPacket; + } + + if( getPacketErr != QTTrack::errNoError ) + { fErr = errInvalidQuickTimeFile; + return false; + } + + + // + // Update our sequence number and timestamp. If we seeked to get here, + // then we need to adjust the additive to account for the shift in + // sequence numbers. + pSequenceNumber = (UInt16 *)((char *)trackEntry->CurPacket + 2); + pTimestamp = (UInt32 *)((char *)trackEntry->CurPacket + 4); + + if( doSeek || (trackEntry->QualityLevel > kAllPackets) ) + trackEntry->SequenceNumberAdditive += (trackEntry->LastSequenceNumber + 1) - ntohs(*pSequenceNumber); + + *pSequenceNumber = htons( (SInt16) (((SInt32) ntohs(*pSequenceNumber)) + trackEntry->BaseSequenceNumberRandomOffset + trackEntry->FileSequenceNumberRandomOffset + trackEntry->SequenceNumberAdditive)); + *pTimestamp = htonl(ntohl(*pTimestamp) + trackEntry->BaseTimestampRandomOffset + trackEntry->FileTimestampRandomOffset); + + // + // Return the packet. + return true; +} diff --git a/QTFileLib/QTRTPFile.h b/QTFileLib/QTRTPFile.h new file mode 100644 index 0000000..189a97f --- /dev/null +++ b/QTFileLib/QTRTPFile.h @@ -0,0 +1,283 @@ +/* + * + * @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@ + * + */ +// $Id: QTRTPFile.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTRTPFile: +// An interface to QTFile for TimeShare. + +#ifndef QTRTPFile_H +#define QTRTPFile_H + + +// +// Includes +#include "OSHeaders.h" +#include "MyAssert.h" +#include "RTPMetaInfoPacket.h" +#include "QTHintTrack.h" + +#ifndef __Win32__ +#include +#endif + +// +// Constants +#define QTRTPFILE_MAX_PACKET_LENGTH 2048 + + +// +// QTRTPFile class +class OSMutex; + +class QTFile; +class QTFile_FileControlBlock; +class QTHintTrack; +class QTHintTrack_HintTrackControlBlock; + +class QTRTPFile { + +public: + // + // Class error codes + enum ErrorCode { + errNoError = 0, + errFileNotFound = 1, + errInvalidQuickTimeFile = 2, + errNoHintTracks = 3, + errTrackIDNotFound = 4, + errCallAgain = 5, + errInternalError = 100 + }; + + + // + // Class typedefs. + struct RTPFileCacheEntry { + // + // Init mutex (do not use this entry until you have acquired and + // released this. + OSMutex *InitMutex; + + // + // File information + char* fFilename; + QTFile *File; + + // + // Reference count for this cache entry + int ReferenceCount; + + // + // List pointers + RTPFileCacheEntry *PrevEntry, *NextEntry; + }; + + struct RTPTrackListEntry { + + // + // Track information + UInt32 TrackID; + QTHintTrack *HintTrack; + QTHintTrack_HintTrackControlBlock *HTCB; + Bool16 IsTrackActive, IsPacketAvailable; + UInt32 QualityLevel; + + // + // Server information + void *Cookie1; + UInt32 Cookie2; + UInt32 SSRC; + UInt16 FileSequenceNumberRandomOffset, BaseSequenceNumberRandomOffset, + LastSequenceNumber; + SInt32 SequenceNumberAdditive; + UInt32 FileTimestampRandomOffset, BaseTimestampRandomOffset; + + // + // Sample/Packet information + UInt32 CurSampleNumber; + UInt32 ConsecutivePFramesSent; + UInt32 TargetPercentage; + UInt32 SampleToSeekTo; + UInt32 LastSyncSampleNumber; + UInt32 NextSyncSampleNumber; + UInt16 NumPacketsInThisSample, CurPacketNumber; + + Float64 CurPacketTime; + char CurPacket[QTRTPFILE_MAX_PACKET_LENGTH]; + UInt32 CurPacketLength; + + // + // List pointers + RTPTrackListEntry *NextTrack; + }; + + +public: + // + // Global initialize function; CALL THIS FIRST! + static void Initialize(void); + + // + // Returns a static array of the RTP-Meta-Info fields supported by QTFileLib. + // It also returns field IDs for the fields it recommends being compressed. + static const RTPMetaInfoPacket::FieldID* GetSupportedRTPMetaInfoFields() { return kMetaInfoFields; } + + // + // Constructors and destructor. + QTRTPFile(Bool16 Debug = false, Bool16 DeepDebug = false); + + virtual ~QTRTPFile(void); + + + // + // Initialization functions. + virtual ErrorCode Initialize(const char * FilePath); + + void AllocateSharedBuffers(UInt32 inUnitSizeInK, UInt32 inBufferInc, UInt32 inBufferSizeUnits, UInt32 inMaxBitRateBuffSizeInBlocks) + { fFile->AllocateBuffers(inUnitSizeInK, inBufferInc, inBufferSizeUnits, inMaxBitRateBuffSizeInBlocks, this->GetBytesPerSecond() * 8 ); + } + + void AllocatePrivateBuffers(UInt32 inUnitSizeInK, UInt32 inNumBuffSizeUnits, UInt32 inMaxBitRateBuffSizeInBlocks); + + // + // Accessors + Float64 GetMovieDuration(void); + UInt64 GetAddedTracksRTPBytes(void); + char * GetSDPFile(int * SDPFileLength); + UInt32 GetBytesPerSecond(void); + + char* GetMoviePath(); + QTFile* GetQTFile() { return fFile; } + + // + // Track functions + + // + // AddTrack + // + // If you would like this track to be an RTP-Meta-Info stream, pass in + // the field names you would like to see + ErrorCode AddTrack(UInt32 TrackID, Bool16 UseRandomOffset = true); + + + Float64 GetTrackDuration(UInt32 TrackID); + UInt32 GetTrackTimeScale(UInt32 TrackID); + + void SetTrackSSRC(UInt32 TrackID, UInt32 SSRC); + void SetTrackCookies(UInt32 TrackID, void * Cookie1, UInt32 Cookie2); + void SetAllowInvalidHintRefs(Bool16 inAllowInvalidHintRefs) { fAllowInvalidHintRefs = inAllowInvalidHintRefs; } + + // + // If you want QTRTPFile to output an RTP-Meta-Info packet instead + // of a normal RTP packet for this track, call this function and + // pass in a proper Field ID array (see RTPMetaInfoPacket.h) to + // tell QTRTPFile which fields to include and which IDs to use with the fields. + // You have to let this function know whether this is a video track or not. + void SetTrackRTPMetaInfo(UInt32 TrackID, RTPMetaInfoPacket::FieldID* inFieldArray, Bool16 isVideo ); + + // + // What sort of packets do you want? + enum + { + kAllPackets = 0, + kNoBFrames = 1, + k75PercentPFrames = 2, + k50PercentPFrames = 3, + k25PercentPFrames = 4, + kKeyFramesOnly = 5, + kKeyFramesPlusOneP = 6 //Special quality level with Key frames followed by 1 P frame + }; + + void SetTrackQualityLevel(RTPTrackListEntry* inEntry, UInt32 inNewLevel); + // + // Packet functions + ErrorCode Seek(Float64 Time, Float64 MaxBackupTime = 3.0); + ErrorCode SeekToPacketNumber(UInt32 inTrackID, UInt64 inPacketNumber); + + UInt32 GetSeekTimestamp(UInt32 TrackID); + Float64 GetRequestedSeekTime() { return fRequestedSeekTime; } + Float64 GetActualSeekTime() { return fSeekTime; } + Float64 GetFirstPacketTransmitTime(); + RTPTrackListEntry* GetLastPacketTrack() { return fLastPacketTrack; } + UInt32 GetNumSkippedSamples() { return fNumSkippedSamples; } + + UInt16 GetNextTrackSequenceNumber(UInt32 TrackID); + Float64 GetNextPacket(char ** Packet, int * PacketLength); + + SInt32 GetMovieHintType(); + Bool16 DropRepeatPackets() { return fDropRepeatPackets; } + Bool16 SetDropRepeatPackets(Bool16 allowRepeatPackets) { (!fHasRTPMetaInfoFieldArray) ? fDropRepeatPackets = allowRepeatPackets : fDropRepeatPackets = false; return fDropRepeatPackets;} + + ErrorCode Error() { return fErr; }; + + Bool16 FindTrackEntry(UInt32 TrackID, RTPTrackListEntry **TrackEntry); +protected: + // + // Protected cache functions and variables. + static OSMutex *gFileCacheMutex, *gFileCacheAddMutex; + static RTPFileCacheEntry *gFirstFileCacheEntry; + + static ErrorCode new_QTFile(const char * FilePath, QTFile ** File, Bool16 Debug = false, Bool16 DeepDebug = false); + static void delete_QTFile(QTFile * File); + + static void AddFileToCache(const char *inFilename, QTRTPFile::RTPFileCacheEntry ** NewListEntry); + static Bool16 FindAndRefcountFileCacheEntry(const char *inFilename, QTRTPFile::RTPFileCacheEntry **CacheEntry); + + // + // Protected member functions. + Bool16 PrefetchNextPacket(RTPTrackListEntry * TrackEntry, Bool16 doSeek = false); + ErrorCode ScanToCorrectSample(); + ErrorCode ScanToCorrectPacketNumber(UInt32 inTrackID, UInt64 inPacketNumber); + + // + // Protected member variables. + Bool16 fDebug, fDeepDebug; + + QTFile *fFile; + QTFile_FileControlBlock *fFCB; + + UInt32 fNumHintTracks; + RTPTrackListEntry *fFirstTrack, *fLastTrack, *fCurSeekTrack; + + char *fSDPFile; + UInt32 fSDPFileLength; + UInt32 fNumSkippedSamples; + + Float64 fRequestedSeekTime, fSeekTime; + + RTPTrackListEntry *fLastPacketTrack; + + UInt32 fBytesPerSecond; + + Bool16 fHasRTPMetaInfoFieldArray; + Bool16 fWasLastSeekASeekToPacketNumber; + Bool16 fDropRepeatPackets; + Bool16 fAllowInvalidHintRefs; + ErrorCode fErr; + + static const RTPMetaInfoPacket::FieldID kMetaInfoFields[]; +}; + +#endif // QTRTPFile diff --git a/QTFileLib/QTTrack.cpp b/QTFileLib/QTTrack.cpp new file mode 100644 index 0000000..4010328 --- /dev/null +++ b/QTFileLib/QTTrack.cpp @@ -0,0 +1,485 @@ +/* + * + * @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@ + * + */ +// +// QTTrack: +// The central point of control for a track in a QTFile. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include +#include + +#include "QTFile.h" + +#include "QTAtom.h" +#include "QTAtom_dref.h" +#include "QTAtom_elst.h" +#include "QTAtom_mdhd.h" +#include "QTAtom_tkhd.h" +#include "QTAtom_stco.h" +#include "QTAtom_stsc.h" +#include "QTAtom_stsd.h" +#include "QTAtom_stss.h" +#include "QTAtom_stsz.h" +#include "QTAtom_stts.h" + +#include "QTTrack.h" + +#include "OSMemory.h" + +// ------------------------------------- +// Macros +// +#define DEBUG_PRINT(s) if(fDebug) qtss_printf s +#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s + +// ------------------------------------- +// Constructors and destructors +// +QTTrack::QTTrack(QTFile * File, QTFile::AtomTOCEntry * Atom, Bool16 Debug, Bool16 DeepDebug) + : fDebug(Debug), fDeepDebug(DeepDebug), + fFile(File), + fIsInitialized(false), + fTrackHeaderAtom(NULL), + fTrackName(NULL), + fMediaHeaderAtom(NULL), + fEditListAtom(NULL), fDataReferenceAtom(NULL), + fTimeToSampleAtom(NULL),fCompTimeToSampleAtom(NULL), fSampleToChunkAtom(NULL), fSampleDescriptionAtom(NULL), + fChunkOffsetAtom(NULL), fSampleSizeAtom(NULL), fSyncSampleAtom(NULL), + fFirstEditMediaTime(0) +{ + // Temporary vars + QTFile::AtomTOCEntry *tempTOCEntry; + + // + // Make a copy of the TOC entry. + memcpy(&fTOCEntry, Atom, sizeof(QTFile::AtomTOCEntry)); + + // + // Load in the track header atom for this track. + if( !fFile->FindTOCEntry(":tkhd", &tempTOCEntry, &fTOCEntry) ) + return; + + fTrackHeaderAtom = NEW QTAtom_tkhd(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fTrackHeaderAtom == NULL ) + return; + if( !fTrackHeaderAtom->Initialize() ) { + delete fTrackHeaderAtom; + fTrackHeaderAtom = NULL; + } + +} + +QTTrack::~QTTrack(void) +{ + // + // Free our variables + if( fTrackHeaderAtom != NULL ) + delete fTrackHeaderAtom; + if( fTrackName != NULL ) + delete[] fTrackName; + + if( fMediaHeaderAtom != NULL ) + delete fMediaHeaderAtom; + + if( fEditListAtom != NULL ) + delete fEditListAtom; + if( fDataReferenceAtom != NULL ) + delete fDataReferenceAtom; + + if( fTimeToSampleAtom != NULL ) + delete fTimeToSampleAtom; + if( fSampleToChunkAtom != NULL ) + delete fSampleToChunkAtom; + if( fSampleDescriptionAtom != NULL ) + delete fSampleDescriptionAtom; + if( fChunkOffsetAtom != NULL ) + delete fChunkOffsetAtom; + if( fSampleSizeAtom != NULL ) + delete fSampleSizeAtom; + if( fSyncSampleAtom != NULL ) + delete fSyncSampleAtom; +} + + + +// ------------------------------------- +// Initialization functions +// +QTTrack::ErrorCode QTTrack::Initialize(void) +{ + // Temporary vars + QTFile::AtomTOCEntry *tempTOCEntry; + + + // + // Don't initialize more than once. + if( IsInitialized() ) + return errNoError; + + // + // Make sure that we were able to read in our track header atom. + if( fTrackHeaderAtom == NULL ) + return errInvalidQuickTimeFile; + + // + // See if this track has a name and load it in. + if( fFile->FindTOCEntry(":udta:name", &tempTOCEntry, &fTOCEntry) ) { + fTrackName = NEW char[ (SInt32) (tempTOCEntry->AtomDataLength + 1) ]; + if( fTrackName != NULL ) + fFile->Read(tempTOCEntry->AtomDataPos, fTrackName, (UInt32) tempTOCEntry->AtomDataLength); + } + + + // + // Load in the media header atom for this track. + if( !fFile->FindTOCEntry(":mdia:mdhd", &tempTOCEntry, &fTOCEntry) ) + return errInvalidQuickTimeFile; + + fMediaHeaderAtom = NEW QTAtom_mdhd(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fMediaHeaderAtom == NULL ) + return errInternalError; + if( !fMediaHeaderAtom->Initialize() ) + return errInvalidQuickTimeFile; + + + // + // Load in the edit list atom for this track. + DEEP_DEBUG_PRINT(("Searching track #%"_U32BITARG_" 'elst' atom.\n", GetTrackID())); + if( fFile->FindTOCEntry(":edts:elst", &tempTOCEntry, &fTOCEntry) ) { + fEditListAtom = NEW QTAtom_elst(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fEditListAtom == NULL ) + return errInternalError; + if( !fEditListAtom->Initialize() ) + return errInvalidQuickTimeFile; + + // + // Compute the first edit's media time. + fFirstEditMediaTime = (UInt32)(( ( (Float64) (SInt64) GetFirstEditMovieTime())/ fFile->GetTimeScale()) * GetTimeScale()); + } else { + fEditListAtom = NULL; + } + + + // + // Load in the data reference atom for this track. + if( !fFile->FindTOCEntry(":mdia:minf:dinf:dref", &tempTOCEntry, &fTOCEntry) ) + return errInvalidQuickTimeFile; + + fDataReferenceAtom = NEW QTAtom_dref(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fDataReferenceAtom == NULL ) + return errInternalError; + if( !fDataReferenceAtom->Initialize() ) + return errInvalidQuickTimeFile; + + + // + // Load in the sample table atoms. + if( !fFile->FindTOCEntry(":mdia:minf:stbl:stts", &tempTOCEntry, &fTOCEntry) ) + return errInvalidQuickTimeFile; + + fTimeToSampleAtom = NEW QTAtom_stts(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fTimeToSampleAtom == NULL ) + return errInternalError; + if( !fTimeToSampleAtom->Initialize() ) + return errInvalidQuickTimeFile; + + if( fFile->FindTOCEntry(":mdia:minf:stbl:ctts", &tempTOCEntry, &fTOCEntry) ) + { + fCompTimeToSampleAtom = NEW QTAtom_ctts(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fCompTimeToSampleAtom == NULL ) + return errInternalError; + if( !fCompTimeToSampleAtom->Initialize() ) + return errInvalidQuickTimeFile; + } + + if( !fFile->FindTOCEntry(":mdia:minf:stbl:stsc", &tempTOCEntry, &fTOCEntry) ) + return errInvalidQuickTimeFile; + + fSampleToChunkAtom = NEW QTAtom_stsc(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fSampleToChunkAtom == NULL ) + return errInternalError; + if( !fSampleToChunkAtom->Initialize() ) + return errInvalidQuickTimeFile; + + + if( !fFile->FindTOCEntry(":mdia:minf:stbl:stsd", &tempTOCEntry, &fTOCEntry) ) + return errInvalidQuickTimeFile; + + fSampleDescriptionAtom = NEW QTAtom_stsd(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fSampleDescriptionAtom == NULL ) + return errInternalError; + if( !fSampleDescriptionAtom->Initialize() ) + return errInvalidQuickTimeFile; + + + UInt16 offSetSize = 0; + Bool16 coFound = false; + + if( fFile->FindTOCEntry(":mdia:minf:stbl:stco", &tempTOCEntry, &fTOCEntry) ) + { + coFound = true; + offSetSize = 4; + } else if( fFile->FindTOCEntry(":mdia:minf:stbl:co64", &tempTOCEntry, &fTOCEntry) ) + { + coFound = true; + offSetSize = 8; + } + + if (!coFound) + return errInvalidQuickTimeFile; + + fChunkOffsetAtom = NEW QTAtom_stco(fFile, tempTOCEntry, offSetSize, fDebug, fDeepDebug); + if( fChunkOffsetAtom == NULL ) + return errInternalError; + if( !fChunkOffsetAtom->Initialize() ) + return errInvalidQuickTimeFile; + + + if( !fFile->FindTOCEntry(":mdia:minf:stbl:stsz", &tempTOCEntry, &fTOCEntry) ) + return errInvalidQuickTimeFile; + + fSampleSizeAtom = NEW QTAtom_stsz(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fSampleSizeAtom == NULL ) + return errInternalError; + if( !fSampleSizeAtom->Initialize() ) + return errInvalidQuickTimeFile; + + if( fFile->FindTOCEntry(":mdia:minf:stbl:stss", &tempTOCEntry, &fTOCEntry) ) { + fSyncSampleAtom = NEW QTAtom_stss(fFile, tempTOCEntry, fDebug, fDeepDebug); + if( fSyncSampleAtom == NULL ) + return errInternalError; + if( !fSyncSampleAtom->Initialize() ) + return errInvalidQuickTimeFile; + } else { + fSyncSampleAtom = NULL; + } + + + // + // This track has been successfully initialiazed. + fIsInitialized = true; + return errNoError; +} + + + +// ------------------------------------- +// Sample functions +// +Bool16 QTTrack::GetSampleInfo(UInt32 SampleNumber, UInt32 * const Length, UInt64 * const Offset, UInt32 * const SampleDescriptionIndex, QTAtom_stsc_SampleTableControlBlock * STCB) +{ + + Assert(STCB != NULL); + +// qtss_printf("GetSampleInfo QTTrack SampleNumber = %"_S32BITARG_" \n", SampleNumber); + + if (STCB->fGetSampleInfo_SampleNumber == SampleNumber && STCB->fGetSampleInfo_Length > 0) + { +// qtss_printf("----- GetSampleInfo Cache Hit QTTrack SampleNumber = %"_S32BITARG_" \n", SampleNumber); + + if (Length) *Length = STCB->fGetSampleInfo_Length; + if (Offset) *Offset = STCB->fGetSampleInfo_Offset; + if (SampleDescriptionIndex) *SampleDescriptionIndex = STCB->fGetSampleInfo_SampleDescriptionIndex; + + return true; + } + + + + + // Temporary vars + UInt32 sampleLength = 0; + UInt32 sampleDescriptionIndex = 0; + // General vars + UInt32 ChunkNumber, SampleOffsetInChunk; + UInt64 sampleFileStartOffset = 0; + UInt64 ChunkOffset = 0; + + + // Locate this sample, compute its offset, and get its size. + if( !SampleNumberToChunkNumber(SampleNumber, &ChunkNumber, &sampleDescriptionIndex, &SampleOffsetInChunk, STCB) ) + return false; + + + if( !fSampleSizeAtom->SampleSize(SampleNumber, &sampleLength) ) + return false; + + if (ChunkNumber == STCB->fGetSampleInfo_LastChunk && (SampleNumber == (STCB->fGetSampleInfo_SampleNumber+1) ) ) + { + + sampleFileStartOffset = STCB->fGetSampleInfo_Offset; + sampleFileStartOffset += STCB->fGetSampleInfo_Length; + + } + else + { + if( !fChunkOffsetAtom->ChunkOffset(ChunkNumber, &ChunkOffset) ) + return false; + + // Walk through all of the samples previous to this one, adding up + // their lengths to figure out what the offset from the start of + // the chunk to this sample is. + + + UInt32 tempSampleLength = fSampleSizeAtom->GetCommonSampleSize(); + sampleFileStartOffset = ChunkOffset; + + if (tempSampleLength > 0) // samples are the same size so just multiply to get size + { sampleFileStartOffset += ( tempSampleLength * SampleOffsetInChunk ); + } + else + { + for( UInt32 CurSample = (SampleNumber - SampleOffsetInChunk);CurSample < SampleNumber; CurSample++) + { + // Get the length of this sample and add it to our offset. + if( !fSampleSizeAtom->SampleSize(CurSample, &tempSampleLength) ) + return false; + sampleFileStartOffset += tempSampleLength; + } + } + + + STCB->fGetSampleInfo_LastChunk = ChunkNumber; + STCB->fGetSampleInfo_LastChunkOffset = (UInt32) ChunkOffset; + STCB->fGetSampleInfo_SampleDescriptionIndex = sampleDescriptionIndex; + } + + STCB->fGetSampleInfo_SampleNumber = SampleNumber; + STCB->fGetSampleInfo_Length = sampleLength; + STCB->fGetSampleInfo_Offset = sampleFileStartOffset; + + if (Length != NULL) *Length = sampleLength; + if (Offset != NULL) *Offset = sampleFileStartOffset; + if (SampleDescriptionIndex != NULL) *SampleDescriptionIndex = sampleDescriptionIndex; + + + + // + // The sample was successfully located. + return true; +} + + +Bool16 QTTrack::GetSizeOfSamplesInChunk(UInt32 chunkNumber, UInt32 * const sizePtr, UInt32 * const firstSampleNumPtr, UInt32 * const lastSampleNumPtr, QTAtom_stsc_SampleTableControlBlock * stcbPtr) +{ + UInt32 firstSample = 0; + UInt32 lastSample = 0; + UInt32 size = 0; + + if (stcbPtr && stcbPtr->fGetSizeOfSamplesInChunk_chunkNumber == chunkNumber) + { +// qtss_printf("QTTrack::GetSizeOfSamplesInChunk cache hit %"_S32BITARG_" \n", chunkNumber); + if (firstSampleNumPtr != NULL) *firstSampleNumPtr = stcbPtr->fGetSizeOfSamplesInChunk_firstSample; + if (lastSampleNumPtr != NULL) *lastSampleNumPtr = stcbPtr->fGetSizeOfSamplesInChunk_lastSample; + if (sizePtr != NULL) *sizePtr = stcbPtr->fGetSizeOfSamplesInChunk_size; + return true; + } + + Bool16 result = GetChunkFirstLastSample(chunkNumber, &firstSample, &lastSample, stcbPtr); + + if (result && (sizePtr != NULL) ) + { + result = SampleRangeSize(firstSample, lastSample, &size); + } + + if (firstSampleNumPtr != NULL) *firstSampleNumPtr = firstSample; + if (lastSampleNumPtr != NULL) *lastSampleNumPtr = lastSample; + if (sizePtr != NULL) *sizePtr = size; + + if (stcbPtr && result) + { + stcbPtr->fGetSizeOfSamplesInChunk_chunkNumber = chunkNumber; + stcbPtr->fGetSizeOfSamplesInChunk_firstSample = firstSample; + stcbPtr->fGetSizeOfSamplesInChunk_lastSample = lastSample; + stcbPtr->fGetSizeOfSamplesInChunk_size = size; + } + + return result; +} + + +Bool16 QTTrack::GetSample(UInt32 SampleNumber, char * Buffer, UInt32 * Length, QTFile_FileControlBlock * FCB, QTAtom_stsc_SampleTableControlBlock * STCB) +{ + // General vars + UInt32 SampleDescriptionIndex; + UInt64 SampleOffset; + + // + // Get the location and size of this sample. + if( !this->GetSampleInfo(SampleNumber, Length, &SampleOffset, &SampleDescriptionIndex, STCB) ) + return false; + + // + // Read in the sample + if( !fDataReferenceAtom->Read(SampleDescriptionIndex, SampleOffset, Buffer, *Length, FCB) ) + return false; + + // + // The sample was successfully read in. + return true; +} + + + +// ------------------------------------- +// Debugging functions +// +void QTTrack::DumpTrack(void) +{ + // + // Dump this track's information. + DEBUG_PRINT(("QTTrack::DumpTrack - Dumping track.\n")); + DEBUG_PRINT(("QTTrack::DumpTrack - ..Track name: \"%s\".\n", fTrackName ? fTrackName : "")); + + // + // Dump the sub-atoms of this track. + if( fTrackHeaderAtom != NULL ) + fTrackHeaderAtom->DumpAtom(); + + if( fMediaHeaderAtom != NULL ) + fMediaHeaderAtom->DumpAtom(); + + if( fDataReferenceAtom != NULL ) + fDataReferenceAtom->DumpAtom(); + + if( fCompTimeToSampleAtom != NULL) + fCompTimeToSampleAtom->DumpAtom(); + if( fTimeToSampleAtom != NULL ) + fTimeToSampleAtom->DumpAtom(); + if( fSampleToChunkAtom != NULL ) + fSampleToChunkAtom->DumpAtom(); + if( fSampleDescriptionAtom != NULL ) + fSampleDescriptionAtom->DumpAtom(); + if( fChunkOffsetAtom != NULL ) + fChunkOffsetAtom->DumpAtom(); + if( fSampleSizeAtom != NULL ) + fSampleSizeAtom->DumpAtom(); +} diff --git a/QTFileLib/QTTrack.h b/QTFileLib/QTTrack.h new file mode 100644 index 0000000..0cb3784 --- /dev/null +++ b/QTFileLib/QTTrack.h @@ -0,0 +1,221 @@ +/* + * + * @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@ + * + */ +// +// QTTrack: +// The central point of control for a track in a QTFile. + +#ifndef QTTrack_H +#define QTTrack_H + + +// +// Includes +#include "QTAtom_dref.h" +#include "QTAtom_elst.h" +#include "QTAtom_mdhd.h" +#include "QTAtom_tkhd.h" +#include "QTAtom_stco.h" +#include "QTAtom_stsc.h" +#include "QTAtom_stsd.h" +#include "QTAtom_stss.h" +#include "QTAtom_stsz.h" +#include "QTAtom_stts.h" + + +// +// External classes +class QTFile; +class QTFile_FileControlBlock; +class QTAtom_stsc_SampleTableControlBlock; +class QTAtom_stts_SampleTableControlBlock; + + +// +// QTTrack class +class QTTrack { + +public: + // + // Class error codes + enum ErrorCode { + errNoError = 0, + errInvalidQuickTimeFile = 1, + errParamError = 2, + errIsSkippedPacket = 3, + errInternalError = 100 + }; + + +public: + // + // Constructors and destructor. + QTTrack(QTFile * File, QTFile::AtomTOCEntry * trakAtom, + Bool16 Debug = false, Bool16 DeepDebug = false); + virtual ~QTTrack(void); + + + // + // Initialization functions. + virtual ErrorCode Initialize(void); + + // + // Accessors. + inline Bool16 IsInitialized(void) { return fIsInitialized; } + + inline const char *GetTrackName(void) { return (fTrackName ? fTrackName : ""); } + inline UInt32 GetTrackID(void) { return fTrackHeaderAtom->GetTrackID(); } + inline UInt64 GetCreationTime(void) { return fTrackHeaderAtom->GetCreationTime(); } + inline UInt64 GetModificationTime(void) { return fTrackHeaderAtom->GetModificationTime(); } + inline SInt64 GetDuration(void) { return (SInt64) fTrackHeaderAtom->GetDuration(); } + inline Float64 GetTimeScale(void) { return fMediaHeaderAtom->GetTimeScale(); } + inline Float64 GetTimeScaleRecip(void) { return fMediaHeaderAtom->GetTimeScaleRecip(); } + inline Float64 GetDurationInSeconds(void) { return GetDuration() / (Float64)GetTimeScale(); } + inline UInt64 GetFirstEditMovieTime(void) + { if(fEditListAtom != NULL) return fEditListAtom->FirstEditMovieTime(); + else return 0; } + inline UInt32 GetFirstEditMediaTime(void) { return fFirstEditMediaTime; } + + // + // Sample functions + Bool16 GetSizeOfSamplesInChunk(UInt32 chunkNumber, UInt32 * const sizePtr, UInt32 * const firstSampleNumPtr, UInt32 * const lastSampleNumPtr, QTAtom_stsc_SampleTableControlBlock * stcbPtr); + + inline Bool16 GetChunkFirstLastSample(UInt32 chunkNumber, UInt32 *firstSample, UInt32 *lastSample, + QTAtom_stsc_SampleTableControlBlock *STCB) + { return fSampleToChunkAtom->GetChunkFirstLastSample(chunkNumber,firstSample, lastSample, STCB); + } + + + inline Bool16 SampleToChunkInfo(UInt32 SampleNumber, UInt32 *samplesPerChunk, UInt32 *ChunkNumber, UInt32 *SampleDescriptionIndex, UInt32 *SampleOffsetInChunk, + QTAtom_stsc_SampleTableControlBlock * STCB) + { return fSampleToChunkAtom->SampleToChunkInfo(SampleNumber,samplesPerChunk, ChunkNumber, SampleDescriptionIndex, SampleOffsetInChunk, STCB); + } + + + inline Bool16 SampleNumberToChunkNumber(UInt32 SampleNumber, UInt32 *ChunkNumber, UInt32 *SampleDescriptionIndex, UInt32 *SampleOffsetInChunk, + QTAtom_stsc_SampleTableControlBlock * STCB) + { return fSampleToChunkAtom->SampleNumberToChunkNumber(SampleNumber, ChunkNumber, SampleDescriptionIndex, SampleOffsetInChunk, STCB); + } + + + inline UInt32 GetChunkFirstSample(UInt32 chunkNumber) + { return fSampleToChunkAtom->GetChunkFirstSample(chunkNumber); + } + + inline Bool16 ChunkOffset(UInt32 ChunkNumber, UInt64 *Offset = NULL) + { return fChunkOffsetAtom->ChunkOffset(ChunkNumber, Offset); + } + + inline Bool16 SampleSize(UInt32 SampleNumber, UInt32 *Size = NULL) + { return fSampleSizeAtom->SampleSize(SampleNumber, Size); + } + + inline Bool16 SampleRangeSize(UInt32 firstSample, UInt32 lastSample, UInt32 *sizePtr = NULL) + { return fSampleSizeAtom->SampleRangeSize(firstSample, lastSample, sizePtr); + } + + Bool16 GetSampleInfo(UInt32 SampleNumber, UInt32 * const Length, UInt64 * const Offset, UInt32 * const SampleDescriptionIndex, + QTAtom_stsc_SampleTableControlBlock * STCB); + + Bool16 GetSample(UInt32 SampleNumber, char * Buffer, UInt32 * Length,QTFile_FileControlBlock * FCB, + QTAtom_stsc_SampleTableControlBlock * STCB); + + inline Bool16 GetSampleMediaTime(UInt32 SampleNumber, UInt32 * const MediaTime, + QTAtom_stts_SampleTableControlBlock * STCB) + { return fTimeToSampleAtom->SampleNumberToMediaTime(SampleNumber, MediaTime, STCB); + } + + inline Bool16 GetSampleNumberFromMediaTime(UInt32 MediaTime, UInt32 * const SampleNumber, + QTAtom_stts_SampleTableControlBlock * STCB) + { return fTimeToSampleAtom->MediaTimeToSampleNumber(MediaTime, SampleNumber, STCB); + } + + + inline void GetPreviousSyncSample(UInt32 SampleNumber, UInt32 * SyncSampleNumber) + { if(fSyncSampleAtom != NULL) fSyncSampleAtom->PreviousSyncSample(SampleNumber, SyncSampleNumber); + else *SyncSampleNumber = SampleNumber; + } + + inline void GetNextSyncSample(UInt32 SampleNumber, UInt32 * SyncSampleNumber) + { if(fSyncSampleAtom != NULL) fSyncSampleAtom->NextSyncSample(SampleNumber, SyncSampleNumber); + else *SyncSampleNumber = SampleNumber + 1; + } + + inline Bool16 IsSyncSample(UInt32 SampleNumber, UInt32 SyncSampleCursor) + { if (fSyncSampleAtom != NULL) return fSyncSampleAtom->IsSyncSample(SampleNumber, SyncSampleCursor); + else return true; + } + // + // Read functions. + inline Bool16 Read(UInt32 SampleDescriptionID, UInt64 Offset, char * const Buffer, UInt32 Length, + QTFile_FileControlBlock * FCB = NULL) + { return fDataReferenceAtom->Read(fSampleDescriptionAtom->SampleDescriptionToDataReference(SampleDescriptionID), Offset, Buffer, Length, FCB); + } + + inline Bool16 GetSampleMediaTimeOffset(UInt32 SampleNumber, UInt32 *mediaTimeOffset, QTAtom_ctts_SampleTableControlBlock * STCB) + { + if (fCompTimeToSampleAtom) + return fCompTimeToSampleAtom->SampleNumberToMediaTimeOffset(SampleNumber, mediaTimeOffset, STCB); + else + return false; + } + // + // Debugging functions. + virtual void DumpTrack(void); + inline void DumpSampleToChunkTable(void) { fSampleToChunkAtom->DumpTable(); } + inline void DumpChunkOffsetTable(void) { fChunkOffsetAtom->DumpTable(); } + inline void DumpSampleSizeTable(void) { fSampleSizeAtom->DumpTable(); } + inline void DumpTimeToSampleTable(void) { fTimeToSampleAtom->DumpTable(); } + inline void DumpCompTimeToSampleTable(void) { if (fCompTimeToSampleAtom) fCompTimeToSampleAtom->DumpTable(); else printf("*** no ctts table ****\n"); } + + +protected: + // + // Protected member variables. + Bool16 fDebug, fDeepDebug; + QTFile *fFile; + QTFile::AtomTOCEntry fTOCEntry; + + Bool16 fIsInitialized; + + QTAtom_tkhd *fTrackHeaderAtom; + char *fTrackName; + + QTAtom_mdhd *fMediaHeaderAtom; + + QTAtom_elst *fEditListAtom; + QTAtom_dref *fDataReferenceAtom; + + QTAtom_stts *fTimeToSampleAtom; + QTAtom_ctts *fCompTimeToSampleAtom; + QTAtom_stsc *fSampleToChunkAtom; + QTAtom_stsd *fSampleDescriptionAtom; + QTAtom_stco *fChunkOffsetAtom; + QTAtom_stsz *fSampleSizeAtom; + QTAtom_stss *fSyncSampleAtom; + + UInt32 fFirstEditMediaTime; +}; + +#endif // QTTrack_H diff --git a/QTFileTools/QTBroadcaster.tproj/QTBroadcaster.cpp b/QTFileTools/QTBroadcaster.tproj/QTBroadcaster.cpp new file mode 100644 index 0000000..2cd4bf4 --- /dev/null +++ b/QTFileTools/QTBroadcaster.tproj/QTBroadcaster.cpp @@ -0,0 +1,222 @@ +/* + * + * @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@ + * + */ + +#include +#include +#include "SafeStdLib.h" +#include +#include + +#ifndef __MacOSX__ +#include "getopt.h" +#include +#endif + +#include +#include +#include +#include +#include + +#include "QTRTPFile.h" + +//extern char *optarg; +//extern int optind; + +int main(int argc, char *argv[]) { + // Temporary vars + int ch; + struct timeval tp; + + // General vars + bool Debug = false, DeepDebug = false; + + const char *IPAddress; + const char *BasePort; + + const char *MovieFilename; + + int s; + QTRTPFile *RTPFile; + + int CurPort; + Float64 StartTime; + extern int optind; + + + // + // Read our command line options + while( (ch = getopt(argc, argv, "dD")) != -1 ) { + switch( ch ) { + case 'd': + Debug = true; + break; + + case 'D': + Debug = true; + DeepDebug = true; + break; + } + } + + argc -= optind; + argv += optind; + + // + // Validate our arguments. + if( argc < 4 ) { + qtss_printf("usage: QTBroadcaster ..\n"); + exit(1); + } + + IPAddress = *argv++; argc--; + BasePort = *argv++; argc--; + MovieFilename = *argv++; argc--; + + + // + // Open the movie. + RTPFile = new QTRTPFile(Debug, DeepDebug); + switch( RTPFile->Initialize(MovieFilename) ) { + case QTRTPFile::errNoError: + break; + + case QTRTPFile::errFileNotFound: + qtss_printf("Error! File not found \"%s\"!\n", MovieFilename); + exit(1); + case QTRTPFile::errNoHintTracks: + qtss_printf("Error! No hint tracks \"%s\"!\n", MovieFilename); + exit(1); + case QTRTPFile::errInvalidQuickTimeFile: + qtss_printf("Error! Invalid movie file \"%s\"!\n", MovieFilename); + exit(1); + case QTRTPFile::errInternalError: + qtss_printf("Error! Internal error opening movie file \"%s\"!\n", MovieFilename); + exit(1); + + case QTRTPFile::errTrackIDNotFound: + case QTRTPFile::errCallAgain: + //noops + break; + } + + + // + // Add the tracks that we're interested in. + CurPort = atoi(BasePort); + while(argc--) { + switch( RTPFile->AddTrack(atoi(*argv)) ) { + case QTRTPFile::errNoError: + case QTRTPFile::errTrackIDNotFound: + case QTRTPFile::errCallAgain: + break; + + case QTRTPFile::errFileNotFound: + case QTRTPFile::errNoHintTracks: + case QTRTPFile::errInvalidQuickTimeFile: + case QTRTPFile::errInternalError: + qtss_printf("Error! Invalid movie file \"%s\"!\n", MovieFilename); + exit(1); + } + + RTPFile->SetTrackCookies(atoi(*argv), NULL, (UInt32 )CurPort); + CurPort += 2; + + (void)RTPFile->GetSeekTimestamp(atoi(*argv)); + argv++; + } + + + // + // Create our socket to broadcast to. + s = socket(AF_INET, SOCK_DGRAM, 0); + if( s == -1 ) { + qtss_printf("Error! Couldn't create socket!\n"); + exit(1); + } + + + // + // Seek to the beginning of the movie. + if( RTPFile->Seek(0.0) != QTRTPFile::errNoError ) { + qtss_printf("Error! Couldn't seek to time 0.0!\n"); + exit(1); + } + + + // + // Send packets.. + gettimeofday(&tp, NULL); + StartTime = tp.tv_sec + ((Float64)tp.tv_usec / 1000000); + while(1) { + // General vars + char *Packet; + int PacketLength; + //int Cookie; + + Float64 SleepTime; + + struct sockaddr_in sin; + + + // + // Get the next packet. + Float64 TransmitTime = RTPFile->GetNextPacket(&Packet, &PacketLength); + if( Packet == NULL ) + break; + + // + // Wait until it is time to send the packet. + gettimeofday(&tp, NULL); + SleepTime = tp.tv_sec + ((Float64)tp.tv_usec / 1000000); + SleepTime = (StartTime + TransmitTime) - SleepTime; + if( SleepTime > 0.0 ) { + qtss_printf("Sleeping for %.2f seconds (TransmitTime=%.2f).\n", SleepTime, TransmitTime); + usleep((unsigned int)(SleepTime * 1000000)); + } + + // + // Send the packet. + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + UInt32 value = RTPFile->GetLastPacketTrack()->Cookie2; + + in_port_t cookievalue = value; + sin.sin_port = htons( cookievalue ); + sin.sin_addr.s_addr = inet_addr(IPAddress); + sendto(s, Packet, PacketLength, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)); + } + + + // + // Close the socket. + close(s); + + // + // Close our RTP file. + delete RTPFile; + + return 0; +} diff --git a/QTFileTools/QTFileInfo.tproj/QTFileInfo.cpp b/QTFileTools/QTFileInfo.tproj/QTFileInfo.cpp new file mode 100644 index 0000000..05d1ffe --- /dev/null +++ b/QTFileTools/QTFileInfo.tproj/QTFileInfo.cpp @@ -0,0 +1,129 @@ +/* + * + * @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@ + * + */ + +#include +#include +#include "SafeStdLib.h" +#include + +#ifndef __MacOSX__ +#include "getopt.h" +#include +#endif + +#include "QTFile.h" + +#include "QTTrack.h" +#include "QTHintTrack.h" + +int main(int argc, char *argv[]) { + // Temporary vars + int ch; + + // General vars + QTTrack *Track; + bool Debug = false, DeepDebug = false; + extern int optind; + + // + // Read our command line options + while( (ch = getopt(argc, argv, "dD")) != -1 ) { + switch( ch ) { + case 'd': + Debug = true; + break; + + case 'D': + Debug = true; + DeepDebug = true; + break; + } + } + + argc -= optind; + argv += optind; + + // + // Validate our arguments. + if( argc != 1 ) { + qtss_printf("usage: QTFileInfo [-d] [-D] \n"); + exit(1); + } + + + // + // Open the movie. + QTFile file(Debug, DeepDebug); + file.Open(*argv); + if(Debug) file.DumpAtomTOC(); + + // + // Print out some information. + qtss_printf("-- Movie %s \n", *argv); + qtss_printf(" Duration : %f\n", file.GetDurationInSeconds()); + + Track = NULL; + while( file.NextTrack(&Track, Track) ) { + // Temporary vars + QTHintTrack *HintTrack; + time_t unixCreationTime = (time_t)Track->GetCreationTime() + (time_t)QT_TIME_TO_LOCAL_TIME; + time_t unixModificationTime = (time_t)Track->GetModificationTime() + (time_t)QT_TIME_TO_LOCAL_TIME; + char buffer[kTimeStrSize]; + struct tm timeResult; + + // + // Initialize the track and dump it. + if( Track->Initialize() != QTTrack::errNoError ) { + qtss_printf("!!! Failed to initialize track !!!\n"); + continue; + } + if(DeepDebug) Track->DumpTrack(); + + // + // Dump some info. + qtss_printf("-- Track #%02"_S32BITARG_" ---------------------------\n", Track->GetTrackID()); + qtss_printf(" Name : %s\n", Track->GetTrackName()); + qtss_printf(" Created on : %s", qtss_asctime(qtss_gmtime(&unixCreationTime, &timeResult), buffer, sizeof(buffer) )); + qtss_printf(" Modified on : %s", qtss_asctime(qtss_gmtime(&unixModificationTime, &timeResult), buffer, sizeof(buffer) )); + + // + // Dump hint information is possible. + if( file.IsHintTrack(Track) ) { + HintTrack = (QTHintTrack *)Track; + + qtss_printf(" Total RTP bytes : %"_64BITARG_"u\n", HintTrack->GetTotalRTPBytes()); + qtss_printf(" Total RTP packets : %"_64BITARG_"u\n", HintTrack->GetTotalRTPPackets()); + qtss_printf(" Average bitrate : %.2f Kbps\n", file.GetDurationInSeconds() == 0 ? 0.0 : ((HintTrack->GetTotalRTPBytes() << 3) / file.GetDurationInSeconds()) / 1024); + qtss_printf(" Average packet size: %"_64BITARG_"u\n", HintTrack->GetTotalRTPPackets() == 0 ? 0 : HintTrack->GetTotalRTPBytes() / HintTrack->GetTotalRTPPackets()); + + UInt32 UDPIPHeaderSize = (56 * HintTrack->GetTotalRTPPackets()); + UInt32 RTPUDPIPHeaderSize = ((56+12) * HintTrack->GetTotalRTPPackets()); + qtss_printf(" Percentage of stream wasted on UDP/IP headers : %.2f\n", HintTrack->GetTotalRTPBytes() == 0 ? 0 : (float)UDPIPHeaderSize / (float)(HintTrack->GetTotalRTPBytes() + UDPIPHeaderSize) * 100); + qtss_printf(" Percentage of stream wasted on RTP/UDP/IP headers: %.2f\n", HintTrack->GetTotalRTPBytes() == 0 ? 0 : (float)RTPUDPIPHeaderSize / (float)(HintTrack->GetTotalRTPBytes() + RTPUDPIPHeaderSize) * 100); + } + } + + return 0; +} diff --git a/QTFileTools/QTFileTest.tproj/QTFileTest.cpp b/QTFileTools/QTFileTest.tproj/QTFileTest.cpp new file mode 100644 index 0000000..f47d639 --- /dev/null +++ b/QTFileTools/QTFileTest.tproj/QTFileTest.cpp @@ -0,0 +1,44 @@ +/* + * + * @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@ + * + */ + +#include +#include +#include "SafeStdLib.h" +#include "QTFile.h" + +int main(int /*argc*/, char *argv[]) { + + // Validate + if( argv[1] == NULL ) + { + qtss_printf("usage: QTFileTest \n"); + exit(1); + } + + QTFile file(true, true); + file.Open(argv[1]); + file.DumpAtomTOC(); + return 0; +} diff --git a/QTFileTools/QTRTPFileTest.tproj/QTRTPFileTest.cpp b/QTFileTools/QTRTPFileTest.tproj/QTRTPFileTest.cpp new file mode 100644 index 0000000..2da5883 --- /dev/null +++ b/QTFileTools/QTRTPFileTest.tproj/QTRTPFileTest.cpp @@ -0,0 +1,368 @@ +/* + * + * @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@ + * + */ + +#include +#include +#include "SafeStdLib.h" +#include +#include + +#ifndef __MacOSX__ +#include "getopt.h" +#include +#endif + +#include "OS.h" +#include "QTRTPFile.h" + +int main(int argc, char *argv[]) { + // Temporary vars + int ch = '\0'; + + // General vars + int fd = -1; + + const char *MovieFilename; + QTRTPFile *RTPFile = NULL; + bool Debug = false, DeepDebug = false; + bool silent = false; + bool trackCache= false; + bool everytrack= false; + bool hintOnly = false; + bool keyFramesOnly = false; + extern int optind; + QTRTPFile::RTPTrackListEntry *trackListEntry = NULL; + + // + // Read our command line options + while( (ch = getopt(argc, argv, "dDhsetk")) != -1 ) { + switch( ch ) { + case 'e': + everytrack = true; + break; + + case 's': + silent = true; + break; + + case 't': + trackCache = true; + break; + + case 'h': + hintOnly = true; + break; + + case 'd': + Debug = true; + break; + + case 'D': + Debug = true; + DeepDebug = true; + break; + case 'k': + keyFramesOnly = true; + break; + } + } + + argc -= optind; + argv += optind; + + // + // Validate our arguments. + if( argc < 1 ) { + qtss_printf("usage: QTRTPFileTest ..\n"); + qtss_printf("usage: -s no packet printfs\n"); + qtss_printf("usage: -e test every hint track\n"); + qtss_printf("usage: -k list only packets belonging to key frames. Specify a single video track with this option\n"); + qtss_printf("usage: -t write packets to track.cache file\n"); + qtss_printf("usage: -h show hinted (.unopt, .opt)\n"); + exit(1); + } + + MovieFilename = *argv++; + argc--; + + if (!hintOnly) + qtss_printf("****************** QTRTPFileTest ******************\n"); + + // + // Open the movie. + RTPFile = new QTRTPFile(Debug, DeepDebug); + switch( RTPFile->Initialize(MovieFilename) ) { + case QTRTPFile::errNoError: + case QTRTPFile::errNoHintTracks: + break; + + case QTRTPFile::errFileNotFound: + qtss_printf("Error! File not found \"%s\"!\n", MovieFilename); + exit(1); + case QTRTPFile::errInvalidQuickTimeFile: + qtss_printf("Error! Invalid movie file \"%s\"!\n", MovieFilename); + exit(1); + case QTRTPFile::errInternalError: + qtss_printf("Error! Internal error opening movie file \"%s\"!\n", MovieFilename); + exit(1); + case QTRTPFile::errTrackIDNotFound: + case QTRTPFile::errCallAgain: + //noops + break; + } + + + // + // Get the SDP file and print it out. + char *SDPFile; + int SDPFileLength; + + { + // + // Get the file + SDPFile = RTPFile->GetSDPFile(&SDPFileLength); + if( SDPFile == NULL ) { + qtss_printf("Error! Could not get SDP file!\n"); + exit(1); + } + if (!hintOnly) + { + write(1, SDPFile, SDPFileLength); + write(1, "\n", 1); + } + } + + // + // Open our file to write the packets out to. + if (trackCache) + { + fd = open("track.cache", O_CREAT | O_TRUNC | O_WRONLY, 0664); + if( fd == -1 ) { + qtss_printf("Error! Could not create output file!\n"); + exit(1); + } + } + if (everytrack || hintOnly) + { + int trackcount = 0; + int hinttracks[20]; + memset(&hinttracks,0,sizeof(hinttracks)); + bool found = false; + char *trackPtr = SDPFile; + while (true) + { + trackPtr = ::strstr(trackPtr,"trackID="); + if (trackPtr != NULL) + { trackPtr+= ::strlen("trackID="); + sscanf(trackPtr, "%d",&hinttracks[trackcount]); + trackcount++; + found = true; + } + else + break; + + } + while (trackcount) + { + switch( RTPFile->AddTrack(hinttracks[trackcount -1]) ) { + case QTRTPFile::errNoError: + case QTRTPFile::errNoHintTracks: + break; + + case QTRTPFile::errFileNotFound: + case QTRTPFile::errInvalidQuickTimeFile: + case QTRTPFile::errInternalError: + qtss_printf("Error! Invalid movie file \"%s\"!\n", MovieFilename); + exit(1); + + case QTRTPFile::errTrackIDNotFound: + case QTRTPFile::errCallAgain: + //noops + break; + } + + RTPFile->SetTrackCookies(hinttracks[trackcount], (char *)hinttracks[trackcount], 0); + (void)RTPFile->GetSeekTimestamp(hinttracks[trackcount]); + trackcount --; + } + } + else + { + // + // Add the tracks that we're interested in. + while(argc--) { + switch( RTPFile->AddTrack(atoi(*argv)) ) { + case QTRTPFile::errNoError: + case QTRTPFile::errNoHintTracks: + case QTRTPFile::errTrackIDNotFound: + case QTRTPFile::errCallAgain: + break; + + case QTRTPFile::errFileNotFound: + case QTRTPFile::errInvalidQuickTimeFile: + case QTRTPFile::errInternalError: + qtss_printf("Error! Invalid movie file \"%s\"!\n", MovieFilename); + exit(1); + } + + RTPFile->FindTrackEntry(atoi(*argv), &trackListEntry); + RTPFile->SetTrackCookies(atoi(*argv), (char *)atoi(*argv), 0); + (void)RTPFile->GetSeekTimestamp(atoi(*argv)); + argv++; + } + } + + + // + // Display some stats about the movie. + + if (!hintOnly) + qtss_printf("Total RTP bytes of all added tracks: %"_64BITARG_"u\n", RTPFile->GetAddedTracksRTPBytes()); + + // + // Seek to the beginning of the movie. + if( RTPFile->Seek(0.0) != QTRTPFile::errNoError ) { + qtss_printf("Error! Couldn't seek to time 0.0!\n"); + exit(1); + } + + // + // Suck down packets.. + UInt32 NumberOfPackets = 0; + Float64 TotalInterpacketDelay = 0.0, + LastPacketTime = 0.0; + + SInt64 startTime = 0; + SInt64 durationTime = 0; + SInt64 packetCount = 0; + + while(1) + { + // Temporary vars + UInt16 tempInt16; + + // General vars + char *Packet; + int PacketLength; + //SInt32 Cookie; + UInt32 RTPTimestamp; + UInt16 RTPSequenceNumber; + int maxHintPackets = 100; // cheat assume this many packets is good enough to assume entire file is the same at these packets + + + // + // Get the next packet. + startTime = OS::Milliseconds(); + if (keyFramesOnly) + RTPFile->SetTrackQualityLevel(trackListEntry, QTRTPFile::kKeyFramesOnly); + + Float64 TransmitTime = RTPFile->GetNextPacket(&Packet, &PacketLength); + SInt64 thisDuration = OS::Milliseconds() - startTime; + durationTime += thisDuration; + packetCount++; + + if( Packet == NULL ) + break; + + if (hintOnly) + { if (--maxHintPackets == 0 ) + break; + continue; + } + + memcpy(&RTPSequenceNumber, Packet + 2, 2); + RTPSequenceNumber = ntohs(RTPSequenceNumber); + memcpy(&RTPTimestamp, Packet + 4, 4); + RTPTimestamp = ntohl(RTPTimestamp); + + if (!hintOnly) + if (!silent) + qtss_printf("TransmitTime = %.2f; SEQ = %u; TS = %"_U32BITARG_"\n", TransmitTime, RTPSequenceNumber, RTPTimestamp); + + if (trackCache) + { + // + // Write out the packet header. + write(fd, (char *)&TransmitTime, 8); // transmitTime + tempInt16 = PacketLength; + write(fd, (char *)&tempInt16, 2); // packetLength + tempInt16 = 0; + write(fd, (char *)&tempInt16, 2); // padding1 + + // + // Write out the packet. + write(fd, Packet, PacketLength); + } + // + // Compute the Inter-packet delay and keep track of it. + if( TransmitTime >= LastPacketTime ) { + TotalInterpacketDelay += TransmitTime - LastPacketTime; + LastPacketTime = TransmitTime; + NumberOfPackets++; + } + + } + + + // + // Compute and display the Inter-packet delay. + if( (!hintOnly) && NumberOfPackets > 0 ) + { + qtss_printf("QTRTPFileTest: Total GetNextPacket durationTime = %"_U32BITARG_"ms packetCount= %"_U32BITARG_"\n",(UInt32)durationTime,(UInt32)packetCount); + qtss_printf("QTRTPFileTest: Average Inter-packet delay: %"_64BITARG_"uus\n", (UInt64)((TotalInterpacketDelay / NumberOfPackets) * 1000 * 1000)); + } + + SInt32 hintType = RTPFile->GetMovieHintType(); // this can only be reliably called after playing all the packets. + if (!hintOnly) + { + if (hintType < 0) + qtss_printf("QTRTPFileTest: HintType=Optimized\n"); + else if (hintType > 0) + qtss_printf("QTRTPFileTest: HintType=Unoptimized\n"); + else + qtss_printf("QTRTPFileTest: HintType=Unknown\n"); + } + else + { + if (hintType < 0) + qtss_printf("%s.opt\n",MovieFilename); + else if (hintType > 0) + qtss_printf("%s.unopt\n",MovieFilename); + else + qtss_printf("%s\n",MovieFilename); + } + + if (trackCache) + { // + // Close the output file. + close(fd); + } + + // + // Close our RTP file. + delete RTPFile; + + return 0; +} diff --git a/QTFileTools/QTRTPGen.tproj/QTRTPGen.cpp b/QTFileTools/QTRTPGen.tproj/QTRTPGen.cpp new file mode 100644 index 0000000..a3c73cf --- /dev/null +++ b/QTFileTools/QTRTPGen.tproj/QTRTPGen.cpp @@ -0,0 +1,218 @@ +/* + * + * @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@ + * + */ + +#include +#include +#include "SafeStdLib.h" +#include +#include +#include + +#ifndef __MacOSX__ +#include "getopt.h" +#include +#endif + +#include "QTFile.h" +#include "QTTrack.h" +#include "QTHintTrack.h" + + +int main(int argc, char *argv[]) { + // Temporary vars + int ch; + UInt16 tempInt16; + UInt32 tempInt32; + Float32 tempFloat32; + Float64 tempFloat64; + + // General vars + int fd; + + const char *MovieFilename; + int TrackNumber; + + QTTrack *Track; + QTHintTrack *HintTrack; + bool Debug = false, DeepDebug = false; + + UInt16 NumPackets; + extern int optind; + + // + // Read our command line options + while( (ch = getopt(argc, argv, "dD")) != -1 ) { + switch( ch ) { + case 'd': + Debug = true; + break; + + case 'D': + Debug = true; + DeepDebug = true; + break; + } + } + + argc -= optind; + argv += optind; + + // + // Validate our arguments. + if( argc != 2 ) { + qtss_printf("usage: QTRTPGen [-d] [-D] \n"); + exit(1); + } + + MovieFilename = *argv++; + TrackNumber = atoi(*argv++); + + + // + // Open the movie. + QTFile file(Debug, DeepDebug); + file.Open(MovieFilename); + + // + // Find the specified track and dump out information about its' samples. + if( !file.FindTrack(TrackNumber, &Track) ) { + qtss_printf("Error! Could not find track number %d in file \"%s\"!", + TrackNumber, MovieFilename); + exit(1); + } + + // + // Make sure that this is a hint track. + if( !file.IsHintTrack(Track) ) { + qtss_printf("Error! Track number %d is not a hint track!\n", TrackNumber); + exit(1); + } + HintTrack = (QTHintTrack *)Track; + + // + // Initialize this track. + HintTrack->Initialize(); + + // + // Dump some information about this track. + { + time_t unixCreationTime = (time_t)HintTrack->GetCreationTime() + (time_t)QT_TIME_TO_LOCAL_TIME; + time_t unixModificationTime = (time_t)HintTrack->GetModificationTime() + (time_t)QT_TIME_TO_LOCAL_TIME; + char buffer[kTimeStrSize]; + struct tm timeResult; + + qtss_printf("-- Track #%02"_S32BITARG_" ---------------------------\n", HintTrack->GetTrackID()); + qtss_printf(" Name : %s\n", HintTrack->GetTrackName()); + qtss_printf(" Created on : %s", qtss_asctime(qtss_gmtime(&unixCreationTime, &timeResult),buffer, sizeof(buffer))); + qtss_printf(" Modified on : %s", qtss_asctime(qtss_gmtime(&unixModificationTime, &timeResult),buffer, sizeof(buffer))); + + qtss_printf(" Total RTP bytes : %"_64BITARG_"u\n", HintTrack->GetTotalRTPBytes()); + qtss_printf(" Total RTP packets : %"_64BITARG_"u\n", HintTrack->GetTotalRTPPackets()); + qtss_printf(" Average bitrate : %.2f Kbps\n",(float) ((HintTrack->GetTotalRTPBytes() << 3) / file.GetDurationInSeconds()) / 1024); + qtss_printf(" Average packet size: %"_64BITARG_"u\n", HintTrack->GetTotalRTPBytes() / HintTrack->GetTotalRTPPackets()); + + UInt32 UDPIPHeaderSize = (56 * HintTrack->GetTotalRTPPackets()); + UInt32 RTPUDPIPHeaderSize = ((56+12) * HintTrack->GetTotalRTPPackets()); + qtss_printf(" Percentage of stream wasted on UDP/IP headers : %.2f\n", (float)UDPIPHeaderSize / (float)(HintTrack->GetTotalRTPBytes() + UDPIPHeaderSize) * 100); + qtss_printf(" Percentage of stream wasted on RTP/UDP/IP headers: %.2f\n", (float)RTPUDPIPHeaderSize / (float)(HintTrack->GetTotalRTPBytes() + RTPUDPIPHeaderSize) * 100); + qtss_printf("\n"); + qtss_printf("\n"); + } + + // + // Open our file to write the packets out to. + fd = open("track.cache", O_CREAT | O_TRUNC | O_WRONLY, 0666); + if( fd == -1 ) { + qtss_printf("Error! Could not create output file!\n"); + exit(1); + } + + // + // Write out the header. + tempInt16 = 1; + write(fd, (char *)&tempInt16, 2); // isCompletelyWritten + tempInt16 = 0; + write(fd, (char *)&tempInt16, 2); // padding1 + tempFloat64 = file.GetDurationInSeconds(); + write(fd, (char *)&tempFloat64, 8); // movieLength + tempInt32 = HintTrack->GetRTPTimescale(); + write(fd, (char *)&tempInt32, 4); // rtpTimescale + tempInt16 = HintTrack->GetRTPSequenceNumberRandomOffset(); + write(fd, (char *)&tempInt16, 2); // seqNumRandomOffset + tempInt16 = 0; + write(fd, (char *)&tempInt16, 2); // padding2 + tempFloat32 = HintTrack->GetTotalRTPBytes() / file.GetDurationInSeconds(); + write(fd, (char *)&tempFloat32, 4); // dataRate + + // + // Go through all of the samples in this track, printing out their offsets + // and sizes. + qtss_printf("Sample # NPkts\n"); + qtss_printf("-------- -----\n"); + UInt32 curSample = 1; + QTHintTrack_HintTrackControlBlock HCB; + while( HintTrack->GetNumPackets(curSample, &NumPackets) == QTTrack::errNoError ) { + // + // Generate all of the packets. + qtss_printf("Generating %u packet(s) in sample #%"_U32BITARG_"..\n", NumPackets, curSample); + for( UInt16 curPacket = 1; curPacket <= NumPackets; curPacket++ ) { + // General vars + #define MAX_PACKET_LEN 2048 + char Packet[MAX_PACKET_LEN]; + UInt32 PacketLength; + Float64 TransmitTime; + + + // + // Generate this packet. + PacketLength = MAX_PACKET_LEN; + HintTrack->GetPacket(curSample, curPacket, + Packet, &PacketLength, + &TransmitTime, false, false, 0,&HCB); + + // + // Write out the packet header. + write(fd, (char *)&TransmitTime, 8); // transmitTime + tempInt16 = PacketLength; + write(fd, (char *)&tempInt16, 2); // packetLength + tempInt16 = 0; + write(fd, (char *)&tempInt16, 2); // padding1 + + // + // Write out the packet. + write(fd, Packet, PacketLength); + } + + // + // Next sample. + curSample++; + } + + // + // Close the output file. + close(fd); + + return 0; +} diff --git a/QTFileTools/QTSDPGen.tproj/QTSDPGen.cpp b/QTFileTools/QTSDPGen.tproj/QTSDPGen.cpp new file mode 100644 index 0000000..74930f9 --- /dev/null +++ b/QTFileTools/QTSDPGen.tproj/QTSDPGen.cpp @@ -0,0 +1,148 @@ +/* + * + * @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@ + * + */ + +#include +#include +#include "SafeStdLib.h" +#include + +#ifndef __MacOSX__ +#include "getopt.h" +#include +#endif + + +#include "QTRTPFile.h" + +int main(int argc, char *argv[]) { + // Temporary vars + int ch; + + // General vars + const char *MovieFilename; + const char *OutputFilename = NULL; + QTRTPFile *RTPFile; + bool Debug = false, DeepDebug = false; + extern char* optarg; + extern int optind; + + // + // Read our command line options + while( (ch = getopt(argc, argv, "dD")) != -1 ) { + switch( ch ) { + case 'd': + Debug = true; + break; + + case 'D': + Debug = true; + DeepDebug = true; + break; + + case 'f': + OutputFilename = optarg; + break; + } + } + + argc -= optind; + argv += optind; + + // + // Validate our arguments. + if( argc < 1 ) { + qtss_printf("usage: QTSDPGen \n"); + exit(1); + } + + //MovieFilename = *argv++; + + while ((MovieFilename = *argv++) != NULL) + { + + // + // Open the movie. + RTPFile = new QTRTPFile(); + if( RTPFile->Initialize(MovieFilename) != QTRTPFile::errNoError ) { + qtss_printf("Error! Could not open movie file \"%s\"!\n", MovieFilename); + continue; + //exit(1); + } + + + // + // Get the SDP file and write it out. + { + // General vars + char *SDPFile; + int SDPFileLength; + + int fdsdp; + char SDPFilename[255 + 1]; + + + // + // Get the file + SDPFile = RTPFile->GetSDPFile(&SDPFileLength); + if( SDPFile == NULL ) { + qtss_printf("Error! Could not get SDP file!\n"); + continue; + //exit(1); + } + + // + // Create our SDP file and write out the data + if( OutputFilename == NULL ) { + fdsdp = STDOUT_FILENO; + } else { + qtss_sprintf(SDPFilename, "%s.sdp", MovieFilename); + fdsdp = open(SDPFilename, O_CREAT | O_TRUNC | O_WRONLY, 0664); + if( fdsdp == -1 ) { + qtss_printf("Error! Could not create SDP file \"%s\"!\n", SDPFilename); + continue; + //exit(1); + } + } + + qtss_printf("\n--%s--\n", MovieFilename); + + write(fdsdp, SDPFile, SDPFileLength); + + qtss_printf("\n"); + + + if( OutputFilename != NULL ) + close(fdsdp); + } + + // + // Close our RTP file. + delete RTPFile; + + } + + return 0; +} + diff --git a/QTFileTools/QTSampleLister.tproj/QTSampleLister.cpp b/QTFileTools/QTSampleLister.tproj/QTSampleLister.cpp new file mode 100644 index 0000000..7f6f311 --- /dev/null +++ b/QTFileTools/QTSampleLister.tproj/QTSampleLister.cpp @@ -0,0 +1,156 @@ +/* + * + * @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@ + * + */ + +#include +#include +#include "SafeStdLib.h" +#include +#include + +#ifndef __MacOSX__ +#include "getopt.h" +#include +#endif + +#include "QTFile.h" +#include "QTTrack.h" + +int main(int argc, char *argv[]) { + // Temporary vars + int ch; + + // General vars + const char *MovieFilename; + int TrackNumber; + + QTTrack *Track; + bool Debug = false, DeepDebug = false, + DumpHTML = false; + + UInt64 Offset; + UInt32 Size, MediaTime; + extern int optind; + + // + // Read our command line options + while( (ch = getopt(argc, argv, "dDH")) != -1 ) { + switch( ch ) { + case 'd': + Debug = true; + break; + + case 'D': + Debug = true; + DeepDebug = true; + break; + + case 'H': + DumpHTML = true; + break; + } + } + + argc -= optind; + argv += optind; + + // + // Validate our arguments. + if( argc != 2 ) { + qtss_printf("usage: QTSampleLister [-d] [-D] [-H] \n"); + exit(1); + } + + MovieFilename = *argv++; + TrackNumber = atoi(*argv++); + + + // + // Open the movie. + QTFile file(Debug, DeepDebug); + file.Open(MovieFilename); + + // + // Find the specified track and dump out information about its' samples. + if( !file.FindTrack(TrackNumber, &Track) ) { + qtss_printf("Error! Could not find track number %d in file \"%s\"!", + TrackNumber, MovieFilename); + exit(1); + } + + // + // Initialize the track. + if( Track->Initialize() != QTTrack::errNoError){ + qtss_printf("Error! Failed to initialize track %d in file \"%s\"!\n", + TrackNumber, MovieFilename); + exit(1); + } + + // + // Dump some information about this track. + if( !DumpHTML ) { + time_t unixCreationTime = (time_t)Track->GetCreationTime() + (time_t)QT_TIME_TO_LOCAL_TIME; + time_t unixModificationTime = (time_t)Track->GetModificationTime() + (time_t)QT_TIME_TO_LOCAL_TIME; + char buffer[kTimeStrSize]; + struct tm timeResult; + + qtss_printf("-- Track #%02"_S32BITARG_" ---------------------------\n", Track->GetTrackID()); + qtss_printf(" Name : %s\n", Track->GetTrackName()); + qtss_printf(" Created on : %s", qtss_asctime(qtss_gmtime(&unixCreationTime, &timeResult),buffer, sizeof(buffer))); + qtss_printf(" Modified on : %s", qtss_asctime(qtss_gmtime(&unixModificationTime, &timeResult),buffer, sizeof(buffer))); + qtss_printf("\n"); + qtss_printf("\n"); + } + + // + // Go through all of the samples in this track, printing out their offsets + // and sizes. + if( !DumpHTML ) { + qtss_printf("Sample # Media Time DataOffset SampleSize\n"); + qtss_printf("-------- ---------- ---------- ----------\n"); + } + + UInt32 curSample = 1; + + QTAtom_stsc_SampleTableControlBlock stsc; + QTAtom_stts_SampleTableControlBlock stts; + while( Track->GetSampleInfo(curSample, &Size, &Offset, NULL, &stsc) ) { + // + // Get some additional information about this sample. + Track->GetSampleMediaTime(curSample, &MediaTime, &stts); + + // + // Dump out the sample. + if( DumpHTML ) + qtss_printf("%010"_64BITARG_"u: track=%02"_U32BITARG_"; size=%"_U32BITARG_"
\n",Offset, Track->GetTrackID(), Size); + else + qtss_printf("%8"_U32BITARG_" - %10"_U32BITARG_" %10"_64BITARG_"u %10"_U32BITARG_"\n", curSample, MediaTime, Offset, Size); + + + // Next sample. + curSample++; + } + + return 0; +} diff --git a/QTFileTools/QTTrackInfo.tproj/QTTrackInfo.cpp b/QTFileTools/QTTrackInfo.tproj/QTTrackInfo.cpp new file mode 100644 index 0000000..4c2a96e --- /dev/null +++ b/QTFileTools/QTTrackInfo.tproj/QTTrackInfo.cpp @@ -0,0 +1,168 @@ +/* + * + * @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@ + * + */ + +/* + + +*/ + +#include +#include +#include "SafeStdLib.h" +#include +#include + +#ifndef __MacOSX__ +#include "getopt.h" +#include +#endif + +#include "QTFile.h" + +#include "QTTrack.h" +#include "QTHintTrack.h" + +int main(int argc, char *argv[]) { + // Temporary vars + int ch; + + // General vars + const char *MovieFilename; + int TrackNumber; + + QTTrack *Track; + QTHintTrack *HintTrack; + bool Debug = false, DeepDebug = false; + char *Table = NULL; + extern char* optarg; + extern int optind; + + // + // Read our command line options + while( (ch = getopt(argc, argv, "dDT:")) != -1 ) { + switch( ch ) { + case 'd': + Debug = true; + break; + + case 'D': + Debug = true; + DeepDebug = true; + break; + + case 'T': + Table = strdup(optarg); + break; + } + } + + argc -= optind; + argv += optind; + + // + // Validate our arguments. + if( argc != 2 ) { + qtss_printf("usage: QTTrackInfo [-d] [-D] [-T ] \n"); + exit(1); + } + + MovieFilename = *argv++; + TrackNumber = atoi(*argv++); + + + // + // Open the movie. + QTFile file(Debug, DeepDebug); + file.Open(MovieFilename); + + // + // Find the specified track and dump out information about its' samples. + if( !file.FindTrack(TrackNumber, &Track) ) { + qtss_printf("Error! Could not find track number %d in file \"%s\"!", + TrackNumber, MovieFilename); + exit(1); + } + + // + // Initialize the track. + if( Track->Initialize() != QTTrack::errNoError ) { + qtss_printf("Error! Failed to initialize track %d in file \"%s\"!\n", + TrackNumber, MovieFilename); + exit(1); + } + + // + // Dump some information about this track. + { + time_t unixCreationTime = (time_t)Track->GetCreationTime() + (time_t)QT_TIME_TO_LOCAL_TIME; + time_t unixModificationTime = (time_t)Track->GetModificationTime() + (time_t)QT_TIME_TO_LOCAL_TIME; + char buffer[kTimeStrSize]; + struct tm timeResult; + + qtss_printf("-- Track #%02"_S32BITARG_" ---------------------------\n", Track->GetTrackID()); + qtss_printf(" Name : %s\n", Track->GetTrackName()); + qtss_printf(" Created on : %s", qtss_asctime(qtss_gmtime(&unixCreationTime, &timeResult),buffer, sizeof(buffer))); + qtss_printf(" Modified on : %s", qtss_asctime(qtss_gmtime(&unixModificationTime, &timeResult),buffer, sizeof(buffer))); + + // + // Dump hint information is possible. + if( file.IsHintTrack(Track) ) { + HintTrack = (QTHintTrack *)Track; + + qtss_printf(" Total RTP bytes : %"_64BITARG_"u\n", HintTrack->GetTotalRTPBytes()); + qtss_printf(" Total RTP packets : %"_64BITARG_"u\n", HintTrack->GetTotalRTPPackets()); + qtss_printf(" Average bitrate : %.2f Kbps\n", ((HintTrack->GetTotalRTPBytes() << 3) / file.GetDurationInSeconds()) / 1024); + qtss_printf(" Average packet size: %"_64BITARG_"u\n", HintTrack->GetTotalRTPBytes() / HintTrack->GetTotalRTPPackets()); + + UInt32 UDPIPHeaderSize = (UInt32) (56 * HintTrack->GetTotalRTPPackets()); + UInt32 RTPUDPIPHeaderSize = (UInt32) ((56+12) * HintTrack->GetTotalRTPPackets()); + qtss_printf(" Percentage of stream wasted on UDP/IP headers : %.2f\n", (float)UDPIPHeaderSize / (float)(HintTrack->GetTotalRTPBytes() + UDPIPHeaderSize) * 100); + qtss_printf(" Percentage of stream wasted on RTP/UDP/IP headers: %.2f\n", (float)RTPUDPIPHeaderSize / (float)(HintTrack->GetTotalRTPBytes() + RTPUDPIPHeaderSize) * 100); + } + + qtss_printf("\n"); + qtss_printf("\n"); + } + + // + // Dump all of the entries in the specified table (if we were given one). + // Go through all of the samples in this track, printing out their offsets + // and sizes. + if( Table != NULL ) { + if( (strcmp(Table, "stco") == 0) || (strcmp(Table, "co64") == 0) ) { + Track->DumpChunkOffsetTable(); + } else if( strcmp(Table, "stsc") == 0 ) { + Track->DumpSampleToChunkTable(); + } else if( strcmp(Table, "stsz") == 0 ) { + Track->DumpSampleSizeTable(); + } else if( strcmp(Table, "stts") == 0 ) { + Track->DumpTimeToSampleTable(); + } else if( strcmp(Table, "ctts") == 0 ) { + Track->DumpCompTimeToSampleTable(); + } + } + + return 0; +} diff --git a/QTFileTools/RTPFileGen.tproj/RTPFileDefs.h b/QTFileTools/RTPFileGen.tproj/RTPFileDefs.h new file mode 100644 index 0000000..1e10efe --- /dev/null +++ b/QTFileTools/RTPFileGen.tproj/RTPFileDefs.h @@ -0,0 +1,91 @@ +/* + * + * @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@ + * + */ + + +/* + This file defines a good file format to use for storing RTP streams. + It is optimized for linear reads through the file, but also allows for + seeking using a "Block Map", similar to a QuickTime sample table but more + compressed. + + RTP PACKET FILE FORMAT DEFINITION: + + 1 RTPFileHeader + __ SDP DATA __ + 1 RTPFileTrackInfo per track + __ BLOCK MAP__ + + packet, packet, packet + + Each packet is: + + 1 RTPFilePacket, followed by data + + No partial packets allowed between blocks, blocks should finish with a pad packet + +*/ + + +#ifndef __RTP_FILE_DEFS__ +#define __RTP_FILE_DEFS__ + +#include "OSHeaders.h" + +#define RTP_FILE_CURRENT_VERSION 0 + +typedef struct RTPFileHeader +{ + UInt32 fVersion; // Version 0 + Float64 fMovieDuration; + UInt32 fSDPLen; + UInt32 fNumTracks; + UInt32 fMaxTrackID; + UInt32 fBlockMapSize; + UInt32 fDataStartPos; + +} RTPFileHeader; + +typedef struct RTPFileTrackInfo +{ + UInt32 fID; + UInt32 fTimescale; + UInt64 fBytesInTrack; + Float64 fDuration; + +} RTPFileTrackInfo; + +typedef struct RTPFilePacket +{ + UInt16 fTrackID; + UInt16 fPacketLength; + Float64 fTransmitTime; + +} RTPFilePacket; + +static const UInt32 kBlockSize = 32768; +static const UInt32 kBlockMask = 0xFFF8000; +static const UInt16 kPaddingBit = 0x8000; + +#endif //__RTP_FILE_DEFS__ diff --git a/QTFileTools/RTPFileGen.tproj/RTPFileGen.cpp b/QTFileTools/RTPFileGen.tproj/RTPFileGen.cpp new file mode 100644 index 0000000..3ce2bc4 --- /dev/null +++ b/QTFileTools/RTPFileGen.tproj/RTPFileGen.cpp @@ -0,0 +1,322 @@ +/* + * + * @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@ + * + */ + + +#include +#include "SafeStdLib.h" +#include +#include +#include +#include + +#ifndef __MacOSX__ +#include "getopt.h" +#include +#endif + + + #include "RTPFileDefs.h" + #include "QTRTPFile.h" + #include "QTHintTrack.h" + #include "OSHeaders.h" + + + class QTRTPGenFile : public QTRTPFile + { + public: + + QTRTPGenFile() : QTRTPFile() {} + virtual ~QTRTPGenFile() {} + + // Accessors + UInt32 GetNumHintTracks() { return fNumHintTracks; } + RTPTrackListEntry* GetFirstTrack() { return fFirstTrack; } + }; + + +UInt8* WriteTempFile(QTRTPFile* inQTRTPFile, int inTempFile, UInt32* outNumBlocks); + + + +int main(int argc, char *argv[]) +{ + + if (argc <= 1) + { + qtss_printf("Usage: rtpfilegen *hintedqtfilename*\n"); + exit(0); + } + + char* theFileName = argv[1]; + + QTRTPFile::Initialize(); + + // Init our qt file + QTRTPGenFile theFile; + QTRTPFile::ErrorCode initErr = theFile.Initialize(theFileName); + switch (initErr) + { + case QTRTPFile::errFileNotFound: + qtss_printf("Movie file not found\n"); exit(0); + case QTRTPFile::errInvalidQuickTimeFile: + qtss_printf("File is not a hinted quicktime file\n"); exit(0); + case QTRTPFile::errNoHintTracks: + qtss_printf("File has no hint tracks\n"); exit(0); + case QTRTPFile::errInternalError: + qtss_printf("Internal error\n"); exit(0); + + case QTRTPFile::errNoError: + case QTRTPFile::errTrackIDNotFound: + case QTRTPFile::errCallAgain: + //noops + break; + } + + // Open our output file + char* outputFileName = new char[::strlen(theFileName) + 5]; + ::strcpy(outputFileName, theFileName); + ::strcat(outputFileName, ".rtp"); + int theOutFile = open(outputFileName, O_WRONLY | O_CREAT | O_TRUNC); + if (theOutFile == -1) + { qtss_printf("Failed to open output file at %s.\n", outputFileName); exit(0); } + + // Open a temp file + char* tempFileName = new char[::strlen(theFileName) + 6]; + ::strcpy(tempFileName, theFileName); + ::strcat(tempFileName, ".temp"); + int theTempFile = open(tempFileName, O_WRONLY | O_CREAT | O_TRUNC); + if (theTempFile == -1) + { qtss_printf("Failed to open temp file at %s.\n", tempFileName); exit(0); } + + // Setup all our tracks + UInt32 theMaxTrackID = 0; // These variables are needed to write the file header + UInt32 theNumTracks = 0; + QTRTPFile::RTPTrackListEntry* curTrack = theFile.GetFirstTrack(); + while (curTrack != NULL) + { + if (theMaxTrackID < curTrack->TrackID) + theMaxTrackID = curTrack->TrackID; + + theNumTracks++; + (void)theFile.AddTrack(curTrack->TrackID); + // Make the cookie be the track ID, so we know what the track ID is when + // we are retreiving packets + theFile.SetTrackCookies(curTrack->TrackID, (void*) NULL, curTrack->TrackID); + + curTrack = curTrack->NextTrack; + } + + // Seek to time 0 + (void)theFile.Seek(0); + + // Write status + qtss_printf("Generating RTP packets to temp file at %s...\n", tempFileName); + + // Write out all the packets to the temp file + UInt32 theNumBlocks = 0; + UInt8* theBlockArray = WriteTempFile(&theFile, theTempFile, &theNumBlocks); + ::close(theTempFile); + + // Write status + qtss_printf("Generating RTP file at %s. Copying packets...\n", outputFileName); + + // Get SDP + int theSDPLen = 0; + char* theSDPFile = theFile.GetSDPFile(&theSDPLen); + + // Write file header + RTPFileHeader theHeader; + theHeader.fSDPLen = theSDPLen; + theHeader.fNumTracks = theFile.GetNumHintTracks(); + theHeader.fMaxTrackID = theMaxTrackID; + theHeader.fBlockMapSize = theNumBlocks; + theHeader.fDataStartPos = theNumBlocks + theSDPLen + sizeof(RTPFileHeader) + + (sizeof(RTPFileTrackInfo) * theNumTracks); + theHeader.fMovieDuration = theFile.GetMovieDuration(); + + int theErr = ::write(theOutFile, &theHeader, sizeof(theHeader)); + if (theErr != sizeof(theHeader)) + { qtss_printf("Write operation failed on output file\n"); exit(0); } + + // Write the SDP data + theErr = ::write(theOutFile, theSDPFile, theSDPLen); + if (theErr != theSDPLen) + { qtss_printf("Write operation failed on output file\n"); exit(0); } + + // Write track info + curTrack = theFile.GetFirstTrack(); + while (curTrack != NULL) + { + RTPFileTrackInfo theTrackInfo; + theTrackInfo.fID = curTrack->TrackID; + theTrackInfo.fTimescale = curTrack->HintTrack->GetRTPTimescale(); + theTrackInfo.fBytesInTrack = curTrack->HintTrack->GetTotalRTPBytes(); + theTrackInfo.fDuration = curTrack->HintTrack->GetDurationInSeconds(); + + theErr = ::write(theOutFile, &theTrackInfo, sizeof(theTrackInfo)); + if (theErr != sizeof(theTrackInfo)) + { qtss_printf("Write operation failed on output file\n"); exit(0); } + + curTrack = curTrack->NextTrack; + } + + // Write the block map + UInt32 numWritten = ::write(theOutFile, theBlockArray, theNumBlocks); + if (numWritten != theNumBlocks) + { qtss_printf("Write operation failed on output file\n"); exit(0); } + + //Copy the temp file into the out file + theTempFile = open(tempFileName, O_RDONLY); + if (theTempFile == -1) + { qtss_printf("Failed to open temp file for reading at %s.\n", tempFileName); exit(0); } + + while (true) + { + char copyBuffer[kBlockSize]; + + // Read a block out of the temp file + theErr = ::read(theTempFile, ©Buffer[0], kBlockSize); + if (theErr <= 0) + break; + + int lenWritten = ::write(theOutFile, ©Buffer[0], theErr); + if (theErr != lenWritten) + { qtss_printf("Write operation failed on output file\n"); exit(0); } + } + + qtss_printf("RTP file generation complete\n"); + + // Delete the temp file + (void)::unlink(tempFileName); + + // close the output file + ::close(theOutFile); + + // We're done! +} + + +// Returns the complete block map +UInt8* WriteTempFile(QTRTPFile* inQTRTPFile, int inTempFile, UInt32* outNumBlocks) +{ + int theErr = -1; + UInt32 theNumPackets = 0; + + // Write all packets to the temp file. Pad out blocks appropriately + UInt32 offsetInBlock = 0; + + // As we write out packets, build our block map + UInt8* theBlockMap = new UInt8[1024]; + UInt32 curBlockMapSize = 1024; + UInt32 curBlockMapIndex = 0; + Float64 theLastBlockTime = 0; + + while (true) + { + RTPFilePacket thePacketHeader; + char* thePacketData = NULL; + int thePacketLength = 0; + + thePacketHeader.fTransmitTime = inQTRTPFile->GetNextPacket(&thePacketData, &thePacketLength); + if (thePacketData == NULL) + // We're done with all tracks + break; + + thePacketHeader.fPacketLength = thePacketLength; + thePacketHeader.fTrackID = inQTRTPFile->GetLastPacketTrack()->Cookie2; + + // Make sure there is enough room in the current block for this + // packet plus a header for the next packet. If not, move onto the next block + if ((offsetInBlock + thePacketHeader.fPacketLength + (2 * sizeof(RTPFilePacket))) > kBlockSize) + { + // Write out a pad packet + RTPFilePacket pad; + pad.fTrackID |= kPaddingBit; + + theErr = ::write(inTempFile, &pad, sizeof(pad)); + if (theErr < (int) sizeof(pad)) + { qtss_printf("Write operation failed on temp file\n"); exit(0); } + + offsetInBlock += sizeof(pad); + SInt32 spaceRemaining = kBlockSize - offsetInBlock; + Assert(spaceRemaining >= 0); + + // Fill out the rest of this block with crap + char* dumpBuf = new char[spaceRemaining]; + ::memset(dumpBuf, 0xFF, spaceRemaining);//Fill with FFs so we can debug easier + theErr = ::write(inTempFile, dumpBuf, spaceRemaining); + if (theErr < spaceRemaining) + { qtss_printf("Write operation failed on temp file\n"); exit(0); } + + delete [] dumpBuf; + + offsetInBlock = 0; + } + + // If this is the first packet in a block, we should mark the time in the + // Write the packet header and the packet data + if (offsetInBlock == 0) + { + // What is the offset between the first packet in the last block and this block? + Float64 theOffset = thePacketHeader.fTransmitTime - theLastBlockTime; + if (theOffset < 0) + theOffset = 0; //This may happen with the stupid negative times + theLastBlockTime = thePacketHeader.fTransmitTime; + if (theLastBlockTime < 0) + theLastBlockTime = 0; //This may happen with the stupid negative times + + Assert(theOffset < 255); + theBlockMap[curBlockMapIndex] = (UInt8)theOffset; + + // We may need to reallocate the block array + curBlockMapIndex++; + if (curBlockMapIndex == curBlockMapSize) + { + UInt8* newBlockArray = new UInt8[curBlockMapSize * 2]; + ::memcpy(newBlockArray, theBlockMap, curBlockMapSize); + curBlockMapSize *= 2; + + delete [] theBlockMap; + theBlockMap = newBlockArray; + } + } + + theNumPackets++; + theErr = ::write(inTempFile, &thePacketHeader, sizeof(thePacketHeader)); + if (theErr < (int) sizeof(thePacketHeader)) + { qtss_printf("Write operation failed on temp file\n"); exit(0); } + + theErr = ::write(inTempFile, thePacketData, thePacketLength); + if (theErr < thePacketLength) + { qtss_printf("Write operation failed on temp file\n"); exit(0); } + + // update our block offset + offsetInBlock += thePacketLength + sizeof(thePacketHeader); + } + + qtss_printf("Finished writing packets. Wrote %"_U32BITARG_" packets to temp file.\n", theNumPackets); + *outNumBlocks = curBlockMapIndex-1; + return theBlockMap; +} diff --git a/RTCPUtilitiesLib/RTCPAPPNADUPacket.cpp b/RTCPUtilitiesLib/RTCPAPPNADUPacket.cpp new file mode 100644 index 0000000..b178eb6 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAPPNADUPacket.cpp @@ -0,0 +1,657 @@ +/* + * + * @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: RTCPAPPNADUPacket.cpp + + Contains: RTCPAPPNADUPacket de-packetizing classes + + +*/ + + +#include "RTCPAPPNADUPacket.h" +#include "MyAssert.h" +#include "OS.h" +#include "OSMemory.h" +#include "StrPtrLen.h" + + + + + +/* RTCPNaduPacket +data: One or more of the following data format blocks may appear + +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P| subtype | PT=APP=204 | length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC/CSRC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| name (ASCII) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ <-------- data block +| SSRC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Playout Delay | NSN | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Reserved | NUN | Free Buffer Space (FBS) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +*/ +char RTCPNaduPacket::sRTCPTestBuffer[]; + + +RTCPNaduPacket::RTCPNaduPacket(Bool16 debug = false): + RTCPAPPPacket(debug), + fNaduDataBuffer(NULL), + fNumBlocks(0) +{ +} + +void RTCPNaduPacket::GetTestPacket(StrPtrLen* resultPtr) +{ +/* +Compound test packet + +lengths are 32 bit words, include header, are minus 1 + +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P| RC | PT=RR=201 | length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P| subtype | PT=SDES=202 | length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P| subtype | PT=APP=204 | length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC/CSRC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| name (ASCII) | PSS0 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+----app specific data PSS0 +| SSRC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Playout Delay | NSN | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Reserved | NUN | Free Buffer Space (FBS) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ----app data may repeat + + + +// rtcp common header + typedef struct { + unsigned int version:2; // protocol version + unsigned int p:1; // padding flag + unsigned int count:5; // varies by packet type + unsigned int pt:8; // RTCP packet type + u_int16 length; // pkt len in words, w/o this word can be 0 + } rtcp_common_t; + + // rtcp compound packet starts with rtcp rr header + // rr data may be empty or not + // nadu app header follows rr header and data if any + + ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +#if 1 //full receiver report with SDES and Nadu + UInt32 *theWriterStart = (UInt32*)sRTCPTestBuffer; + UInt32 *theWriter = (UInt32*)sRTCPTestBuffer; + + *(theWriter++) = htonl(0x81c90007); // 1 RR packet header, full report + *(theWriter++) = htonl(0x2935F2D6); // 1 Sender SSRC = 691401430 + *(theWriter++) = htonl(0x6078CE22); // 1 SSRC_1 = 1618529826 + *(theWriter++) = htonl(0x01000001); // fraction lost | cumulative num packets lost 1% , 1 packet + *(theWriter++) = htonl(0x0000361A); // extended highest seq number received = 13850 + *(theWriter++) = htonl(0x00C7ED4D); // interarrival jitter = 13102413 + *(theWriter++) = htonl(0x00000000); // LSR last sender report = 0 + *(theWriter++) = htonl(0x04625238); // Delay since last SR (DLSR) = 73552440 (garbage) + + *(theWriter++) = htonl(0x81ca0005); // 1 SDES packet header, + *(theWriter++) = htonl(0x2935F2D6); // 1 Sender SSRC = 691401430 + *(theWriter++) = htonl(0x010A5344); // 1 CNAME = 01, len=10, "SD" + *(theWriter++) = htonl(0x45532043); // 1 CNAME = "ES C" + *(theWriter++) = htonl(0x4e414d45); // 1 CNAME = "NAME" + *(theWriter++) = htonl(0x00000000); // NULL item = end of list + 32bit padding + + + + *(theWriter++) = htonl(0x80CC0000); // 1 APP packet header, needs len -> assigned beow + + UInt32 *appPacketLenStart = theWriter; + + *(theWriter++) = htonl(FOUR_CHARS_TO_INT('S', 'S', 'R', 'C')); //nadu ssrc + *(theWriter++) = htonl(FOUR_CHARS_TO_INT('P', 'S', 'S', '0')); //nadu app packet name + + // first (typically only) ssrc block + *(theWriter++) = htonl(0x423A35C7); //ssrc = 1111111111 + *(theWriter++) = htonl(0x2B6756CE); //delay | nsn = 11111 | 22222 + *(theWriter++) = htonl(0xFFFFAD9C); //nun | fbs= 31 | 44444 + + // optional 2nd or more ssrc blocks + *(theWriter++) = htonl(0x84746B8E); //ssrc = 222222222 + *(theWriter++) = htonl(0x2B6756CE); //delay | nsn = 11111 | 22222 + *(theWriter++) = htonl(0xFFFFAD9C); //nun | fbs= 31 | 44444 + + UInt16 *packetLenOffsetPtr = &( (UInt16*)theWriterStart)[29]; + UInt16 packetLenInWords = htons( ((UInt32*)theWriter - (UInt32*)appPacketLenStart) ) ; + + *packetLenOffsetPtr = packetLenInWords; + qtss_printf("packetLenInWords =%lu\n", ntohs(packetLenInWords)); + UInt32 len = (char*)theWriter - (char*)theWriterStart; + if (resultPtr) + resultPtr->Set(sRTCPTestBuffer, len); + +#endif + +#if 0 //full receiver report with Nadu + UInt32 *theWriterStart = (UInt32*)sRTCPTestBuffer; + UInt32 *theWriter = (UInt32*)sRTCPTestBuffer; + + *(theWriter++) = htonl(0x80c90007); // 1 RR packet header, empty len is ok but could be a full report + *(theWriter++) = htonl(0x2935F2D6); // 1 SSRC = 691401430 + *(theWriter++) = htonl(0x6078CE22); // 1 SSRC_1 = 1618529826 + *(theWriter++) = htonl(0x01000001); // fraction lost | cumulative num packets lost 1% , 1 packet + *(theWriter++) = htonl(0x0000361A); // extended highest seq number received = 13850 + *(theWriter++) = htonl(0x00C7ED4D); // interarrival jitter = 13102413 + *(theWriter++) = htonl(0x00000000); // LSR last sender report = 0 + *(theWriter++) = htonl(0x04625238); // Delay since last SR (DLSR) = 73552440 (garbage) + + + + *(theWriter++) = htonl(0x80CC0000); // 1 APP packet header, needs len -> assigned beow + + UInt32 *appPacketLenStart = theWriter; + + *(theWriter++) = htonl(FOUR_CHARS_TO_INT('S', 'S', 'R', 'C')); //nadu ssrc + *(theWriter++) = htonl(FOUR_CHARS_TO_INT('P', 'S', 'S', '0')); //nadu app packet name + + // first (typically only) ssrc block + *(theWriter++) = htonl(0x423A35C7); //ssrc = 1111111111 + *(theWriter++) = htonl(0x2B6756CE); //delay | nsn = 11111 | 22222 + *(theWriter++) = htonl(0xFFFFAD9C); //nun | fbs= 31 | 44444 + + // optional 2nd or more ssrc blocks + *(theWriter++) = htonl(0x84746B8E); //ssrc = 222222222 + *(theWriter++) = htonl(0x2B6756CE); //delay | nsn = 11111 | 22222 + *(theWriter++) = htonl(0xFFFFAD9C); //nun | fbs= 31 | 44444 + + UInt16 *packetLenOffsetPtr = &( (UInt16*)theWriterStart)[17]; + UInt16 packetLenInWords = htons( (UInt32*)theWriter - (UInt32*)appPacketLenStart) ; + + *packetLenOffsetPtr = packetLenInWords; + + UInt32 len = (char*)theWriter - (char*)theWriterStart; + if (resultPtr) + resultPtr->Set(sRTCPTestBuffer, len); + +#endif + +#if 0 //empty receiver report with NADU + UInt32 *theWriterStart = (UInt32*)sRTCPTestBuffer; + UInt32 *theWriter = (UInt32*)sRTCPTestBuffer; + + *(theWriter++) = htonl(0x80c90000); // 1 RR packet header, empty len is ok but could be a full report + + *(theWriter++) = htonl(0x80CC0000); // 1 APP packet header, needs len -> assigned beow + + UInt32 *appPacketLenStart = theWriter; + + *(theWriter++) = htonl(FOUR_CHARS_TO_INT('S', 'S', 'R', 'C')); //nadu ssrc + *(theWriter++) = htonl(FOUR_CHARS_TO_INT('P', 'S', 'S', '0')); //nadu app packet name + + // first (typically only) ssrc block + *(theWriter++) = htonl(0x423A35C7); //ssrc = 1111111111 + *(theWriter++) = htonl(0x2B6756CE); //delay | nsn = 11111 | 22222 + *(theWriter++) = htonl(0xFFFFAD9C); //nun | fbs= 31 | 44444 + + // optional 2nd or more ssrc blocks + *(theWriter++) = htonl(0x84746B8E); //ssrc = 222222222 + *(theWriter++) = htonl(0x2B6756CE); //delay | nsn = 11111 | 22222 + *(theWriter++) = htonl(0xFFFFAD9C); //nun | fbs= 31 | 44444 + + UInt16 *packetLenOffsetPtr = &( (UInt16*)theWriterStart)[3]; + UInt16 packetLenInWords = htons( (UInt32*)theWriter - (UInt32*)appPacketLenStart) ; + + *packetLenOffsetPtr = packetLenInWords; + + UInt32 len = (char*)theWriter - (char*)theWriterStart; + if (resultPtr) + resultPtr->Set(sRTCPTestBuffer, len); +#endif + +/* + +sample run of the test packet below: +---------------------------------------- +RTPStream::TestRTCPPackets received packet inPacketPtr.Ptr=0xf0080568 inPacketPtr.len =20 +testing RTCPNaduPacket using packet inPacketPtr.Ptr=0xe2c38 inPacketPtr.len =40 +>recv sess=1: RTCP RR recv_sec=6.812 type=video size=40 H_vers=2, H_pad=0, H_rprt_count=0, H_type=201, H_length=0, H_ssrc=-2134114296 +>recv sess=1: RTCP APP recv_sec=6.813 type=video size=36 H_vers=2, H_pad=0, H_rprt_count=0, H_type=204, H_length=8, H_ssrc=1397969475 + +NADU Packet + Block Index = 0 (h_ssrc = 1111111111, h_playoutdelay = 11111, h_sequence_num = 22222, h_nun_unit_num = 31, h_fbs_free_buf = 44444) + Block Index = 1 (h_ssrc = 2222222222, h_playoutdelay = 11111, h_sequence_num = 22222, h_nun_unit_num = 31, h_fbs_free_buf = 44444) + +Dumping Nadu List (list size = 3 record count=48) +------------------------------------------------------------- +NADU Record: list_index = 2 list_recordID = 48 + Block Index = 0 (h_ssrc = 1111111111, h_playoutdelay = 11111, h_sequence_num = 22222, h_nun_unit_num = 31, h_fbs_free_buf = 44444) + Block Index = 1 (h_ssrc = 2222222222, h_playoutdelay = 11111, h_sequence_num = 22222, h_nun_unit_num = 31, h_fbs_free_buf = 44444) +NADU Record: list_index = 1 list_recordID = 47 + Block Index = 0 (h_ssrc = 1111111111, h_playoutdelay = 11111, h_sequence_num = 22222, h_nun_unit_num = 31, h_fbs_free_buf = 44444) + Block Index = 1 (h_ssrc = 2222222222, h_playoutdelay = 11111, h_sequence_num = 22222, h_nun_unit_num = 31, h_fbs_free_buf = 44444) +NADU Record: list_index = 0 list_recordID = 46 + Block Index = 0 (h_ssrc = 1111111111, h_playoutdelay = 11111, h_sequence_num = 22222, h_nun_unit_num = 31, h_fbs_free_buf = 44444) + Block Index = 1 (h_ssrc = 2222222222, h_playoutdelay = 11111, h_sequence_num = 22222, h_nun_unit_num = 31, h_fbs_free_buf = 44444) + + +*/ + +} + + + +// use if you don't know what kind of packet this is +Bool16 RTCPNaduPacket::ParseNaduPacket(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + + if (!this->ParseAPPPacket(inPacketBuffer, inPacketLength)) + return false; + + if (this->GetAppPacketName() != RTCPNaduPacket::kNaduPacketName) + return false; + + return true; +} + + +Bool16 RTCPNaduPacket::ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + + if (!this->ParseNaduPacket(inPacketBuffer, inPacketLength) ) + return false; + + UInt32 *naduDataBuffer = (UInt32 *) (this->GetPacketBuffer()+kNaduDataOffset); + + int wordsLen = this->GetPacketLength() - 2; + if (wordsLen < 3) // min is 3 + return false; + + if (0 !=(wordsLen % 3))// blocks are 3x32bits so there is a bad block somewhere. + return false; + + fNumBlocks = wordsLen / 3; + + if (0 == fNumBlocks) + return false; + + if (fNumBlocks > 100) // too many + return false; + + fNaduDataBuffer = naduDataBuffer; + + if (0) //testing + this->DumpNaduPacket(); + + return true; + + } + +void RTCPNaduPacket::DumpNaduPacket() +{ + char printName[5]; + (void) this->GetAppPacketName(printName, sizeof(printName)); + qtss_printf(" H_app_packet_name = %s, ", printName ); + + qtss_printf("\n"); + SInt32 count = 0; + for (; count < fNumBlocks ; count ++) + { + + UInt32 ssrc = this->GetSSRC(count); + UInt32 ssrcIndex = this->GetSSRCBlockIndex(ssrc); + UInt16 playoutDelay = this->GetPlayOutDelay(count); + UInt16 nsn = this->GetNSN(count); + UInt16 nun = this->GetNUN(count); + UInt16 fbs = this->GetFBS(count); + qtss_printf(" "); + qtss_printf("RTCP APP NADU Report[%"_U32BITARG_"] ", ssrcIndex); + qtss_printf("h_ssrc = %"_U32BITARG_, ssrc); + qtss_printf(", h_playoutdelay = %u", playoutDelay); + qtss_printf(", h_sequence_num = %u", nsn); + qtss_printf(", h_nun_unit_num = %u", nun); + qtss_printf(", h_fbs_free_buf = %u", fbs); + + qtss_printf("\n"); + } + } + + + + +SInt32 RTCPNaduPacket::GetSSRCBlockIndex(UInt32 inSSRC) +{ + UInt32 *blockBuffer = NULL; + SInt32 count = 0; + UInt32 ssrc = 0; + + if (NULL == fNaduDataBuffer) + return -1; + + for (; count < fNumBlocks ; count ++) + { + blockBuffer = fNaduDataBuffer + (count * 3); + ssrc = (UInt32) ntohl(*(UInt32*)&blockBuffer[kOffsetNaduSSRC]); + + if (ssrc == inSSRC) + return count; + + } + + return -1; +} + +UInt32 RTCPNaduPacket::GetSSRC(SInt32 index) +{ + + if (index < 0) + return 0; + + if (NULL == fNaduDataBuffer) + return 0; + + if (index >= fNumBlocks) + return 0; + + UInt32 *blockBufferPtr = fNaduDataBuffer + (index * 3); + UInt32 ssrc = (UInt32) ntohl(*(UInt32*)&blockBufferPtr[kOffsetNaduSSRC]); + + return ssrc; + +} + +UInt16 RTCPNaduPacket::GetPlayOutDelay(SInt32 index) +{ + if (index < 0) + return 0; + + if (NULL == fNaduDataBuffer) + return 0; + + if (index >= fNumBlocks) + return 0; + + UInt32 *blockBufferPtr = fNaduDataBuffer + (index * 3); + UInt16 delay = (UInt16) ( ( ntohl(*(UInt32*)&blockBufferPtr[kOffsetNaduPlayoutDelay]) & kPlayoutMask) >> 16); + + return delay; +} + +UInt16 RTCPNaduPacket::GetNSN(SInt32 index) +{ + if (index < 0) + return 0; + + if (NULL == fNaduDataBuffer) + return 0; + + if (index >= fNumBlocks) + return 0; + + UInt32 *blockBufferPtr = fNaduDataBuffer + (index * 3); + UInt16 nsn = (UInt16) ( ntohl(blockBufferPtr[kOffsetNSN]) & kNSNMask ); + + return nsn; +} + +UInt16 RTCPNaduPacket::GetNUN(SInt32 index) +{ + if (index < 0) + return 0; + + if (NULL == fNaduDataBuffer) + return 0; + + if (index >= fNumBlocks) + return 0; + + UInt32 *blockBufferPtr = fNaduDataBuffer + (index * 3); + UInt16 nun = (UInt16) ((ntohl(blockBufferPtr[kOffsetNUN]) & kNUNMask) >> 16); + + return nun; +} + +UInt16 RTCPNaduPacket::GetFBS(SInt32 index) +{ + if (index < 0) + return 0; + + if (NULL == fNaduDataBuffer) + return 0; + + if (index >= fNumBlocks) + return 0; + + UInt32 *blockBufferPtr = fNaduDataBuffer + (index * 3); + UInt16 fbs = (UInt16) ntohl(blockBufferPtr[kOffsetFBS]) & kFBSMask; + + return fbs; +} + +void RTCPNaduPacket::Dump() +{ + this->DumpNaduPacket(); + +} + +/* class NaduReport */ +NaduReport::NaduReport(UInt8* inPacketBuffer, UInt32 inPacketLength, UInt32 id) +{ + fPacketBuffer = NEW UInt8[inPacketLength+1]; + fPacketBuffer[inPacketLength] = 0; + fLength = inPacketLength; + ::memcpy(fPacketBuffer, inPacketBuffer, inPacketLength); + fNaduPacket.ParseAPPData(fPacketBuffer, inPacketLength); + fid = id; +} + + + +/* class NaduList */ + +void NaduList::Initialize(UInt32 listSize) +{ + + fNaduReportList = NEW NaduReport *[listSize]; + ::memset( (void *) fNaduReportList, 0, sizeof(NaduReport*) * listSize); //initialize ptr array with 0. + fListSize = listSize; + +} + +NaduReport* NaduList::GetReport(UInt32 id) +{ + + if (NULL == fNaduReportList) + return NULL; + + + NaduReport *result = fNaduReportList[this->IDtoIndex(id)]; + if (result && result->getID() == id) + return result; + return NULL; + +} + +UInt32 NaduList::GetReportIndex(UInt32 id) +{ + + if (NULL == fNaduReportList) + return 0; + + UInt32 index = this->IDtoIndex(id); + NaduReport *result = fNaduReportList[index]; + if (result && result->getID() == id) + return index; + return 0; + +} + +NaduReport* NaduList::GetLastReport() +{ + if (NULL == fNaduReportList || fcurrentIndexCount == 0) + return NULL; + + UInt32 index = this->IDtoIndex(fcurrentIndexCount); + return fNaduReportList[index]; + +} + +NaduReport* NaduList::GetPreviousReport(NaduReport* theReport) +{ + if (NULL == theReport) + return NULL; + + return this->GetReport(theReport->getID() - 1); + +} + + +NaduReport* NaduList::GetNextReport(NaduReport* theReport) +{ + if (NULL == theReport) + return NULL; + + return this->GetReport(theReport->getID() + 1); + +} + +NaduReport* NaduList::GetEarliestReport() +{ + + if ( fcurrentIndexCount > fListSize) + return fNaduReportList[fcurrentIndexCount % fListSize]; + + return fNaduReportList[0]; +} + + +Bool16 NaduList::AddReport(UInt8* inPacketBuffer, UInt32 inPacketLength, UInt32 *outID) +{ + if (NULL == fNaduReportList) + return false; + + UInt32 resultID = ++fcurrentIndexCount; + UInt32 index =this->IDtoIndex(fcurrentIndexCount); + + if (fNaduReportList[index] != 0) + delete fNaduReportList[index]; + + fNaduReportList[index] = NEW NaduReport(inPacketBuffer,inPacketLength, resultID); + + if (outID) + *outID = resultID; + + return true; + +} + + + +UInt32 NaduList::LastReportedFreeBuffSizeBytes() +{ + NaduReport* currentReportPtr = this->GetLastReport(); + if (NULL == currentReportPtr) + return 0; + + RTCPNaduPacket *theNADUPacketData = currentReportPtr->GetNaduPacket(); + if (NULL == theNADUPacketData) + return 0; + + return ((UInt32) theNADUPacketData->GetFBS(0)) * 64; //64 byte blocks are in the report +} + +UInt32 NaduList::LastReportedTimeDelayMilli() +{ + NaduReport* currentReportPtr = this->GetLastReport(); + if (NULL == currentReportPtr) + return 0; + + RTCPNaduPacket *theNADUPacketData = currentReportPtr->GetNaduPacket(); + if (NULL == theNADUPacketData) + return 0; + + return theNADUPacketData->GetPlayOutDelay(0); +} + +UInt16 NaduList::GetLastReportedNSN() +{ + NaduReport* currentReportPtr = this->GetLastReport(); + if (NULL == currentReportPtr) + return 0; + + RTCPNaduPacket *theNADUPacketData = currentReportPtr->GetNaduPacket(); + if (NULL == theNADUPacketData) + return 0; + + return theNADUPacketData->GetNSN(0); +} + +void NaduList::DumpList() +{ + + qtss_printf("\nDumping Nadu List (list size = %"_U32BITARG_" record count=%"_U32BITARG_")\n",fListSize, fcurrentIndexCount); + qtss_printf("-------------------------------------------------------------\n"); + NaduReport* lastReportPtr = this->GetLastReport(); + NaduReport* earliestReportPtr = this->GetEarliestReport(); + UInt32 thisID = 0; + UInt32 stopID = 0; + if (earliestReportPtr) + stopID = earliestReportPtr->getID(); + + while (lastReportPtr) + { + thisID = lastReportPtr->getID(); + qtss_printf("NADU Record: list_index = %"_U32BITARG_" list_recordID = %"_U32BITARG_"\n", this->GetReportIndex(thisID), thisID); + lastReportPtr->GetNaduPacket()->Dump(); + if (thisID == stopID) + break; + + thisID --; + lastReportPtr = this->GetReport(thisID); + } + +} diff --git a/RTCPUtilitiesLib/RTCPAPPNADUPacket.h b/RTCPUtilitiesLib/RTCPAPPNADUPacket.h new file mode 100644 index 0000000..a629a44 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAPPNADUPacket.h @@ -0,0 +1,240 @@ +/* + * + * @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: RTCPAPPNADUPacket.h + + Contains: RTCPAPPNADUPacket de-packetizing class + + + +*/ + +#ifndef _RTCPAPPNADUPACKET_H_ +#define _RTCPAPPNADUPACKET_H_ + +#include "RTCPAPPPacket.h" +#include "StrPtrLen.h" + + + + + + +class RTCPNaduPacket : public RTCPAPPPacket +{ +public: + + RTCPNaduPacket(Bool16 debug); + virtual ~RTCPNaduPacket() {} + + //Call this before any accessor method. Returns true if successful, false otherwise + virtual Bool16 ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength); + + // Call to parse if you don't know what kind of packet this is + Bool16 ParseNaduPacket(UInt8* inPacketBuffer, UInt32 inPacketLength); + + UInt32 GetNumReportBlocks() { return fNumBlocks; }; + + SInt32 GetSSRCBlockIndex(UInt32 inSSRC); + + UInt32 GetSSRC(SInt32 index); + + UInt16 GetPlayOutDelay(SInt32 index); + + UInt16 GetNSN(SInt32 index); + + UInt16 GetNUN(SInt32 index); + + UInt16 GetFBS(SInt32 index); + + void DumpNaduPacket(); + + static void GetTestPacket(StrPtrLen* resultPtr); + virtual void Dump(); //Override + + enum + { + kNaduDataOffset = 12, + //32 bit word offsets + kOffsetNaduSSRC = 0, //SSRC for this report + kOffsetNaduPlayoutDelay = 1, + kOffsetNSN = 1, + kOffsetNUN = 2, + kOffsetFBS = 2, + + + kPlayoutMask = 0xFFFF0000UL, + kNSNMask = 0x0000FFFFUL, + kNUNMask = 0x001F0000UL, + kFBSMask = 0x0000FFFFUL, + kReservedPlayoutDelayValue = 0xFFFF, + kMaximumReportableFreeBufferSpace = (0xFFFF * 64) //the maximum amount of free buffer space reportable in bytes + }; + + enum //The 4 character name in the APP packet + { + kNaduPacketName = FOUR_CHARS_TO_INT('P', 'S', 'S', '0') //QTSS + }; + + enum + { + kSupportedNaduPacketVersion = 0 + }; + +private: + void ParseAndStore(); + UInt32* fNaduDataBuffer; + SInt32 fNumBlocks; + static char sRTCPTestBuffer[256]; + +}; + + + + +/* + +3GPP TS 26.234 V6.4.0 (2005-06) + +6.6 APP: Application-defined RTCP packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| subtype | PT=APP=204 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | name (ASCII) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | application-dependent data | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +For rate adaptation the name and subtype fields must be set to the following values: + +name: The NADU APP data format is detected through the name "PSS0", i.e. 0x50535330 and the subtype. + +subtype: This field shall be set to 0 for the NADU format. + +length: The number of 32 bit words Ð1, as defined in RFC 3550 [9]. This means that the field will be 2+3*N, where N is the number of sources reported on. The length field will typically be 5, i.e. 24 bytes packets. application-dependent + +data: One or more of the following data format blocks (as described in Figure 4) can be included in the application-dependent data location of the APP packet. The APP packets length field is used to detect how many blocks of data are present. The block shall be sent for the SSRCs for which there are a report block, part of either a Receiver Report or a Sender Report, included in the RTCP compound packet. An NADU APP packet shall not contain any other data format than the one described in figure 4 below. + +0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Playout Delay | NSN | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Reserved | NUN | Free Buffer Space (FBS) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +Figure 4: Data format block for NADU reporting + +SSRC: The SSRC of the media stream the buffered packets belong to. + +Playout delay (16 bits): The difference between the scheduled playout time of the next ADU to be decoded and the time of sending the NADU APP packet, as measured by the media playout clock, expressed in milliseconds. The client may +choose not to indicate this value by using the reserved value (Ox FFFF). In case of an empty buffer, the playout delay is not defined and the client should also use the reserved value 0xFFFF for this field. The playout delay allows the server to have a more precise value of the amount of time before the client will underflow. The playout delay shall be computed until the actual media playout (i.e., audio playback or video display). + +NSN (16 bits): The RTP sequence number of the next ADU to be decoded for the SSRC reported on. In the case where the buffer does not contain any packets for this SSRC, the next not yet received sequence number shall be reported, i.e. +an NSN value that is one larger than the least significant 16 bits of the RTCP SR or RR report block's "extended highest sequence number received". + +NUN (5 bits): The unit number (within the RTP packet) of the next ADU to be decoded. The first unit in a packet has a unit number equal to zero. The unit number is incremented by one for each ADU in an RTP packet. In the case of an +audio codec, an ADU is defined as an audio frame. In the case of H.264 (AVC), an ADU is defined as a NAL unit. In the case of H.263 and MPEG4 Visual Simple Profile, each packet carries a single ADU and the NUN field shall be thus +set to zero. Future additions of media encoding or transports capable of having more than one ADU in each RTP payload shall define what shall be counted as an ADU for this format. + +FBS (16 bit): The amount of free buffer space available in the client at the time of reporting. The reported free buffer space shall all be part of the buffer space that has been reported as available for adaptation by the 3GPP-Adaptation RTSP header, see clause 5.3.2.2. The amount of free buffer space are reported in number of complete 64 byte blocks, thus allowing for up to 4194304 bytes to be reported as free. If more is available, it shall be reported as the maximal amount available, i.e. 4194304 with a field value 0xffff. + +Reserved (11 bits): These bits are not used and shall be set to 0 and shall be ignored by the receiver. + + + + */ + + +class NaduReport +{ + +public: + NaduReport(UInt8* inPacketBuffer, UInt32 inPacketLength, UInt32 id); + ~NaduReport() { delete fPacketBuffer; } + UInt8* getBuffer() { return fPacketBuffer; } + UInt32 getLength() { return fLength; } + UInt32 getID() { return fid; } + RTCPNaduPacket * GetNaduPacket(){ return &fNaduPacket; } + + UInt8* fPacketBuffer; + UInt32 fLength; + RTCPNaduPacket fNaduPacket; + UInt32 fid; + +}; + + +// Keep track of the last listSize nadu reports and access them as needed. +// DumpList prints each report while walking backward from the most recent. + +class NaduList +{ +public: + NaduList(): fNaduReportList(NULL), fcurrentIndexCount(0), fListSize(0) {}; + ~NaduList() { + for (int i = 0; i < fListSize; i++) { + if (fNaduReportList[i] != 0) { + delete fNaduReportList[i]; + fNaduReportList[i] = 0; + } + } + delete [] fNaduReportList; + } + void Initialize(UInt32 listSize = 3); + + Bool16 AddReport(UInt8* inPacketBuffer, UInt32 inPacketLength, UInt32 *outID); + + NaduReport* GetReport(UInt32 id); + NaduReport* GetLastReport(); + NaduReport* GetEarliestReport(); + NaduReport* GetPreviousReport(NaduReport* theReport); + NaduReport* GetNextReport(NaduReport* theReport); + UInt32 LastReportedFreeBuffSizeBytes(); + UInt32 LastReportedTimeDelayMilli(); + UInt16 GetLastReportedNSN(); + + void DumpList(); + +private: + UInt32 GetReportIndex(UInt32 id); + UInt32 IDtoIndex (UInt32 id) { return (id -1) % fListSize; } + NaduReport** fNaduReportList; + UInt32 fcurrentIndexCount; + UInt32 fListSize; + + +}; + + + +#endif //_RTCPAPPNADUPACKET_H_ diff --git a/RTCPUtilitiesLib/RTCPAPPPacket.cpp b/RTCPUtilitiesLib/RTCPAPPPacket.cpp new file mode 100644 index 0000000..dc0807d --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAPPPacket.cpp @@ -0,0 +1,87 @@ +/* + * + * @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: RTCPAPPPacket.cpp + + Contains: RTCPAPPPacket de-packetizing classes + + +*/ + + +#include "RTCPAPPPacket.h" +#include "MyAssert.h" +#include "OS.h" +#include "OSMemory.h" +#include "ResizeableStringFormatter.h" + + +RTCPAPPPacket::RTCPAPPPacket(Bool16 debug) : + fRTCPAPPDataBuffer(NULL), + fAPPDataBufferSize(0), + mDumpArray(NULL), + mDumpArrayStrDeleter(NULL), + fDumpReport(), + fDebug(debug) +{ + if (fDebug) + { + mDumpArray = NEW char[kmDumpArraySize]; + mDumpArray[0] = '\0'; + mDumpArrayStrDeleter.Set(mDumpArray); + } + +} + +void RTCPAPPPacket::Dump()//Override +{ + + + RTCPPacket::Dump(); + fDumpReport.PutTerminator(); + qtss_printf("%s\n", fDumpReport.GetBufPtr()); + fDumpReport.Reset(); +} + + +Bool16 RTCPAPPPacket::ParseAPPPacketHeader(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + if (inPacketLength < kRTCPPacketSizeInBytes + kRTCPAPPHeaderSizeInBytes) + return false; + + return true; +} + +Bool16 RTCPAPPPacket::ParseAPPPacket(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + if (false == this->ParsePacket(inPacketBuffer, inPacketLength) ) // base class + return false; + + return this->ParseAPPPacketHeader(inPacketBuffer, inPacketLength); + +} + + + diff --git a/RTCPUtilitiesLib/RTCPAPPPacket.h b/RTCPUtilitiesLib/RTCPAPPPacket.h new file mode 100644 index 0000000..ac5d409 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAPPPacket.h @@ -0,0 +1,126 @@ +/* + * + * @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: RTCPAPPPacket.h + + Contains: RTCPAPPPacket de-packetizing classes + + + +*/ + +#ifndef _RTCPAPPPACKET_H_ +#define _RTCPAPPPACKET_H_ + +#include "RTCPPacket.h" +#include "StrPtrLen.h" +#include "ResizeableStringFormatter.h" + +#define APPEND_TO_DUMP_ARRAY(f, v) {if (fDebug && mDumpArray != NULL) { (void)::snprintf(mDumpArray,kmDumpArraySize, f, v); fDumpReport.Put(mDumpArray); } } + +class RTCPAPPPacket : public RTCPPacket +{ + +public: + RTCPAPPPacket(Bool16 debug = false); + virtual ~RTCPAPPPacket() {}; + virtual void Dump(); + virtual Bool16 ParseAPPPacket(UInt8* inPacketBuffer, UInt32 inPacketLength); //default app header check + virtual Bool16 ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength) { return false; }; //derived class implements + inline FourCharCode GetAppPacketName(char *outName = NULL, UInt32 len = 0); + inline UInt32 GetAppPacketSSRC(); + + + UInt8* fRTCPAPPDataBuffer; //points into RTCPPacket::fReceiverPacketBuffer should be set past the app header + UInt32 fAPPDataBufferSize; + + enum + { + kAppSSRCOffset = 4, + kAppNameOffset = 8, //byte offset to four char App identifier //All are UInt32 + + kRTCPAPPHeaderSizeInBytes = 4, // + kmDumpArraySize = 1024 + }; + + char* mDumpArray; + StrPtrLenDel mDumpArrayStrDeleter; + ResizeableStringFormatter fDumpReport; + Bool16 fDebug; + +private: + virtual Bool16 ParseAPPPacketHeader(UInt8* inPacketBuffer, UInt32 inPacketLength); + +}; + + + +/**************** RTCPAPPPacket inlines *******************************/ + +inline FourCharCode RTCPAPPPacket::GetAppPacketName(char *outName, UInt32 len) +{ + + UInt32 packetName = (UInt32) (*(UInt32*)&(GetPacketBuffer()[kAppNameOffset]) ) ; + + if (outName) + { if (len > 4) + { *((UInt32*)outName) = packetName; + outName[4] = 0; + } + else if (len > 0) + outName[0] = 0; + } + + return ntohl(packetName); +} + + +inline UInt32 RTCPAPPPacket::GetAppPacketSSRC() +{ + return (UInt32) ntohl(*(UInt32*)&(GetPacketBuffer()[kAppSSRCOffset]) ) ; +} + + + + + +/* +6.6 APP: Application-defined RTCP packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| subtype | PT=APP=204 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | name (ASCII) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | application-dependent data | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + */ + +#endif //_RTCPAPPPACKET_H_ diff --git a/RTCPUtilitiesLib/RTCPAPPQTSSPacket.cpp b/RTCPUtilitiesLib/RTCPAPPQTSSPacket.cpp new file mode 100644 index 0000000..ce8f7bc --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAPPQTSSPacket.cpp @@ -0,0 +1,316 @@ +/* + * + * @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: RTCPAPPQTSSPacket.cpp + + Contains: RTCPAPPQTSSPacket de-packetizing classes + + +*/ + + +#include "RTCPAPPQTSSPacket.h" +#include "MyAssert.h" +#include "OS.h" +#include "OSMemory.h" + + +RTCPCompressedQTSSPacket::RTCPCompressedQTSSPacket(Bool16 debug) : + RTCPAPPPacket(debug), + fReceiverBitRate(0), + fAverageLateMilliseconds(0), + fPercentPacketsLost(0), + fAverageBufferDelayMilliseconds(0), + fIsGettingBetter(false), + fIsGettingWorse(false), + fNumEyes(0), + fNumEyesActive(0), + fNumEyesPaused(0), + fOverbufferWindowSize(kUInt32_Max), + + //Proposed - are these there yet? + fTotalPacketsReceived(0), + fTotalPacketsDropped(0), + fTotalPacketsLost(0), + fClientBufferFill(0), + fFrameRate(0), + fExpectedFrameRate(0), + fAudioDryCount(0) +{ +} + +// use if you don't know what kind of packet this is +Bool16 RTCPCompressedQTSSPacket::ParseCompressedQTSSPacket(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + if (!this->ParseAPPPacket(inPacketBuffer, inPacketLength)) + return false; + + if (this->GetAppPacketName() != RTCPCompressedQTSSPacket::kCompressedQTSSPacketName) + return false; + + if (inPacketLength < kQTSSDataOffset) + return false; + + //figure out how many 32-bit words remain in the buffer + UInt32 theMaxDataLen = inPacketLength - kQTSSDataOffset; + theMaxDataLen /= 4; + + //if the number of 32 bit words reported in the packet is greater than the theoretical limit, + //return an error + if (this->GetQTSSPacketLength() > theMaxDataLen) + return false; + + if (this->GetQTSSPacketVersion() != kSupportedCompressedQTSSVersion) + return false; + + if (this->GetReportCount() > 0) + return false; + + + return true; +} + + + +// You know the packet type and just want to parse it now +Bool16 RTCPCompressedQTSSPacket::ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + + if ( !this->ParseCompressedQTSSPacket(inPacketBuffer, inPacketLength) ) + return false; + + APPEND_TO_DUMP_ARRAY("%s","\n RTCP APP QTSS Report "); + + char printName[5]; + (void) this->GetAppPacketName(printName, sizeof(printName)); + APPEND_TO_DUMP_ARRAY("H_app_packet_name = %s, ", printName ); + APPEND_TO_DUMP_ARRAY("H_ssrc = %"_U32BITARG_", ", this->GetPacketSSRC()); + APPEND_TO_DUMP_ARRAY("H_src_ID = %"_U32BITARG_", ", this->GetQTSSReportSourceID()); + APPEND_TO_DUMP_ARRAY("H_vers=%d, ", this->GetQTSSPacketVersion()); + APPEND_TO_DUMP_ARRAY("H_packt_len=%d", this->GetQTSSPacketLength()); + + UInt8* qtssDataBuffer = this->GetPacketBuffer()+kQTSSDataOffset; + + //packet length is given in words + UInt32 bytesRemaining = this->GetQTSSPacketLength() * 4; + while ( bytesRemaining >= 4 ) //items must be at least 32 bits + { + // DMS - There is no guarentee that qtssDataBuffer will be 4 byte aligned, because + // individual APP packet fields can be 6 bytes or 4 bytes or 8 bytes. So we have to + // use the 4-byte align protection functions. Sparc and MIPS processors will crash otherwise + UInt32 theHeader = ntohl(OS::GetUInt32FromMemory((UInt32*)&qtssDataBuffer[kQTSSItemTypeOffset])); + UInt16 itemType = (UInt16)((theHeader & kQTSSItemTypeMask) >> kQTSSItemTypeShift); + UInt8 itemVersion = (UInt8)((theHeader & kQTSSItemVersionMask) >> kQTSSItemVersionShift); + UInt8 itemLengthInBytes = (UInt8)(theHeader & kQTSSItemLengthMask); + + APPEND_TO_DUMP_ARRAY("\n h_type=%.2s(", (char*)&itemType); + APPEND_TO_DUMP_ARRAY(", h_vers=%u", itemVersion); + APPEND_TO_DUMP_ARRAY(", h_size=%u", itemLengthInBytes); + + qtssDataBuffer += sizeof(UInt32); //advance past the above UInt16's & UInt8's (point it at the actual item data) + + //Update bytesRemaining (move it past current item) + //This itemLengthInBytes is part of the packet and could therefore be bogus. + //Make sure not to overstep the end of the buffer! + bytesRemaining -= sizeof(UInt32); + if (itemLengthInBytes > bytesRemaining) + break; //don't walk off the end of the buffer + //itemLengthInBytes = bytesRemaining; + bytesRemaining -= itemLengthInBytes; + + switch (itemType) + { + case TW0_CHARS_TO_INT( 'r', 'r' ): //'rr': //'rrcv': + { + fReceiverBitRate = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); + qtssDataBuffer += sizeof(fReceiverBitRate); + APPEND_TO_DUMP_ARRAY(", rcvr_bit_rate=%"_U32BITARG_"", fReceiverBitRate); + } + break; + + case TW0_CHARS_TO_INT('l', 't'): //'lt': //'late': + { + fAverageLateMilliseconds = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fAverageLateMilliseconds); + APPEND_TO_DUMP_ARRAY(", avg_late=%u", fAverageLateMilliseconds); + } + break; + + case TW0_CHARS_TO_INT('l', 's'): // 'ls': //'loss': + { + fPercentPacketsLost = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fPercentPacketsLost); + APPEND_TO_DUMP_ARRAY(", percent_loss=%u", fPercentPacketsLost); + } + break; + + case TW0_CHARS_TO_INT('d', 'l'): //'dl': //'bdly': + { + fAverageBufferDelayMilliseconds = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fAverageBufferDelayMilliseconds); + APPEND_TO_DUMP_ARRAY(", avg_buf_delay=%u", fAverageBufferDelayMilliseconds); + } + break; + + case TW0_CHARS_TO_INT(':', ')' ): //:) + { + fIsGettingBetter = true; + APPEND_TO_DUMP_ARRAY(", quality=%s","better"); + } + break; + case TW0_CHARS_TO_INT(':', '(' ): // ':(': + { + fIsGettingWorse = true; + APPEND_TO_DUMP_ARRAY(", quality=%s","worse"); + } + break; + + case TW0_CHARS_TO_INT(':', '|' ): // ':|': + { + fIsGettingWorse = true; + APPEND_TO_DUMP_ARRAY(", quality=%s","same"); + } + break; + + case TW0_CHARS_TO_INT('e', 'y' ): //'ey': //'eyes': + { + fNumEyes = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); + qtssDataBuffer += sizeof(fNumEyes); + APPEND_TO_DUMP_ARRAY(", eyes=%"_U32BITARG_"", fNumEyes); + + if (itemLengthInBytes >= 2) + { + fNumEyesActive = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); + qtssDataBuffer += sizeof(fNumEyesActive); + APPEND_TO_DUMP_ARRAY(", eyes_actv=%"_U32BITARG_"", fNumEyesActive); + } + if (itemLengthInBytes >= 3) + { + fNumEyesPaused = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); + qtssDataBuffer += sizeof(fNumEyesPaused); + APPEND_TO_DUMP_ARRAY(", eyes_pausd=%"_U32BITARG_"", fNumEyesPaused); + } + } + break; + + case TW0_CHARS_TO_INT('p', 'r' ): // 'pr': //'prcv': + { + fTotalPacketsReceived = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); + qtssDataBuffer += sizeof(fTotalPacketsReceived); + APPEND_TO_DUMP_ARRAY(", pckts_rcvd=%"_U32BITARG_"", fTotalPacketsReceived); + } + break; + + case TW0_CHARS_TO_INT('p', 'd'): //'pd': //'pdrp': + { + fTotalPacketsDropped = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fTotalPacketsDropped); + APPEND_TO_DUMP_ARRAY(", pckts_drppd=%u", fTotalPacketsDropped); + } + break; + + case TW0_CHARS_TO_INT('p', 'l'): //'pl': //'p???': + { + fTotalPacketsLost = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fTotalPacketsLost); + APPEND_TO_DUMP_ARRAY(", ttl_pckts_lost=%u", fTotalPacketsLost); + } + break; + + + case TW0_CHARS_TO_INT('b', 'l'): //'bl': //'bufl': + { + fClientBufferFill = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fClientBufferFill); + APPEND_TO_DUMP_ARRAY(", buffr_fill=%u", fClientBufferFill); + } + break; + + + case TW0_CHARS_TO_INT('f', 'r'): //'fr': //'frat': + { + fFrameRate = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fFrameRate); + APPEND_TO_DUMP_ARRAY(", frame_rate=%u", fFrameRate); + } + break; + + + case TW0_CHARS_TO_INT('x', 'r'): //'xr': //'xrat': + { + fExpectedFrameRate = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fExpectedFrameRate); + APPEND_TO_DUMP_ARRAY(", xpectd_frame_rate=%u", fExpectedFrameRate); + } + break; + + + case TW0_CHARS_TO_INT('d', '#'): //'d#': //'dry#': + { + fAudioDryCount = ntohs(*(UInt16*)qtssDataBuffer); + qtssDataBuffer += sizeof(fAudioDryCount); + APPEND_TO_DUMP_ARRAY(", aud_dry_count=%u", fAudioDryCount); + } + break; + + case TW0_CHARS_TO_INT('o', 'b'): //'ob': // overbuffer window size + { + fOverbufferWindowSize = ntohl(OS::GetUInt32FromMemory((UInt32*)qtssDataBuffer)); + qtssDataBuffer += sizeof(fOverbufferWindowSize); + APPEND_TO_DUMP_ARRAY(", ovr_buffr_windw_siz=%"_U32BITARG_"", fOverbufferWindowSize); + } + break; + + default: + { + if (fDebug) + { + char s[12] = ""; + qtss_sprintf(s, " [%.2s]", (char*)&itemType); + WarnV(false, "Unknown APP('QTSS') item type"); + WarnV(false, s); + } + } + + break; + } // switch (itemType) + + + APPEND_TO_DUMP_ARRAY("%s", "), "); + + } //while ( bytesRemaining >= 4 ) + + + return true; +} + +void RTCPCompressedQTSSPacket::Dump()//Override +{ + APPEND_TO_DUMP_ARRAY("%s", "\n"); + RTCPAPPPacket::Dump(); + +} + + diff --git a/RTCPUtilitiesLib/RTCPAPPQTSSPacket.h b/RTCPUtilitiesLib/RTCPAPPQTSSPacket.h new file mode 100644 index 0000000..6a2ed11 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAPPQTSSPacket.h @@ -0,0 +1,272 @@ +/* + * + * @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: RTCPAPPQTSSPacket.h + + Contains: RTCPAPPQTSSPacket de-packetizing classes + + + +*/ + +#ifndef _RTCPAPPQTSSPACKET_H_ +#define _RTCPAPPQTSSPACKET_H_ + +#include "RTCPAPPPacket.h" +#include "StrPtrLen.h" + +/****** RTCPCompressedQTSSPacket is the packet type that the client actually sends ******/ +class RTCPCompressedQTSSPacket : public RTCPAPPPacket +{ +public: + + RTCPCompressedQTSSPacket(Bool16 debug = false); + virtual ~RTCPCompressedQTSSPacket() {} + + //Call this before any accessor method. Returns true if successful, false otherwise + virtual Bool16 ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength); + + // Call to parse if you don't know what kind of packet this is + Bool16 ParseCompressedQTSSPacket(UInt8* inPacketBuffer, UInt32 inPacketLength); + inline UInt32 GetQTSSReportSourceID(); + inline UInt16 GetQTSSPacketVersion(); + inline UInt16 GetQTSSPacketLength(); //In 'UInt32's + + + inline UInt32 GetReceiverBitRate() {return fReceiverBitRate;} + inline UInt16 GetAverageLateMilliseconds() {return fAverageLateMilliseconds;} + inline UInt16 GetPercentPacketsLost() {return fPercentPacketsLost;} + inline UInt16 GetAverageBufferDelayMilliseconds() {return fAverageBufferDelayMilliseconds;} + inline Bool16 GetIsGettingBetter() {return fIsGettingBetter;} + inline Bool16 GetIsGettingWorse() {return fIsGettingWorse;} + inline UInt32 GetNumEyes() {return fNumEyes;} + inline UInt32 GetNumEyesActive() {return fNumEyesActive;} + inline UInt32 GetNumEyesPaused() {return fNumEyesPaused;} + inline UInt32 GetOverbufferWindowSize() {return fOverbufferWindowSize;} + + //Proposed - are these there yet? + inline UInt32 GetTotalPacketReceived() {return fTotalPacketsReceived;} + inline UInt16 GetTotalPacketsDropped() {return fTotalPacketsDropped;} + inline UInt16 GetTotalPacketsLost() {return fTotalPacketsLost;} + inline UInt16 GetClientBufferFill() {return fClientBufferFill;} + inline UInt16 GetFrameRate() {return fFrameRate;} + inline UInt16 GetExpectedFrameRate() {return fExpectedFrameRate;} + inline UInt16 GetAudioDryCount() {return fAudioDryCount;} + + virtual void Dump(); //Override + + static void GetTestPacket(StrPtrLen* resultPtr) {} + + UInt32 fReceiverBitRate; + UInt16 fAverageLateMilliseconds; + UInt16 fPercentPacketsLost; + UInt16 fAverageBufferDelayMilliseconds; + Bool16 fIsGettingBetter; + Bool16 fIsGettingWorse; + UInt32 fNumEyes; + UInt32 fNumEyesActive; + UInt32 fNumEyesPaused; + UInt32 fOverbufferWindowSize; + + //Proposed - are these there yet? + UInt32 fTotalPacketsReceived; + UInt16 fTotalPacketsDropped; + UInt16 fTotalPacketsLost; + UInt16 fClientBufferFill; + UInt16 fFrameRate; + UInt16 fExpectedFrameRate; + UInt16 fAudioDryCount; + + enum // QTSS App Header offsets + { + + kQTSSDataOffset = 20, // in bytes from packet start + kQTSSReportSourceIDOffset = 3, //in 32 bit words SSRC for this report + kQTSSPacketVersionOffset = 4, // in 32bit words + kQTSSPacketVersionMask = 0xFFFF0000UL, + kQTSSPacketVersionShift = 16, + kQTSSPacketLengthOffset = 4, // in 32bit words + kQTSSPacketLengthMask = 0x0000FFFFUL, + + }; + + enum // QTSS App Data Offsets + { + + //Individual item offsets/masks + kQTSSItemTypeOffset = 0, //SSRC for this report + kQTSSItemTypeMask = 0xFFFF0000UL, + kQTSSItemTypeShift = 16, + kQTSSItemVersionOffset = 0, + kQTSSItemVersionMask = 0x0000FF00UL, + kQTSSItemVersionShift = 8, + kQTSSItemLengthOffset = 0, + kQTSSItemLengthMask = 0x000000FFUL, + kQTSSItemDataOffset = 4, + + //version we support currently + kSupportedCompressedQTSSVersion = 0 + }; + + enum //The 4 character name in the APP packet + { + kCompressedQTSSPacketName = FOUR_CHARS_TO_INT('Q', 'T', 'S', 'S') //QTSS + }; + + +private: + void ParseAndStore(); + +}; + +inline UInt32 RTCPCompressedQTSSPacket::GetQTSSReportSourceID() +{ + return (UInt32) ntohl( ((UInt32*)this->GetPacketBuffer())[kQTSSReportSourceIDOffset] ) ; +} + + +inline UInt16 RTCPCompressedQTSSPacket::GetQTSSPacketVersion() +{ + UInt32 field = ((UInt32*)this->GetPacketBuffer())[kQTSSPacketVersionOffset]; + UInt16 vers = (UInt16) ( ( ntohl(field) & kQTSSPacketVersionMask) >> kQTSSPacketVersionShift ); + return vers; +} + +inline UInt16 RTCPCompressedQTSSPacket::GetQTSSPacketLength() +{ + UInt32 field = ((UInt32*)this->GetPacketBuffer())[kQTSSPacketLengthOffset]; + return (UInt16) ( (UInt32) ntohl(field) & kQTSSPacketLengthMask ); +} + +/* +QTSS APP: QTSS Application-defined RTCP packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| subtype | PT=APP=204 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | name (ASCII) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ <---- app data start + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | version | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | field name='ob' other | version=0 | length=4 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Over-buffer window size in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +fieldnames = rr, lt, ls, dl, :), :|, :(, ey, pr, pd, pl, bl, fr, xr, d#, ob + + */ + + + + + +/****** RTCPqtssPacket is apparently no longer sent by the client ******/ +class RTCPqtssPacket : public RTCPAPPPacket +{ +public: + + RTCPqtssPacket() : RTCPAPPPacket() {} + virtual ~RTCPqtssPacket() {} + + //Call this before any accessor method. Returns true if successful, false otherwise + virtual Bool16 ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength); + + //Call this before any accessor method. Returns true if successful, false otherwise + Bool16 ParseQTSSPacket(UInt8* inPacketBuffer, UInt32 inPacketLength); + + + inline UInt32 GetReceiverBitRate() {return fReceiverBitRate;} + inline UInt32 GetAverageLateMilliseconds() {return fAverageLateMilliseconds;} + inline UInt32 GetPercentPacketsLost() {return fPercentPacketsLost;} + inline UInt32 GetAverageBufferDelayMilliseconds() {return fAverageBufferDelayMilliseconds;} + inline Bool16 GetIsGettingBetter() {return fIsGettingBetter;} + inline Bool16 GetIsGettingWorse() {return fIsGettingWorse;} + inline UInt32 GetNumEyes() {return fNumEyes;} + inline UInt32 GetNumEyesActive() {return fNumEyesActive;} + inline UInt32 GetNumEyesPaused() {return fNumEyesPaused;} + + //Proposed - are these there yet? + inline UInt32 GetTotalPacketReceived() {return fTotalPacketsReceived;} + inline UInt32 GetTotalPacketsDropped() {return fTotalPacketsDropped;} + inline UInt32 GetClientBufferFill() {return fClientBufferFill;} + inline UInt32 GetFrameRate() {return fFrameRate;} + inline UInt32 GetExpectedFrameRate() {return fExpectedFrameRate;} + inline UInt32 GetAudioDryCount() {return fAudioDryCount;} + + + +private: + + void ParseAndStore(); + + UInt32 fReportSourceID; + UInt16 fAppPacketVersion; + UInt16 fAppPacketLength; //In 'UInt32's + + UInt32 fReceiverBitRate; + UInt32 fAverageLateMilliseconds; + UInt32 fPercentPacketsLost; + UInt32 fAverageBufferDelayMilliseconds; + Bool16 fIsGettingBetter; + Bool16 fIsGettingWorse; + UInt32 fNumEyes; + UInt32 fNumEyesActive; + UInt32 fNumEyesPaused; + + //Proposed - are these there yet? + UInt32 fTotalPacketsReceived; + UInt32 fTotalPacketsDropped; + UInt32 fClientBufferFill; + UInt32 fFrameRate; + UInt32 fExpectedFrameRate; + UInt32 fAudioDryCount; + + enum + { + //Individual item offsets/masks + kQTSSItemTypeOffset = 0, //SSRC for this report + kQTSSItemVersionOffset = 4, + kQTSSItemVersionMask = 0xFFFF0000UL, + kQTSSItemVersionShift = 16, + kQTSSItemLengthOffset = 4, + kQTSSItemLengthMask = 0x0000FFFFUL, + kQTSSItemDataOffset = 8, + + //version we support currently + kSupportedQTSSVersion = 0 + }; + + +}; + + +#endif //_RTCPAPPQTSSPACKET_H_ diff --git a/RTCPUtilitiesLib/RTCPAckPacket.cpp b/RTCPUtilitiesLib/RTCPAckPacket.cpp new file mode 100644 index 0000000..674fcde --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAckPacket.cpp @@ -0,0 +1,118 @@ +/* + * + * @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: RTCPAckPacket.cpp + + Contains: RTCPAckPacket de-packetizing class + + +*/ + + +#include "RTCPAckPacket.h" +#include "RTCPPacket.h" +#include "MyAssert.h" +#include "OS.h" +#include "OSMemory.h" +#include "OSArrayObjectDeleter.h" +#include + + +// use if you don't know what kind of packet this is +Bool16 RTCPAckPacket::ParseAckPacket(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + + if (!this->ParseAPPPacket(inPacketBuffer, inPacketLength)) + return false; + + if (this->GetAppPacketName() == RTCPAckPacket::kAckPacketName) + return true; + + if (this->GetAppPacketName() == RTCPAckPacket::kAckPacketAlternateName) + return true; + + return false; + +} + + +Bool16 RTCPAckPacket::ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + if ( !this->ParseAckPacket(inPacketBuffer, inPacketLength) ) + return false; + + + fRTCPAckBuffer = inPacketBuffer; + + // + // Check whether this is an ack packet or not. + if ( (inPacketLength < kAckMaskOffset) || (!this->IsAckPacketType() ) ) + return false; + + Assert(inPacketLength == (UInt32)((this->GetPacketLength() * 4)) + RTCPPacket::kRTCPHeaderSizeInBytes); + fAckMaskSize = inPacketLength - kAckMaskOffset; + + return true; +} + +Bool16 RTCPAckPacket::IsAckPacketType() +{ + // While we are moving to a new type, check for both + UInt32 theAppType = this->GetAppPacketName(); + +// if ( theAppType == kAckPacketAlternateName ) qtss_printf("ack\n"); +// if ( theAppType == kAckPacketName ) qtss_printf("qtack\n"); + + return this->IsAckType(theAppType); +} + +void RTCPAckPacket::Dump() +{ + UInt16 theSeqNum = this->GetAckSeqNum(); + UInt16 thePacketLen = this->GetPacketLength(); + UInt32 theAckMaskSizeInBits = this->GetAckMaskSizeInBits(); + + char name[5]; + name[4] = 0; + + ::memcpy(name, &fRTCPAckBuffer[kAppPacketTypeOffset],4); + UInt16 numBufferBytes = (UInt16) ( (7 * theAckMaskSizeInBits) + 1 ); + char *maskBytesBuffer = NEW char[numBufferBytes]; + OSCharArrayDeleter deleter(maskBytesBuffer); + maskBytesBuffer[0] = 0; + maskBytesBuffer[numBufferBytes -1] = 0; + for (UInt32 maskCount = 0; maskCount < theAckMaskSizeInBits; maskCount++) + { + if (this->IsNthBitEnabled(maskCount)) + { + qtss_sprintf(&maskBytesBuffer[::strlen(maskBytesBuffer)],"%"_U32BITARG_", ", theSeqNum + 1 + maskCount); + } + } + Assert(::strlen(maskBytesBuffer) < numBufferBytes); + qtss_printf(" H_name=%s H_seq=%u H_len=%u mask_size=%"_U32BITARG_" seq_nums_bit_set=%s\n", + name, theSeqNum,thePacketLen,theAckMaskSizeInBits, maskBytesBuffer); + +} + diff --git a/RTCPUtilitiesLib/RTCPAckPacket.h b/RTCPUtilitiesLib/RTCPAckPacket.h new file mode 100644 index 0000000..3d42d38 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAckPacket.h @@ -0,0 +1,151 @@ +/* + * + * @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: RTCPAckPacket.h + + Contains: RTCPAckPacket de-packetizing class + + + +*/ + +#ifndef _RTCPACKPACKET_H_ +#define _RTCPACKPACKET_H_ + +#include "OSHeaders.h" +#include "RTCPAPPPacket.h" +#include +#include "SafeStdLib.h" +#ifndef __Win32__ +#include +#endif + +class RTCPAckPacket : public RTCPAPPPacket +{ + public: + +/* + + RTCP app ACK packet + + # bytes description + ------- ----------- + 4 rtcp header + 4 SSRC of receiver + 4 app type ('qtak') + 2 reserved (set to 0) + 2 seqNum + +*/ + + // + // This class is not derived from RTCPPacket as a performance optimization. + // Instead, it is assumed that the RTCP packet validation has already been + // done. + RTCPAckPacket() : fRTCPAckBuffer(NULL), fAckMaskSize(0) {} + virtual ~RTCPAckPacket() {} + + // Call to parse if you don't know what kind of packet this is + // Returns true if this is an Ack packet, false otherwise. + // Assumes that inPacketBuffer is a pointer to a valid RTCP packet header. + Bool16 ParseAckPacket(UInt8* inPacketBuffer, UInt32 inPacketLength); + + virtual Bool16 ParseAPPData(UInt8* inPacketBuffer, UInt32 inPacketLength); + + inline UInt16 GetAckSeqNum(); + inline UInt32 GetAckMaskSizeInBits() { return fAckMaskSize * 8; } + inline Bool16 IsNthBitEnabled(UInt32 inBitNumber); + inline UInt16 GetPacketLength(); + void Dump(); + static void GetTestPacket(StrPtrLen* resultPtr) {} //todo + + enum + { + kAckPacketName = FOUR_CHARS_TO_INT('q', 't', 'a', 'k'), // 'qtak' documented Apple reliable UDP packet type + kAckPacketAlternateName = FOUR_CHARS_TO_INT('a', 'c', 'k', ' '), // 'ack ' required by QT 5 and earlier + }; + private: + + UInt8* fRTCPAckBuffer; + UInt32 fAckMaskSize; + + Bool16 IsAckPacketType(); + + enum + { + kAppPacketTypeOffset = 8, + kAckSeqNumOffset = 16, + kAckMaskOffset = 20, + kPacketLengthMask = 0x0000FFFFUL, + }; + + + + inline Bool16 IsAckType(UInt32 theAppType) { return ( (theAppType == kAckPacketAlternateName) || (theAppType == kAckPacketName) );} +}; + + +Bool16 RTCPAckPacket::IsNthBitEnabled(UInt32 inBitNumber) +{ + // Don't need to do endian conversion because we're dealing with 8-bit numbers + UInt8 bitMask = 128; + return *(fRTCPAckBuffer + kAckMaskOffset + (inBitNumber >> 3)) & (bitMask >>= inBitNumber & 7); +} + +UInt16 RTCPAckPacket::GetAckSeqNum() +{ + return (UInt16) (ntohl(*(UInt32*)&fRTCPAckBuffer[kAckSeqNumOffset])); +} + +inline UInt16 RTCPAckPacket::GetPacketLength() +{ + return (UInt16) ( ntohl(*(UInt32*)fRTCPAckBuffer) & kPacketLengthMask); +} + + + + +/* +6.6 Ack Packet format + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| subtype | PT=APP=204 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | name (ASCII) = 'qtak' | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Reserved | Seq num | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Mask... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + */ + +#endif //_RTCPAPPPACKET_H_ diff --git a/RTCPUtilitiesLib/RTCPAckPacketFmt.h b/RTCPUtilitiesLib/RTCPAckPacketFmt.h new file mode 100644 index 0000000..5ca1b44 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPAckPacketFmt.h @@ -0,0 +1,158 @@ +/* + * + * @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: RTCPAckPacketFmt.h + + Some useful things for Generating Ack Packets +*/ + +#ifndef _RTCPACKPACKETFMT_H_ +#define _RTCPACKPACKETFMT_H_ + +#include +#include +#include "StrPtrLen.h" +#include "OSHeaders.h" +#include "MyAssert.h" + +class RTCPAckPacketFmt +{ + enum { + RTP_VERSION = 2, + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204 + }; + + struct RTCPAckHeader { + +#if 0 + //ackheader + unsigned int version:2; /* protocol version */ + unsigned int p:1; /* padding flag */ + unsigned int subtype:5; /* the subtype of ack */ + unsigned int pt:8; /* RTCP packet type */ +#endif + + UInt16 ackheader; + UInt16 length; /* pkt len in words, w/o this word */ + UInt32 SSRC; /* sender generating this packet */ + UInt32 name; /* four ASCII chars */ + UInt32 SSRC1; /* SSRC of the stream */ + UInt16 reserved; + UInt16 seqNum; + }; + + public: + RTCPAckPacketFmt() : fBitMaskSize(0) {} + RTCPAckPacketFmt(StrPtrLen &newBuffer) : fBitMaskSize(0) { SetBuffer(newBuffer); } + RTCPAckPacketFmt(char *newBuffer, UInt32 bufLen) : fBitMaskSize(0) { SetBuffer(newBuffer, bufLen); } + + //Setting the buffer resets the packet + void SetBuffer(StrPtrLen &newBuffer) { return this->SetBuffer(newBuffer.Ptr, newBuffer.Len); } + void SetBuffer(char *newBuffer, UInt32 bufLen) + { + Assert(sizeof(RTCPAckHeader) == 20); + Assert(bufLen >= sizeof(RTCPAckHeader)); + + fBitMaskSize = 0; + fBuf.Set(newBuffer, bufLen); + + //fill in the header + RTCPAckHeader &header = *reinterpret_cast(fBuf.Ptr); + ::memset(&header, 0, sizeof(header)); + header.ackheader = htons(0x80CC); //(RTP_VERSION << 14) + RTCP_APP; + header.length = htons(GetPacketLen() / 4 - 1); + header.name = htonl(FOUR_CHARS_TO_INT('q', 't', 'a', 'k')); + } + + void SetSSRC(UInt32 SSRC) + { + RTCPAckHeader &header = *reinterpret_cast(fBuf.Ptr); + header.SSRC = htonl(SSRC); + } + + //Can handle duplicates + void SetAcks(SVector AckList, UInt32 serverSSRC) + { + Assert(!AckList.empty()); + ::qsort(AckList.begin(), AckList.size(), sizeof(UInt32), UInt32Compare); + + RTCPAckHeader &header = *reinterpret_cast(fBuf.Ptr); + header.SSRC1 = htonl(serverSSRC); + header.seqNum = htons(static_cast(AckList.front())); + + fBitMaskSize = 0; + if (AckList.front() == AckList.back()) //no mask is needed + return; + + //figure out how big the mask is supposed to be + UInt32 slotsInMaskNeeded = AckList.back() - AckList.front(); + fBitMaskSize = slotsInMaskNeeded % 32 == 0 ? (slotsInMaskNeeded / 32) * 4 : (slotsInMaskNeeded / 32 + 1) * 4; + + header.length = htons(GetPacketLen() / 4 - 1); + Assert(fBuf.Len >= GetPacketLen()); + + UInt32 *mask = reinterpret_cast(fBuf.Ptr + sizeof(RTCPAckHeader)); + ::memset(mask, 0, fBitMaskSize); + + //calculate where the bit to set is supposed to be and sets the bits + for(UInt32 i = 1; i < AckList.size(); ++i) + { + if (AckList.front() == AckList[i]) + continue; + + UInt32 diff = AckList[i] - (AckList.front() + 1); + UInt32 maskIndex = diff / 32; + UInt32 shiftSize = diff % 32; + mask[maskIndex] |= 0x80000000 >> shiftSize; + Assert(maskIndex * 4 < fBitMaskSize); + } + + //restore big-endianess of the mask + for(UInt32 i = 0; i < fBitMaskSize / 4; ++i) + mask[i] = htonl(mask[i]); + } + + //The length of packet written out + UInt32 GetPacketLen() { return sizeof(RTCPAckHeader) + fBitMaskSize; } + StrPtrLen GetBufferRemaining() { return StrPtrLen(fBuf.Ptr + GetPacketLen(), fBuf.Len - GetPacketLen()); } + StrPtrLen GetPacket() { return StrPtrLen(fBuf.Ptr, GetPacketLen()); } + + private: + static int UInt32Compare(const void *left, const void *right) + { + UInt32 l = *reinterpret_cast(left); + UInt32 r = *reinterpret_cast(right); + return l < r ? -1 : l > r ? 1 : 0; + } + + StrPtrLen fBuf; + UInt32 fBitMaskSize; // in bytes, not words +}; + +#endif //_RTCPACKPACKETFMT_H_ diff --git a/RTCPUtilitiesLib/RTCPNADUPacketFmt.h b/RTCPUtilitiesLib/RTCPNADUPacketFmt.h new file mode 100644 index 0000000..554c8ed --- /dev/null +++ b/RTCPUtilitiesLib/RTCPNADUPacketFmt.h @@ -0,0 +1,132 @@ +/* + * + * @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: RTCPAPPNADUPacketFmt.h + + Some useful things for Generating a 3GPP NADU packet +*/ + +#ifndef _RTCPNADUPACKETFMT_H_ +#define _RTCPNADUPACKETFMT_H_ + +#include "StrPtrLen.h" +#include "arpa/inet.h" +#include "OSHeaders.h" +#include "MyAssert.h" + +class RTCPNADUPacketFmt +{ + enum { + RTP_VERSION = 2, + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204 + }; + + struct NADUHeader { +#if 0 + //naduheader + unsigned int version:2; /* protocol version */ + unsigned int p:1; /* padding flag */ + unsigned int subtype:5; /* 0 */ + unsigned int pt:8; /* RTCP packet type -- which should be RTCP_APP */ +#endif + + UInt16 naduHeader; + UInt16 length; /* packet length in words, minus 1 word */ + UInt32 SSRC; /* SSRC of packet sender */ + UInt32 name; /* in ASCII */ + }; + + struct NADUBlock { + UInt32 SSRC; /* data source being reported */ + UInt16 delay; /* the playout delay, in milliseconds */ + UInt16 NSN; /* the RTP sequence number of the next ADU to be decoded; if the buffer is empty then the next not yet received sequence number */ + UInt16 NUN; /* the unit number of the next ADU to be decoded */ + UInt16 FBS; /* the free buffer space, in complete 64 byte blocks */ + }; + + public: + RTCPNADUPacketFmt() : fNumNADUBlocks(0) {} + RTCPNADUPacketFmt(StrPtrLen &newBuffer) : fNumNADUBlocks(0) { SetBuffer(newBuffer); } + RTCPNADUPacketFmt(char *newBuffer, UInt32 bufLen) : fNumNADUBlocks(0) { SetBuffer(newBuffer, bufLen); } + + //Setting the buffer resets the packet content + void SetBuffer(StrPtrLen &newBuffer) { return this->SetBuffer(newBuffer.Ptr, newBuffer.Len); } + void SetBuffer(char *newBuffer, UInt32 bufLen) + { + Assert(sizeof(NADUHeader) == 12); + Assert(sizeof(NADUBlock) == 12); + Assert(bufLen >= sizeof(NADUHeader)); + + fBuf.Set(newBuffer, bufLen); + fNumNADUBlocks = 0; + + //fill in the header + NADUHeader &header = *reinterpret_cast(fBuf.Ptr); + ::memset(&header, 0, sizeof(header)); + header.naduHeader = htons(0x80CC); //(RTP_VERSION << 14) + RTCP_APP; + header.name = htonl(FOUR_CHARS_TO_INT('P', 'S', 'S', '0')); + header.length = htons(GetPacketLen() / 4 - 1); + } + + //units are in milliseconds and in bytes; use a playoutDelay of kUInt32_Max if the buffer is empty + void AddNADUBlock(UInt32 SSRC, UInt32 nextSeqNum, UInt8 nextUnitNum, UInt32 freeBufferSpace, UInt32 playoutDelay = kUInt32_Max) + { + Assert(fBuf.Len >= GetPacketLen() + sizeof(NADUBlock)); + NADUBlock &nadu = *reinterpret_cast(fBuf.Ptr + GetPacketLen()); + ::memset(&nadu, 0, sizeof(NADUBlock)); + + fNumNADUBlocks++; + reinterpret_cast(fBuf.Ptr)->length = htons(GetPacketLen() / 4 - 1); + + nadu.SSRC = htonl(SSRC); + nadu.NSN = htons(static_cast(nextSeqNum)); + nadu.NUN = htons(nextUnitNum & 0x1F); + + //Use reserved value of 0xffff for undefined + playoutDelay = MIN(0xffff, playoutDelay); + nadu.delay = htons(static_cast(playoutDelay)); + + //the free buffer space is reported in 64 bytes blocks, and maximum value is 0xffff + freeBufferSpace = MIN(0xffff, freeBufferSpace / 64); + nadu.FBS = htons(static_cast(freeBufferSpace)); + } + + void SetSSRC(UInt32 SSRC) { reinterpret_cast(fBuf.Ptr)->SSRC = htonl(SSRC); } + + //The length of packet written out + UInt32 GetPacketLen() { return sizeof(NADUHeader) + sizeof(NADUBlock) * fNumNADUBlocks; } + StrPtrLen GetBufferRemaining() { return StrPtrLen(fBuf.Ptr + GetPacketLen(), fBuf.Len - GetPacketLen()); } + StrPtrLen GetPacket() { return StrPtrLen(fBuf.Ptr, GetPacketLen()); } + + private: + StrPtrLen fBuf; + UInt32 fNumNADUBlocks; +}; + +#endif //_RTCPNADUPACKETFMT_H_ diff --git a/RTCPUtilitiesLib/RTCPPacket.cpp b/RTCPUtilitiesLib/RTCPPacket.cpp new file mode 100644 index 0000000..c939205 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPPacket.cpp @@ -0,0 +1,177 @@ +/* + * + * @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: RTCPPacket.cpp + + Contains: RTCPReceiverPacket de-packetizing classes + +*/ + + +#include "RTCPPacket.h" +#include "RTCPAckPacket.h" +#include "OS.h" +#include + +#define RTCP_PACKET_DEBUG 0 + +//returns true if successful, false otherwise +Bool16 RTCPPacket::ParsePacket(UInt8* inPacketBuffer, UInt32 inPacketLen) +{ + if (inPacketLen < kRTCPPacketSizeInBytes) + return false; + + fReceiverPacketBuffer = inPacketBuffer; + if (RTCP_PACKET_DEBUG) qtss_printf("RTCPPacket::ParsePacket first 4 bytes of packet=%x \n", ntohl( *(UInt32 *)inPacketBuffer)); + + //the length of this packet can be no less than the advertised length (which is + //in 32-bit words, so we must multiply) plus the size of the header (4 bytes) + if (RTCP_PACKET_DEBUG) qtss_printf("RTCPPacket::ParsePacket len=%"_U32BITARG_" min allowed=%"_U32BITARG_"\n", inPacketLen,(UInt32)((this->GetPacketLength() * 4) + kRTCPHeaderSizeInBytes)); + if (inPacketLen < (UInt32)((this->GetPacketLength() * 4) + kRTCPHeaderSizeInBytes)) + { if (RTCP_PACKET_DEBUG) qtss_printf("RTCPPacket::ParsePacket invalid len=%"_U32BITARG_"\n", inPacketLen); + return false; + } + + //do some basic validation on the packet + if (this->GetVersion() != kSupportedRTCPVersion) + { if (RTCP_PACKET_DEBUG) qtss_printf("RTCPPacket::ParsePacket unsupported version\n"); + return false; + } + + return true; +} + +void RTCPReceiverPacket::Dump()//Override +{ + RTCPPacket::Dump(); + qtss_printf("\n"); + for (int i = 0;iGetReportCount(); i++) + { + qtss_printf( " RTCP RR Report[%d] H_ssrc=%"_U32BITARG_", H_frac_lost=%d, H_tot_lost=%"_U32BITARG_", H_high_seq=%"_U32BITARG_" H_jit=%"_U32BITARG_", H_last_sr_time=%"_U32BITARG_", H_last_sr_delay=%"_U32BITARG_" \n", + i, + this->GetReportSourceID(i), + this->GetFractionLostPackets(i), + this->GetTotalLostPackets(i), + this->GetHighestSeqNumReceived(i), + this->GetJitter(i), + this->GetLastSenderReportTime(i), + this->GetLastSenderReportDelay(i) ); + } + + +} + + +Bool16 RTCPReceiverPacket::ParseReport(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + Bool16 ok = this->ParsePacket(inPacketBuffer, inPacketLength); + if (!ok) + return false; + + fRTCPReceiverReportArray = inPacketBuffer + kRTCPPacketSizeInBytes; + + //this is the maximum number of reports there could possibly be + int theMaxReports = (inPacketLength - kRTCPPacketSizeInBytes) / kReportBlockOffsetSizeInBytes; + + //if the number of receiver reports is greater than the theoretical limit, return an error. + if (this->GetReportCount() > theMaxReports) + { if (RTCP_PACKET_DEBUG) printf("RTCPReceiverPacket::ParseReport this rtcp report count=%d > max reports=%d\n",this->GetReportCount(), theMaxReports); + return false; + } + + return true; +} + +UInt32 RTCPReceiverPacket::GetCumulativeFractionLostPackets() +{ + float avgFractionLost = 0; + for (short i = 0; i < this->GetReportCount(); i++) + { + avgFractionLost += this->GetFractionLostPackets(i); + avgFractionLost /= (i+1); + } + + return (UInt32)avgFractionLost; +} + + +UInt32 RTCPReceiverPacket::GetCumulativeJitter() +{ + float avgJitter = 0; + for (short i = 0; i < this->GetReportCount(); i++) + { + avgJitter += this->GetJitter(i); + avgJitter /= (i + 1); + } + + return (UInt32)avgJitter; +} + + +UInt32 RTCPReceiverPacket::GetCumulativeTotalLostPackets() +{ + UInt32 totalLostPackets = 0; + for (short i = 0; i < this->GetReportCount(); i++) + { + totalLostPackets += this->GetTotalLostPackets(i); + } + + return totalLostPackets; +} + + +Bool16 RTCPSenderReportPacket::ParseReport(UInt8* inPacketBuffer, UInt32 inPacketLength) +{ + Bool16 ok = this->ParsePacket(inPacketBuffer, inPacketLength); + if (!ok) + return false; + if (inPacketLength < kRTCPPacketSizeInBytes + kRTCPSRPacketSenderInfoInBytes) + return false; + + fRTCPReceiverReportArray = inPacketBuffer + kRTCPPacketSizeInBytes + kRTCPSRPacketSenderInfoInBytes; + + //this is the maximum number of reports there could possibly be + int theNumReports = (inPacketLength - kRTCPPacketSizeInBytes - kRTCPSRPacketSenderInfoInBytes) / kReportBlockOffsetSizeInBytes; + + //if the number of receiver reports is greater than the theoretical limit, return an error. + if (this->GetReportCount() > theNumReports) + return false; + + return true; +} + + +void RTCPPacket::Dump() +{ + qtss_printf( "H_vers=%d, H_pad=%d, H_rprt_count=%d, H_type=%d, H_length=%d, H_ssrc=%"_S32BITARG_"", + this->GetVersion(), + (int)this->GetHasPadding(), + this->GetReportCount(), + (int)this->GetPacketType(), + (int)this->GetPacketLength(), + this->GetPacketSSRC() ); +} + + diff --git a/RTCPUtilitiesLib/RTCPPacket.h b/RTCPUtilitiesLib/RTCPPacket.h new file mode 100644 index 0000000..b46abd5 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPPacket.h @@ -0,0 +1,334 @@ + /* + * + * @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: RTCPPacket.h + + Contains: RTCPReceiverPacket de-packetizing classes + + +*/ + +//#define DEBUG_RTCP_PACKETS 1 + + +#ifndef _RTCPPACKET_H_ +#define _RTCPPACKET_H_ + +#include +#include "SafeStdLib.h" +#ifndef __Win32__ +#include +#include +#endif + +#include "OSHeaders.h" + +class RTCPPacket +{ +public: + + // Packet types + enum + { + kReceiverPacketType = 201, //UInt32 + kSDESPacketType = 202, //UInt32 + kAPPPacketType = 204 //UInt32 + }; + + + RTCPPacket() : fReceiverPacketBuffer(NULL) {} + virtual ~RTCPPacket() {} + + //Call this before any accessor method. Returns true if successful, false otherwise + Bool16 ParsePacket(UInt8* inPacketBuffer, UInt32 inPacketLen); + + inline int GetVersion(); + inline Bool16 GetHasPadding(); + inline int GetReportCount(); + inline UInt8 GetPacketType(); + inline UInt16 GetPacketLength(); //in 32-bit words + inline UInt32 GetPacketSSRC(); + inline SInt16 GetHeader(); + UInt8* GetPacketBuffer() { return fReceiverPacketBuffer; } + + //Bool16 IsValidPacket(); + + virtual void Dump(); + + enum + { + kRTCPPacketSizeInBytes = 8, //All are UInt32s + kRTCPHeaderSizeInBytes = 4 + }; + +protected: + + UInt8* fReceiverPacketBuffer; + + enum + { + kVersionOffset = 0, + kVersionMask = 0xC0000000UL, + kVersionShift = 30, + kHasPaddingOffset = 0, + kHasPaddingMask = 0x20000000UL, + kReportCountOffset = 0, + kReportCountMask = 0x1F000000UL, + kReportCountShift = 24, + kPacketTypeOffset = 0, + kPacketTypeMask = 0x00FF0000UL, + kPacketTypeShift = 16, + kPacketLengthOffset = 0, + kPacketLengthMask = 0x0000FFFFUL, + kPacketSourceIDOffset = 4, //packet sender SSRC + kPacketSourceIDSize = 4, // + kSupportedRTCPVersion = 2 + }; + +}; + + + + +class SourceDescriptionPacket : public RTCPPacket + +{ + +public: + + SourceDescriptionPacket() : RTCPPacket() {} + + Bool16 ParseSourceDescription(UInt8* inPacketBuffer, UInt32 inPacketLength) + { return ParsePacket(inPacketBuffer, inPacketLength); } + +private: +}; + + + + +class RTCPReceiverPacket : public RTCPPacket +{ +public: + + RTCPReceiverPacket() : RTCPPacket(), fRTCPReceiverReportArray(NULL) {} + + //Call this before any accessor method. Returns true if successful, false otherwise + virtual Bool16 ParseReport(UInt8* inPacketBuffer, UInt32 inPacketLength); + + inline UInt32 GetReportSourceID(int inReportNum); + UInt8 GetFractionLostPackets(int inReportNum); + UInt32 GetTotalLostPackets(int inReportNum); + inline UInt32 GetHighestSeqNumReceived(int inReportNum); + inline UInt32 GetJitter(int inReportNum); + inline UInt32 GetLastSenderReportTime(int inReportNum); + inline UInt32 GetLastSenderReportDelay(int inReportNum); //expressed in units of 1/65536 seconds + + UInt32 GetCumulativeFractionLostPackets(); + UInt32 GetCumulativeTotalLostPackets(); + UInt32 GetCumulativeJitter(); + + //Bool16 IsValidPacket(); + + virtual void Dump(); //Override + +protected: + inline int RecordOffset(int inReportNum); + + UInt8* fRTCPReceiverReportArray; //points into fReceiverPacketBuffer + + enum + { + kReportBlockOffsetSizeInBytes = 24, //All are UInt32s + + kReportBlockOffset = kPacketSourceIDOffset + kPacketSourceIDSize, + + kReportSourceIDOffset = 0, //SSRC for this report + kFractionLostOffset = 4, + kFractionLostMask = 0xFF000000UL, + kFractionLostShift = 24, + kTotalLostPacketsOffset = 4, + kTotalLostPacketsMask = 0x00FFFFFFUL, + kHighestSeqNumReceivedOffset = 8, + kJitterOffset = 12, + kLastSenderReportOffset = 16, + kLastSenderReportDelayOffset = 20 + }; +}; + +class RTCPSenderReportPacket : public RTCPReceiverPacket +{ +public: + Bool16 ParseReport(UInt8* inPacketBuffer, UInt32 inPacketLength); + SInt64 GetNTPTimeStamp() + { + UInt32* fieldPtr = (UInt32*)&fReceiverPacketBuffer[kSRPacketNTPTimeStampMSW]; + SInt64 timestamp = ntohl(*fieldPtr); + fieldPtr = (UInt32*)&fReceiverPacketBuffer[kSRPacketNTPTimeStampLSW]; + return (timestamp << 32) | ntohl(*fieldPtr); + } + UInt32 GetRTPTimeStamp() + { + UInt32* fieldPtr = (UInt32*)&fReceiverPacketBuffer[kSRPacketRTPTimeStamp]; + return ntohl(*fieldPtr); + } +protected: + enum + { + kRTCPSRPacketSenderInfoInBytes = 20, + kSRPacketNTPTimeStampMSW = 8, + kSRPacketNTPTimeStampLSW = 12, + kSRPacketRTPTimeStamp = 16 + }; +}; + + +/************** RTCPPacket inlines **************/ +inline int RTCPPacket::GetVersion() +{ + UInt32* theVersionPtr = (UInt32*)&fReceiverPacketBuffer[kVersionOffset]; + UInt32 theVersion = ntohl(*theVersionPtr); + return (int) ((theVersion & kVersionMask) >> kVersionShift); +} + +inline Bool16 RTCPPacket::GetHasPadding() +{ + UInt32* theHasPaddingPtr = (UInt32*)&fReceiverPacketBuffer[kHasPaddingOffset]; + UInt32 theHasPadding = ntohl(*theHasPaddingPtr); + return (Bool16) (theHasPadding & kHasPaddingMask); +} + +inline int RTCPPacket::GetReportCount() +{ + UInt32* theReportCountPtr = (UInt32*)&fReceiverPacketBuffer[kReportCountOffset]; + UInt32 theReportCount = ntohl(*theReportCountPtr); + return (int) ((theReportCount & kReportCountMask) >> kReportCountShift); +} + +inline UInt8 RTCPPacket::GetPacketType() +{ + UInt32* thePacketTypePtr = (UInt32*)&fReceiverPacketBuffer[kPacketTypeOffset]; + UInt32 thePacketType = ntohl(*thePacketTypePtr); + return (UInt8) ((thePacketType & kPacketTypeMask) >> kPacketTypeShift); +} + +inline UInt16 RTCPPacket::GetPacketLength() +{ + UInt32* fieldPtr = (UInt32*)&fReceiverPacketBuffer[kPacketLengthOffset]; + UInt32 field = ntohl(*fieldPtr); + return (UInt16) (field & kPacketLengthMask); +} + +inline UInt32 RTCPPacket::GetPacketSSRC() +{ + UInt32* fieldPtr = (UInt32*)&fReceiverPacketBuffer[kPacketSourceIDOffset]; + UInt32 field = ntohl(*fieldPtr); + return field; +} + +inline SInt16 RTCPPacket::GetHeader(){ return (SInt16) ntohs(*(SInt16*)&fReceiverPacketBuffer[0]) ;} + +/************** RTCPReceiverPacket inlines **************/ +inline int RTCPReceiverPacket::RecordOffset(int inReportNum) +{ + return inReportNum*kReportBlockOffsetSizeInBytes; +} + + +inline UInt32 RTCPReceiverPacket::GetReportSourceID(int inReportNum) +{ + return (UInt32) ntohl(*(UInt32*)&fRTCPReceiverReportArray[this->RecordOffset(inReportNum)+kReportSourceIDOffset]) ; +} + +inline UInt8 RTCPReceiverPacket::GetFractionLostPackets(int inReportNum) +{ + return (UInt8) ( (ntohl(*(UInt32*)&fRTCPReceiverReportArray[this->RecordOffset(inReportNum)+kFractionLostOffset]) & kFractionLostMask) >> kFractionLostShift ); +} + + +inline UInt32 RTCPReceiverPacket::GetTotalLostPackets(int inReportNum) +{ + return (ntohl(*(UInt32*)&fRTCPReceiverReportArray[this->RecordOffset(inReportNum)+kTotalLostPacketsOffset]) & kTotalLostPacketsMask ); +} + + +inline UInt32 RTCPReceiverPacket::GetHighestSeqNumReceived(int inReportNum) +{ + return (UInt32) ntohl(*(UInt32*)&fRTCPReceiverReportArray[this->RecordOffset(inReportNum)+kHighestSeqNumReceivedOffset]) ; +} + +inline UInt32 RTCPReceiverPacket::GetJitter(int inReportNum) +{ + return (UInt32) ntohl(*(UInt32*)&fRTCPReceiverReportArray[this->RecordOffset(inReportNum)+kJitterOffset]) ; +} + + +inline UInt32 RTCPReceiverPacket::GetLastSenderReportTime(int inReportNum) +{ + return (UInt32) ntohl(*(UInt32*)&fRTCPReceiverReportArray[this->RecordOffset(inReportNum)+kLastSenderReportOffset]) ; +} + + +inline UInt32 RTCPReceiverPacket::GetLastSenderReportDelay(int inReportNum) +{ + return (UInt32) ntohl(*(UInt32*)&fRTCPReceiverReportArray[this->RecordOffset(inReportNum)+kLastSenderReportDelayOffset]) ; +} + + +/* +Receiver Report +--------------- + 0 1 2 3 + 0 0 0 1 1 1 1 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|V=2|P| RC | PT=RR=201 | length | header ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| SSRC of packet sender | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| SSRC_1 (SSRC of first source) | report ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +| fraction lost | cumulative number of packets lost | 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| extended highest sequence number received | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| interarrival jitter | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| last SR (LSR) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| delay since last SR (DLSR) | ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| SSRC_2 (SSRC of second source) | report ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block +: ... : 2 ++=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +| profile-specific extensions | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + +*/ + +#endif //_RTCPPACKET_H_ diff --git a/RTCPUtilitiesLib/RTCPRRPacket.h b/RTCPUtilitiesLib/RTCPRRPacket.h new file mode 100644 index 0000000..7ed8665 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPRRPacket.h @@ -0,0 +1,153 @@ +/* + * + * @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: RTCPRRPacket.h + + Some useful things for Generating Receiver Report Packet +*/ + +#ifndef _RTCPRRPACKET_H_ +#define _RTCPRRPACKET_H_ + +#include "StrPtrLen.h" +#include "arpa/inet.h" +#include "OSHeaders.h" +#include "MyAssert.h" + +class RTCPRRPacket +{ + enum { + RTP_VERSION = 2, + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204, + + MAX_REPORTS = 0x1F // 5 bits + }; + + struct RTCPRRHeader { +#if 0 + //receiver report header + unsigned int version:2; /* protocol version */ + unsigned int p:1; /* padding flag */ + unsigned int count:5; /* varies by packet type */ + unsigned int pt:8; /* RTCP packet type */ +#endif + UInt16 rrheader; + UInt16 length; /* packet length in words, minus 1 word */ + UInt32 SSRC; /* SSRC of packet sender */ + }; + + struct RTCPReportBlock { + UInt32 ssrc; /* data source being reported */ + unsigned int fraction:8; /* fraction lost since last SR/RR */ + int lost:24; /* cumul. no. of pkts lost (signed!) */ + UInt32 last_seq; /* extended last seq. no. received */ + UInt32 jitter; /* interarrival jitter */ + UInt32 lsr; /* last SR packet from this source */ + UInt32 dlsr; /* delay since last SR packet */ + }; + + public: + RTCPRRPacket() : fNumReportBlocks(0) {} + RTCPRRPacket(StrPtrLen &newBuffer) : fNumReportBlocks(0) { SetBuffer(newBuffer); } + RTCPRRPacket(char *newBuffer, UInt32 bufLen) : fNumReportBlocks(0) { SetBuffer(newBuffer, bufLen); } + + + //Setting the buffer resets the packet content + void SetBuffer(StrPtrLen &newBuffer) { return this->SetBuffer(newBuffer.Ptr, newBuffer.Len); } + void SetBuffer(char *newBuffer, UInt32 bufLen) + { + Assert(sizeof(RTCPRRHeader) == 8); + Assert(sizeof(RTCPReportBlock) == 24); + Assert(bufLen >= sizeof(RTCPRRHeader)); + + fBuf.Set(newBuffer, bufLen); + fNumReportBlocks = 0; + + //fill in the header + RTCPRRHeader &header = *reinterpret_cast(fBuf.Ptr); + ::memset(&header, 0, sizeof(header)); + header.rrheader = htons(0x80C9); //(RTP_VERSION << 14) + RTCP_RR; + header.length = htons(GetPacketLen() / 4 - 1); + } + + void SetSSRC(UInt32 SSRC) { reinterpret_cast(fBuf.Ptr)->SSRC = htonl(SSRC); } + + + void SetCount(UInt16 count) + { + if (count > MAX_REPORTS) //5 bits + { return; + } + + UInt16 newVal = ntohs(*reinterpret_cast(fBuf.Ptr)); + count <<= 8; + newVal |= count; + *reinterpret_cast(fBuf.Ptr) = htons(newVal); + + } + + void AddReportBlock(UInt32 SSRC, UInt8 fractionLost, SInt32 cumLostPackets, UInt32 highestSeqNum, UInt32 lsr, UInt32 dlsr) + { + Assert(fBuf.Len >= GetPacketLen() + sizeof(RTCPReportBlock)); + if (fNumReportBlocks >= MAX_REPORTS) + { return; + } + + RTCPReportBlock &reportBlock = *reinterpret_cast(fBuf.Ptr + GetPacketLen()); + ::memset(&reportBlock, 0, sizeof(RTCPReportBlock)); + + reportBlock.ssrc = htonl(SSRC); + reportBlock.fraction = fractionLost; + reportBlock.last_seq = htonl(highestSeqNum); + reportBlock.lsr = htonl(lsr); + reportBlock.dlsr = htonl(dlsr); + + //since the cumulative packets lost is a 24 bit signed integer, its clamped between 0x7fffff and 0x800000) + if(cumLostPackets > 0x7fffff) + reportBlock.lost = htonl(0x7fffff); + else if (cumLostPackets < static_cast(0xff800000)) + reportBlock.lost = htonl(0x800000); + else + reportBlock.lost = htonl(cumLostPackets); + + SetCount(++fNumReportBlocks); + reinterpret_cast(fBuf.Ptr)->length = htons(GetPacketLen() / 4 - 1); + } + + //The length of packet written out + UInt32 GetPacketLen() { return sizeof(RTCPRRHeader) + sizeof(RTCPReportBlock) * fNumReportBlocks; } + StrPtrLen GetBufferRemaining() { return StrPtrLen(fBuf.Ptr + GetPacketLen(), fBuf.Len - GetPacketLen()); } + StrPtrLen GetPacket() { return StrPtrLen(fBuf.Ptr, GetPacketLen()); } + + private: + StrPtrLen fBuf; + UInt32 fNumReportBlocks; +}; + +#endif //_RTCPRRPACKET_H_ diff --git a/RTCPUtilitiesLib/RTCPSRPacket.cpp b/RTCPUtilitiesLib/RTCPSRPacket.cpp new file mode 100644 index 0000000..39054f1 --- /dev/null +++ b/RTCPUtilitiesLib/RTCPSRPacket.cpp @@ -0,0 +1,119 @@ +/* + * + * @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: RTCPSRPacket.cpp + + Contains: A class that writes a RTCP Sender Report + + Change History (most recent first): + + + +*/ + +#include + +#include "RTCPSRPacket.h" +#include "MyAssert.h" +#include "OS.h" + +RTCPSRPacket::RTCPSRPacket() +{ + // Write as much of the Sender Report as is possible + char theTempCName[kMaxCNameLen]; + UInt32 cNameLen = RTCPSRPacket::GetACName(theTempCName); + + //write the SR & SDES headers + UInt32* theSRWriter = (UInt32*)&fSenderReportBuffer; + *theSRWriter = htonl(0x80c80006); + theSRWriter += 7; //number of UInt32s in an SR. + + //SDES length is the length of the CName, plus 2 32bit words, plus the 32bit word for the SSRC + *theSRWriter = htonl(0x81ca0000 + (cNameLen >> 2) + 1); + ::memcpy(&fSenderReportBuffer[kSenderReportSizeInBytes], theTempCName, cNameLen); + fSenderReportSize = kSenderReportSizeInBytes + cNameLen; + +/* + SERVER INFO PACKET FORMAT +struct qtss_rtcp_struct +{ + RTCPHeader header; + UInt32 ssrc; // ssrc of rtcp originator + OSType name; + UInt32 senderSSRC; + SInt16 reserved; + SInt16 length; // bytes of data (atoms) / 4 + // qtsi_rtcp_atom structures follow +}; +*/ + + // + // Write the SERVER INFO APP packet + UInt32* theAckInfoWriter = (UInt32*)&fSenderReportBuffer[fSenderReportSize]; + *theAckInfoWriter = htonl(0x81cc0006); + theAckInfoWriter += 2; + *(theAckInfoWriter++) = htonl(FOUR_CHARS_TO_INT('q', 't', 's', 'i')); // Ack Info APP name + theAckInfoWriter++; // leave space for the ssrc (again) + *(theAckInfoWriter++) = htonl(2); // 2 UInt32s for the 'at' field + *(theAckInfoWriter++) = htonl(FOUR_CHARS_TO_INT( 'a', 't', 0, 4 )); + fSenderReportWithServerInfoSize = (char*)(theAckInfoWriter+1) - fSenderReportBuffer; + + UInt32* theByeWriter = (UInt32*)&fSenderReportBuffer[fSenderReportWithServerInfoSize]; + *theByeWriter = htonl(0x81cb0001); +} + +UInt32 RTCPSRPacket::GetACName(char* ioCNameBuffer) +{ + static char* sCNameBase = "QTSS"; + + //clear out the whole buffer + ::memset(ioCNameBuffer, 0, kMaxCNameLen); + + //cName identifier + ioCNameBuffer[0] = 1; + + //Unique cname is constructed from the base name and the current time + qtss_sprintf(&ioCNameBuffer[1], " %s%"_64BITARG_"d", sCNameBase, OS::Milliseconds() / 1000); + UInt32 cNameLen = ::strlen(ioCNameBuffer); + //2nd byte of CName should be length + ioCNameBuffer[1] = (UInt8) (cNameLen - 2);//don't count indicator or length byte + + // This function assumes that the cName is the only item in this SDES chunk + // (see RTP rfc for details). + // The RFC says that the item (the cName) should not be NULL terminated, but + // the chunk *must* be NULL terminated. And padded to a 32-bit boundary. + // + // qtss_sprintf already put a NULL terminator in the cName buffer. So all we have to + // do is pad out to the boundary. + cNameLen += 1; //add on the NULL character + UInt32 paddedLength = cNameLen + (4 - (cNameLen % 4)); + + // Pad, and zero out as we pad. + for (; cNameLen < paddedLength; cNameLen++) + ioCNameBuffer[cNameLen] = '\0'; + + Assert((cNameLen % 4) == 0); + return cNameLen; +} diff --git a/RTCPUtilitiesLib/RTCPSRPacket.h b/RTCPUtilitiesLib/RTCPSRPacket.h new file mode 100644 index 0000000..a3b9a4d --- /dev/null +++ b/RTCPUtilitiesLib/RTCPSRPacket.h @@ -0,0 +1,163 @@ +/* + * + * @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: RTCPSRPacket.h + + Contains: A class that writes a RTCP Sender Report + + Change History (most recent first): + + +*/ + +#ifndef __RTCP_SR_PACKET__ +#define __RTCP_SR_PACKET__ + +#include "OSHeaders.h" +#include "OS.h" +#include "MyAssert.h" + +#ifndef __Win32__ +#include //definition of htonl +#endif + +class RTCPSRPacket +{ + public: + + enum + { + kSRPacketType = 200, //UInt32 + kByePacketType = 203 + }; + + RTCPSRPacket(); + ~RTCPSRPacket() {} + + // ACCESSORS + + void* GetSRPacket() { return &fSenderReportBuffer[0]; } + UInt32 GetSRPacketLen() { return fSenderReportWithServerInfoSize; } + UInt32 GetSRWithByePacketLen() { return fSenderReportWithServerInfoSize + kByeSizeInBytes; } + + void* GetServerInfoPacket() { return &fSenderReportBuffer[fSenderReportSize]; } + UInt32 GetServerInfoPacketLen() { return kServerInfoSizeInBytes; } + + // + // MODIFIERS + + // + // FOR SR + inline void SetSSRC(UInt32 inSSRC); + inline void SetClientSSRC(UInt32 inClientSSRC); + + inline void SetNTPTimestamp(SInt64 inNTPTimestamp); + inline void SetRTPTimestamp(UInt32 inRTPTimestamp); + + inline void SetPacketCount(UInt32 inPacketCount); + inline void SetByteCount(UInt32 inByteCount); + + // + // FOR SERVER INFO APP PACKET + inline void SetAckTimeout(UInt32 inAckTimeoutInMsec); + + //RTCP support requires generating unique CNames for each session. + //This function generates a proper cName and returns its length. The buffer + //passed in must be at least kMaxCNameLen + enum + { + kMaxCNameLen = 60 //Uint32 + }; + static UInt32 GetACName(char* ioCNameBuffer); + + private: + + enum + { + kSenderReportSizeInBytes = 36, + kServerInfoSizeInBytes = 28, + kByeSizeInBytes = 8 + }; + char fSenderReportBuffer[kSenderReportSizeInBytes + kMaxCNameLen + kServerInfoSizeInBytes + kByeSizeInBytes]; + UInt32 fSenderReportSize; + UInt32 fSenderReportWithServerInfoSize; + +}; + +inline void RTCPSRPacket::SetSSRC(UInt32 inSSRC) +{ + // Set SSRC in SR + ((UInt32*)&fSenderReportBuffer)[1] = htonl(inSSRC); + + // Set SSRC in SDES + ((UInt32*)&fSenderReportBuffer)[8] = htonl(inSSRC); + + // Set SSRC in SERVER INFO + Assert((fSenderReportSize & 3) == 0); + ((UInt32*)&fSenderReportBuffer)[(fSenderReportSize >> 2) + 1] = htonl(inSSRC); + + // Set SSRC in BYE + Assert((fSenderReportWithServerInfoSize & 3) == 0); + ((UInt32*)&fSenderReportBuffer)[(fSenderReportWithServerInfoSize >> 2) + 1] = htonl(inSSRC); +} + +inline void RTCPSRPacket::SetClientSSRC(UInt32 inClientSSRC) +{ + // + // Set Client SSRC in SERVER INFO + ((UInt32*)&fSenderReportBuffer)[(fSenderReportSize >> 2) + 3] = htonl(inClientSSRC); +} + +inline void RTCPSRPacket::SetNTPTimestamp(SInt64 inNTPTimestamp) +{ +#if ALLOW_NON_WORD_ALIGN_ACCESS + ((SInt64*)&fSenderReportBuffer)[1] = OS::HostToNetworkSInt64(inNTPTimestamp); +#else + SInt64 temp = OS::HostToNetworkSInt64(inNTPTimestamp); + ::memcpy(&((SInt64*)&fSenderReportBuffer)[1], &temp, sizeof(temp)); +#endif +} + +inline void RTCPSRPacket::SetRTPTimestamp(UInt32 inRTPTimestamp) +{ + ((UInt32*)&fSenderReportBuffer)[4] = htonl(inRTPTimestamp); +} + +inline void RTCPSRPacket::SetPacketCount(UInt32 inPacketCount) +{ + ((UInt32*)&fSenderReportBuffer)[5] = htonl(inPacketCount); +} + +inline void RTCPSRPacket::SetByteCount(UInt32 inByteCount) +{ + ((UInt32*)&fSenderReportBuffer)[6] = htonl(inByteCount); +} + +inline void RTCPSRPacket::SetAckTimeout(UInt32 inAckTimeoutInMsec) +{ + ((UInt32*)&fSenderReportBuffer)[(fSenderReportWithServerInfoSize >> 2) - 1] = htonl(inAckTimeoutInMsec); +} + +#endif //__RTCP_SR_PACKET__ diff --git a/RTPMetaInfoLib/RTPMetaInfoPacket.cpp b/RTPMetaInfoLib/RTPMetaInfoPacket.cpp new file mode 100644 index 0000000..423d369 --- /dev/null +++ b/RTPMetaInfoLib/RTPMetaInfoPacket.cpp @@ -0,0 +1,241 @@ +/* + * + * @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@ + * + */ +// +// RTPMetaInfo.h: +// Some defs for RTP-Meta-Info payloads. + + +#include "RTPMetaInfoPacket.h" +#include "MyAssert.h" +#include "StringParser.h" +#include "OS.h" +#include + +#ifndef __Win32__ +#include +#endif + +const RTPMetaInfoPacket::FieldName RTPMetaInfoPacket::kFieldNameMap[] = +{ + TW0_CHARS_TO_INT('p', 'p'), + TW0_CHARS_TO_INT('t', 't'), + TW0_CHARS_TO_INT('f', 't'), + TW0_CHARS_TO_INT('p', 'n'), + TW0_CHARS_TO_INT('s', 'q'), + TW0_CHARS_TO_INT('m', 'd') +}; + +const UInt32 RTPMetaInfoPacket::kFieldLengthValidator[] = +{ + 8, //pp + 8, //tt + 2, //ft + 8, //pn + 2, //sq + 0, //md + 0 //illegal / unknown +}; + + +RTPMetaInfoPacket::FieldIndex RTPMetaInfoPacket::GetFieldIndexForName(FieldName inName) +{ + for (int x = 0; x < kNumFields; x++) + { + if (inName == kFieldNameMap[x]) + return x; + } + return kIllegalField; +} + +void RTPMetaInfoPacket::ConstructFieldIDArrayFromHeader(StrPtrLen* inHeader, FieldID* ioFieldIDArray) +{ + for (UInt32 x = 0; x < kNumFields; x++) + ioFieldIDArray[x] = kFieldNotUsed; + + // + // Walk through the fields in this header + StringParser theParser(inHeader); + + UInt16 fieldNameValue = 0; + + while (theParser.GetDataRemaining() > 0) + { + StrPtrLen theFieldP; + (void)theParser.GetThru(&theFieldP, ';'); + + // + // Corrupt or something... just bail + if (theFieldP.Len < 2) + break; + + // + // Extract the Field Name and convert it to a Field Index + ::memcpy (&fieldNameValue, theFieldP.Ptr, sizeof(UInt16)); + FieldIndex theIndex = RTPMetaInfoPacket::GetFieldIndexForName(ntohs(fieldNameValue)); +// FieldIndex theIndex = RTPMetaInfoPacket::GetFieldIndexForName(ntohs(*(UInt16*)theFieldP.Ptr)); + + // + // Get the Field ID if there is one. + FieldID theID = kUncompressed; + if (theFieldP.Len > 3) + { + StringParser theIDExtractor(&theFieldP); + theIDExtractor.ConsumeLength(NULL, 3); + theID = theIDExtractor.ConsumeInteger(NULL); + } + + if (theIndex != kIllegalField) + ioFieldIDArray[theIndex] = theID; + } +} + + +Bool16 RTPMetaInfoPacket::ParsePacket(UInt8* inPacketBuffer, UInt32 inPacketLen, FieldID* inFieldIDArray) +{ + UInt8* theFieldP = inPacketBuffer + 12; // skip RTP header + UInt8* theEndP = inPacketBuffer + inPacketLen; + + SInt64 sInt64Val = 0; + UInt16 uInt16Val = 0; + + while (theFieldP < (theEndP - 2)) + { + FieldIndex theFieldIndex = kIllegalField; + UInt32 theFieldLen = 0; + FieldName* theFieldName = (FieldName*)theFieldP; + + if (*theFieldName & 0x8000) + { + Assert(inFieldIDArray != NULL); + + // If this is a compressed field, find to which field the ID maps + UInt8 theFieldID = *theFieldP & 0x7F; + + for (int x = 0; x < kNumFields; x++) + { + if (theFieldID == inFieldIDArray[x]) + { + theFieldIndex = x; + break; + } + } + + theFieldLen = *(theFieldP + 1); + theFieldP += 2; + } + else + { + // This is not a compressed field. Make sure there is enough room + // in the packet for this to make sense + if (theFieldP >= (theEndP - 4)) + break; + + ::memcpy(&uInt16Val, theFieldP, sizeof(uInt16Val)); + theFieldIndex = this->GetFieldIndexForName(ntohs(uInt16Val)); + + ::memcpy(&uInt16Val, theFieldP + 2, sizeof(uInt16Val)); + theFieldLen = ntohs(uInt16Val); + theFieldP += 4; + } + + // + // Validate the length of this field if possible. + // If the field is of the wrong length, return an error. + if ((kFieldLengthValidator[theFieldIndex] > 0) && + (kFieldLengthValidator[theFieldIndex] != theFieldLen)) + return false; + if ((theFieldP + theFieldLen) > theEndP) + return false; + + // + // We now know what field we are dealing with, so store off + // the proper value depending on the field + switch (theFieldIndex) + { + case kPacketPosField: + { + ::memcpy(&sInt64Val, theFieldP, sizeof(sInt64Val)); + fPacketPosition = (UInt64)OS::NetworkToHostSInt64(sInt64Val); + break; + } + case kTransTimeField: + { + ::memcpy(&sInt64Val, theFieldP, sizeof(sInt64Val)); + fTransmitTime = (UInt64)OS::NetworkToHostSInt64(sInt64Val); + break; + } + case kFrameTypeField: + { + fFrameType = ntohs(*((FrameTypeField*)theFieldP)); + break; + } + case kPacketNumField: + { + ::memcpy(&sInt64Val, theFieldP, sizeof(sInt64Val)); + fPacketNumber = (UInt64)OS::NetworkToHostSInt64(sInt64Val); + break; + } + case kSeqNumField: + { + + ::memcpy(&uInt16Val, theFieldP, sizeof(uInt16Val)); + fSeqNum = ntohs(uInt16Val); + break; + } + case kMediaDataField: + { + fMediaDataP = theFieldP; + fMediaDataLen = theFieldLen; + break; + } + default: + break; + } + + // + // Skip onto the next field + theFieldP += theFieldLen; + } + return true; +} + +UInt8* RTPMetaInfoPacket::MakeRTPPacket(UInt32* outPacketLen) +{ + if (fMediaDataP == NULL) + return NULL; + + // + // Just move the RTP header to right before the media data. + ::memmove(fMediaDataP - 12, fPacketBuffer, 12); + + // + // Report the length of the resulting RTP packet + if (outPacketLen != NULL) + *outPacketLen = fMediaDataLen + 12; + + return fMediaDataP - 12; +} + + diff --git a/RTPMetaInfoLib/RTPMetaInfoPacket.h b/RTPMetaInfoLib/RTPMetaInfoPacket.h new file mode 100644 index 0000000..98fc77b --- /dev/null +++ b/RTPMetaInfoLib/RTPMetaInfoPacket.h @@ -0,0 +1,148 @@ +/* + * + * @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@ + * + */ +// +// RTPMetaInfoPacket.h: +// Some defs for RTP-Meta-Info payloads. This class also parses RTP meta info packets + +#ifndef __QTRTP_META_INFO_PACKET_H__ +#define __QTRTP_META_INFO_PACKET_H__ + +#include +#include "SafeStdLib.h" +#include "OSHeaders.h" +#include "StrPtrLen.h" + +class RTPMetaInfoPacket +{ + public: + + // + // RTP Meta Info Fields + + enum + { + kPacketPosField = 0, //TW0_CHARS_TO_INT('p', 'p'), + kTransTimeField = 1, //TW0_CHARS_TO_INT('t', 't'), + kFrameTypeField = 2, //TW0_CHARS_TO_INT('f', 't'), + kPacketNumField = 3, //TW0_CHARS_TO_INT('p', 'n'), + kSeqNumField = 4, //TW0_CHARS_TO_INT('s', 'q'), + kMediaDataField = 5, //TW0_CHARS_TO_INT('m', 'd'), + + kIllegalField = 6, + kNumFields = 6 + }; + typedef UInt16 FieldIndex; + + // + // Types + + typedef UInt16 FieldName; + typedef SInt32 FieldID; + + // + // Special field IDs + enum + { + kUncompressed = -1, // No field ID (not a compressed field) + kFieldNotUsed = -2 // This field is not being used + }; + + // + // Routine that converts the above enum items into real field names + static FieldName GetFieldNameForIndex(FieldIndex inIndex) { return kFieldNameMap[inIndex]; } + static FieldIndex GetFieldIndexForName(FieldName inName); + + // + // Routine that constructs a standard FieldID Array out of a x-RTP-Meta-Info header + static void ConstructFieldIDArrayFromHeader(StrPtrLen* inHeader, FieldID* ioFieldIDArray); + + // + // Values for the Frame Type Field + enum + { + kUnknownFrameType = 0, + kKeyFrameType = 1, + kBFrameType = 2, + kPFrameType = 3 + }; + typedef UInt16 FrameTypeField; + + + // + // CONSTRUCTOR + + RTPMetaInfoPacket() : fPacketBuffer(NULL), + fPacketLen(0), + fTransmitTime(0), + fFrameType(kUnknownFrameType), + fPacketNumber(0), + fPacketPosition(0), + fMediaDataP(NULL), + fMediaDataLen(0), + fSeqNum(0) {} + ~RTPMetaInfoPacket() {} + + // + // Call this to parse the RTP-Meta-Info packet. + // Pass in an array of FieldIDs, make sure it is kNumFields in length. + // This function will use the array as a guide to tell which field IDs in the + // packet refer to which fields. + Bool16 ParsePacket(UInt8* inPacketBuffer, UInt32 inPacketLen, FieldID* inFieldIDArray); + + // + // Call this if you would like to rewrite the Meta-Info packet + // as a normal RTP packet (strip off the extensions). Note that + // this will overwrite data in the buffer! + // Returns a pointer to the new RTP packet, and its length + UInt8* MakeRTPPacket(UInt32* outPacketLen); + + // + // Field Accessors + SInt64 GetTransmitTime() { return fTransmitTime; } + FrameTypeField GetFrameType() { return fFrameType; } + UInt64 GetPacketNumber() { return fPacketNumber; } + UInt64 GetPacketPosition() { return fPacketPosition; } + UInt8* GetMediaDataP() { return fMediaDataP; } + UInt32 GetMediaDataLen() { return fMediaDataLen; } + UInt16 GetSeqNum() { return fSeqNum; } + + private: + + UInt8* fPacketBuffer; + UInt32 fPacketLen; + + SInt64 fTransmitTime; + FrameTypeField fFrameType; + UInt64 fPacketNumber; + UInt64 fPacketPosition; + UInt8* fMediaDataP; + UInt32 fMediaDataLen; + UInt16 fSeqNum; + + static const FieldName kFieldNameMap[]; + static const UInt32 kFieldLengthValidator[]; +}; + +#endif // __QTRTP_META_INFO_PACKET_H__ diff --git a/RTSPClientLib/ClientSession.cpp b/RTSPClientLib/ClientSession.cpp new file mode 100644 index 0000000..204d330 --- /dev/null +++ b/RTSPClientLib/ClientSession.cpp @@ -0,0 +1,1168 @@ +/* + * + * @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: ClientSession.cpp + + Contains: . + + + +*/ + +#include +//#include +#include "ClientSession.h" +#include "OSMemory.h" +#include "SafeStdLib.h" +#include "OSHeaders.h" +#include "OS.h" +#include "RTPPacket.h" +#include "RTCPPacket.h" +#include "RTCPRRPacket.h" +#include "RTCPAckPacketFmt.h" +#include "RTCPNADUPacketFmt.h" + +//These two parameters governs how the client determines whether an incoming RTP packet is within the range of the sequence number or not. +enum { + kMaxDropOut = 3000, //The sequence number can be kMaxDropOut ahead of the reference. + kMaxMisorder = 100, //The sequence number can be kMaxMisorder behind of the reference. + kMaxUDPPacketSize = 1450 +}; + +#define CLIENT_SESSION_DEBUG 0 + +static const SInt64 kMaxWaitTimeInMsec = 5000; +static const SInt64 kIdleTimeoutInMsec = 20000; // Time out in 20 seconds if nothing's doing +static const SInt16 kSanitySeqNumDifference = 3000; //how large a difference can two sequence number be and still be considered to be in range + +UInt32 ClientSession::sActiveConnections = 0; +UInt32 ClientSession::sPlayingConnections = 0; +UInt32 ClientSession::sTotalConnectionAttempts = 0; + +UInt32 ClientSession::sBytesReceived = 0; +UInt32 ClientSession::sPacketsReceived = 0; + +char* ConvertBytesToCHexString( void* inValue, const UInt32 inValueLen) +{ + static const char* kHEXChars={ "0123456789ABCDEF" }; + + UInt8* theDataPtr = (UInt8*) inValue; + UInt32 len = inValueLen *2; + + char *theString = NEW char[len+1]; + char *resultStr = theString; + if (theString != NULL) + { + UInt8 temp; + UInt32 count = 0; + for (count = 0; count < inValueLen; count++) + { + temp = *theDataPtr++; + *theString++ = kHEXChars[temp >> 4]; + *theString++ = kHEXChars[temp & 0xF]; + } + *theString = 0; + } + return resultStr; +} + + +ClientSession::ClientSession( UInt32 inAddr, UInt16 inPort, char* inURL, + ClientType inClientType, + UInt32 inDurationInSec, UInt32 inStartPlayTimeInSec, + UInt32 inRTCPIntervalInMS, UInt32 inOptionsIntervalInSec, + UInt32 inHTTPCookie, Bool16 inAppendJunkData, UInt32 inReadInterval, + UInt32 inSockRcvBufSize, Float32 inLateTolerance, char* inMetaInfoFields, + Float32 inSpeed, UInt32 verboseLevel, char* inPacketRangePlayHeader, UInt32 inOverbufferWindowSizeInK, + Bool16 sendOptions, Bool16 requestRandomData, SInt32 randomDataSize, Bool16 enable3GPP, + UInt32 GBW, UInt32 MBW, UInt32 MTD, Bool16 enableForcePlayoutDelay, UInt32 playoutDelay, + UInt32 bandwidth, UInt32 bufferSpace, UInt32 delayTime, UInt32 startPlayDelay, + char *controlID, char *name, char *password) +: fSocket(NULL), + fClient(NULL), + fTimeoutTask(this, kIdleTimeoutInMsec), + + fDurationInSec(inDurationInSec - inStartPlayTimeInSec), + fStartPlayTimeInSec(inStartPlayTimeInSec), + fRTCPIntervalInMs(inRTCPIntervalInMS), + fOptionsIntervalInSec(inOptionsIntervalInSec), + + fOptions(sendOptions), + fOptionsRequestRandomData(requestRandomData), + fOptionsRandomDataSize(randomDataSize), + fTransactionStartTimeMilli(0), + + fState(kSendingDescribe), + fDeathReason(kDiedNormally), + fNumSetups(0), + fUDPSocketArray(NULL), + + fPlayTime(0), + fTotalPlayTime(0), + fLastRTCPTime(0), + fTeardownImmediately(false), + fAppendJunk(inAppendJunkData), + fReadInterval(inReadInterval), + fSockRcvBufSize(inSockRcvBufSize), + + fSpeed(inSpeed), + fPacketRangePlayHeader(inPacketRangePlayHeader), + + fGuarenteedBitRate(GBW), + fMaxBitRate(MBW), + fMaxTransferDelay(MTD), + fEnableForcePlayoutDelay(enableForcePlayoutDelay), + fPlayoutDelay(playoutDelay), + fBandwidth(bandwidth), + fBufferSpace(bufferSpace), + fDelayTime(delayTime), + fStartPlayDelay(startPlayDelay), + fEnable3GPP(enable3GPP), + + //fStats(NULL), + fOverbufferWindowSizeInK(inOverbufferWindowSizeInK), + fCurRTCPTrack(0), + fNumPacketsReceived(0), + fNumBytesReceived(0), + fVerboseLevel(verboseLevel), + fPlayerSimulator(verboseLevel) +{ + this->SetTaskName("RTSPClientLib:ClientSession"); + StrPtrLen theURL(inURL); + + if (true == sendOptions) + fState = kSendingOptions; + +#if CLIENT_SESSION_DEBUG + fVerboseLevel = kUInt32_Max; //maximum possible unsigned int value in 2's complement +#endif + if ( fVerboseLevel >= 2) + { + in_addr inAddrStruct; + inAddrStruct.s_addr = inAddr; + qtss_printf("Connecting to: %s, port %d\n", inet_ntoa(inAddrStruct), inPort); + } + + // + // Construct the appropriate ClientSocket type depending on what type of client we are supposed to be + switch (inClientType) + { + case kRTSPUDPClientType: + { + fControlType = kRawRTSPControlType; + fTransportType = kUDPTransportType; + fSocket = NEW TCPClientSocket(Socket::kNonBlockingSocketType); + break; + } + case kRTSPTCPClientType: + { + fControlType = kRawRTSPControlType; + fTransportType = kTCPTransportType; + fSocket = NEW TCPClientSocket(Socket::kNonBlockingSocketType); + break; + } + case kRTSPHTTPClientType: + { + fControlType = kRTSPHTTPControlType; + fTransportType = kTCPTransportType; + fSocket = NEW HTTPClientSocket(theURL, inHTTPCookie, Socket::kNonBlockingSocketType); + break; + } + case kRTSPHTTPDropPostClientType: + { + fControlType = kRTSPHTTPDropPostControlType; + fTransportType = kTCPTransportType; + fSocket = NEW HTTPClientSocket(theURL, inHTTPCookie, Socket::kNonBlockingSocketType); + break; + } + case kRTSPReliableUDPClientType: + { + fControlType = kRawRTSPControlType; + fTransportType = kReliableUDPTransportType; + fSocket = NEW TCPClientSocket(Socket::kNonBlockingSocketType); + break; + } + default: + { + qtss_printf("ClientSession: Attempt to create unsupported client type.\n"); + ::exit(-1); + } + } + + fSocket->Set(inAddr, inPort); + + // + // Construct the client object using this socket. + fClient = NEW RTSPClient(fSocket); + fClient->SetVerboseLevel(fVerboseLevel); + fClient->Set(theURL); + fClient->SetSetupParams(inLateTolerance, inMetaInfoFields); + fClient->SetBandwidth(fBandwidth); + if(controlID != NULL) + fClient->SetControlID(controlID); + + if (enable3GPP) + { + fClient->Set3GPPLinkChars(fGuarenteedBitRate, fMaxBitRate, fMaxTransferDelay); + fClient->Set3GPPRateAdaptation(fBufferSpace, fDelayTime); + } + + //user name and password + if (name != NULL && password != NULL) + { + fClient->SetName(name); + fClient->SetPassword(password); + } + + // + // Start the connection process going + this->Signal(Task::kStartEvent); +} + +ClientSession::~ClientSession() +{ + if (fUDPSocketArray != NULL) + { + for (UInt32 x = 0; x < fSDPParser.GetNumStreams() * 2; x++) + { + OS_Error theErr = OS_NoErr; + + while (theErr == OS_NoErr) + { + UInt32 theRemoteAddr = 0; + UInt32 theLength = 0; + UInt16 theRemotePort = 0; + char thePacketBuf[2048]; + + // Get a packet from one of the UDP sockets. + theErr = fUDPSocketArray[x]->RecvFrom(&theRemoteAddr, &theRemotePort, + &thePacketBuf[0], 2048, + &theLength); + } + delete fUDPSocketArray[x]; + } + } + + delete [] fUDPSocketArray; + delete fClient; + delete fSocket; + //delete fStats; +} + + +SInt64 ClientSession::Run() +{ + EventFlags theEvents = this->GetEvents(); + + if (theEvents & Task::kStartEvent) + { + sActiveConnections++; + sTotalConnectionAttempts++; + //Sometimes the clientSession can be told to stop before it has a chance to start the connection + if (theEvents & ClientSession::kTeardownEvent) + fTeardownImmediately = true; + else + { + Assert(theEvents == Task::kStartEvent); + // Determine a random connection interval, and go away until that time comes around. + // Next time the event received would be Task::kIdleEvent, and the initial state is kSendingDescribe + return ((UInt32) ::rand()) % kMaxWaitTimeInMsec + 1; + } + } + + // + if (theEvents & Task::kTimeoutEvent) + { + if(fState == kDone) + return 0; + if ( fVerboseLevel >= 2) + qtss_printf("Session timing out.\n"); + fDeathReason = kSessionTimedout; + fState = kDone; + return 0; + } + + // + // If we've been told to TEARDOWN, do so. + if (theEvents & ClientSession::kTeardownEvent) + { + if ( fVerboseLevel >= 2) + qtss_printf("Session tearing down immediately.\n"); + fTeardownImmediately = true; + } + + // We have been told to delete ourselves. Do so... NOW!!!!!!!!!!!!!!! + if (theEvents & Task::kKillEvent) + { + if ( fVerboseLevel >= 2) + qtss_printf("Session killed.\n"); + sActiveConnections--; + return -1; + } + + // Refresh the timeout. There is some legit activity going on... + fTimeoutTask.RefreshTimeout(); + + OS_Error theErr = OS_NoErr; + + while ((theErr == OS_NoErr) && (fState != kDone)) + { + // + // Do the appropriate thing depending on our current state + switch (fState) + { + case kSendingOptions: + { + if (true == fOptionsRequestRandomData) + theErr = fClient->SendOptionsWithRandomDataRequest(fOptionsRandomDataSize); + else + theErr = fClient->SendOptions(); + + if ( fVerboseLevel >= 3) + qtss_printf("Sent OPTIONS. Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", theErr, fClient->GetStatus()); + if (0 == fTransactionStartTimeMilli) + fTransactionStartTimeMilli = OS::Milliseconds(); + + if (theErr == OS_NoErr) + { + + // Check that the OPTIONS response is a 200 OK. If not, bail + if (fClient->GetStatus() != 200) + { + theErr = ENOTCONN; // Exit the state machine + break; + } + else + { + if ( fVerboseLevel >= 3) + { + qtss_printf("--- Options transaction time ms = %qd ---\n", (SInt64) ( OS::Milliseconds() - fTransactionStartTimeMilli) ); + SInt32 receivedLength = (SInt32) fClient->GetContentLength(); + if (receivedLength != 0) + qtss_printf("--- Options Request Random Data Received requested = %"_S32BITARG_" received = %"_S32BITARG_" ---\n", fOptionsRandomDataSize, receivedLength); + + StrPtrLenDel theBody(ConvertBytesToCHexString(fClient->GetContentBody(), receivedLength)); + theBody.PrintStr("\n"); + } + fState = kSendingDescribe; + } + } + + break; + } + case kSendingDescribe: + { + theErr = fClient->SendDescribe(fAppendJunk); + if ( fVerboseLevel >= 3) + qtss_printf("Sent DESCRIBE. Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", theErr, fClient->GetStatus()); + if (theErr == OS_NoErr) + { + // Check that the DESCRIBE response is a 200 OK. If not, bail + if (fClient->GetStatus() != 200) + { + theErr = ENOTCONN; // Exit the state machine + break; + } + else + { + // + // We've sent a describe and gotten a response from the server. + // Parse the response and look for track information. + + fSDPParser.Parse(fClient->GetContentBody(), fClient->GetContentLength()); + + // + // The SDP must have been misformatted. + if (fSDPParser.GetNumStreams() == 0) + fDeathReason = kBadSDP; + + // + // We have valid SDP. If this is a UDP connection, construct a UDP + // socket array to act as incoming sockets. + if ((fTransportType == kUDPTransportType) || (fTransportType == kReliableUDPTransportType)) + this->SetupUDPSockets(); + + // + // Setup client stats + //delete fStats; + //fStats = NEW TrackStats[fSDPParser.GetNumStreams()]; + fStats.resize(fSDPParser.GetNumStreams()); + //::memset(fStats, 0, sizeof(TrackStats) * fSDPParser.GetNumStreams()); + + + } + fPlayerSimulator.Setup(fSDPParser.GetNumStreams(), fDelayTime, fStartPlayDelay); + fState = kSendingSetup; + } + break; + } + case kSendingSetup: + { + // The SETUP request is different depending on whether we are interleaving or not + if (fTransportType == kUDPTransportType) + { + theErr = fClient->SendUDPSetup(fSDPParser.GetStreamInfo(fNumSetups)->fTrackID, + fUDPSocketArray[fNumSetups*2]->GetLocalPort()); + } + else if (fTransportType == kTCPTransportType) + { + fSocket->SetRcvSockBufSize(fSockRcvBufSize); // Make the rcv buf really big + theErr = fClient->SendTCPSetup(fSDPParser.GetStreamInfo(fNumSetups)->fTrackID,fNumSetups * 2, (fNumSetups * 2) +1); + } + else if (fTransportType == kReliableUDPTransportType) + { + theErr = fClient->SendReliableUDPSetup(fSDPParser.GetStreamInfo(fNumSetups)->fTrackID, + fUDPSocketArray[fNumSetups*2]->GetLocalPort()); + } + if ( fVerboseLevel >= 3) + qtss_printf("Sent SETUP #%"_U32BITARG_". Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", + fNumSetups, theErr, fClient->GetStatus()); + // + // If this SETUP request / response is complete, check for errors, and if + // it succeeded, move onto the next SETUP. If we're done setting up all tracks, + // move onto PLAY. + if (theErr == OS_NoErr) + { + if (fClient->GetStatus() != 200) + { + theErr = ENOTCONN; // Exit the state machine + break; + } + else + { + // Record the server port for RTCPs. + fStats[fNumSetups].fDestRTCPPort = fClient->GetServerPort() + 1; + + //obtain the sampling rate of this stream + StringParser parser = StringParser(&fSDPParser.GetStreamInfo(fNumSetups)->fPayloadName); + parser.GetThru(NULL, '/'); + UInt32 samplingRate = parser.ConsumeInteger(NULL); + Assert(samplingRate != 0); + + //Generates the client SSRC + SInt64 ms = OS::Microseconds(); + UInt32 ssrc = static_cast((ms >> 32) ^ ms) + ::rand(); + fStats[fNumSetups].fClientSSRC = ssrc + fNumSetups; + + fPlayerSimulator.SetupTrack(fNumSetups, samplingRate, fBufferSpace); + fNumSetups++; + if (fNumSetups == fSDPParser.GetNumStreams()) + fState = kSendingPlay; + } + } + break; + } + case kSendingPlay: + { + if (fPacketRangePlayHeader != NULL) + theErr = fClient->SendPacketRangePlay(fPacketRangePlayHeader, fSpeed); + else + theErr = fClient->SendPlay(fStartPlayTimeInSec, fSpeed); + if ( fVerboseLevel >= 3) + qtss_printf("Sent PLAY. Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", theErr, fClient->GetStatus()); + // + // If this PLAY request / response is complete, then we are done with setting + // up all the client crap. Now all we have to do is receive the data until it's + // time to send the TEARDOWN + if (theErr == OS_NoErr) + { + if (fClient->GetStatus() != 200) + { + theErr = ENOTCONN; // Exit the state machine + break; + } + + // Mark down the SSRC for each track, if possible. + for (UInt32 ssrcCount = 0; ssrcCount < fSDPParser.GetNumStreams(); ssrcCount++) + fStats[ssrcCount].fServerSSRC = fClient->GetSSRCByTrack(fSDPParser.GetStreamInfo(ssrcCount)->fTrackID); + + fState = kPlaying; + sPlayingConnections++; + + // + // Start our duration timer. Use this to figure out when to send a teardown + fPlayTime = fLastRTCPTime = OS::Milliseconds(); + + if(fVerboseLevel >= 1) + { + for (UInt32 i = 0; i < fSDPParser.GetNumStreams(); i++) + { + QTSS_RTPPayloadType type = fSDPParser.GetStreamInfo(i)->fPayloadType; + qtss_printf("Receiving track %"_U32BITARG_", trackID=%"_U32BITARG_", %s at time %"_S64BITARG_"\n", + i, fSDPParser.GetStreamInfo(i)->fTrackID, + type == qtssVideoPayloadType ? "video" : type == qtssAudioPayloadType ? "audio" : "unknown", + OS::Milliseconds()); + } + } + } + break; + } + case kPlaying: + { + // Should we send a teardown? We should if either we've been told to teardown, or if our time has run out + SInt64 curTime = OS::Milliseconds(); + fTotalPlayTime = curTime - fPlayTime; + if (((curTime - fPlayTime) > fDurationInSec * 1000) || (fTeardownImmediately)) + { + sPlayingConnections--; + fState = kSendingTeardown; + break; + } + + //Send RTCP if necessary; if we are using TCP for media transport, than 1 set of RTCP total is enough + Bool16 sendRTCP = ((curTime - fLastRTCPTime) > fRTCPIntervalInMs) && (fTransportType != kTCPTransportType); + sendRTCP |= (fPlayTime == fLastRTCPTime); //send the first RTCP ASAP. + if (sendRTCP) + { + //(void) fClient->SendSetParameter(); // test for keep alives and error responses + //(void) fClient->SendOptions(); // test for keep alives and error responses + for ( ; fCurRTCPTrack < fSDPParser.GetNumStreams(); fCurRTCPTrack++) + { + OS_Error err = this->SendRTCPPackets(fCurRTCPTrack); + if (fTransportType == kTCPTransportType && err != OS_NoErr) + { + theErr = err; //if error happens on a TCP RTCP socket, then bail + break; + } + } + if (theErr != OS_NoErr) + break; + + //Done sending the RTCP's + fCurRTCPTrack = 0; + fLastRTCPTime = (curTime == fLastRTCPTime) ? curTime + 1 : curTime; + + //Drop the POST portion of the HTTP connection after every send + if (fControlType == kRTSPHTTPDropPostControlType) + ((HTTPClientSocket*)fSocket)->ClosePost(); + } + + //Now read the media data + theErr = this->ReadMediaData(); + + if ((theErr == EINPROGRESS) || (theErr == EAGAIN) || (theErr == OS_NoErr)) + theErr = OS_NoErr; //ignore control flow errors here + else + { + sPlayingConnections--; + break; + } + curTime = OS::Milliseconds(); + SInt64 nextRTCPTime = fLastRTCPTime + fRTCPIntervalInMs; + //return curTime < nextRTCPTime ? nextRTCPTime - curTime : fReadInterval; + return curTime < nextRTCPTime ? MIN(nextRTCPTime - curTime, fReadInterval) : 1; + } + + case kSendingTeardown: + { + theErr = fClient->SendTeardown(); + if ( fVerboseLevel >= 3) + qtss_printf("Sending TEARDOWN. Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", theErr, fClient->GetStatus()); + // Once the TEARDOWN is complete, we are done, so mark ourselves as dead, and wait + // for the owner of this object to delete us + if (theErr == OS_NoErr) + fState = kDone; + + break; + } + } + } + + if ((theErr == EINPROGRESS) || (theErr == EAGAIN)) + { + // + // Request an async event + fSocket->GetSocket()->SetTask(this); + fSocket->GetSocket()->RequestEvent(fSocket->GetEventMask()); + } + else if (theErr != OS_NoErr) + { + // + // We encountered some fatal error with the socket. Record this as a connection failure + if (fState == kSendingTeardown) + fDeathReason = kTeardownFailed; + else if (fState == kPlaying) + fDeathReason = kDiedWhilePlaying; + else if (fClient->GetStatus() != 200) + fDeathReason = kRequestFailed; + else + fDeathReason = kConnectionFailed; + + fState = kDone; + } + + if ( fVerboseLevel >= 2) + if (fState == kDone) + qtss_printf("Client connection complete. Death reason = %"_U32BITARG_"\n", fDeathReason); + + return 0; +} + + + +void ClientSession::SetupUDPSockets() +{ + + static UInt16 sCurrentRTPPortToUse = 6970; + static const UInt16 kMinRTPPort = 6970; + static const UInt16 kMaxRTPPort = 36970; + + OS_Error theErr = OS_NoErr; + + // + // Create a UDP socket pair (RTP, RTCP) for each stream + fUDPSocketArray = NEW UDPSocket*[fSDPParser.GetNumStreams() * 2]; + for (UInt32 x = 0; x < fSDPParser.GetNumStreams() * 2; x++) + { + fUDPSocketArray[x] = NEW UDPSocket(this, Socket::kNonBlockingSocketType); + theErr = fUDPSocketArray[x]->Open(); + if (theErr != OS_NoErr) + { + qtss_printf("ClientSession: Failed to open a UDP socket.\n"); + ::exit(-1); + } + } + + for (UInt32 y = 0; y < fSDPParser.GetNumStreams(); y++) + { + for (UInt32 portCheck = 0; true; portCheck++) + { + theErr = fUDPSocketArray[y * 2]->Bind(INADDR_ANY, sCurrentRTPPortToUse); + if (theErr == OS_NoErr) + theErr = fUDPSocketArray[(y*2)+1]->Bind(INADDR_ANY, sCurrentRTPPortToUse + 1); + + sCurrentRTPPortToUse += 2; + if (sCurrentRTPPortToUse > 30000) + sCurrentRTPPortToUse = 6970; + + if (theErr == OS_NoErr) + { + // This is a good pair. Set the rcv buf on the RTP socket to be really big + fUDPSocketArray[y * 2]->SetSocketRcvBufSize(fSockRcvBufSize); + break; + } + + if (sCurrentRTPPortToUse == kMaxRTPPort) + sCurrentRTPPortToUse = kMinRTPPort; + if (portCheck == 5000) + { + // Make sure we don't loop forever trying to bind a UDP socket. If we can't + // after a certain point, just bail... + qtss_printf("ClientSession: Failed to bind a UDP socket.\n"); + ::exit(-1); + } + } + } + if ( fVerboseLevel >= 3) + qtss_printf("Opened UDP sockets for %"_U32BITARG_" streams\n", fSDPParser.GetNumStreams()); +} + +//Will keep reading until all the packets buffered up has been read. +OS_Error ClientSession::ReadMediaData() +{ + // For iterating over the array of UDP sockets + UInt32 theUDPSockIndex = 0; + OS_Error theErr = OS_NoErr; + + while (true) + { + // + // If the media data is being interleaved, get it from the control connection + UInt32 theTrackID = 0; + UInt32 theLength = 0; + Bool16 isRTCP = false; + char* thePacket = NULL; + + if (fTransportType == kTCPTransportType) + { + thePacket = NULL; + theErr = fClient->GetMediaPacket(&theTrackID, &isRTCP, &thePacket, &theLength); + if (thePacket == NULL) + break; + } + else + { + static const UInt32 kMaxPacketSize = 2048; + + UInt32 theRemoteAddr = 0; + UInt16 theRemotePort = 0; + char thePacketBuf[kMaxPacketSize]; + + // Get a packet from one of the UDP sockets. + theErr = fUDPSocketArray[theUDPSockIndex]->RecvFrom(&theRemoteAddr, &theRemotePort, + &thePacketBuf[0], kMaxPacketSize, + &theLength); + if ((theErr != OS_NoErr) || (theLength == 0)) + { + //Finished processing all the UDP packets that have been buffered up by the lower layer. + if ((fTransportType == kReliableUDPTransportType) && (!(theUDPSockIndex & 1))) + { + UInt32 trackIndex = TrackID2TrackIndex(fSDPParser.GetStreamInfo(theUDPSockIndex / 2)->fTrackID); + SendAckPackets(trackIndex); + /* + for (UInt32 trackIndex = 0; trackIndex < fSDPParser.GetNumStreams(); trackIndex++) + { + if (fSDPParser.GetStreamInfo(trackIndex)->fTrackID == fSDPParser.GetStreamInfo(theUDPSockIndex / 2)->fTrackID) + { + SendAckPackets(trackIndex); + + //if (fStats[trackCount].fHighestSeqNumValid) + // If we are supposed to be sending acks, and we just finished + // receiving all packets for this track that are available at this time, + // send an ACK packet + //this->AckPackets(trackCount, 0, false); + } + } + */ + } + + theUDPSockIndex++; + if (theUDPSockIndex == fSDPParser.GetNumStreams() * 2) + break; + continue; + } + + theTrackID = fSDPParser.GetStreamInfo(theUDPSockIndex / 2)->fTrackID; + isRTCP = (theUDPSockIndex & 1); + thePacket = &thePacketBuf[0]; + } + // + // We have a valid packet. Invoke the packet handler function + if (isRTCP) + this->ProcessRTCPPacket(thePacket, theLength, theTrackID); + else + this->ProcessRTPPacket(thePacket, theLength, theTrackID); + } + return theErr; +} + +void ClientSession::ProcessRTPPacket(char* inPacket, UInt32 inLength, UInt32 inTrackID) +{ + UInt32 trackIndex = TrackID2TrackIndex(inTrackID); + if (trackIndex == kUInt32_Max) + { if(fVerboseLevel >= 3) + qtss_printf("ClientSession::ProcessRTPPacket fatal packet processing error. unknown track\n"); + return; + } + TrackStats &trackStats = fStats[trackIndex]; + + if(fVerboseLevel >= 3) + { + SInt64 curTime = OS::Milliseconds(); + qtss_printf("Processing incoming packets at time %"_S64BITARG_"\n", curTime); + } + + //first validate the header and check the SSRC + Bool16 badPacket = false; + RTPPacket rtpPacket = RTPPacket(inPacket, inLength); + if (!rtpPacket.HeaderIsValid()) + badPacket = true; + else + { + if (trackStats.fServerSSRC != 0) + { + if (rtpPacket.GetSSRC() != trackStats.fServerSSRC) + badPacket = true; + } + else //obtain the SSRC from the first packet if it's not available + trackStats.fServerSSRC = rtpPacket.GetSSRC(); + } + + if(badPacket) + { + trackStats.fNumMalformedPackets++; + if (fVerboseLevel >= 1) + qtss_printf("TrackID=%"_U32BITARG_", len=%"_U32BITARG_"; malformed packet\n", inTrackID, inLength); + return; + } + + //Now check the sequence number + UInt32 packetSeqNum = kUInt32_Max; + if (trackStats.fHighestSeqNum == kUInt32_Max) //this is the first sequence number received + packetSeqNum = trackStats.fBaseSeqNum = trackStats.fHighestSeqNum = static_cast(rtpPacket.GetSeqNum()); + else + packetSeqNum = CalcSeqNum(trackStats.fHighestSeqNum, rtpPacket.GetSeqNum()); + + if (packetSeqNum == kUInt32_Max) //sequence number is out of range + { + trackStats.fNumOutOfBoundPackets++; + if (fVerboseLevel >= 2) + qtss_printf("TrackID=%"_U32BITARG_", len=%"_U32BITARG_", seq=%u"", ref(32)=%"_U32BITARG_"; out of bound packet\n", + inTrackID, inLength, rtpPacket.GetSeqNum(), trackStats.fHighestSeqNum); + } + else + { + //the packet is good -- update statisics + Bool16 packetIsOutOfOrder = false; + if (trackStats.fHighestSeqNum <= packetSeqNum) + trackStats.fHighestSeqNum = packetSeqNum; + else + packetIsOutOfOrder = true; + + fNumPacketsReceived++; + sPacketsReceived++; + trackStats.fNumPacketsReceived++; + fNumBytesReceived += inLength; + sBytesReceived += inLength; + trackStats.fNumBytesReceived += inLength; + + //record this sequence number so that it can be acked later on + if (fTransportType == kReliableUDPTransportType) + trackStats.fPacketsToAck.push_back(packetSeqNum); + + if (fVerboseLevel >= 3) + qtss_printf("TrackID=%"_U32BITARG_", len=%"_U32BITARG_", seq(32)=%"_U32BITARG_", ref(32)=%"_U32BITARG_"; good packet\n", + inTrackID, inLength, packetSeqNum, trackStats.fHighestSeqNum); + + //RTP-Meta-Info + RTPMetaInfoPacket::FieldID* theMetaInfoFields = fClient->GetFieldIDArrayByTrack(inTrackID); + if (theMetaInfoFields != NULL) + { + // + // This packet is an RTP-Meta-Info packet. Parse it out and print out the results + RTPMetaInfoPacket theMetaInfoPacket; + Bool16 packetOK = theMetaInfoPacket.ParsePacket((UInt8*)inPacket, inLength, theMetaInfoFields); + if (!packetOK) + { + if( fVerboseLevel >= 2) + qtss_printf("Received invalid RTP-Meta-Info packet\n"); + } + else if( fVerboseLevel >= 2) + { + qtss_printf("---\n"); + qtss_printf("TrackID: %"_U32BITARG_"\n", inTrackID); + qtss_printf("Packet transmit time: %"_64BITARG_"d\n", theMetaInfoPacket.GetTransmitTime()); + qtss_printf("Frame type: %u\n", theMetaInfoPacket.GetFrameType()); + qtss_printf("Packet number: %"_64BITARG_"u\n", theMetaInfoPacket.GetPacketNumber()); + qtss_printf("Packet position: %"_64BITARG_"u\n", theMetaInfoPacket.GetPacketPosition()); + qtss_printf("Media data length: %"_U32BITARG_"\n", theMetaInfoPacket.GetMediaDataLen()); + } + } + + + if (fEnable3GPP) + { + UInt32 timeStamp = rtpPacket.GetTimeStamp(); + Bool16 packetIsDuplicate = fPlayerSimulator.ProcessRTPPacket(trackIndex, inLength, rtpPacket.GetBody().Len, packetSeqNum, timeStamp); + if(packetIsOutOfOrder && !packetIsDuplicate) + trackStats.fNumOutOfOrderPackets++; + } + } +} + + +void ClientSession::ProcessRTCPPacket(char* inPacket, UInt32 inLength, UInt32 inTrackID) +{ + UInt32 trackIndex = TrackID2TrackIndex(inTrackID); + if (trackIndex == kUInt32_Max) + { + if (fVerboseLevel >= 3) + qtss_printf("ClientSession::ProcessRTCPPacket fatal packet processing error. unknown track\n"); + return; + } + TrackStats &trackStats = fStats[trackIndex]; + + SInt64 curTime = OS::Milliseconds(); + if(fVerboseLevel >= 2) + qtss_printf("Processing incoming RTCP packets on track %"_U32BITARG_" at time %"_S64BITARG_"\n", trackIndex, curTime); + + //first validate the header and check the SSRC + RTCPSenderReportPacket packet; + Bool16 badPacket = !packet.ParseReport(reinterpret_cast(inPacket), inLength); + if (!badPacket) + { + if (trackStats.fServerSSRC != 0) + { + if (packet.GetPacketSSRC() != trackStats.fServerSSRC) + badPacket = true; + } + else //obtain the SSRC from the first packet if it's not available + trackStats.fServerSSRC = packet.GetPacketSSRC(); + } + + if(badPacket) + { + if (fVerboseLevel >= 1) + qtss_printf("TrackID=%"_U32BITARG_", len=%"_U32BITARG_"; malformed RTCP packet\n", inTrackID, inLength); + return; + } + + //Now obtains the NTP timestamp and the current time -- we'll need it for the LSR field of the receiver report + trackStats.fLastSenderReportNTPTime = packet.GetNTPTimeStamp(); + trackStats.fLastSenderReportLocalTime = curTime; +} + + +void ClientSession::SendAckPackets(UInt32 inTrackIndex) +{ + TrackStats &trackStats = fStats[inTrackIndex]; + + if (trackStats.fPacketsToAck.empty()) + return; + trackStats.fNumAcks++; + + if(fVerboseLevel >= 3) + { + SInt64 curTime = OS::Milliseconds(); + qtss_printf("Sending %"_U32BITARG_" acks at time %"_S64BITARG_" on track %"_U32BITARG_"\n", + trackStats.fPacketsToAck.size(), curTime, inTrackIndex); + } + + char sendBuffer[kMaxUDPPacketSize]; + + //First send an empty Receivor Report + RTCPRRPacket RRPacket = RTCPRRPacket(sendBuffer, kMaxUDPPacketSize); + RRPacket.SetSSRC(trackStats.fClientSSRC); + StrPtrLen buffer = RRPacket.GetBufferRemaining(); + + //Now send the Ack packets + RTCPAckPacketFmt ackPacket = RTCPAckPacketFmt(buffer); + ackPacket.SetSSRC(trackStats.fClientSSRC); + ackPacket.SetAcks(trackStats.fPacketsToAck, trackStats.fServerSSRC); + trackStats.fPacketsToAck.clear(); + + UInt32 packetLength = RRPacket.GetPacketLen() + ackPacket.GetPacketLen(); + + // Send the packet + Assert(trackStats.fDestRTCPPort != 0); + fUDPSocketArray[(inTrackIndex*2)+1]->SendTo(fSocket->GetHostAddr(), trackStats.fDestRTCPPort, sendBuffer, packetLength); +} + +OS_Error ClientSession::SendRTCPPackets(UInt32 trackIndex) +{ + TrackStats &trackStats = fStats[trackIndex]; + + char buffer[kMaxUDPPacketSize]; + //::memset(buffer, 0, kMaxUDPPacketSize); + + //First send the RTCP Receiver Report packet + RTCPRRPacket RRPacket = RTCPRRPacket(buffer, kMaxUDPPacketSize); + RRPacket.SetSSRC(trackStats.fClientSSRC); + + UInt8 fracLost = 0; + SInt32 cumLostPackets = 0; + UInt32 lsr = 0; + UInt32 dlsr = 0; + SInt64 curTime = OS::Milliseconds(); + if (trackStats.fHighestSeqNum != kUInt32_Max) + { + CalcRTCPRRPacketsLost(trackIndex, fracLost, cumLostPackets); + + //Now get the middle 32 bits of the NTP time stamp and send it as the LSR + lsr = static_cast(trackStats.fLastSenderReportNTPTime >> 16); + + //Get the time difference expressed as units of 1/65536 seconds + if (trackStats.fLastSenderReportLocalTime != 0) + dlsr = static_cast(OS::TimeMilli_To_Fixed64Secs(curTime - trackStats.fLastSenderReportLocalTime) >> 16); + + RRPacket.AddReportBlock(trackStats.fServerSSRC, fracLost, cumLostPackets, trackStats.fHighestSeqNum, lsr, dlsr); + } + + StrPtrLen remainingBuf = RRPacket.GetBufferRemaining(); + UInt32 *theWriter = reinterpret_cast(remainingBuf.Ptr); + + // RECEIVER REPORT + /* + *(theWriter++) = htonl(0x81c90007); // 1 src RR packet + *(theWriter++) = htonl(0); + *(theWriter++) = htonl(0); + *(theWriter++) = htonl(0); + *(theWriter++) = htonl(trackStats.fHighestSeqNum == kUInt32_Max ? 0 : trackStats.fHighestSeqNum); //EHSN + *(theWriter++) = 0; // don't do jitter yet. + *(theWriter++) = 0; // don't do last SR timestamp + *(theWriter++) = 0; // don't do delay since last SR + */ + + //The implementation should be sending an SDES to conform to the standard...but its not done. + + if(fTransportType == kRTSPReliableUDPClientType) + { + // APP PACKET - QoS info + *(theWriter++) = htonl(0x80CC000C); + //*(ia++) = htonl(trk[i].TrackSSRC); + *(theWriter++) = htonl(trackStats.fClientSSRC); + // this QTSS changes after beta to 'qtss' + *(theWriter++) = htonl(FOUR_CHARS_TO_INT('Q', 'T', 'S', 'S')); + //*(ia++) = toBigEndian_ulong(trk[i].rcvrSSRC); + *(theWriter++) = htonl(trackStats.fServerSSRC); + *(theWriter++) = htonl(8); // number of 4-byte quants below + #define RR 0x72720004 + #define PR 0x70720004 + #define PD 0x70640002 + #define OB 0x6F620004 + *(theWriter++) = htonl(RR); + //unsigned int now, secs; + //now = microseconds(); + //secs = now - trk[i].last_rtcp_packet_sent_us / USEC_PER_SEC; + //if (secs) + // temp = trk[i].bytes_received_since_last_rtcp / secs; + //else + // temp = 0; + //*(ia++) = htonl(temp); + *(theWriter++) = htonl(0); + *(theWriter++) = htonl(PR); + //*(ia++) = htonl(trk[i].rtp_num_received); + *(theWriter++) = htonl(0); + //*(theWriter++) = htonl(PL); + //*(ia++) = htonl(trk[i].rtp_num_lost); + //*(theWriter++) = htonl(0); + *(theWriter++) = htonl(OB); + *(theWriter++) = htonl(fOverbufferWindowSizeInK * 1024); + *(theWriter++) = htonl(PD); + *(theWriter++) = htonl(0); // should be a short, but we need to pad to a long for the entire RTCP app packet + } + + char *buf = reinterpret_cast(theWriter); + UInt32 packetLen = buf - buffer; + UInt32 playoutDelay = 0; + + if (fEnable3GPP && trackStats.fHighestSeqNum != kUInt32_Max) + { + //now add the RTCP NADU packet + RTCPNADUPacketFmt nadu = RTCPNADUPacketFmt(buf, kMaxUDPPacketSize - packetLen); + nadu.SetSSRC(trackStats.fClientSSRC); + + //If there is no packet in the buffer, use the extended highest sequence number received + 1 + UInt32 seqNum = fPlayerSimulator.GetNextSeqNumToDecode(trackIndex); + if(seqNum == kUInt32_Max) + seqNum = trackStats.fHighestSeqNum + 1; + + if (fEnableForcePlayoutDelay) + playoutDelay = fPlayoutDelay; + else + playoutDelay = fPlayerSimulator.GetPlayoutDelay(trackIndex); + + nadu.AddNADUBlock(0, seqNum, 0, fPlayerSimulator.GetFreeBufferSpace(trackIndex), playoutDelay); + + packetLen += nadu.GetPacketLen(); + } + + if(fVerboseLevel >= 2) + { + qtss_printf("Sending receiver report at time %"_S64BITARG_" on track %"_U32BITARG_"; lostFrac=%u, lost=%"_S32BITARG_ + ", FBS=%"_U32BITARG_", delay=%"_U32BITARG_", lsr=%"_U32BITARG_", dlsr=%"_U32BITARG_"\n", + curTime, trackIndex, fracLost, cumLostPackets, + fPlayerSimulator.GetFreeBufferSpace(trackIndex), + playoutDelay, + lsr, dlsr); + } + + // Send the packet + if (fUDPSocketArray != NULL) + { + Assert(trackStats.fDestRTCPPort != 0); + fUDPSocketArray[(trackIndex*2)+1]->SendTo(fSocket->GetHostAddr(), trackStats.fDestRTCPPort, buffer, packetLen); + } + else + { + OS_Error theErr = fClient->PutMediaPacket(fSDPParser.GetStreamInfo(trackIndex)->fTrackID, true, buffer, packetLen); + if (theErr != OS_NoErr) + return theErr; + + } + return OS_NoErr; +} + + +//The lost packets in the RTCP RR are defined differently then the GetNumPacketsLost function. See RFC 3550 6.4.1 and A.3 +void ClientSession::CalcRTCPRRPacketsLost(UInt32 trackIndex, UInt8 &outFracLost, SInt32 &outCumLostPackets) +{ + TrackStats &trackStats = fStats[trackIndex]; + + if (trackStats.fHighestSeqNum == kUInt32_Max) + { + outFracLost = 0; + outCumLostPackets = 0; + return; + } + + UInt32 expected = trackStats.fHighestSeqNum - trackStats.fBaseSeqNum + 1; + UInt32 expectedInterval = expected - trackStats.fExpectedPrior; + UInt32 receivedInterval = trackStats.fNumPacketsReceived - trackStats.fReceivedPrior; + + trackStats.fExpectedPrior = expected; + trackStats.fReceivedPrior = trackStats.fNumPacketsReceived; + + if (expectedInterval == 0 || expectedInterval < receivedInterval) + outFracLost = 0; + else + { + UInt32 lostInterval = expectedInterval - receivedInterval; + outFracLost = static_cast((lostInterval << 8) / expectedInterval); + } + outCumLostPackets = expected - trackStats.fNumPacketsReceived; +} + +//If newSeqNum is no more than kMaxMisorder behind and kMaxDropOut ahead of referenceSeqNum(modulo 2^16), returns the corresponding +//32 bit sequence number. Otherwise returns kUInt32_Max +UInt32 ClientSession::CalcSeqNum(UInt32 referenceSeqNum, UInt16 newSeqNum) +{ + + UInt16 refSeqNum16 = static_cast(referenceSeqNum); + UInt32 refSeqNumHighBits = referenceSeqNum >> 16; + + if (static_cast(newSeqNum - refSeqNum16) <= kMaxDropOut) + { + //new sequence number is ahead and is in range + if (newSeqNum >= refSeqNum16) + return (refSeqNumHighBits << 16) | newSeqNum; //no wrap-around + else + return ((refSeqNumHighBits + 1) << 16) | newSeqNum; //wrapped-around + } + else if (static_cast(refSeqNum16 - newSeqNum) < kMaxMisorder) + { + //new sequence number is behind and is in range + if (newSeqNum < refSeqNum16) + return (refSeqNumHighBits << 16) | newSeqNum; //no wrap-around + else if (refSeqNumHighBits != 0) + return ((refSeqNumHighBits - 1) << 16) | newSeqNum; //wrapped-around + } + return kUInt32_Max; //bad sequence number(out of range) + /* + if (refSeqNum16 <= newSeqNum) + { + UInt16 diff = newSeqNum - refSeqNum16; + if (diff <= kMaxDropOut) //new sequence number is ahead and is in range, no overflow + return (refSeqNumHighBits << 16) | newSeqNum; + + diff = kUInt16_Max - newSeqNum; + diff += refSeqNum16 + 1; + if (diff <= kMaxMisorder && refSeqNumHighBits != 0) //new sequence number is behind, underflow + return ((refSeqNumHighBits - 1) << 16) | newSeqNum; + } + else + { + UInt16 diff = refSeqNum16 - newSeqNum; + if (diff <= kMaxMisorder) //new sequence number is behind and is in range, no underflow + return (refSeqNumHighBits << 16) | newSeqNum; + + diff = kUInt16_Max - refSeqNum16; + diff += newSeqNum + 1; + if (diff <= kMaxDropOut) //new sequence number is ahead and is in range, overflow + return ((refSeqNumHighBits + 1) << 16) | newSeqNum; + } + return kUInt32_Max; //Bad sequence number + */ +} diff --git a/RTSPClientLib/ClientSession.h b/RTSPClientLib/ClientSession.h new file mode 100644 index 0000000..6cd0207 --- /dev/null +++ b/RTSPClientLib/ClientSession.h @@ -0,0 +1,340 @@ +/* + * + * @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: ClientSession.h + + +*/ + +#ifndef __CLIENT_SESSION__ +#define __CLIENT_SESSION__ + +#include "Task.h" +#include "TimeoutTask.h" + +#include "SVector.h" +#include "RTSPClient.h" +#include "ClientSocket.h" +#include "SDPSourceInfo.h" +#include "UDPSocket.h" +#include "PlayerSimulator.h" + +class ClientSession : public Task +{ + public: + + enum + { + kRTSPUDPClientType = 0, + kRTSPTCPClientType = 1, + kRTSPHTTPClientType = 2, + kRTSPHTTPDropPostClientType = 3, + kRTSPReliableUDPClientType = 4 + }; + typedef UInt32 ClientType; + + //The constructor will signal itself with Task::kStartEvent + ClientSession( UInt32 inAddr, UInt16 inPort, char* inURL, + ClientType inClientType, + UInt32 inDurationInSec, UInt32 inStartPlayTimeInSec, + UInt32 inRTCPIntervalInMS, UInt32 inOptionsIntervalInSec, + UInt32 inHTTPCookie, Bool16 inAppendJunkData, UInt32 inReadInterval, + UInt32 inSockRcvBufSize, Float32 inLateTolerance, char* inMetaInfoFields, + Float32 inSpeed, UInt32 verboseLevel, char* inPacketRangePlayHeader, UInt32 inOverbufferWindowSizeInK, + Bool16 sendOptions, Bool16 requestRandomData, SInt32 randomDataSize, Bool16 enable3GPP, + UInt32 GBW = 0, UInt32 MBW = 0, UInt32 MTD = 0, Bool16 enableForcePlayoutDelay = false, UInt32 playoutDelay = 0, + UInt32 bandwidth = 0, UInt32 bufferSpace = 0, UInt32 delayTime = 0, UInt32 startPlayDelay = 0, + char *controlID = NULL, char *name = NULL, char *password = NULL); + + virtual ~ClientSession(); + + // + // Signals. + // + // Send a kKillEvent to delete this object. + // Send a kTeardownEvent to tell the object to send a TEARDOWN and abort + + enum + { + kTeardownEvent = 0x00000100 + }; + + virtual SInt64 Run(); + + // + // States. Find out what the object is currently doing + enum + { + kSendingOptions = 0, + kSendingDescribe = 1, + kSendingSetup = 2, + kSendingPlay = 3, + kPlaying = 4, + kSendingTeardown = 5, + kDone = 6 + }; + // + // Why did this session die? + enum + { + kDiedNormally = 0, // Session went fine + kTeardownFailed = 1, // Teardown failed, but session stats are all valid + kRequestFailed = 2, // Session couldn't be setup because the server returned an error + kBadSDP = 3, // Server sent back some bad SDP + kSessionTimedout = 4, // Server not responding + kConnectionFailed = 5, // Couldn't connect at all. + kDiedWhilePlaying = 6 // Connection was forceably closed while playing the movie + }; + + // + // Once this client session is completely done with the TEARDOWN and ready to be + // destructed, this will return true. Until it returns true, this object should not + // be deleted. When it does return true, this object should be deleted. + Bool16 IsDone() { return fState == kDone; } + + // + // ACCESSORS + + RTSPClient* GetClient() { return fClient; } + ClientSocket* GetSocket() { return fSocket; } + SDPSourceInfo* GetSDPInfo() { return &fSDPParser; } + UInt32 GetState() { return fState; } + + // When this object is in the kDone state, this will tell you why the session died. + UInt32 GetReasonForDying() { return fDeathReason; } + UInt32 GetRequestStatus() { return fClient->GetStatus(); } + + // Tells you the total time we were receiving packets. You can use this + // for computing bit rate + SInt64 GetTotalPlayTimeInMsec() { return fTotalPlayTime; } + + QTSS_RTPPayloadType GetTrackType(UInt32 inTrackIndex) + { return fSDPParser.GetStreamInfo(inTrackIndex)->fPayloadType; } + UInt32 GetNumPacketsReceived(UInt32 inTrackIndex) { return fStats[inTrackIndex].fNumPacketsReceived; } + UInt32 GetNumBytesReceived(UInt32 inTrackIndex) { return fStats[inTrackIndex].fNumBytesReceived; } + UInt32 GetNumPacketsOutOfOrder(UInt32 inTrackIndex) { return fStats[inTrackIndex].fNumOutOfOrderPackets; } + UInt32 GetNumOutOfBoundPackets(UInt32 inTrackIndex) { return fStats[inTrackIndex].fNumOutOfBoundPackets; } + UInt32 GetNumAcks(UInt32 inTrackIndex) { return fStats[inTrackIndex].fNumAcks; } + UInt32 Get3gNumPacketsLost(UInt32 inTrackIndex) { return fPlayerSimulator.GetNumPacketsLost(inTrackIndex); } + UInt32 Get3gNumDuplicates(UInt32 inTrackIndex) { return fPlayerSimulator.GetNumDuplicates(inTrackIndex); } + UInt32 Get3gNumLatePackets(UInt32 inTrackIndex) { return fPlayerSimulator.GetNumLatePackets(inTrackIndex); } + UInt32 Get3gNumBufferOverflowedPackets(UInt32 inTrackIndex) { return fPlayerSimulator.GetNumBufferOverflowedPackets(inTrackIndex); } + //include packets with bad SSRC + UInt32 GetNumMalformedPackets(UInt32 inTrackIndex) + { return fStats[inTrackIndex].fNumMalformedPackets; } + + //Will reset the counter everytime it is called + UInt32 GetSessionPacketsReceived() { UInt32 result = fNumPacketsReceived; fNumPacketsReceived = 0; return result; } + // + // Global stats + static UInt32 GetActiveConnections() { return sActiveConnections; } + static UInt32 GetPlayingConnections() { return sPlayingConnections; } + static UInt32 GetConnectionAttempts() { return sTotalConnectionAttempts; } + //The following two functions will reset the global counter every time it is called + static UInt32 GetConnectionBytesReceived() { UInt32 result = sBytesReceived; sBytesReceived = 0; return result; } + static UInt32 GetConnectionPacketsReceived() { UInt32 result = sPacketsReceived; sPacketsReceived = 0; return result; } + + + private: + + enum + { + kRawRTSPControlType = 0, + kRTSPHTTPControlType = 1, + kRTSPHTTPDropPostControlType= 2 + }; + typedef UInt32 ControlType; + + enum + { + kUDPTransportType = 0, + kReliableUDPTransportType = 1, + kTCPTransportType = 2 + }; + typedef UInt32 TransportType; + + //Returns kUInt32_Max if there is no track with such trackID + UInt32 TrackID2TrackIndex(UInt32 trackID) + { + for (UInt32 trackIndex = 0; trackIndex < fSDPParser.GetNumStreams(); trackIndex++) + { + if (fSDPParser.GetStreamInfo(trackIndex)->fTrackID == trackID) + return trackIndex; + } + return kUInt32_Max; + } + + ClientSocket* fSocket; // Connection object + RTSPClient* fClient; // Manages the client connection + SDPSourceInfo fSDPParser; // Parses the SDP in the DESCRIBE response + TimeoutTask fTimeoutTask; // Kills this connection in the event the server isn't responding + + ControlType fControlType; + TransportType fTransportType; + UInt32 fDurationInSec; + UInt32 fStartPlayTimeInSec; + UInt32 fRTCPIntervalInMs; + UInt32 fOptionsIntervalInSec; + + Bool16 fOptions; + Bool16 fOptionsRequestRandomData; + SInt32 fOptionsRandomDataSize; + SInt64 fTransactionStartTimeMilli; + + UInt32 fState; // For managing the state machine + UInt32 fDeathReason; + UInt32 fNumSetups; + UDPSocket** fUDPSocketArray; + + //these values starts as soon as the RTSP Play is completed; does not corresonds to actual media play time + SInt64 fPlayTime; + SInt64 fTotalPlayTime; + SInt64 fLastRTCPTime; + + Bool16 fTeardownImmediately; + Bool16 fAppendJunk; + UInt32 fReadInterval; + UInt32 fSockRcvBufSize; + + Float32 fSpeed; + char* fPacketRangePlayHeader; + + //These values are for the wireless links only -- not end-to-end + //Units are in kbps, milliseconds, and bytes + UInt32 fGuarenteedBitRate; + UInt32 fMaxBitRate; + UInt32 fMaxTransferDelay; + Bool16 fEnableForcePlayoutDelay; + UInt32 fPlayoutDelay; + UInt32 fBandwidth; //bps + //the buffer space is per stream, not total space + UInt32 fBufferSpace; + UInt32 fDelayTime; //target buffering delay + UInt32 fStartPlayDelay; //how much buffer should we keep before we start playing? in milliseconds + Bool16 fEnable3GPP; + + // Client stats + struct TrackStats + { + //Modified by ClientSession + UInt32 fNumPacketsReceived; //track only good packets(but include late and duplicates) + UInt32 fNumBytesReceived; //includes RTP header + UInt32 fNumOutOfOrderPackets; //excludes duplicates + UInt32 fNumOutOfBoundPackets; + UInt32 fNumMalformedPackets; //include packets with bad SSRC + UInt32 fNumAcks; //cumulative; counts ACK packets with masks as 1 ACK + + UInt16 fDestRTCPPort; + UInt32 fServerSSRC; //0 for not available + UInt32 fClientSSRC; + + //Used for the DLSR and LSR field of the RTCP + SInt64 fLastSenderReportNTPTime; + SInt64 fLastSenderReportLocalTime; + + //These values are used to calculate the fraction lost and cumulative number of packets lost field in the RTCP RR packet. + //See RFC 3550 6.4.1 and A.3 + + //fHighestSeqNum is the highest valid sequence number received; note that this is 32 bits so that it never overflows. + //An initial value of kUInt32_Max is used as an invalid marker(such that no valid sequence number has been received yet). + UInt32 fHighestSeqNum; + UInt32 fBaseSeqNum; + UInt32 fExpectedPrior; + UInt32 fReceivedPrior; + + SVector fPacketsToAck; + TrackStats() : fNumPacketsReceived(0), fNumBytesReceived(0), fNumOutOfOrderPackets(0), fNumOutOfBoundPackets(0), + fNumMalformedPackets(0), fNumAcks(0), fDestRTCPPort(0), fServerSSRC(0), fClientSSRC(0), fLastSenderReportNTPTime(0), + fLastSenderReportLocalTime(0), fHighestSeqNum(kUInt32_Max), fBaseSeqNum(0), fExpectedPrior(0), fReceivedPrior(0) + { } + }; + + /* Client stats + struct TrackStats + { + enum + { + kSeqNumMapSize = 100, + kHalfSeqNumMap = 50 + }; + + UInt16 fDestRTCPPort; + UInt32 fNumPacketsReceived; + UInt32 fNumBytesReceived; + UInt32 fNumLostPackets; + UInt32 fNumOutOfOrderPackets; + UInt32 fNumThrownAwayPackets; + UInt8 fSequenceNumberMap[kSeqNumMapSize]; + UInt16 fWrapSeqNum; + UInt32 fSSRC; + Bool16 fIsSSRCValid; + + UInt16 fHighestSeqNum; + UInt16 fLastAckedSeqNum; + Bool16 fHighestSeqNumValid; + + UInt32 fNumAcks; + UInt32 fNumDuplicates; + + }; + */ + UInt32 fOverbufferWindowSizeInK; + UInt32 fCurRTCPTrack; //track index not track id + UInt32 fNumPacketsReceived; //track only good packets(but include late and duplicates; see RFC3550 6.4.1) + UInt32 fNumBytesReceived; //includes RTP header + + UInt32 fVerboseLevel; + + SVector fStats; //the index of this vector is the same as fSDPParser.GetStreamInfo + + PlayerSimulator fPlayerSimulator; + + //TrackStats* fStats; + + // + // Global stats + static UInt32 sActiveConnections; + static UInt32 sPlayingConnections; + static UInt32 sTotalConnectionAttempts; + static UInt32 sBytesReceived; + static UInt32 sPacketsReceived; + + // + // Helper functions for Run() + void SetupUDPSockets(); + void ProcessRTPPacket(char* inPacket, UInt32 inLength, UInt32 inTrackID); + void ProcessRTCPPacket(char* inPacket, UInt32 inLength, UInt32 inTrackID); + OS_Error ReadMediaData(); + OS_Error SendRTCPPackets(UInt32 trackIndex); + void SendAckPackets(UInt32 inTrackIndex); + + //Calculates the RTCP RR's fraction lost and cumulative number of packets lost field info. + void CalcRTCPRRPacketsLost(UInt32 trackIndex, UInt8 &outFracLost, SInt32 &outCumLostPackets); + + //Returns kUInt32_Max if newSeqNum is out of bound, otherwise returns the corresponding 32 bit sequence number. + static UInt32 CalcSeqNum(UInt32 referenceSeqNum, UInt16 newSeqNum); +}; + +#endif //__CLIENT_SESSION__ diff --git a/RTSPClientLib/ClientSocket.cpp b/RTSPClientLib/ClientSocket.cpp new file mode 100644 index 0000000..a4e39d1 --- /dev/null +++ b/RTSPClientLib/ClientSocket.cpp @@ -0,0 +1,358 @@ +/* + * + * @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: ClientSocket.cpp + + + +*/ +#ifndef __Win32__ +#include +#include +#include +#include +#include +#include +#include +#endif + + +#include "ClientSocket.h" +#include "OSMemory.h" +#include "base64.h" +#include "MyAssert.h" + +#define CLIENT_SOCKET_DEBUG 0 + + +ClientSocket::ClientSocket() +: fHostAddr(0), + fHostPort(0), + fEventMask(0), + fSocketP(NULL), + fSendBuffer(fSendBuf, 0), + fSentLength(0) +{} + +OS_Error ClientSocket::Open(TCPSocket* inSocket) +{ + OS_Error theErr = OS_NoErr; + if (!inSocket->IsBound()) + { + theErr = inSocket->Open(); + if (theErr == OS_NoErr) + theErr = inSocket->Bind(0, 0); + + if (theErr != OS_NoErr) + return theErr; + + inSocket->NoDelay(); +#if __FreeBSD__ || __MacOSX__ + // no KeepAlive -- probably should be off for all platforms. +#else + inSocket->KeepAlive(); +#endif + + } + return theErr; +} + +OS_Error ClientSocket::Connect(TCPSocket* inSocket) +{ + OS_Error theErr = this->Open(inSocket); + Assert(theErr == OS_NoErr); + if (theErr != OS_NoErr) + return theErr; + + if (!inSocket->IsConnected()) + { + theErr = inSocket->Connect(fHostAddr, fHostPort); + if ((theErr == EINPROGRESS) || (theErr == EAGAIN)) + { + fSocketP = inSocket; + fEventMask = EV_RE | EV_WR; + return theErr; + } + } + return theErr; +} + +OS_Error ClientSocket::Send(char* inData, const UInt32 inLength) +{ + iovec theVec[1]; + theVec[0].iov_base = (char*)inData; + theVec[0].iov_len = inLength; + + return this->SendV(theVec, 1); +} + +OS_Error ClientSocket::SendSendBuffer(TCPSocket* inSocket) +{ + OS_Error theErr = OS_NoErr; + UInt32 theLengthSent = 0; + + if (fSendBuffer.Len == 0) + return OS_NoErr; + + do + { + // theLengthSent should be reset to zero before passing its pointer to Send function + // otherwise the old value will be used and it will go into an infinite loop sometimes + theLengthSent = 0; + // + // Loop, trying to send the entire message. + theErr = inSocket->Send(fSendBuffer.Ptr + fSentLength, fSendBuffer.Len - fSentLength, &theLengthSent); + fSentLength += theLengthSent; + + } while (theLengthSent > 0); + + if (theErr == OS_NoErr) + fSendBuffer.Len = fSentLength = 0; // Message was sent + else + { + // Message wasn't entirely sent. Caller should wait for a read event on the POST socket + fSocketP = inSocket; + fEventMask = EV_WR; + } + return theErr; +} + + +TCPClientSocket::TCPClientSocket(UInt32 inSocketType) + : fSocket(NULL, inSocketType) +{ + // + // It is necessary to open the socket right when we construct the + // object because the QTSSSplitterModule that uses this class uses + // the socket file descriptor in the QTSS_CreateStreamFromSocket call. + fSocketP = &fSocket; + this->Open(&fSocket); +} + +void TCPClientSocket::SetOptions(int sndBufSize,int rcvBufSize) +{ //set options on the socket + + //qtss_printf("TCPClientSocket::SetOptions sndBufSize=%d,rcvBuf=%d,keepAlive=%d,noDelay=%d\n",sndBufSize,rcvBufSize,(int)keepAlive,(int)noDelay); + int err = 0; + err = ::setsockopt(fSocket.GetSocketFD(), SOL_SOCKET, SO_SNDBUF, (char*)&sndBufSize, sizeof(int)); + AssertV(err == 0, OSThread::GetErrno()); + + err = ::setsockopt(fSocket.GetSocketFD(), SOL_SOCKET, SO_RCVBUF, (char*)&rcvBufSize, sizeof(int)); + AssertV(err == 0, OSThread::GetErrno()); + +#if __FreeBSD__ || __MacOSX__ + struct timeval time; + //int len = sizeof(time); + time.tv_sec = 0; + time.tv_usec = 0; + + err = ::setsockopt(fSocket.GetSocketFD(), SOL_SOCKET, SO_RCVTIMEO, (char*)&time, sizeof(time)); + AssertV(err == 0, OSThread::GetErrno()); + + err = ::setsockopt(fSocket.GetSocketFD(), SOL_SOCKET, SO_SNDTIMEO, (char*)&time, sizeof(time)); + AssertV(err == 0, OSThread::GetErrno()); +#endif + +} + +OS_Error TCPClientSocket::SendV(iovec* inVec, UInt32 inNumVecs) +{ + if (fSendBuffer.Len == 0) + { + for (UInt32 count = 0; count < inNumVecs; count++) + { + ::memcpy(fSendBuffer.Ptr + fSendBuffer.Len, inVec[count].iov_base, inVec[count].iov_len); + fSendBuffer.Len += inVec[count].iov_len; + Assert(fSendBuffer.Len < ClientSocket::kSendBufferLen); + } + } + + OS_Error theErr = this->Connect(&fSocket); + if (theErr != OS_NoErr) + return theErr; + + return this->SendSendBuffer(&fSocket); +} + +OS_Error TCPClientSocket::Read(void* inBuffer, const UInt32 inLength, UInt32* outRcvLen) +{ + this->Connect(&fSocket); + OS_Error theErr = fSocket.Read(inBuffer, inLength, outRcvLen); + if (theErr != OS_NoErr) + fEventMask = EV_RE; + return theErr; +} + + +HTTPClientSocket::HTTPClientSocket(const StrPtrLen& inURL, UInt32 inCookie, UInt32 inSocketType) +: fCookie(inCookie), + fSocketType(inSocketType), + fGetReceived(0), + + fGetSocket(NULL, inSocketType), + fPostSocket(NULL) +{ + fURL.Ptr = NEW char[inURL.Len + 1]; + fURL.Len = inURL.Len; + ::memcpy(fURL.Ptr, inURL.Ptr, inURL.Len); + fURL.Ptr[fURL.Len] = '\0'; +} + +HTTPClientSocket::~HTTPClientSocket() +{ + delete [] fURL.Ptr; + delete fPostSocket; +} + +OS_Error HTTPClientSocket::Read(void* inBuffer, const UInt32 inLength, UInt32* outRcvLen) +{ + // + // Bring up the GET connection if we need to + if (!fGetSocket.IsConnected()) + { +#if CLIENT_SOCKET_DEBUG + qtss_printf("HTTPClientSocket::Read: Sending GET\n"); +#endif + qtss_sprintf(fSendBuffer.Ptr, "GET %s HTTP/1.0\r\nX-SessionCookie: %"_U32BITARG_"\r\nAccept: application/x-rtsp-rtp-interleaved\r\nUser-Agent: QTSS/2.0\r\n\r\n", fURL.Ptr, fCookie); + fSendBuffer.Len = ::strlen(fSendBuffer.Ptr); + Assert(fSentLength == 0); + } + + OS_Error theErr = this->Connect(&fGetSocket); + if (theErr != OS_NoErr) + return theErr; + + if (fSendBuffer.Len > 0) + { + theErr = this->SendSendBuffer(&fGetSocket); + if (theErr != OS_NoErr) + return theErr; + fSentLength = 1; // So we know to execute the receive code below. + } + + // We are done sending the GET. If we need to receive the GET response, do that here + if (fSentLength > 0) + { + *outRcvLen = 0; + do + { + // Loop, trying to receive the entire response. + theErr = fGetSocket.Read(&fSendBuffer.Ptr[fGetReceived], kSendBufferLen - fGetReceived, outRcvLen); + fGetReceived += *outRcvLen; + + // Check to see if we've gotten a \r\n\r\n. If we have, then we've received + // the entire GET + fSendBuffer.Ptr[fGetReceived] = '\0'; + char* theGetEnd = ::strstr(fSendBuffer.Ptr, "\r\n\r\n"); + + if (theGetEnd != NULL) + { + // We got the entire GET response, so we are ready to move onto + // real RTSP response data. First skip past the \r\n\r\n + theGetEnd += 4; + +#if CLIENT_SOCKET_DEBUG + qtss_printf("HTTPClientSocket::Read: Received GET response\n"); +#endif + + // Whatever remains is part of an RTSP request, so move that to + // the beginning of the buffer and blow away the GET + *outRcvLen = fGetReceived - (theGetEnd - fSendBuffer.Ptr); + ::memcpy(inBuffer, theGetEnd, *outRcvLen); + fGetReceived = fSentLength = 0; + return OS_NoErr; + } + + Assert(fGetReceived < inLength); + } while (*outRcvLen > 0); + +#if CLIENT_SOCKET_DEBUG + qtss_printf("HTTPClientSocket::Read: Waiting for GET response\n"); +#endif + // Message wasn't entirely received. Caller should wait for a read event on the GET socket + Assert(theErr != OS_NoErr); + fSocketP = &fGetSocket; + fEventMask = EV_RE; + return theErr; + } + + theErr = fGetSocket.Read(&((char*)inBuffer)[fGetReceived], inLength - fGetReceived, outRcvLen); + if (theErr != OS_NoErr) + { +#if CLIENT_SOCKET_DEBUG + //qtss_printf("HTTPClientSocket::Read: Waiting for data\n"); +#endif + fSocketP = &fGetSocket; + fEventMask = EV_RE; + } +#if CLIENT_SOCKET_DEBUG + //else + //qtss_printf("HTTPClientSocket::Read: Got some data\n"); +#endif + return theErr; +} + +OS_Error HTTPClientSocket::SendV(iovec* inVec, UInt32 inNumVecs) +{ + // + // Bring up the POST connection if we need to + if (fPostSocket == NULL) + fPostSocket = NEW TCPSocket(NULL, fSocketType); + + if (!fPostSocket->IsConnected()) + { +#if CLIENT_SOCKET_DEBUG + qtss_printf("HTTPClientSocket::Send: Sending POST\n"); +#endif + qtss_sprintf(fSendBuffer.Ptr, "POST %s HTTP/1.0\r\nX-SessionCookie: %"_U32BITARG_"\r\nAccept: application/x-rtsp-rtp-interleaved\r\nUser-Agent: QTSS/2.0\r\n\r\n", fURL.Ptr, fCookie); + fSendBuffer.Len = ::strlen(fSendBuffer.Ptr); + this->EncodeVec(inVec, inNumVecs); + } + + OS_Error theErr = this->Connect(fPostSocket); + if (theErr != OS_NoErr) + return theErr; + + // + // If we have nothing to send currently, this should be a new message, in which case + // we can encode it and send it + if (fSendBuffer.Len == 0) + this->EncodeVec(inVec, inNumVecs); + +#if CLIENT_SOCKET_DEBUG + //qtss_printf("HTTPClientSocket::Send: Sending data\n"); +#endif + return this->SendSendBuffer(fPostSocket); +} + +void HTTPClientSocket::EncodeVec(iovec* inVec, UInt32 inNumVecs) +{ + for (UInt32 count = 0; count < inNumVecs; count++) + { + fSendBuffer.Len += ::Base64encode(fSendBuffer.Ptr + fSendBuffer.Len, (char*)inVec[count].iov_base, inVec[count].iov_len); + Assert(fSendBuffer.Len < ClientSocket::kSendBufferLen); + fSendBuffer.Len = ::strlen(fSendBuffer.Ptr); //Don't trust what the above function returns for a length + } +} diff --git a/RTSPClientLib/ClientSocket.h b/RTSPClientLib/ClientSocket.h new file mode 100644 index 0000000..5edb524 --- /dev/null +++ b/RTSPClientLib/ClientSocket.h @@ -0,0 +1,165 @@ +/* + * + * @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: ClientSocket.h + + + +*/ + + +#ifndef __CLIENT_SOCKET__ +#define __CLIENT_SOCKET__ + +#include "OSHeaders.h" +#include "TCPSocket.h" + +class ClientSocket +{ + public: + + ClientSocket(); + virtual ~ClientSocket() {} + + void Set(UInt32 hostAddr, UInt16 hostPort) + { fHostAddr = hostAddr; fHostPort = hostPort; } + + // + // Sends data to the server. If this returns EAGAIN or EINPROGRESS, call again + // until it returns OS_NoErr or another error. On subsequent calls, you need not + // provide a buffer. + // + // When this call returns EAGAIN or EINPROGRESS, caller should use GetEventMask + // and GetSocket to wait for a socket event. + OS_Error Send(char* inData, const UInt32 inLength); + + // + // Sends an ioVec to the server. Same conditions apply as above function + virtual OS_Error SendV(iovec* inVec, UInt32 inNumVecs) = 0; + + // + // Reads data from the server. If this returns EAGAIN or EINPROGRESS, call + // again until it returns OS_NoErr or another error. This call may return OS_NoErr + // and 0 for rcvLen, in which case you should call it again. + // + // When this call returns EAGAIN or EINPROGRESS, caller should use GetEventMask + // and GetSocket to wait for a socket event. + virtual OS_Error Read(void* inBuffer, const UInt32 inLength, UInt32* outRcvLen) = 0; + + // + // ACCESSORS + UInt32 GetHostAddr() { return fHostAddr; } + virtual UInt32 GetLocalAddr() = 0; + + // If one of the above methods returns EWOULDBLOCK or EINPROGRESS, you + // can check this to see what events you should wait for on the socket + UInt32 GetEventMask() { return fEventMask; } + Socket* GetSocket() { return fSocketP; } + + virtual void SetRcvSockBufSize(UInt32 inSize) = 0; + + protected: + + // Generic connect function + OS_Error Connect(TCPSocket* inSocket); + // Generic open function + OS_Error Open(TCPSocket* inSocket); + + OS_Error SendSendBuffer(TCPSocket* inSocket); + + UInt32 fHostAddr; + UInt16 fHostPort; + + UInt32 fEventMask; + Socket* fSocketP; + + enum + { + kSendBufferLen = 2048 + }; + + // Buffer for sends. + char fSendBuf[kSendBufferLen + 1]; + StrPtrLen fSendBuffer; + UInt32 fSentLength; +}; + +class TCPClientSocket : public ClientSocket +{ + public: + + TCPClientSocket(UInt32 inSocketType); + virtual ~TCPClientSocket() {} + + // + // Implements the ClientSocket Send and Receive interface for a TCP connection + virtual OS_Error SendV(iovec* inVec, UInt32 inNumVecs); + virtual OS_Error Read(void* inBuffer, const UInt32 inLength, UInt32* outRcvLen); + + virtual UInt32 GetLocalAddr() { return fSocket.GetLocalAddr(); } + virtual void SetRcvSockBufSize(UInt32 inSize) { fSocket.SetSocketRcvBufSize(inSize); } + virtual void SetOptions(int sndBufSize = 8192,int rcvBufSize=1024); + + virtual UInt16 GetLocalPort() { return fSocket.GetLocalPort(); } + + private: + + TCPSocket fSocket; +}; + +class HTTPClientSocket : public ClientSocket +{ + public: + + HTTPClientSocket(const StrPtrLen& inURL, UInt32 inCookie, UInt32 inSocketType); + virtual ~HTTPClientSocket(); + + // + // Closes the POST half of the RTSP / HTTP connection + void ClosePost() { delete fPostSocket; fPostSocket = NULL; } + + // + // Implements the ClientSocket Send and Receive interface for an RTSP / HTTP connection + virtual OS_Error SendV(iovec* inVec, UInt32 inNumVecs); + // Both SendV and Read use the fSendBuffer; so you cannot have both operations be running at the same time. + virtual OS_Error Read(void* inBuffer, const UInt32 inLength, UInt32* outRcvLen); + + virtual UInt32 GetLocalAddr() { return fGetSocket.GetLocalAddr(); } + virtual void SetRcvSockBufSize(UInt32 inSize) { fGetSocket.SetSocketRcvBufSize(inSize); } + + private: + + void EncodeVec(iovec* inVec, UInt32 inNumVecs); + + StrPtrLen fURL; + UInt32 fCookie; + + UInt32 fSocketType; + UInt32 fGetReceived; + TCPSocket fGetSocket; + TCPSocket* fPostSocket; +}; + +#endif //__CLIENT_SOCKET__ diff --git a/RTSPClientLib/PlayerSimulator.h b/RTSPClientLib/PlayerSimulator.h new file mode 100644 index 0000000..338909a --- /dev/null +++ b/RTSPClientLib/PlayerSimulator.h @@ -0,0 +1,366 @@ +/* + * + * @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: PlayerSimulator.h + + Simulates a client; track duplicate/late/missing packets +*/ + +#ifndef _PLAYERSIMULATOR_H_ +#define _PLAYERSIMULATOR_H_ + +#include "SafeStdLib.h" +#include "OSHeaders.h" +#include "OS.h" +#include "SVector.h" + +/* Macros for min/max. */ +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +class PlayerSimulator +{ + public: + PlayerSimulator(UInt32 verboseLevel = 0) + : fTargetBufferingDelay(0), fStartPlayDelay(0), fLocalStartPlayTime(0), fCurrentMediaTime(0), fVerboseLevel(verboseLevel), fIsPlaying(false) + {} + + //call Setup and then SetupTrack per stream to initialize the class. Use a targetBufferingDelay of 0 to start playing immediately + //The PlayerSimulator can be reused by calling Setup and SetupTrack again + void Setup(UInt32 numTracks, UInt32 targetBufferingDelay = 0, UInt32 startPlayDelay = 0) + { + fTargetBufferingDelay = targetBufferingDelay; + fStartPlayDelay = startPlayDelay; + fLocalStartPlayTime = 0; + fIsPlaying = false; + fTrackInfo.clear(); + fTrackInfo.resize(numTracks); + } + //Use a bufferSize of 0 to have an unlimited buffer size; + void SetupTrack(UInt32 trackIndex, UInt32 samplingRate, UInt32 bufferSize = 0) + { + fTrackInfo[trackIndex].fBufferSize = bufferSize == 0 ? kUInt32_Max : bufferSize; + fTrackInfo[trackIndex].fSamplingRate = samplingRate; + } + + //Please note that PlayerSimulator use track index, not track ID; payloadSize should exclude the RTP header. + //Returns true if the packet is a duplicate; the sequence number of the RTP packet MUST be in range. + Bool16 ProcessRTPPacket(UInt32 trackIndex, UInt32 packetLen, UInt32 payloadSize, UInt32 seqNum, UInt32 timeStamp) + { + Assert(trackIndex <= fTrackInfo.size()); + TrackInfo &trackInfo = fTrackInfo[trackIndex]; + SVector &playBuffer = trackInfo.fPlayBuffer; + + Bool16 addPacketToBuffer = false; + Bool16 packetIsDuplicate = false; + + if (fIsPlaying) + { + AdvanceTime(); + if(seqNum < trackInfo.fNextSeqNum) + { + //definitely a late packet; try to see if the packet is one of the lost packets + UInt32 index = trackInfo.fLostPackets.find(seqNum); + if (index != trackInfo.fLostPackets.size()) + { + //a late, non-duplicate packet + trackInfo.fNumLatePackets++; + trackInfo.fLostPackets.erase(index); + } + else //a late-duplicate + { + trackInfo.fNumDuplicates++; + packetIsDuplicate = true; + } + } + else + { + //look in the play buffer + if (playBuffer.empty() || seqNum < playBuffer.front().fSeqNum) + { + //the packet has an earlier sequence number than any other packets in the buffer; it may still be late + if (RTPTime2MediaTime(trackIndex, timeStamp) <= fCurrentMediaTime) + trackInfo.fNumLatePackets++; + if (GetFreeBufferSpace(trackIndex) >= packetLen) + playBuffer.insert(0, PacketInfo(seqNum, timeStamp, packetLen)); + else + trackInfo.fNumBufferOverflowedPackets++; + } + else + addPacketToBuffer = true; + } + } + else + { + if (playBuffer.empty()) + { + //This is the first packet in the stream + if (GetFreeBufferSpace(trackIndex) >= packetLen) + playBuffer.push_back(PacketInfo(seqNum, timeStamp, packetLen)); + else + trackInfo.fNumBufferOverflowedPackets++; + } + else + addPacketToBuffer = true; + } + + if (addPacketToBuffer) + { + //check the buffer to see if the packet is a duplicate + UInt32 index = 0; + for(index = 0; index < playBuffer.size(); ++index) + { + if (seqNum == playBuffer[index].fSeqNum) + { + //a non-late, duplicate packet + trackInfo.fNumDuplicates++; + packetIsDuplicate = true; + break; + } + else if (seqNum < playBuffer[index].fSeqNum) //found where to insert + break; + } + + if(!packetIsDuplicate) + { + //a non-late non-duplicate -- may be out of order + if (GetFreeBufferSpace(trackIndex) >= packetLen) + playBuffer.insert(index, PacketInfo(seqNum, timeStamp, packetLen)); + else + trackInfo.fNumBufferOverflowedPackets++; + } + } + + if(!fIsPlaying) + { + //is it time to start playing? + Bool16 haveEnoughBuffer = true; + for(UInt32 index = 0; index < fTrackInfo.size(); ++index) + { + if (fTrackInfo[index].fPlayBuffer.empty() || GetBufferingDelay(index) <= fStartPlayDelay) + { + haveEnoughBuffer = false; + break; + } + } + + if(haveEnoughBuffer) //lets start playing! + { + fIsPlaying = true; + fLocalStartPlayTime = OS::Milliseconds(); + + for(UInt32 index = 0; index < fTrackInfo.size(); ++index) + { + //for each track, set the next sequence number and the earliest RTP timestamp + TrackInfo &curTrack = fTrackInfo[index]; + curTrack.fRTPTimeStampBase = curTrack.fPlayBuffer.front().fTimeStamp; + curTrack.fNextSeqNum = curTrack.fPlayBuffer.front().fSeqNum; + + if(fVerboseLevel >= 1) + qtss_printf("Track %"_U32BITARG_" is now playing; initial seq=%"_U32BITARG_", initial time stamp=%"_U32BITARG_"\n", + index, curTrack.fNextSeqNum, curTrack.fRTPTimeStampBase); + } + AdvanceTime(); + } + } + + if(fVerboseLevel >= 2) + { + if (fIsPlaying) + qtss_printf("Processed packet: track=%"_U32BITARG_", len=%"_U32BITARG_", seq=%"_U32BITARG_", time=%"_U32BITARG_"(%"_U32BITARG_"); " + "bufferingDelay=%"_U32BITARG_", FBS=%"_U32BITARG_", media time=%"_U32BITARG_"\n", + trackIndex, packetLen, seqNum, timeStamp, RTPTime2MediaTime(trackIndex, timeStamp), + GetBufferingDelay(trackIndex), GetFreeBufferSpace(trackIndex), fCurrentMediaTime); + else + qtss_printf("Processed packet: track=%"_U32BITARG_", len=%"_U32BITARG_", seq=%"_U32BITARG_", time=%"_U32BITARG_"(%"_U32BITARG_"); " + "bufferingDelay=%"_U32BITARG_", FBS=%"_U32BITARG_"\n", + trackIndex, packetLen, seqNum, timeStamp, RTPTime2MediaTime(trackIndex, timeStamp), + GetBufferingDelay(trackIndex), GetFreeBufferSpace(trackIndex)); + } + + return packetIsDuplicate; + } + + //returns kUInt32_Max if the buffer is empty; otherwise returns the first sequence number in the buffer + UInt32 GetNextSeqNumToDecode(UInt32 trackIndex) + { + SVector &playBuffer = fTrackInfo[trackIndex].fPlayBuffer; + return playBuffer.empty() ? kUInt32_Max : playBuffer.front().fSeqNum; + } + + //in milliseconds; returns kUInt32_Max if the stream is not playing or if the buffer is empty + UInt32 GetPlayoutDelay(UInt32 trackIndex) + { + SVector &playBuffer = fTrackInfo[trackIndex].fPlayBuffer; + if(!fIsPlaying || playBuffer.empty()) + return kUInt32_Max; + else + { + UInt32 nextPacketTime = playBuffer.front().fTimeStamp; + return RTPTime2MediaTime(trackIndex, nextPacketTime) - fCurrentMediaTime; + } + } + + //in milliseconds; returns kUInt32_Max if the buffer is empty + UInt32 GetBufferingDelay(UInt32 trackIndex) + { + SVector &playBuffer = fTrackInfo[trackIndex].fPlayBuffer; + if(playBuffer.empty()) + return kUInt32_Max; + + UInt64 delta = playBuffer.back().fTimeStamp - playBuffer.front().fTimeStamp; + UInt32 bufferDelay = static_cast((delta * 1000) / fTrackInfo[trackIndex].fSamplingRate); + if (fIsPlaying) + bufferDelay += GetPlayoutDelay(trackIndex); + return bufferDelay; + } + + //in bytes + UInt32 GetFreeBufferSpace(UInt32 trackIndex) + { + SVector &playBuffer = fTrackInfo[trackIndex].fPlayBuffer; + UInt32 bytesInBuffer = 0; + for(UInt32 i = 0; i < playBuffer.size(); ++i) + bytesInBuffer += playBuffer[i].fPayloadSize; + Assert(fTrackInfo[trackIndex].fBufferSize >= bytesInBuffer); + return fTrackInfo[trackIndex].fBufferSize - bytesInBuffer; + } + + //The lost packets returned by this function is NOT the same as the lost packets defined by the RTCP RR. + UInt32 GetNumPacketsLost(UInt32 trackIndex) { return fTrackInfo[trackIndex].fLostPackets.size(); } + UInt32 GetNumLatePackets(UInt32 trackIndex) { return fTrackInfo[trackIndex].fNumLatePackets; } + UInt32 GetNumBufferOverflowedPackets(UInt32 trackIndex) { return fTrackInfo[trackIndex].fNumBufferOverflowedPackets; } + UInt32 GetNumDuplicates(UInt32 trackIndex) { return fTrackInfo[trackIndex].fNumDuplicates; } + + private: + struct PacketInfo + { + UInt32 fSeqNum; //kUInt32_Max is invalid sequence number + UInt32 fTimeStamp; + UInt32 fPayloadSize; + + PacketInfo() : fSeqNum(0), fTimeStamp(0), fPayloadSize(0) {} + PacketInfo(UInt32 seqNum, UInt32 timeStamp, UInt32 payloadSize) + : fSeqNum(seqNum), fTimeStamp(timeStamp), fPayloadSize(payloadSize) + {} + }; + struct TrackInfo + { + UInt32 fNumDuplicates; + UInt32 fNumBufferOverflowedPackets; //number of packets lost due to buffer overflow + UInt32 fNumLatePackets; //packet that did not arrive on time, but did arrive + + UInt32 fBufferSize; //in bytes + UInt32 fSamplingRate; //in samples per second + + //These two values have meaning only while the stream is playing. + //All packets with sequence number less than fNextSeqNum have already expired, and all other packets PROBABLY goes into the buffer + UInt32 fRTPTimeStampBase; + UInt32 fNextSeqNum; + + SVector fLostPackets; //All the packets with seq number less than fNextSeqNum that has not yet arrived. + SVector fPlayBuffer; //Can contain only packets with seq number >= than fNextSeqNum + + TrackInfo() : fNumDuplicates(0), fNumBufferOverflowedPackets(0), fNumLatePackets(0), fBufferSize(0), fSamplingRate(0), + fRTPTimeStampBase(0), fNextSeqNum(0) + {} + }; + + //Advances the curent media play time according to the wallclock time; remove packets in the buffer whose timestamp has expired, + //increases fNextSeqNum, and find out which packet is missing and put them on the lost packts list. + //Also updates the current media play time -- all packets with an earlier timestamp has expired, and all packets with a later timestamp + //must be in the buffer or is not received yet. + void AdvanceTime() + { + fCurrentMediaTime = LocalTime2MediaTime(OS::Milliseconds()); + for(UInt32 trackIndex = 0; trackIndex < fTrackInfo.size(); ++trackIndex) + { + TrackInfo &trackInfo = fTrackInfo[trackIndex]; + SVector &playBuffer = trackInfo.fPlayBuffer; + UInt32 prevTimeStamp = 0; + + //go through the play buffer until a packet with a later timestamp than the current time is found + UInt32 i = 0; + for(; i < playBuffer.size(); ++i) + { + if(prevTimeStamp > playBuffer[i].fTimeStamp) + { if(fVerboseLevel >= 2) //this can happen from out of order packets from udp network routing or retransmits from rudp. It should never happen over tcp or http. + qtss_printf("WARNING: RTP timestamp is not monotonic! seq=%"_U32BITARG_" timestamp=%"_U32BITARG_"\n", playBuffer[i].fSeqNum, playBuffer[i].fTimeStamp); + } + else + prevTimeStamp = playBuffer[i].fTimeStamp; + + if (fCurrentMediaTime >= RTPTime2MediaTime(trackIndex, playBuffer[i].fTimeStamp)) + { + //the packet has expired; add packets that has not yet arrived to the lost packet list + while(trackInfo.fNextSeqNum != playBuffer[i].fSeqNum) + { + trackInfo.fLostPackets.push_back(trackInfo.fNextSeqNum); + trackInfo.fNextSeqNum++; + } + trackInfo.fNextSeqNum++; + } + else + break; + } + //remove packets that have expired + playBuffer.erase(0, i); + } + } + + //can be called only when the media is playing; media time is measured in milliseconds + UInt32 LocalTime2MediaTime(SInt64 localTime) + { + SInt64 delta = localTime - fLocalStartPlayTime; + Assert(fIsPlaying && (SInt64) 0 <= (SInt64) delta && delta <= (SInt64)kUInt32_Max); + return delta; + } + + UInt32 RTPTime2MediaTime(UInt32 trackIndex, UInt32 RTPTimestamp) + { + TrackInfo &trackInfo = fTrackInfo[trackIndex]; + UInt32 RTPTimestampBase = RTPTimestamp; + if(fIsPlaying) + RTPTimestampBase = trackInfo.fRTPTimeStampBase; + else if (!trackInfo.fPlayBuffer.empty()) + RTPTimestampBase = trackInfo.fPlayBuffer.front().fTimeStamp; + UInt64 delta = RTPTimestamp - RTPTimestampBase; + return static_cast((delta*1000) / trackInfo.fSamplingRate); + } + + SVector fTrackInfo; + UInt32 fTargetBufferingDelay; //in milliseconds + UInt32 fStartPlayDelay; //in milliseconds + SInt64 fLocalStartPlayTime; //in UNIX time(milliseconds); + UInt32 fCurrentMediaTime; //current media time(in millisecond); valid only while playing + UInt32 fVerboseLevel; + Bool16 fIsPlaying; +}; + +#endif //_PLAYERSIMULATOR_H_ diff --git a/RTSPClientLib/RTPPacket.h b/RTSPClientLib/RTPPacket.h new file mode 100644 index 0000000..7dfff1b --- /dev/null +++ b/RTSPClientLib/RTPPacket.h @@ -0,0 +1,113 @@ +/* + * + * @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: RTPPacket.h + + Some useful things for parsing RTPPackets. +*/ +#ifndef _RTPPACKET_H_ +#define _RTPPACKET_H_ + +#include "arpa/inet.h" +#include "OSHeaders.h" +#include "StrPtrLen.h" + +class RTPPacket +{ + public: + enum { + RTP_VERSION = 2, + + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204 + }; + + /* + * RTP data header + */ + struct RTPHeader { + #if 0 + //rtp header + unsigned int version:2; /* protocol version */ + unsigned int p:1; /* padding flag */ + unsigned int x:1; /* header extension flag */ + unsigned int cc:4; /* CSRC count */ + unsigned int m:1; /* marker bit */ + unsigned int pt:7; /* payload type */ + #endif + UInt16 rtpheader; + UInt16 seq; /* sequence number */ + UInt32 ts; /* timestamp */ + UInt32 ssrc; /* synchronization source */ + //UInt32 csrc[1]; /* optional CSRC list */ + }; + + RTPPacket(StrPtrLen inPacket) + : fPacket(reinterpret_cast(inPacket.Ptr)), fLen(inPacket.Len) + {} + RTPPacket(char *inPacket = NULL, UInt32 inLen = NULL) + : fPacket(reinterpret_cast(inPacket)), fLen(inLen) + {} + + UInt8 GetPayloadType() const { return ntohs(fPacket->rtpheader) & 0x007F; } + UInt8 GetCSRCCount() const { return (ntohs(fPacket->rtpheader) & 0x0F00 ) >> 8; } + + //The following get functions will convert from network byte order to host byte order. + //Conversely the set functions will convert from host byte order to network byte order. + UInt16 GetSeqNum() const { return ntohs(fPacket->seq); } + void SetSeqNum(UInt16 seqNum) { fPacket->seq = htons(seqNum); } + + UInt32 GetTimeStamp() const { return ntohl(fPacket->ts); } + void SetTimeStamp(UInt32 timeStamp) { fPacket->ts = htonl(timeStamp); } + + UInt32 GetSSRC() const { return ntohl(fPacket->ssrc); } + void SetSSRC(UInt32 SSRC) { fPacket->ssrc = htonl(SSRC); } + + //Includes the variable CSRC portion + UInt32 GetHeaderLen() const { return sizeof(RTPHeader) + GetCSRCCount() * 4; } + + StrPtrLen GetBody() const { return StrPtrLen(reinterpret_cast(fPacket) + GetHeaderLen(), fLen - GetHeaderLen()); } + + //Returns true if the header is not bad; do some very basic checking + Bool16 HeaderIsValid() const + { + Assert(sizeof(RTPHeader) == 12); + if (fLen < sizeof(RTPHeader)) + return false; + if ( ( ntohs(fPacket->rtpheader) >> 14) != RTP_VERSION ) + return false; + if (GetHeaderLen() > fLen) + return false; + return true; + } + + RTPHeader * fPacket; + UInt32 fLen; //total length of the packet, including the header +}; + +#endif diff --git a/RTSPClientLib/RTSPClient.cpp b/RTSPClientLib/RTSPClient.cpp new file mode 100644 index 0000000..d3e51bc --- /dev/null +++ b/RTSPClientLib/RTSPClient.cpp @@ -0,0 +1,1977 @@ +/* + * + * @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: RTSPClient.cpp + + Contains: . + + +*/ + +#ifndef __Win32__ +#include +#include +#include +#include +#include + +#endif + + +#include "RTSPClient.h" +#include "StringParser.h" +#include "OSMemory.h" +#include "OSHeaders.h" +#include "OSArrayObjectDeleter.h" + +#include + +#define ENABLE_AUTHENTICATION 1 + +// STRTOCHAR is used in verbose mode and for debugging +static char temp[2048]; +static char * STRTOCHAR(StrPtrLen *theStr) +{ + temp[0] = 0; + UInt32 len = theStr->Len < 2047 ? theStr->Len : 2047; + if (theStr->Len > 0 || NULL != theStr->Ptr) + { memcpy(temp,theStr->Ptr,len); + temp[len] = 0; + } + else + strcpy(temp,"Empty Ptr or len is 0"); + return temp; +} + +//======== includes for authentication ====== +#include "base64.h" +#include "md5digest.h" +#include "OS.h" +//=========================================== +static UInt8 sWhiteQuoteOrEOLorEqual[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 // \t is a stop + 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, //30-39 ' ' , '"' is a stop + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, //40-49 ',' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, //60-69 '=' is a stop + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; +static UInt8 sNOTWhiteQuoteOrEOLorEqual[] = // don't stop +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, //0-9 // on '\t' + 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, //10-19 // '\r', '\n' + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //20-29 + 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, //30-39 // ' ' + 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, //40-49 // ',' + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //50-59 + 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, //60-69 // '=' + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //70-79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //80-89 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //90-99 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //100-109 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //110-119 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //120-129 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //130-139 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //140-149 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //150-159 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //160-169 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //170-179 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //180-189 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //190-199 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //200-209 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //210-219 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //220-229 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //230-239 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, //240-249 + 1, 1, 1, 1, 1, 1 //250-255 +}; + +StrPtrLen Authenticator::sAuthorizationStr("Authorization:"); +StrPtrLen Authenticator::sAuthBasicStr("Basic"); +StrPtrLen Authenticator::sAuthDigestStr("Digest"); +StrPtrLen Authenticator::sUsernameStr("username"); +StrPtrLen Authenticator::sRealmStr("realm"); +StrPtrLen Authenticator::sWildCardMatch("*"); +StrPtrLen Authenticator::sTrue("true"); +StrPtrLen Authenticator::sFalse("false"); + +Authenticator::Authenticator() +{ + char *emptyBuff = ""; + StrPtrLen emptySPL(emptyBuff); + this->SetName(&emptySPL); + this->SetPassword(&emptySPL); + this->SetRealm(&emptySPL); + +} + +void Authenticator::Clean() +{ + delete [] fAuthBuffer.Ptr; fAuthBuffer.Set(NULL,0); + delete [] fNameSPL.Ptr; fNameSPL.Set(NULL,0); + delete [] fPasswordSPL.Ptr; fPasswordSPL.Set(NULL,0); + delete [] fRealmSPL.Ptr;fRealmSPL.Set(NULL,0); + delete [] fMethodSPL.Ptr; fMethodSPL.Set(NULL,0); + delete [] fURISPL.Ptr ; fURISPL.Set(NULL); +} + + +Bool16 Authenticator::ParseRealm(StringParser *realmParserPtr) +{ + StrPtrLen authRealmTag(""); + Bool16 result = false; + this->ParseTag(realmParserPtr,&authRealmTag); + if (authRealmTag.EqualIgnoreCase(Authenticator::sRealmStr.Ptr, Authenticator::sRealmStr.Len)) + { + result = this->GetParamValueAsNewCopy(realmParserPtr, &this->fRealmSPL); + } + + return result; +} + +void Authenticator::SetName(StrPtrLen *inNamePtr) +{ this->CopyParam(inNamePtr, &this->fNameSPL); + } + +void Authenticator::SetPassword(StrPtrLen *inPasswordPtr) +{ this->CopyParam(inPasswordPtr, &fPasswordSPL); +} + +void Authenticator::SetMethod(StrPtrLen *inMethodStr) +{ this->CopyParam(inMethodStr, &fMethodSPL); +} + +void Authenticator::SetRealm(StrPtrLen *inRealmPtr) +{ this->CopyParam(inRealmPtr, &fRealmSPL); +} + +void Authenticator::SetURI(StrPtrLen *inURIPtr) +{ // always send absolute path. + Assert(inURIPtr); + UInt16 uriLen = (UInt16) (inURIPtr->Len + 2); + + delete [] fURISPL.Ptr; + fURISPL.Ptr = NEW char[uriLen]; + fURISPL.Len = inURIPtr->Len; + memset(fURISPL.Ptr, 0, uriLen); + char *destinationPtr = fURISPL.Ptr; + if (*inURIPtr->Ptr != '/') + { *destinationPtr = '/'; + destinationPtr ++; + fURISPL.Len++; + } + memcpy(destinationPtr, inURIPtr->Ptr,inURIPtr->Len); + +} + + +void Authenticator::ParseTag(StringParser *parserPtr,StrPtrLen *outTagPtr) +{ + Assert(parserPtr); + Assert(outTagPtr); + outTagPtr->Ptr = NULL; + outTagPtr->Len = 0; + + parserPtr->ConsumeUntil(NULL,sNOTWhiteQuoteOrEOLorEqual); + parserPtr->ConsumeUntil(outTagPtr, sWhiteQuoteOrEOLorEqual); // stop on whitespace " or = + + //qtss_printf("Authenticator::ParseTag =%s\n",STRTOCHAR(outTagPtr)); +} + +Bool16 Authenticator::CopyParam(StrPtrLen *inPtr, StrPtrLen *destPtr) +{ + Assert(inPtr); + Assert(destPtr); + + delete [] destPtr->Ptr; destPtr->Set(NULL,0); + destPtr->Ptr = NEW char[inPtr->Len + 1]; + + if (destPtr->Ptr == NULL) + { destPtr->Len = 0; + return false; + } + + destPtr->Ptr[inPtr->Len] = 0; + destPtr->Len = inPtr->Len; + if (destPtr->Len > 0) + { ::memcpy(destPtr->Ptr,inPtr->Ptr,inPtr->Len); + } + return true; +} + + +Bool16 Authenticator::GetParamValue(StringParser *valueSourcePtr, StrPtrLen *outParamValuePtr) +{ + Assert(valueSourcePtr); + Assert(outParamValuePtr); + Assert(outParamValuePtr->Ptr == NULL); + StrPtrLen temp; + outParamValuePtr->Set(NULL,0); + + { char temp[1024]; + memcpy(temp,valueSourcePtr->GetCurrentPosition(),valueSourcePtr->GetDataRemaining()); + temp[valueSourcePtr->GetDataRemaining()] = 0; + } + + valueSourcePtr->ConsumeUntil(&temp, sNOTWhiteQuoteOrEOLorEqual); + if ( (temp.Len > 0) && ( '"' == temp.Ptr[temp.Len -1] ) )// if quote read to next quote or end + { + valueSourcePtr->ConsumeUntil(outParamValuePtr, '"'); + //qtss_printf("Found quoted value=%s\n",STRTOCHAR(outParamValuePtr)); + } + else // get just the non-whitespace or EOL + { + //qtss_printf("Authenticator::GetParamValue No quotes\n"); + valueSourcePtr->ConsumeWhitespace(); + valueSourcePtr->ConsumeUntilWhitespace(outParamValuePtr); + } + + //qtss_printf("Authenticator::GetParamValue len = %"_U32BITARG_" =%s\n",outParamValuePtr->Len, STRTOCHAR(outParamValuePtr)); + + return true; +} +Bool16 Authenticator::GetParamValueAsNewCopy(StringParser *valueSourcePtr, StrPtrLen *outParamValueCopyPtr) +{ + StrPtrLen theParamValue; + if (!this->GetParamValue(valueSourcePtr, &theParamValue)) + return false; + + return this->CopyParam(&theParamValue, outParamValueCopyPtr); +} + +Bool16 Authenticator::GetMatchListParamValueAsNewCopy(StringParser *valueSourcePtr, StrPtrLen *inMatchListParamValuePtr, SInt16 numToMatch, StrPtrLen *outParamValueCopyPtr) +{ + StrPtrLen theParamValue; + if (!this->GetParamValue(valueSourcePtr, &theParamValue)) + return false; + + if(inMatchListParamValuePtr && numToMatch > 0) + { StringParser paramList(&theParamValue); + StrPtrLen theListParamValue; + while (this->GetParamValue(¶mList, &theListParamValue)) + { for (SInt16 count = 0;count < numToMatch; count++) + { + if ( theListParamValue.Equal(inMatchListParamValuePtr->Ptr) + || sWildCardMatch.Equal (inMatchListParamValuePtr->Ptr) + ) + { return this->CopyParam(&theListParamValue, outParamValueCopyPtr); + } + inMatchListParamValuePtr++; + } + } + return false; + } + return this->CopyParam(&theParamValue, outParamValueCopyPtr); +} + +void Authenticator::ResetRequestLen(StrPtrLen *theRequestPtr, StrPtrLen *theParamsPtr) +{ // makes a new buffer and copies everything after first \r\n into the buffer and terminates the req. + // after the \r\n + + static const char *requestParamStart = "\r\n"; + Assert (theRequestPtr); + Assert (theRequestPtr->Ptr); + Assert (theRequestPtr->Len > ::strlen(requestParamStart) ); + + Assert (theParamsPtr); + Assert (theParamsPtr->Ptr == NULL); + Assert (theParamsPtr->Len == 0); + + char *theLastChar = ::strstr(theRequestPtr->Ptr, requestParamStart); + if (theLastChar) + { StrPtrLen tempParams; + tempParams.Ptr = &theLastChar[2]; + tempParams.Len = ::strlen(&theLastChar[2]); + CopyParam(&tempParams, theParamsPtr); + theLastChar[2] = 0; // terminate after \r\n + } + +} + + +//char * Authenticator::GetRequestHeader( StrPtrLen *inSourceStr, StrPtrLen *searchHeaderStr,StrPtrLen *outHeaderStr) +char * Authenticator::GetRequestHeader( StrPtrLen *inSourceStr, StrPtrLen *searchHeaderStr) +{ + StrPtrLen headers; + StrPtrLen headersTerminator("\r\n\r\n"); + char *endSourceCharPtr = inSourceStr->FindString(&headersTerminator); + if (endSourceCharPtr == NULL) + return NULL; + + //qtss_printf("Authenticator::GetRequestHeader source=%s\n find=|%s|\n", inSourceStr->Ptr, headersTerminator.Ptr); + headers.Set(inSourceStr->Ptr,endSourceCharPtr - inSourceStr->Ptr); + + return headers.FindStringIgnoreCase(searchHeaderStr); +} + +void Authenticator::RemoveAuthLine(StrPtrLen *theRequestPtr) +{ + Assert( theRequestPtr); + Assert( theRequestPtr->Ptr); + Assert( theRequestPtr->Len == ::strlen(theRequestPtr->Ptr) ); + + if (theRequestPtr->Ptr != NULL) + { + char *theHeaderStart = GetRequestHeader(theRequestPtr, &Authenticator::sAuthorizationStr); + char *eol = StrPtrLen(theHeaderStart).FindString("\r\n"); + + // finally remove the Authorization: line + if (theHeaderStart && eol) + { strcpy(theHeaderStart,eol + 2); + } + } + +} + + +//=========================================== + +// request tags +StrPtrLen DigestAuth::sResponseStr("response"); //the response +StrPtrLen DigestAuth::sUriStr("uri"); // copy of of the URL must +StrPtrLen DigestAuth::sCnonceStr("cnonce"); // in request only if qop is in response + +// response tags +StrPtrLen DigestAuth::sStaleStr("stale"); + +// request and response tags +StrPtrLen DigestAuth::sQopStr("qop"); // quoted string list of one or more options -- in request if in response +StrPtrLen DigestAuth::sNonceStr("nonce"); // quoted string return back to the server in the request +StrPtrLen DigestAuth::sNonceCountStr("nc"); // in request only if qop is in response +StrPtrLen DigestAuth::sOpaqueStr("opaque");// quoted string return back to the server in the request +StrPtrLen DigestAuth::sDomainStr("domain"); // quoted string list of one or more URLs on in response +StrPtrLen DigestAuth::sAlgorithmStr("algorithm"); // quoted string list of one or more URLs on in response + +// response values +StrPtrLen DigestAuth::sQopAuthStr("auth"); +StrPtrLen DigestAuth::sQopAuthIntStr("auth-int"); +StrPtrLen DigestAuth::sMD5Str("MD5"); +StrPtrLen DigestAuth::sMD5SessStr("MD5-sess"); + + +DigestAuth::DigestAuth() +{ + ReqFieldsClean(); + fAlgorithm = 0; + fStale = false; + fNonceCount = 0; + +} +Bool16 DigestAuth::ParseParams(StrPtrLen *authParamsPtr) +{ + Bool16 result = false; + StrPtrLen authTag(""); + + if (authParamsPtr != NULL && authParamsPtr->Ptr != NULL) do + { fNonceCount = 0; + + //qtss_printf("DigestAuth::ParseParams authParams=%s\n",STRTOCHAR(authParamsPtr)); + + StringParser paramParser(authParamsPtr); + if (!this->ParseRealm(¶mParser)) + break; + + while (paramParser.GetDataRemaining() > 0) + { + this->ParseTag(¶mParser,&authTag); + + //qtss_printf("parsed tag = %s\n",STRTOCHAR(&authTag)); + + if (authTag.EqualIgnoreCase(this->sNonceStr.Ptr, this->sNonceStr.Len)) + { // NONCE in Response + result = this->GetParamValueAsNewCopy(¶mParser, &this->fnonce); + if (!result) + { + break; + } + else continue; + } + + if (authTag.EqualIgnoreCase(this->sStaleStr.Ptr, this->sStaleStr.Len)) + { // STALE in Response + result = this->GetParamValueAsNewCopy(¶mParser, &this->fStaleStr); + if (!result) + { + break; + } + else + { + if (this->fStaleStr.EqualIgnoreCase(Authenticator::sTrue.Ptr, Authenticator::sTrue.Len)) + { fStale = true; + } + else + { fStale = false; + } + continue; + } + } + + if (authTag.EqualIgnoreCase(this->sOpaqueStr.Ptr, this->sOpaqueStr.Len)) + { // OPAQUE in Response + result = this->GetParamValueAsNewCopy(¶mParser, &this->fopaque); + if (!result) + { + break; + } + else continue; + } + + if (authTag.EqualIgnoreCase(this->sQopStr.Ptr, this->sQopStr.Len)) + { // QOP in Response + StrPtrLen matchList[2]; + matchList[0].Set(sQopAuthStr.Ptr,sQopAuthStr.Len); + matchList[1].Set(sQopAuthIntStr.Ptr,sQopAuthIntStr.Len); + result = Authenticator::GetMatchListParamValueAsNewCopy(¶mParser, matchList, 2, &this->fqop); + if (!result) + { + break; + } + else continue; + } + + if (authTag.EqualIgnoreCase(this->sAlgorithmStr.Ptr, this->sAlgorithmStr.Len)) + { // ALGORITHM in Response + // This is for completeness we only do MD5 + result = this->GetParamValueAsNewCopy(¶mParser, &this->fAlgorithmStr); + if (!result) + { + break; + } + else continue; + } + + if (authTag.EqualIgnoreCase(this->sDomainStr.Ptr, this->sDomainStr.Len)) + { // DOMAIN in Response + // just get the first URL in the domain list + result = Authenticator::GetMatchListParamValueAsNewCopy(¶mParser, &Authenticator::sWildCardMatch, 1, &this->fqop); + if (!result) + { + break; + } + else continue; + } + } + + } while (false); + + return result; +} + +void DigestAuth::AddAuthParam(StrPtrLen *theTagPtr, StrPtrLen *theValuePtr, Bool16 quoted) +{ + Assert(fReqFields.fNumFields < kMaxReqParams); + Assert(theTagPtr); // must be valid + if (fReqFields.fNumFields < kMaxReqParams && theValuePtr && theValuePtr->Ptr && theValuePtr->Len) + { + fReqFields.fNumFields ++; + fReqFields.fReqParamTags[fReqFields.fNumFields -1] = theTagPtr; + fReqFields.fReqParamValues[fReqFields.fNumFields -1] = theValuePtr; + fReqFields.fQuoted[fReqFields.fNumFields -1] = quoted; + //qtss_printf("DigestAuth::AddAuthParam [%"_U32BITARG_"] tag=%s ",fReqFields.fNumFields -1, STRTOCHAR(theTagPtr)); + //qtss_printf("value=%s \n",STRTOCHAR(theValuePtr)); + } + else + { + //qtss_printf("DigestAuth::AddAuthParam ignored [%"_U32BITARG_"] tag=%s value=%s \n",fReqFields.fNumFields -1, STRTOCHAR(theTagPtr),STRTOCHAR(theValuePtr)); + } + +} + +void DigestAuth::SetNonceCountStr() +{ + char tempBuff[32]; + qtss_sprintf(tempBuff,"%08x",fNonceCount); // return hex string value + StrPtrLen tempCountStr(tempBuff); + CopyParam(&tempCountStr, &fNonceCountStr); +} + +void DigestAuth::GenerateAuthorizationRequestLine(StrPtrLen *requestPtr) +{ + + static const UInt32 ktempbuffSize = 1024; + char tempBuffer[ktempbuffSize]; + UInt32 buffsize = this->ParamsLen(requestPtr); + delete [] fAuthBuffer.Ptr; + fAuthBuffer.Ptr = NEW char[buffsize]; + memset(fAuthBuffer.Ptr,0,buffsize); + fAuthBuffer.Len = buffsize; + + qtss_sprintf(fAuthBuffer.Ptr, "%s %s ",sAuthorizationStr.Ptr, sAuthDigestStr.Ptr); + + //qtss_printf ("DigestAuth::GenerateAuthorizationRequestLine buffsize = %"_U32BITARG_" fAuthBuffer= %s\n",buffsize, fAuthBuffer.Ptr); + + StrPtrLen *theTagPtr; + StrPtrLen *theValuePtr; + UInt32 paramLen; + Bool16 quoted; + for (SInt32 paramIndex = 0; paramIndex < fReqFields.fNumFields; paramIndex++) + { + paramLen = 0; + theTagPtr = fReqFields.fReqParamTags[paramIndex]; + theValuePtr = fReqFields.fReqParamValues[paramIndex]; + Assert(theTagPtr); // shouldn't ever happen + + paramLen += theTagPtr->Len; + quoted = fReqFields.fQuoted[paramIndex]; + if (theValuePtr) // this can be NULL + paramLen += theValuePtr->Len + 5; + + Assert(paramLen < ktempbuffSize); + if (quoted) + { if(theValuePtr && theValuePtr->Len > 0) + qtss_sprintf(tempBuffer, "%s=\"%s\"",theTagPtr->Ptr, theValuePtr->Ptr); + else // empty send: param="" + qtss_sprintf(tempBuffer, "%s=\"\"",theTagPtr->Ptr); + } + else + { if(theValuePtr && theValuePtr->Len > 0) + qtss_sprintf(tempBuffer, "%s=%s",theTagPtr->Ptr, theValuePtr->Ptr); + else // empty send: param="" + qtss_sprintf(tempBuffer, "%s=\"\"",theTagPtr->Ptr); + } + //qtss_printf("add %s to auth line\n",tempBuffer); + strcat(fAuthBuffer.Ptr,tempBuffer); + if (paramIndex < fReqFields.fNumFields -1) + strcat(fAuthBuffer.Ptr,","); + + //qtss_printf("DigestAuth::GenerateAuthorizationRequestLine bufferLen=%"_S32BITARG_" fAuthBuffer= %s\n",::strlen(fAuthBuffer.Ptr),fAuthBuffer.Ptr); + } + + ::strcat(fAuthBuffer.Ptr, "\r\n"); + + //qtss_printf("DigestAuth::GenerateAuthorizationRequestLine bufferLen=%"_S32BITARG_" fAuthBuffer= %s\n",::strlen(fAuthBuffer.Ptr),fAuthBuffer.Ptr); +} + +void DigestAuth::ResetAuthParams() +{ + ReqFieldsClean(); + delete [] fAuthBuffer.Ptr; fAuthBuffer.Set(NULL,0); + +} + +void DigestAuth::MakeRequestDigest() +{ + delete [] fRequestDigestStr.Ptr; + fRequestDigestStr.Ptr = NULL; + StrPtrLen emptyStr; + StrPtrLen hA1; + + /* + qtss_printf("DigestAuth::MakeRequestDigest fNameSPL=%s\n",STRTOCHAR(&fNameSPL)); + qtss_printf("DigestAuth::MakeRequestDigest fRealmSPL=%s\n",STRTOCHAR(&fRealmSPL)); + qtss_printf("DigestAuth::MakeRequestDigest fPasswordSPL=%s\n",STRTOCHAR(&fPasswordSPL)); + qtss_printf("DigestAuth::MakeRequestDigest fnonce=%s\n",STRTOCHAR(&fnonce)); + qtss_printf("DigestAuth::MakeRequestDigest fcnonce=%s\n",STRTOCHAR(&fcnonce)); + */ + + ::CalcHA1( &sMD5Str, &fNameSPL, &fRealmSPL, &fPasswordSPL, &fnonce, &fcnonce, &hA1); + + /* + qtss_printf("DigestAuth::MakeRequestDigest CalcHA1=%s\n",STRTOCHAR(&hA1)); + qtss_printf("DigestAuth::MakeRequestDigest fnonce=%s\n",STRTOCHAR(&fnonce)); + qtss_printf("DigestAuth::MakeRequestDigest fNonceCountStr=%s\n",STRTOCHAR(&fNonceCountStr)); + qtss_printf("DigestAuth::MakeRequestDigest fcnonce=%s\n",STRTOCHAR(&fcnonce)); + qtss_printf("DigestAuth::MakeRequestDigest fqop=%s\n",STRTOCHAR(&fqop)); + qtss_printf("DigestAuth::MakeRequestDigest fMethodSPL=%s\n",STRTOCHAR(&fMethodSPL)); + qtss_printf("DigestAuth::MakeRequestDigest fURISPL=%s\n",STRTOCHAR(&fURISPL)); + qtss_printf("DigestAuth::MakeRequestDigest emptyStr=%s\n",STRTOCHAR(&emptyStr)); + qtss_printf("DigestAuth::MakeRequestDigest fMethodSPL=%s\n",STRTOCHAR(&emptyStr)); + */ + + ::CalcRequestDigest(&hA1, &fnonce, &fNonceCountStr, &fcnonce, &fqop, &fMethodSPL, &fURISPL, &emptyStr, &fRequestDigestStr); + delete [] hA1.Ptr; + //qtss_printf("DigestAuth::MakeRequestDigest fRequestDigestStr=%s\n",STRTOCHAR(&fRequestDigestStr)); +} + +void DigestAuth::MakeCNonce() +{ + fAuthTime = OS::UnixTime_Secs(); + + char timeStr[64]; + qtss_sprintf(timeStr,"%"_U32BITARG_"",(UInt32) fAuthTime); + StrPtrLen timeSPL(timeStr); + + delete [] fcnonce.Ptr; + fcnonce.Ptr = NULL; + StrPtrLen emptyStr; + StrPtrLen hA1; + ::CalcHA1( &sMD5Str, &timeSPL, &fRealmSPL, &fPasswordSPL, &fnonce, &timeSPL, &hA1); + ::CalcRequestDigest(&hA1, &fnonce, &timeSPL, &timeSPL, &fqop, &timeSPL, &fURISPL, &emptyStr, &fcnonce); + delete [] hA1.Ptr; +} + +void DigestAuth::AttachAuthParams(StrPtrLen *theRequestPtr) +{ + StrPtrLen requestParams; + + //qtss_printf(" DigestAuth::AttachAuthParams request IN =%s\n", STRTOCHAR(theRequestPtr)); + + char* hasAuthorization = ::strstr(theRequestPtr->Ptr, sAuthorizationStr.Ptr); + if (NULL != hasAuthorization) + return; + + this->ResetRequestLen(theRequestPtr,&requestParams); + this->ResetAuthParams(); + + fNonceCount ++; + AddAuthParam( &sUsernameStr, &fNameSPL, true); + AddAuthParam( &sRealmStr, &fRealmSPL, true); + AddAuthParam( &sNonceStr, &fnonce, true); + AddAuthParam( &sOpaqueStr, &fopaque, true); + AddAuthParam( &sUriStr, &fURISPL, true); + + if (fqop.Ptr != NULL) // + { + AddAuthParam( &sQopStr, &sQopAuthStr, false);// only auth + SetNonceCountStr(); + AddAuthParam( &sNonceCountStr, &fNonceCountStr, false);// only auth + MakeCNonce(); + AddAuthParam(&sCnonceStr, &fcnonce, true); + } + + MakeRequestDigest(); + AddAuthParam (&sResponseStr, &fRequestDigestStr, true); + GenerateAuthorizationRequestLine(theRequestPtr); + ::strcat(theRequestPtr->Ptr, fAuthBuffer.Ptr); + ::strcat(theRequestPtr->Ptr, requestParams.Ptr); // put the request params back + theRequestPtr->Len = ::strlen(theRequestPtr->Ptr); + delete [] requestParams.Ptr; requestParams.Set(NULL,0); + //qtss_printf(" DigestAuth::AttachAuthParams request OUT =%s\n", STRTOCHAR(theRequestPtr)); +} + +UInt32 DigestAuth::ParamsLen(StrPtrLen *requestPtr) +{ UInt32 fieldLens = requestPtr->Len; + StrPtrLen *theParamPtr = NULL; + StrPtrLen *theTagPtr = NULL; + SInt32 numParams = 0; + + while (numParams != fReqFields.fNumFields ) + { + theParamPtr = fReqFields.fReqParamValues[numParams]; + theTagPtr = fReqFields.fReqParamTags[numParams]; + if (theParamPtr != NULL) + { fieldLens += theParamPtr->Len; + } + if (theTagPtr != NULL) + { fieldLens += theTagPtr->Len; + } + fieldLens += 5;// room for spaces or " = and , + numParams ++; + } + + return fieldLens; +} + +DigestAuth::~DigestAuth() +{ ResetAuthParams(); + delete [] fNonceCountStr.Ptr; fNonceCountStr.Set(NULL,0); + delete [] fRequestDigestStr.Ptr;fRequestDigestStr.Set(NULL,0); + delete [] fURIStr.Ptr; fURIStr.Set(NULL,0); + delete [] fcnonce.Ptr; fcnonce.Set(NULL,0); + delete [] fnonce.Ptr; fnonce.Set(NULL,0); + delete [] fopaque.Ptr; fopaque.Set(NULL,0); + delete [] fqop.Ptr; fqop.Set(NULL,0); + delete [] fAlgorithmStr.Ptr; fAlgorithmStr.Set(NULL,0); + delete [] fStaleStr.Ptr; fStaleStr.Set(NULL,0); + Clean(); +} + +//=========================================== + +Bool16 BasicAuth::ParseParams(StrPtrLen *authParamsPtr) +{ + StringParser realmParser(authParamsPtr); + return this->ParseRealm(&realmParser); +} + +UInt32 BasicAuth::ParamsLen(StrPtrLen *requestPtr) +{ + return requestPtr->Len + fNameSPL.Len + fPasswordSPL.Len + 1; +} + +void BasicAuth::AttachAuthParams(StrPtrLen *theRequestPtr) +{ + char* hasAuthorization = ::strstr(theRequestPtr->Ptr, sAuthorizationStr.Ptr); + if (NULL != hasAuthorization) + return; + + UInt32 buffLen = ParamsLen(theRequestPtr); + if (fAuthBuffer.Ptr == NULL) + { fAuthBuffer.Ptr = NEW char[buffLen]; + memset(fAuthBuffer.Ptr,0,buffLen); + fAuthBuffer.Len = buffLen; + } + StrPtrLen requestParams; + this->ResetRequestLen(theRequestPtr,&requestParams); + //qtss_printf("BasicAuth::parsed requestParams.Ptr = %s \n",requestParams.Ptr); + + char unEncodedBuffer[80]; + qtss_sprintf(unEncodedBuffer, "%s:%s",fNameSPL.Ptr, fPasswordSPL.Ptr); + //qtss_printf("unEncodedBuffer=%s\n",unEncodedBuffer); + ::Base64encode(fEncodedBuffer, unEncodedBuffer,::strlen(unEncodedBuffer)); + qtss_sprintf(fAuthBuffer.Ptr, "%s %s %s\r\n",sAuthorizationStr.Ptr, sAuthBasicStr.Ptr, fEncodedBuffer); + ::strcat(theRequestPtr->Ptr, fAuthBuffer.Ptr); + ::strcat(theRequestPtr->Ptr, requestParams.Ptr); // put the request back + delete [] requestParams.Ptr; + theRequestPtr->Len = ::strlen(theRequestPtr->Ptr); + + //qtss_printf("BasicAuth::theRequestPtr->Ptr = %s \n",theRequestPtr->Ptr); +} + +//=========================================== + + +Authenticator *AuthParser::ParseChallenge(StrPtrLen *challengePtr) +{ + Bool16 result = false; + Authenticator *authenticator = NULL; + StrPtrLen theChallenge; + StrPtrLen headerTerminator("\r"); + + if (NULL == challengePtr) + return NULL; + + StringParser authParser(challengePtr); + StrPtrLen authWord; + StrPtrLen authParams; + + // consume WWW-Authenticate + authParser.ConsumeUntilWhitespace(&authWord); + authParser.ConsumeWhitespace(); + + // Get the authentication type + authParser.ConsumeUntilWhitespace(&authWord); + authParser.ConsumeWhitespace(); + + // Get the params + authParser.GetThruEOL(&authParams); + if (authWord.EqualIgnoreCase(Authenticator::sAuthBasicStr.Ptr, Authenticator::sAuthBasicStr.Len)) + { + + authenticator = NEW BasicAuth(); + Assert(authenticator); + if (authenticator) + result = authenticator->ParseParams(&authParams); + } + else if (authWord.EqualIgnoreCase(Authenticator::sAuthDigestStr.Ptr, Authenticator::sAuthDigestStr.Len)) + { + authenticator = NEW DigestAuth(); + Assert(authenticator); + if (authenticator) + result = authenticator->ParseParams(&authParams); + } + + return authenticator; +} + +//=========================================== + + + +static char* sEmptyString = ""; +char* RTSPClient::sUserAgent = "None"; +char* RTSPClient::sControlID = "trackID"; + +RTSPClient::InterleavedParams RTSPClient::sInterleavedParams; + +RTSPClient::RTSPClient(ClientSocket* inSocket, Bool16 verbosePrinting, char* inUserAgent) +: fAuthenticator(NULL), + fSocket(inSocket), + fVerboseLevel(verbosePrinting ? 1 : 0), + fCSeq(1), + fStatus(0), + fSessionID(sEmptyString), + fServerPort(0), + fContentLength(0), + fSetupHeaders(NULL), + fNumChannelElements(kMinNumChannelElements), + fNumFieldIDElements(0), + fFieldIDMapSize(kMinNumChannelElements), + fPacketBuffer(NULL), + fPacketBufferOffset(0), + fPacketOutstanding(false), + fRecvContentBuffer(NULL), + fContentRecvLen(0), + fHeaderRecvLen(0), + fHeaderLen(0), + fSetupTrackID(0), + fState(kInitial), + fAuthAttempted(false), + fTransportMode(kPlayMode), + fPacketDataInHeaderBufferLen(0), + fPacketDataInHeaderBuffer(NULL), + fUserAgent(NULL), + fControlID(RTSPClient::sControlID), + fGuarenteedBitRate(0), + fMaxBitRate(0), + fMaxTransferDelay(0), + fBandwidth(0), + fBufferSpace(0), + fDelayTime(0) +{ + fChannelTrackMap = NEW ChannelMapElem[kMinNumChannelElements]; + ::memset(fChannelTrackMap, 0, sizeof(ChannelMapElem) * kMinNumChannelElements); + + fFieldIDMap = NEW FieldIDArrayElem[kMinNumChannelElements]; + ::memset(fFieldIDMap, 0, sizeof(FieldIDArrayElem) * kMinNumChannelElements); + + ::memset(fSendBuffer, 0,kReqBufSize + 1); + ::memset(fRecvHeaderBuffer, 0,kReqBufSize + 1); + + fSetupHeaders = NEW char[2]; + fSetupHeaders[0] = '\0'; + + ::memset(&sInterleavedParams, 0, sizeof(sInterleavedParams)); + + if (inUserAgent != NULL) + { + fUserAgent = NEW char[::strlen(inUserAgent) + 1]; + ::strcpy(fUserAgent, inUserAgent); + } + else + { + fUserAgent = NEW char[::strlen(sUserAgent) + 1]; + ::strcpy(fUserAgent, sUserAgent); + } +} + +RTSPClient::~RTSPClient() +{ + delete [] fRecvContentBuffer; + delete [] fURL.Ptr; + delete [] fName.Ptr; + delete [] fPassword.Ptr; + if (fSessionID.Ptr != sEmptyString) + delete [] fSessionID.Ptr; + + delete [] fSetupHeaders; + delete [] fChannelTrackMap; + delete [] fFieldIDMap; + delete [] fPacketBuffer; + + delete fAuthenticator; + + delete [] fUserAgent; + if (fControlID != RTSPClient::sControlID) + delete [] fControlID; +} + +void RTSPClient::SetControlID(char* controlID) +{ + if (NULL == controlID) + return; + + if (fControlID != RTSPClient::sControlID) + delete [] fControlID; + + fControlID = NEW char[::strlen(controlID) + 1]; + ::strcpy(fControlID, controlID); + +} + +void RTSPClient::SetName(char *name) +{ + Assert (name); + delete [] fName.Ptr; + fName.Ptr = NEW char[::strlen(name) + 2]; + ::strcpy(fName.Ptr, name); + fName.Len = ::strlen(name); +} + +void RTSPClient::SetPassword(char *password) +{ + Assert (password); + delete [] fPassword.Ptr; + fPassword.Ptr = NEW char[::strlen(password) + 2]; + ::strcpy(fPassword.Ptr, password); + fPassword.Len = ::strlen(password); +} + +void RTSPClient::Set(const StrPtrLen& inURL) +{ + delete [] fURL.Ptr; + fURL.Ptr = NEW char[inURL.Len + 2]; + fURL.Len = inURL.Len; + char* destPtr = fURL.Ptr; + + // add a leading '/' to the url if it isn't a full URL and doesn't have a leading '/' + if ( !inURL.NumEqualIgnoreCase("rtsp://", strlen("rtsp://")) && inURL.Ptr[0] != '/') + { + *destPtr = '/'; + destPtr++; + fURL.Len++; + } + ::memcpy(destPtr, inURL.Ptr, inURL.Len); + fURL.Ptr[fURL.Len] = '\0'; +} + +void RTSPClient::SetSetupParams(Float32 inLateTolerance, char* inMetaInfoFields) +{ + delete [] fSetupHeaders; + fSetupHeaders = NEW char[256]; + fSetupHeaders[0] = '\0'; + + if (inLateTolerance != 0) + qtss_sprintf(fSetupHeaders, "x-Transport-Options: late-tolerance=%f\r\n", inLateTolerance); + if ((inMetaInfoFields != NULL) && (::strlen(inMetaInfoFields) > 0)) + qtss_sprintf(fSetupHeaders + ::strlen(fSetupHeaders), "x-RTP-Meta-Info: %s\r\n", inMetaInfoFields); +} + +OS_Error RTSPClient::SendDescribe(Bool16 inAppendJunkData) +{ + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","DESCRIBE"); + + StringFormatter fmt(fSendBuffer, kReqBufSize); + fmt.PutFmtStr( + "DESCRIBE %s RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "Accept: application/sdp\r\n" + "User-agent: %s\r\n", + fURL.Ptr, fCSeq, fUserAgent); + if (fBandwidth != 0) + fmt.PutFmtStr("Bandwidth: %"_U32BITARG_"\r\n", fBandwidth); + + if (inAppendJunkData) + { + fmt.PutFmtStr("Content-Length: 200\r\n\r\n"); + for(UInt32 i = 0; i < 200; ++i) + fmt.PutChar('d'); + /* + qtss_sprintf(fSendBuffer, "DESCRIBE %s RTSP/1.0\r\nCSeq: %"_U32BITARG_"\r\nAccept: application/sdp\r\nContent-Length: 200\r\nUser-agent: %s\r\n\r\n", fURL.Ptr, fCSeq, fUserAgent); + UInt32 theBufLen = ::strlen(fSendBuffer); + Assert((theBufLen + 200) < kReqBufSize); + for (UInt32 x = theBufLen; x < (theBufLen + 200); x++) + fSendBuffer[x] = 'd'; + fSendBuffer[theBufLen + 200] = '\0'; + */ + } + else + { + fmt.PutFmtStr("\r\n"); + //qtss_sprintf(fSendBuffer, "DESCRIBE %s RTSP/1.0\r\nCSeq: %"_U32BITARG_"\r\nAccept: application/sdp\r\nUser-agent: %s\r\n\r\n", fURL.Ptr, fCSeq, fUserAgent); + } + fmt.PutTerminator(); + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendSetParameter() +{ + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","SET_PARAMETER"); + qtss_sprintf(fSendBuffer, "SET_PARAMETER %s RTSP/1.0\r\nCSeq:%"_U32BITARG_"\r\nUser-agent: %s\r\n\r\n", fURL.Ptr, fCSeq, fUserAgent); + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendOptions() +{ + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","OPTIONS"); + qtss_sprintf(fSendBuffer, "OPTIONS * RTSP/1.0\r\nCSeq:%"_U32BITARG_"\r\nUser-agent: %s\r\n\r\n", fCSeq, fUserAgent); + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendOptionsWithRandomDataRequest(SInt32 dataSize) +{ + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","OPTIONS"); + qtss_sprintf(fSendBuffer, "OPTIONS * RTSP/1.0\r\nCSeq:%"_U32BITARG_"\r\nUser-agent: %s\r\nRequire: x-Random-Data-Size\r\nx-Random-Data-Size: %"_S32BITARG_"\r\n\r\n", fCSeq, fUserAgent, dataSize); + } + return this->DoTransaction(); +} + + +OS_Error RTSPClient::SendReliableUDPSetup(UInt32 inTrackID, UInt16 inClientPort) +{ + fSetupTrackID = inTrackID; // Needed when SETUP response is received. + fSendBuffer[0] = 0; + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","SETUP"); + + StringFormatter fmt(fSendBuffer, kReqBufSize); + + if (fTransportMode == kPushMode) + fmt.PutFmtStr( + "SETUP %s/%s=%"_U32BITARG_" RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "%sTransport: RTP/AVP;unicast;client_port=%u-%u;mode=record\r\n" + "x-Retransmit: our-retransmit\r\n" + "User-agent: %s\r\n", + fURL.Ptr,fControlID, inTrackID, fCSeq, fSessionID.Ptr, inClientPort, inClientPort + 1, fUserAgent); + else + fmt.PutFmtStr( + "SETUP %s/%s=%"_U32BITARG_" RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "%sTransport: RTP/AVP;unicast;client_port=%u-%u\r\n" + "x-Retransmit: our-retransmit\r\n" + "User-agent: %s\r\n", + fURL.Ptr,fControlID, inTrackID, fCSeq, fSessionID.Ptr, inClientPort, inClientPort + 1, fUserAgent); + + if (fBandwidth != 0) + fmt.PutFmtStr("Bandwidth: %"_U32BITARG_"\r\n", fBandwidth); + + Attach3GPPHeaders(fmt, inTrackID); + fmt.PutFmtStr("\r\n"); + fmt.PutTerminator(); + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendUDPSetup(UInt32 inTrackID, UInt16 inClientPort) +{ + fSetupTrackID = inTrackID; // Needed when SETUP response is received. + + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","SETUP"); + + StringFormatter fmt(fSendBuffer, kReqBufSize); + + if (fTransportMode == kPushMode) + fmt.PutFmtStr( + "SETUP %s/%s=%"_U32BITARG_" RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "%sTransport: RTP/AVP;unicast;client_port=%u-%u;mode=record\r\n" + "User-agent: %s\r\n", + fURL.Ptr,fControlID, inTrackID, fCSeq, fSessionID.Ptr, inClientPort, inClientPort + 1, fUserAgent); + else + fmt.PutFmtStr( + "SETUP %s/%s=%"_U32BITARG_" RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "%sTransport: RTP/AVP;unicast;client_port=%u-%u\r\n" + "%sUser-agent: %s\r\n", + fURL.Ptr,fControlID, inTrackID, fCSeq, fSessionID.Ptr, inClientPort, inClientPort + 1, fSetupHeaders, fUserAgent); + + if (fBandwidth != 0) + fmt.PutFmtStr("Bandwidth: %"_U32BITARG_"\r\n", fBandwidth); + + Attach3GPPHeaders(fmt, inTrackID); + fmt.PutFmtStr("\r\n"); + fmt.PutTerminator(); + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendTCPSetup(UInt32 inTrackID, UInt16 inClientRTPid, UInt16 inClientRTCPid) +{ + fSetupTrackID = inTrackID; // Needed when SETUP response is received. + + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","SETUP"); + + StringFormatter fmt(fSendBuffer, kReqBufSize); + + if (fTransportMode == kPushMode) + fmt.PutFmtStr( + "SETUP %s/%s=%"_U32BITARG_" RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "%sTransport: RTP/AVP/TCP;unicast;mode=record;interleaved=%u-%u\r\n" + "User-agent: %s\r\n", + fURL.Ptr,fControlID, inTrackID, fCSeq, fSessionID.Ptr,inClientRTPid, inClientRTCPid, fUserAgent); + else + fmt.PutFmtStr( + "SETUP %s/%s=%"_U32BITARG_" RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "%sTransport: RTP/AVP/TCP;unicast;interleaved=%u-%u\r\n" + "%sUser-agent: %s\r\n", + fURL.Ptr,fControlID, inTrackID, fCSeq, fSessionID.Ptr, inClientRTPid, inClientRTCPid,fSetupHeaders, fUserAgent); + + if (fBandwidth != 0) + fmt.PutFmtStr("Bandwidth: %"_U32BITARG_"\r\n", fBandwidth); + + Attach3GPPHeaders(fmt, inTrackID); + fmt.PutFmtStr("\r\n"); + fmt.PutTerminator(); + + } + + return this->DoTransaction(); + +} + +OS_Error RTSPClient::SendPlay(UInt32 inStartPlayTimeInSec, Float32 inSpeed, UInt32 inTrackID) +{ + char speedBuf[128]; + speedBuf[0] = '\0'; + + if (inSpeed != 1) + qtss_sprintf(speedBuf, "Speed: %f5.2\r\n", inSpeed); + + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","PLAY"); + + StringFormatter fmt(fSendBuffer, kReqBufSize); + + fmt.PutFmtStr( + "PLAY %s RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "%sRange: npt=%"_U32BITARG_".0-\r\n" + "%sx-prebuffer: maxtime=3.0\r\n" + "User-agent: %s\r\n", + fURL.Ptr, fCSeq, fSessionID.Ptr, inStartPlayTimeInSec, speedBuf, fUserAgent); + + if (fBandwidth != 0) + fmt.PutFmtStr("Bandwidth: %"_U32BITARG_"\r\n", fBandwidth); + + Attach3GPPHeaders(fmt, inTrackID); + fmt.PutFmtStr("\r\n"); + fmt.PutTerminator(); + + //qtss_sprintf(fSendBuffer, "PLAY %s RTSP/1.0\r\nCSeq: %"_U32BITARG_"\r\n%sRange: npt=7.0-\r\n%sx-prebuffer: maxtime=3.0\r\nUser-agent: %s\r\n\r\n", fURL.Ptr, fCSeq, fSessionID.Ptr, speedBuf, fUserAgent); + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendPacketRangePlay(char* inPacketRangeHeader, Float32 inSpeed) +{ + char speedBuf[128]; + speedBuf[0] = '\0'; + + if (inSpeed != 1) + qtss_sprintf(speedBuf, "Speed: %f5.2\r\n", inSpeed); + + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","PLAY"); + + StringFormatter fmt(fSendBuffer, kReqBufSize); + + fmt.PutFmtStr( + "PLAY %s RTSP/1.0\r\n" + "CSeq: %"_U32BITARG_"\r\n" + "%sx-Packet-Range: %s\r\n" + "%sUser-agent: %s\r\n\r\n", + fURL.Ptr, fCSeq, fSessionID.Ptr, inPacketRangeHeader, speedBuf, fUserAgent); + + if (fBandwidth != 0) + fmt.PutFmtStr("Bandwidth: %"_U32BITARG_"\r\n", fBandwidth); + + Attach3GPPHeaders(fmt); + fmt.PutFmtStr("\r\n"); + fmt.PutTerminator(); + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendReceive(UInt32 inStartPlayTimeInSec) +{ + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","RECORD"); + qtss_sprintf(fSendBuffer, "RECORD %s RTSP/1.0\r\nCSeq: %"_U32BITARG_"\r\n%sRange: npt=%"_U32BITARG_".0-\r\nUser-agent: %s\r\n\r\n", fURL.Ptr, fCSeq, fSessionID.Ptr, inStartPlayTimeInSec, fUserAgent); + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendAnnounce(char *sdp) +{ +//ANNOUNCE rtsp://server.example.com/permanent_broadcasts/TestBroadcast.sdp RTSP/1.0 + if (!IsTransactionInProgress()) + { + qtss_sprintf(fMethod,"%s","ANNOUNCE"); + if (sdp == NULL) + qtss_sprintf(fSendBuffer, "ANNOUNCE %s RTSP/1.0\r\nCSeq: %"_U32BITARG_"\r\nAccept: application/sdp\r\nUser-agent: %s\r\n\r\n", fURL.Ptr, fCSeq, fUserAgent); + else + { UInt32 len = strlen(sdp); + if(len > kReqBufSize) + return OS_NotEnoughSpace; + qtss_sprintf(fSendBuffer, "ANNOUNCE %s RTSP/1.0\r\nCSeq: %"_U32BITARG_"\r\nContent-Type: application/sdp\r\nUser-agent: %s\r\nContent-Length: %"_U32BITARG_"\r\n\r\n%s", fURL.Ptr, fCSeq, fUserAgent, len, sdp); + } + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::SendRTSPRequest(iovec* inRequest, UInt32 inNumVecs) +{ + if (!IsTransactionInProgress()) + { + UInt32 curOffset = 0; + for (UInt32 x = 0; x < inNumVecs; x++) + { + ::memcpy(fSendBuffer + curOffset, inRequest[x].iov_base, inRequest[x].iov_len); + curOffset += inRequest[x].iov_len; + } + Assert(kReqBufSize > curOffset); + fSendBuffer[curOffset] = '\0'; + } + return this->DoTransaction(); +} + +OS_Error RTSPClient::PutMediaPacket(UInt32 inTrackID, Bool16 isRTCP, char* inData, UInt16 inLen) +{ + // Find the right channel number for this trackID + for (int x = 0; x < fNumChannelElements; x++) + { + if ((fChannelTrackMap[x].fTrackID == inTrackID) && (fChannelTrackMap[x].fIsRTCP == isRTCP)) + { + char header[5]; + header[0] = '$'; + header[1] = (UInt8)x; + UInt16* theLenP = (UInt16*)header; + theLenP[1] = htons(inLen); + + // + // Build the iovec + iovec ioVec[2]; + ioVec[0].iov_len = 4; + ioVec[0].iov_base = header; + ioVec[1].iov_len = inLen; + ioVec[1].iov_base = inData; + + // + // Send it + return fSocket->SendV(ioVec, 2); + } + } + + return OS_NoErr; +} + + +OS_Error RTSPClient::SendInterleavedWrite(UInt8 channel, UInt16 len, char*data,Bool16 *getNext) +{ + + Assert(len < RTSPClient::kReqBufSize); + + iovec ioVEC[1]; + struct iovec* iov = &ioVEC[0]; + UInt16 interleavedLen =0; + UInt16 sendLen = 0; + + if (sInterleavedParams.extraLen > 0) + { *getNext = false; // can't handle new packet now. Send it again + ioVEC[0].iov_base = sInterleavedParams.extraBytes; + ioVEC[0].iov_len = sInterleavedParams.extraLen; + sendLen = sInterleavedParams.extraLen; + } + else + { *getNext = true; // handle a new packet + fSendBuffer[0] = '$'; + fSendBuffer[1] = channel; + UInt16 netlen = htons(len); + memcpy(&fSendBuffer[2],&netlen,2); + memcpy(&fSendBuffer[4],data,len); + + interleavedLen = len+4; + ioVEC[0].iov_base=&fSendBuffer[0]; + ioVEC[0].iov_len= interleavedLen; + sendLen = interleavedLen; + sInterleavedParams.extraChannel =channel; + } + + UInt32 outLenSent; + OS_Error theErr = fSocket->GetSocket()->WriteV(iov, 1,&outLenSent); + if (theErr != 0) + outLenSent = 0; + + if(fVerboseLevel >= 3) + qtss_printf("RTSPClient::SendInterleavedWrite Send channel=%u bufferlen=%u err=%"_S32BITARG_" outLenSent=%"_U32BITARG_"\n",(UInt16) sInterleavedParams.extraChannel, sendLen,theErr,outLenSent); + if (theErr == 0 && outLenSent != sendLen) + { if (sInterleavedParams.extraLen > 0) // sending extra len so keep sending it. + { + if (fVerboseLevel >= 3) + qtss_printf("RTSPClient::SendInterleavedWrite partial Send channel=%u bufferlen=%u err=%"_S32BITARG_" amountSent=%"_U32BITARG_" \n",(UInt16) sInterleavedParams.extraChannel,sendLen,theErr,outLenSent); + sInterleavedParams.extraLen = sendLen - outLenSent; + sInterleavedParams.extraByteOffset += outLenSent; + sInterleavedParams.extraBytes = &fSendBuffer[sInterleavedParams.extraByteOffset]; + } + else // we were sending a new packet so record the data + { + if (fVerboseLevel >= 3) + qtss_printf("RTSPClient::SendInterleavedWrite partial Send channel=%u bufferlen=%u err=%"_S32BITARG_" amountSent=%"_U32BITARG_" \n",(UInt16) channel,sendLen,theErr,outLenSent); + sInterleavedParams.extraBytes = &fSendBuffer[outLenSent]; + sInterleavedParams.extraLen = sendLen - outLenSent; + sInterleavedParams.extraChannel = channel; + sInterleavedParams.extraByteOffset = outLenSent; + } + } + else // either an error occured or we sent everything ok + { + if (theErr == 0) + { + if (sInterleavedParams.extraLen > 0) // we were busy sending some old data and it all got sent + { + if (fVerboseLevel >= 3) + qtss_printf("RTSPClient::SendInterleavedWrite FULL Send channel=%u bufferlen=%u err=%"_S32BITARG_" amountSent=%"_U32BITARG_" \n",(UInt16) sInterleavedParams.extraChannel,sendLen,theErr,outLenSent); + } + else + { // it all worked so ask for more data + if (fVerboseLevel >= 3) + qtss_printf("RTSPClient::SendInterleavedWrite FULL Send channel=%u bufferlen=%u err=%"_S32BITARG_" amountSent=%"_U32BITARG_" \n",(UInt16) channel,sendLen,theErr,outLenSent); + } + sInterleavedParams.extraLen = 0; + sInterleavedParams.extraBytes = NULL; + sInterleavedParams.extraByteOffset = 0; + } + else // we got an error so nothing was sent + { + if (fVerboseLevel >= 3) + qtss_printf("RTSPClient::SendInterleavedWrite Send ERR sending=%"_S32BITARG_" \n",theErr); + + if (sInterleavedParams.extraLen == 0) // retry the new packet + { + sInterleavedParams.extraBytes = &fSendBuffer[0]; + sInterleavedParams.extraLen = sendLen; + sInterleavedParams.extraChannel = channel; + sInterleavedParams.extraByteOffset = 0; + } + } + } + return theErr; +} + +OS_Error RTSPClient::SendTeardown() +{ + if (!IsTransactionInProgress()) + { qtss_sprintf(fMethod,"%s","TEARDOWN"); + qtss_sprintf(fSendBuffer, "TEARDOWN %s RTSP/1.0\r\nCSeq: %"_U32BITARG_"\r\n%sUser-agent: %s\r\n\r\n", fURL.Ptr, fCSeq, fSessionID.Ptr, fUserAgent); + } + return this->DoTransaction(); +} + + +OS_Error RTSPClient::GetMediaPacket(UInt32* outTrackID, Bool16* outIsRTCP, char** outBuffer, UInt32* outLen) +{ + static const UInt32 kPacketHeaderLen = 4; + static const UInt32 kMaxPacketSize = 4096; + + // We need to buffer until we get a full packet. + if (fPacketBuffer == NULL) + fPacketBuffer = NEW char[kMaxPacketSize]; + + if (fPacketOutstanding) + { + // The previous call to this function returned a packet successfully. We've been holding + // onto that packet data until now... Now we can blow it away. + UInt16* thePacketLenP = (UInt16*)fPacketBuffer; + UInt16 thePacketLen = ntohs(thePacketLenP[1]); + + Assert(fPacketBuffer[0] == '$'); + + // Move the leftover data (part of the next packet) to the beginning of the buffer + Assert(fPacketBufferOffset >= (thePacketLen + kPacketHeaderLen)); + fPacketBufferOffset -= thePacketLen + kPacketHeaderLen; + ::memmove(fPacketBuffer, &fPacketBuffer[thePacketLen + kPacketHeaderLen], fPacketBufferOffset); +#if DEBUG + if (fPacketBufferOffset > 0) + { + Assert(fPacketBuffer[0] == '$'); + } +#endif + + fPacketOutstanding = false; + } + + if (fPacketDataInHeaderBufferLen > 0) + { + // + // If there is some packet data in the header buffer, clear it out + if (fVerboseLevel >= 3) + qtss_printf("%"_U32BITARG_" bytes of packet data in header buffer\n",fPacketDataInHeaderBufferLen); + + Assert(fPacketDataInHeaderBuffer[0] == '$'); + Assert(fPacketDataInHeaderBufferLen < (kMaxPacketSize - fPacketBufferOffset)); + ::memcpy(&fPacketBuffer[fPacketBufferOffset], fPacketDataInHeaderBuffer, fPacketDataInHeaderBufferLen); + fPacketBufferOffset += fPacketDataInHeaderBufferLen; + fPacketDataInHeaderBufferLen = 0; + } + + Assert(fPacketBufferOffset < kMaxPacketSize); + UInt32 theRecvLen = 0; + OS_Error theErr = fSocket->Read(&fPacketBuffer[fPacketBufferOffset], kMaxPacketSize - fPacketBufferOffset, &theRecvLen); + if (theErr != OS_NoErr) + return theErr; + + fPacketBufferOffset += theRecvLen; + Assert(fPacketBufferOffset <= kMaxPacketSize); + + if (fPacketBufferOffset > kPacketHeaderLen) + { + Assert(fPacketBuffer[0] == '$'); + UInt16* thePacketLenP = (UInt16*)fPacketBuffer; + UInt16 thePacketLen = ntohs(thePacketLenP[1]); + UInt8 channelIndex = fPacketBuffer[1]; + if (fPacketBufferOffset >= (thePacketLen + kPacketHeaderLen)) + { + // We have a complete packet. Return it to the caller. + Assert(channelIndex < fNumChannelElements); // This is really not a safe assert, but anyway...] + if (channelIndex >= fNumChannelElements) + return -1; + + *outTrackID = fChannelTrackMap[channelIndex].fTrackID; + *outIsRTCP = fChannelTrackMap[channelIndex].fIsRTCP; + *outLen = thePacketLen; + + // Next time we call this function, we will blow away the packet, but until then + // we leave it untouched. + fPacketOutstanding = true; + *outBuffer = &fPacketBuffer[kPacketHeaderLen]; + + return OS_NoErr; + } + } + return OS_NoErr; +} + +UInt32 RTSPClient::GetSSRCByTrack(UInt32 inTrackID) +{ + for (UInt32 x = 0; x < fSSRCMap.size(); x++) + { + if (inTrackID == fSSRCMap[x].fTrackID) + return fSSRCMap[x].fSSRC; + } + return 0; +} + +RTPMetaInfoPacket::FieldID* RTSPClient::GetFieldIDArrayByTrack(UInt32 inTrackID) +{ + for (UInt32 x = 0; x < fNumFieldIDElements; x++) + { + if (inTrackID == fFieldIDMap[x].fTrackID) + return fFieldIDMap[x].fFieldIDs; + } + return NULL; +} + +OS_Error RTSPClient::DoTransaction() +{ + OS_Error theErr = OS_NoErr; + StrPtrLen theRequest(fSendBuffer, ::strlen(fSendBuffer)); + StrPtrLen theMethod(fMethod); + + for(;;) + { + switch(fState) + { + //Initial state: getting ready to send the request; the authenticator is initialized if it exists. + //This is the only state where a new request can be made. + case kInitial: + if (fAuthenticator != NULL) + { + fAuthAttempted = true; + fAuthenticator->RemoveAuthLine(&theRequest); // reset to original request + fAuthenticator->ResetAuthParams(); // if we had a 401 on an authenticated request clean up old params and try again with the new response data + fAuthenticator->SetName(&fName); + fAuthenticator->SetPassword(&fPassword); + fAuthenticator->SetMethod(&theMethod); + fAuthenticator->SetURI(&fURL); + fAuthenticator->AttachAuthParams(&theRequest); + } + fCSeq++; //this assumes that the sequence number will not be read again until the next transaction + fPacketDataInHeaderBufferLen = 0; + + fState = kRequestSending; + break; + + //Request Sending state: keep on calling Send while Send returns EAGAIN or EINPROGRESS + case kRequestSending: + theErr = fSocket->Send(theRequest.Ptr, theRequest.Len); + + if (theErr != OS_NoErr) + { + if (fVerboseLevel >= 3) + qtss_printf("RTSPClient::DoTransaction Send len=%"_U32BITARG_" err = %"_S32BITARG_"\n", theRequest.Len, theErr); + return theErr; + } + if (fVerboseLevel >= 1) + qtss_printf("\n-----REQUEST-----len=%"_U32BITARG_"\n%s\n", theRequest.Len, STRTOCHAR(&theRequest)); + + + //Done sending request; moving onto the response + fContentRecvLen = 0; + fHeaderRecvLen = 0; + fHeaderLen = 0; + ::memset(fRecvHeaderBuffer, 0, kReqBufSize+1); + + fState = kResponseReceiving; + break; + + //Response Receiving state: keep on calling ReceiveResponse while it returns EAGAIN or EINPROGRESS + case kResponseReceiving: + //Header Received state: the response header has been received(and parsed), but the entity(response content) has not been completely received + case kHeaderReceived: + theErr = this->ReceiveResponse(); //note that this function can change the fState + + if (fVerboseLevel >= 3) + qtss_printf("RTSPClient::DoTransaction ReceiveResponse fStatus=%"_U32BITARG_" len=%"_U32BITARG_" err = %"_S32BITARG_"\n",fStatus, fHeaderRecvLen, theErr); + + if (theErr != OS_NoErr) + return theErr; + + //The response has been completely received and parsed. If the response is 401 unauthorized, then redo the request with authorization + fState = kInitial; + if (fStatus == 401 && fAuthenticator != NULL && !fAuthAttempted) + break; + else + return OS_NoErr; + break; + } + } + Assert(false); //not reached + return 0; +} + + +//This implementation cannot parse interleaved headers with entity content. +OS_Error RTSPClient::ReceiveResponse() +{ + Assert(fState == kResponseReceiving | fState == kHeaderReceived); + OS_Error theErr = OS_NoErr; + + while (fState == kResponseReceiving) + { + UInt32 theRecvLen = 0; + //fRecvHeaderBuffer[0] = 0; + theErr = fSocket->Read(&fRecvHeaderBuffer[fHeaderRecvLen], kReqBufSize - fHeaderRecvLen, &theRecvLen); + if (theErr != OS_NoErr) + return theErr; + + fHeaderRecvLen += theRecvLen; + fRecvHeaderBuffer[fHeaderRecvLen] = 0; + if (fVerboseLevel >= 1) + qtss_printf("\n-----RESPONSE (len: %"_U32BITARG_")----\n%s\n", fHeaderRecvLen, fRecvHeaderBuffer); + + //fRecvHeaderBuffer[fHeaderRecvLen] = '\0'; + // Check to see if we've gotten a complete header, and if the header has even started + // The response may not start with the response if we are interleaving media data, + // in which case there may be leftover media data in the stream. If we encounter any + // of this cruft, we can just strip it off. + char* theHeaderStart = ::strstr(fRecvHeaderBuffer, "RTSP"); + if (theHeaderStart == NULL) + { + fHeaderRecvLen = 0; + continue; + } + else if (theHeaderStart != fRecvHeaderBuffer) + { + //strip off everything before the RTSP + fHeaderRecvLen -= theHeaderStart - fRecvHeaderBuffer; + ::memmove(fRecvHeaderBuffer, theHeaderStart, fHeaderRecvLen); + //fRecvHeaderBuffer[fHeaderRecvLen] = '\0'; + } + + char* theResponseData = ::strstr(fRecvHeaderBuffer, "\r\n\r\n"); + + if (theResponseData != NULL) + { + // skip past the \r\n\r\n + theResponseData += 4; + + // We've got a new response + fState = kHeaderReceived; + + // Figure out how much of the content body we've already received + // in the header buffer. If we are interleaving, this may also be packet data + fHeaderLen = theResponseData - &fRecvHeaderBuffer[0]; + fContentRecvLen = fHeaderRecvLen - fHeaderLen; + + // Zero out fields that will change with every RTSP response + fServerPort = 0; + fStatus = 0; + fContentLength = 0; + + // Parse the response. + StrPtrLen theData(fRecvHeaderBuffer, fHeaderLen); + StringParser theParser(&theData); + + theParser.ConsumeLength(NULL, 9); //skip past RTSP/1.0 + fStatus = theParser.ConsumeInteger(NULL); + + StrPtrLen theLastHeader; + while (theParser.GetDataRemaining() > 0) + { + static StrPtrLen sSessionHeader("Session"); + static StrPtrLen sContentLenHeader("Content-length"); + static StrPtrLen sTransportHeader("Transport"); + static StrPtrLen sRTPInfoHeader("RTP-Info"); + static StrPtrLen sRTPMetaInfoHeader("x-RTP-Meta-Info"); + static StrPtrLen sAuthenticateHeader("WWW-Authenticate"); + static StrPtrLen sSameAsLastHeader(" ,"); + + StrPtrLen theKey; + theParser.GetThruEOL(&theKey); + + if (theKey.NumEqualIgnoreCase(sSessionHeader.Ptr, sSessionHeader.Len)) + { + if (fSessionID.Len == 0) + { + // Copy the session ID and store it. + // First figure out how big the session ID is. We copy + // everything up until the first ';' returned from the server + UInt32 keyLen = 0; + while ((theKey.Ptr[keyLen] != ';') && (theKey.Ptr[keyLen] != '\r') && (theKey.Ptr[keyLen] != '\n')) + keyLen++; + + // Append an EOL so we can stick this thing transparently into the SETUP request + + fSessionID.Ptr = NEW char[keyLen + 3]; + fSessionID.Len = keyLen + 2; + ::memcpy(fSessionID.Ptr, theKey.Ptr, keyLen); + ::memcpy(fSessionID.Ptr + keyLen, "\r\n", 2);//Append a EOL + fSessionID.Ptr[keyLen + 2] = '\0'; + } + } + else if (theKey.NumEqualIgnoreCase(sContentLenHeader.Ptr, sContentLenHeader.Len)) + { + //exclusive with interleaved + StringParser theCLengthParser(&theKey); + theCLengthParser.ConsumeUntil(NULL, StringParser::sDigitMask); + fContentLength = theCLengthParser.ConsumeInteger(NULL); + + delete [] fRecvContentBuffer; + fRecvContentBuffer = NEW char[fContentLength + 1]; + ::memset(fRecvContentBuffer, '\0', fContentLength + 1); + + // Immediately copy the bit of the content body that we've already + // read off of the socket. + Assert(fContentRecvLen <= fContentLength) + ::memcpy(fRecvContentBuffer, theResponseData, fContentRecvLen); + } + else if (theKey.NumEqualIgnoreCase(sAuthenticateHeader.Ptr, sAuthenticateHeader.Len)) + { + if (fVerboseLevel >= 3) + qtss_printf("\n--CHALLENGE RECEIVED\n"); + #if ENABLE_AUTHENTICATION + if (fAuthenticator != NULL) // already have an authenticator + delete fAuthenticator; + + fAuthenticator = fAuthenticationParser.ParseChallenge(&theKey); + Assert(fAuthenticator != NULL); + if (!fAuthenticator) + return 401; // what to do? the challenge is bad can't authenticate. + else if (fVerboseLevel >= 3) + { if (fAuthenticator->GetType() == Authenticator::kBasicType) + qtss_printf("--CREATED BASIC AUTHENTICATOR\n"); + else if (fAuthenticator->GetType() == Authenticator::kDigestType) + qtss_printf("--CREATED DIGEST AUTHENTICATOR\n"); + } + #else + if (fVerboseLevel >= 3) + qtss_printf("--AUTHENTICATION IS DISABLED\n"); + #endif + } + else if (theKey.NumEqualIgnoreCase(sTransportHeader.Ptr, sTransportHeader.Len)) + { + StringParser theTransportParser(&theKey); + StrPtrLen theSubHeader; + + while (theTransportParser.GetDataRemaining() > 0) + { + static StrPtrLen sServerPort("server_port"); + static StrPtrLen sInterleaved("interleaved"); + static StrPtrLen sSSRC("ssrc"); + + theTransportParser.GetThru(&theSubHeader, ';'); + if (theSubHeader.NumEqualIgnoreCase(sServerPort.Ptr, sServerPort.Len)) + { + StringParser thePortParser(&theSubHeader); + thePortParser.ConsumeUntil(NULL, StringParser::sDigitMask); + fServerPort = (UInt16) thePortParser.ConsumeInteger(NULL); + } + else if (theSubHeader.NumEqualIgnoreCase(sInterleaved.Ptr, sInterleaved.Len)) //exclusive with Content-length + this->ParseInterleaveSubHeader(&theSubHeader); + else if (theSubHeader.NumEqualIgnoreCase(sSSRC.Ptr, sSSRC.Len)) + { + StringParser ssrcParser(&theSubHeader); + StrPtrLen ssrcStr; + + ssrcParser.GetThru(NULL, '='); + ssrcParser.ConsumeWhitespace(); + ssrcParser.ConsumeUntilWhitespace(&ssrcStr); + + if (ssrcStr.Len > 0) + { + OSArrayObjectDeleter ssrcCStr = ssrcStr.GetAsCString(); + unsigned long ssrc = ::strtoul(ssrcCStr.GetObject(), NULL, 16); + fSSRCMap.push_back(SSRCMapElem(fSetupTrackID, ssrc)); + } + } + } + } + else if (theKey.NumEqualIgnoreCase(sRTPInfoHeader.Ptr, sRTPInfoHeader.Len)) + ParseRTPInfoHeader(&theKey); + else if (theKey.NumEqualIgnoreCase(sRTPMetaInfoHeader.Ptr, sRTPMetaInfoHeader.Len)) + ParseRTPMetaInfoHeader(&theKey); + else if (theKey.NumEqualIgnoreCase(sSameAsLastHeader.Ptr, sSameAsLastHeader.Len)) + { + // + // If the last header was an RTP-Info header + if (theLastHeader.NumEqualIgnoreCase(sRTPInfoHeader.Ptr, sRTPInfoHeader.Len)) + ParseRTPInfoHeader(&theKey); + } + theLastHeader = theKey; + } + + // + // Check to see if there is any packet data in the header buffer; everything that is left should be packet data + if (fContentRecvLen > fContentLength) + { + fPacketDataInHeaderBuffer = theResponseData + fContentLength; + fPacketDataInHeaderBufferLen = fContentRecvLen - fContentLength; + } + } + else if (fHeaderRecvLen == kReqBufSize) //the "\r\n" is not found --> read more data + return ENOBUFS; // This response is too big for us to handle! + } + + //the advertised data length is less than what has been received...need to read more data + while (fContentLength > fContentRecvLen) + { + UInt32 theContentRecvLen = 0; + theErr = fSocket->Read(&fRecvContentBuffer[fContentRecvLen], fContentLength - fContentRecvLen, &theContentRecvLen); + if (theErr != OS_NoErr) + { + //fEventMask = EV_RE; + return theErr; + } + fContentRecvLen += theContentRecvLen; + } + return OS_NoErr; +} + +//DOES NOT CURRENT WORK AS ADVERTISED; so I took it out +//RTP-Info has sequence number, not SSRC +void RTSPClient::ParseRTPInfoHeader(StrPtrLen* inHeader) +{ +/* + static StrPtrLen sURL("url"); + StringParser theParser(inHeader); + theParser.ConsumeUntil(NULL, 'u'); // consume until "url" + + if (fNumSSRCElements == fSSRCMapSize) + { + SSRCMapElem* theNewMap = NEW SSRCMapElem[fSSRCMapSize * 2]; + ::memset(theNewMap, 0, sizeof(SSRCMapElem) * (fSSRCMapSize * 2)); + ::memcpy(theNewMap, fSSRCMap, sizeof(SSRCMapElem) * fNumSSRCElements); + fSSRCMapSize *= 2; + delete [] fSSRCMap; + fSSRCMap = theNewMap; + } + + fSSRCMap[fNumSSRCElements].fTrackID = 0; + fSSRCMap[fNumSSRCElements].fSSRC = 0; + + // Parse out the trackID & the SSRC + StrPtrLen theRTPInfoSubHeader; + (void)theParser.GetThru(&theRTPInfoSubHeader, ';'); + + while (theRTPInfoSubHeader.Len > 0) + { + static StrPtrLen sURLSubHeader("url"); + static StrPtrLen sSSRCSubHeader("ssrc"); + + //DOES NOT WORK IF THE URL contains NUMBERS!!!!!! + if (theRTPInfoSubHeader.NumEqualIgnoreCase(sURLSubHeader.Ptr, sURLSubHeader.Len)) + { + StringParser theURLParser(&theRTPInfoSubHeader); + theURLParser.ConsumeUntil(NULL, StringParser::sDigitMask); + fSSRCMap[fNumSSRCElements].fTrackID = theURLParser.ConsumeInteger(NULL); + } + // This RTP-Info header should not have an ssrc field!!! + else if (theRTPInfoSubHeader.NumEqualIgnoreCase(sSSRCSubHeader.Ptr, sSSRCSubHeader.Len)) + { + StringParser theURLParser(&theRTPInfoSubHeader); + theURLParser.ConsumeUntil(NULL, StringParser::sDigitMask); + fSSRCMap[fNumSSRCElements].fSSRC = theURLParser.ConsumeInteger(NULL); + } + + // Move onto the next parameter + (void)theParser.GetThru(&theRTPInfoSubHeader, ';'); + } + + fNumSSRCElements++;*/ +} + +void RTSPClient::ParseRTPMetaInfoHeader(StrPtrLen* inHeader) +{ + // + // Reallocate the array if necessary + if (fNumFieldIDElements == fFieldIDMapSize) + { + FieldIDArrayElem* theNewMap = NEW FieldIDArrayElem[fFieldIDMapSize * 2]; + ::memset(theNewMap, 0, sizeof(FieldIDArrayElem) * (fFieldIDMapSize * 2)); + ::memcpy(theNewMap, fFieldIDMap, sizeof(FieldIDArrayElem) * fNumFieldIDElements); + fFieldIDMapSize *= 2; + delete [] fFieldIDMap; + fFieldIDMap = theNewMap; + } + + // + // Build the FieldIDArray for this track + RTPMetaInfoPacket::ConstructFieldIDArrayFromHeader(inHeader, fFieldIDMap[fNumFieldIDElements].fFieldIDs); + fFieldIDMap[fNumFieldIDElements].fTrackID = fSetupTrackID; + fNumFieldIDElements++; +} + +void RTSPClient::ParseInterleaveSubHeader(StrPtrLen* inSubHeader) +{ + StringParser theChannelParser(inSubHeader); + + // Parse out the channel numbers + theChannelParser.ConsumeUntil(NULL, StringParser::sDigitMask); + UInt8 theRTPChannel = (UInt8) theChannelParser.ConsumeInteger(NULL); + theChannelParser.ConsumeLength(NULL, 1); + UInt8 theRTCPChannel = (UInt8) theChannelParser.ConsumeInteger(NULL); + + UInt8 theMaxChannel = theRTCPChannel; + if (theRTPChannel > theMaxChannel) + theMaxChannel = theRTPChannel; + + // Reallocate the channel-track array if it is too little + if (theMaxChannel >= fNumChannelElements) + { + ChannelMapElem* theNewMap = NEW ChannelMapElem[theMaxChannel + 1]; + ::memset(theNewMap, 0, sizeof(ChannelMapElem) * (theMaxChannel + 1)); + ::memcpy(theNewMap, fChannelTrackMap, sizeof(ChannelMapElem) * fNumChannelElements); + fNumChannelElements = theMaxChannel + 1; + delete [] fChannelTrackMap; + fChannelTrackMap = theNewMap; + } + + // Copy the relevent information into the channel-track array. + fChannelTrackMap[theRTPChannel].fTrackID = fSetupTrackID; + fChannelTrackMap[theRTPChannel].fIsRTCP = false; + fChannelTrackMap[theRTCPChannel].fTrackID = fSetupTrackID; + fChannelTrackMap[theRTCPChannel].fIsRTCP = true; +} + +//Use a trackID of kUInt32_Max to turn the Rate-Adaptation header off. +void RTSPClient::Attach3GPPHeaders(StringFormatter &fmt, UInt32 inTrackID) +{ + if (fGuarenteedBitRate != 0 | fMaxBitRate != 0 | fMaxTransferDelay != 0) + { + fmt.PutFmtStr("3GPP-Link-Char: url=\"%s\"", fURL.Ptr); + + if (fGuarenteedBitRate != 0) + fmt.PutFmtStr("; GBW=%"_U32BITARG_, fGuarenteedBitRate); + if (fMaxBitRate != 0) + fmt.PutFmtStr("; MBW=%"_U32BITARG_, fMaxBitRate); + if (fMaxTransferDelay != 0) + fmt.PutFmtStr("; MTD=%"_U32BITARG_, fMaxTransferDelay); + fmt.PutFmtStr("\r\n"); + } + + if ((fBufferSpace != 0 | fDelayTime != 0) && inTrackID != kUInt32_Max) + { + fmt.PutFmtStr("3GPP-Adaptation: "); + + fmt.PutFmtStr("url=\"%s/%s=%"_U32BITARG_"\"", fURL.Ptr, fControlID, inTrackID); + if (fBufferSpace != 0) + fmt.PutFmtStr("; size=%"_U32BITARG_, fBufferSpace); + if (fDelayTime != 0) + fmt.PutFmtStr("; target-time=%"_U32BITARG_, fDelayTime); + fmt.PutFmtStr("\r\n"); + } +} + +#define _RTSPCLIENTTESTING_ 0 + +#include "SocketUtils.h" +#include "OS.h" + +#if _RTSPCLIENTTESTING_ +int main(int argc, char * argv[]) +{ + OS::Initialize(); + Socket::Initialize(); + SocketUtils::Initialize(); + + UInt32 ipAddr = (UInt32)ntohl(::inet_addr("17.221.41.111")); + UInt16 port = 554; + StrPtrLen theURL("rtsp://17.221.41.111/mystery.mov"); + + RTSPClient theClient(Socket::kNonBlockingSocketType); + theClient.Set(ipAddr, port, theURL); + + OS_Error theErr = EINVAL; + while (theErr != OS_NoErr) + { + theErr = theClient.SendDescribe(); + sleep(1); + } + Assert(theClient.GetStatus() == 200); + theErr = EINVAL; + while (theErr != OS_NoErr) + { + theErr = theClient.SendSetup(3, 6790); + sleep(1); + } + //qtss_printf("Server port: %d\n", theClient.GetServerPort()); + Assert(theClient.GetStatus() == 200); + theErr = EINVAL; + while (theErr != OS_NoErr) + { + theErr = theClient.SendSetup(4, 6792); + sleep(1); + } + //qtss_printf("Server port: %d\n", theClient.GetServerPort()); + Assert(theClient.GetStatus() == 200); + theErr = EINVAL; + while (theErr != OS_NoErr) + { + theErr = theClient.SendPlay(); + sleep(1); + } + Assert(theClient.GetStatus() == 200); + theErr = EINVAL; + while (theErr != OS_NoErr) + { + theErr = theClient.SendTeardown(); + sleep(1); + } + Assert(theClient.GetStatus() == 200); +} +#endif diff --git a/RTSPClientLib/RTSPClient.h b/RTSPClientLib/RTSPClient.h new file mode 100644 index 0000000..169ca9e --- /dev/null +++ b/RTSPClientLib/RTSPClient.h @@ -0,0 +1,474 @@ +/* + * + * @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: RTSPClient.h + + Works only for QTSS. + Assumes that different streams within a session is distinguished by trackID= + For example, streams within sample.mov would be refered to as sample.mov/trackID=4 + Does not work if the URL contains digits!!! +*/ + +#ifndef __RTSP_CLIENT_H__ +#define __RTSP_CLIENT_H__ + +#include "OSHeaders.h" +#include "StrPtrLen.h" +#include "TCPSocket.h" +#include "ClientSocket.h" +#include "RTPMetaInfoPacket.h" +#include "StringFormatter.h" +#include "SVector.h" + +// for authentication +#include "StringParser.h" +class Authenticator +{ + + public: + enum { kAuthBufferLen = 1024 }; + + enum + { + kNoType = 0, + kBasicType = 1, + kDigestType = 2 // higher number is stronger type + }; + + Authenticator(); + virtual ~Authenticator() {}; + virtual SInt16 GetType() { return 0;}; + virtual Bool16 ParseParams(StrPtrLen *authParamsPtr) {return 0;}; + virtual void AttachAuthParams(StrPtrLen *theRequestPtr) {}; + virtual void ResetAuthParams() {}; + + StrPtrLen fAuthBuffer; + + StrPtrLen fNameSPL;// client + StrPtrLen fPasswordSPL;// client + StrPtrLen fRealmSPL; // server + StrPtrLen fURISPL; // client + StrPtrLen fMethodSPL; // client -- must be all caps + time_t fAuthTime; + + //the outHeaderStr argument is not actually implemented + //char *GetRequestHeader( StrPtrLen *inSourceStr, StrPtrLen *searchHeaderStr,StrPtrLen *outHeaderStr = NULL); + char *GetRequestHeader( StrPtrLen *inSourceStr, StrPtrLen *searchHeaderStr); + //Does nothing if the Authorization header is not found + void RemoveAuthLine(StrPtrLen *theRequestPtr); + + static Bool16 CopyParam(StrPtrLen *inPtr, StrPtrLen *outPtr); + + void SetName(StrPtrLen *inNamePtr); + void SetPassword(StrPtrLen *inPasswordPtr); + void SetMethod(StrPtrLen *inMethodStr); + void SetRealm(StrPtrLen *inRealmPtr); + void SetURI(StrPtrLen *inURIPtr); + + void ResetRequestLen(StrPtrLen *theRequestPtr, StrPtrLen *theParamsPtr); + void ParseTag(StringParser *parserPtr,StrPtrLen *outTagPtr); + + Bool16 GetParamValue(StringParser *valueSourcePtr, StrPtrLen *outParamValuePtr); + Bool16 GetParamValueAsNewCopy(StringParser *valueSourcePtr, StrPtrLen *outParamValueCopyPtr); + Bool16 GetMatchListParamValueAsNewCopy(StringParser *valueSourcePtr, StrPtrLen *inMatchListParamValuePtr, SInt16 numToMatch, StrPtrLen *outParamValueCopyPtr); + Bool16 ParseRealm(StringParser *realmParserPtr); + + static StrPtrLen sAuthorizationStr; + static StrPtrLen sRealmStr; + static StrPtrLen sAuthBasicStr; + static StrPtrLen sAuthDigestStr; + static StrPtrLen sUsernameStr; + static StrPtrLen sWildCardMatch; + static StrPtrLen sTrue; + static StrPtrLen sFalse; + + void Clean(); + + + private: + + StrPtrLen fCurrentAuthLine; +}; + +class BasicAuth : public Authenticator +{ + public: + BasicAuth() {}; + ~BasicAuth() {Clean();} + + SInt16 GetType() { return kBasicType; }; + Bool16 ParseParams(StrPtrLen *authParamsPtr); + void AttachAuthParams(StrPtrLen *theRequestPtr); + UInt32 ParamsLen(StrPtrLen *requestParams); + void ResetAuthParams() {}; + protected: + enum { kEncodedBufferLen = 256 }; + char fEncodedBuffer[kEncodedBufferLen]; + +}; + +class DigestAuth : public Authenticator +{ + public: + + SInt16 GetType() { return kDigestType; }; + Bool16 ParseParams(StrPtrLen *authParamsPtr); + void AttachAuthParams(StrPtrLen *theRequestPtr); + void ResetAuthParams(); + + StrPtrLen fcnonce; + StrPtrLen fNonceCountStr; + SInt16 fNonceCount; + + StrPtrLen fnonce; + StrPtrLen fopaque; + + StrPtrLen fqop; + StrPtrLen fAlgorithmStr; + StrPtrLen fStaleStr; + StrPtrLen fURIStr; + StrPtrLen fRequestDigestStr; + + SInt16 fAlgorithm; + Bool16 fStale; + + void GenerateAuthorizationRequestLine(StrPtrLen *requestPtr); + DigestAuth(); + ~DigestAuth(); + + enum {kMaxReqParams = 10}; + + private: + + struct ReqFields + { + StrPtrLen *fReqParamTags[kMaxReqParams]; + StrPtrLen *fReqParamValues[kMaxReqParams]; + Bool16 fQuoted[kMaxReqParams]; + SInt16 fNumFields; + }; + ReqFields fReqFields; + void ReqFieldsClean() { memset( (void *) fReqFields.fReqParamTags,0,sizeof(StrPtrLen *) * kMaxReqParams); + memset( (void *) fReqFields.fReqParamValues,0,sizeof(StrPtrLen *) * kMaxReqParams); + memset( (void *) fReqFields.fQuoted,0,sizeof(Bool16) * kMaxReqParams); + fReqFields.fNumFields = 0; + } + + void AddAuthParam(StrPtrLen *theTagPtr, StrPtrLen *theValuePtr, Bool16 quoted); + + UInt32 ParamsLen(StrPtrLen *requestParams); + void SetNonceCountStr(); + void MakeRequestDigest(); + void MakeCNonce(); + +// request tags + static StrPtrLen sCnonceStr; + static StrPtrLen sUriStr; + static StrPtrLen sResponseStr; + static StrPtrLen sNonceCountStr; + +// response tags + static StrPtrLen sStaleStr; + +// request and response tags + static StrPtrLen sNonceStr; + static StrPtrLen sQopStr; + static StrPtrLen sOpaqueStr; + static StrPtrLen sDomainStr; + static StrPtrLen sAlgorithmStr; + +// response values + static StrPtrLen sQopAuthStr; + static StrPtrLen sQopAuthIntStr; + static StrPtrLen sMD5Str; + static StrPtrLen sMD5SessStr; + +}; + +class AuthParser +{ + public: + AuthParser() {}; + ~AuthParser() {} + + Authenticator *ParseChallenge(StrPtrLen *challenge); +}; + +class RTSPClient +{ + public: + + // + // Before using this class, you must set the User Agent this way. + static void SetUserAgentStr(char* inUserAgent) { sUserAgent = inUserAgent; } + + // verbosePrinting = print out all requests and responses + RTSPClient(ClientSocket* inSocket, Bool16 verbosePrinting = false, char* inUserAgent = NULL); + ~RTSPClient(); + + // This must be called before any other function. Sets up very important info; sets the URL + void Set(const StrPtrLen& inURL); + + // + // This function allows you to add some special-purpose headers to your + // SETUP request if you want. These are mainly used for the caching proxy protocol, + // though there may be other uses. + // + // inLateTolerance: default = 0 (don't care) + // inMetaInfoFields: default = NULL (don't want RTP-Meta-Info packets + // inSpeed: default = 1 (normal speed) + void SetSetupParams(Float32 inLateTolerance, char* inMetaInfoFields); + + // Send specified RTSP request to server, wait for complete response. + // These return EAGAIN if transaction is in progress, OS_NoErr if transaction + // was successful, EINPROGRESS if connection is in progress, or some other + // error if transaction failed entirely. + OS_Error SendDescribe(Bool16 inAppendJunkData = false); + + OS_Error SendReliableUDPSetup(UInt32 inTrackID, UInt16 inClientPort); + OS_Error SendUDPSetup(UInt32 inTrackID, UInt16 inClientPort); + OS_Error SendTCPSetup(UInt32 inTrackID, UInt16 inClientRTPid, UInt16 inClientRTCPid); + OS_Error SendPlay(UInt32 inStartPlayTimeInSec, Float32 inSpeed = 1, UInt32 inTrackID = kUInt32_Max); //use a inTrackID of kUInt32_Max to turn off per stream headers + OS_Error SendPacketRangePlay(char* inPacketRangeHeader, Float32 inSpeed = 1); + OS_Error SendReceive(UInt32 inStartPlayTimeInSec); + OS_Error SendAnnounce(char *sdp); + OS_Error SendTeardown(); + OS_Error SendInterleavedWrite(UInt8 channel, UInt16 len, char*data,Bool16 *getNext); + + OS_Error SendSetParameter(); + OS_Error SendOptions(); + OS_Error SendOptionsWithRandomDataRequest(SInt32 dataSize); + // + // If you just want to send a generic request, do it this way + OS_Error SendRTSPRequest(iovec* inRequest, UInt32 inNumVecs); + + // + // Once you call all of the above functions, assuming they return an error, you + // should call DoTransaction until it returns OS_NoErr, then you can move onto your + // next request + OS_Error DoTransaction(); + + // + // If any of the tracks are being interleaved, this fetches a media packet from + // the control connection. This function assumes that SendPlay has already completed + // successfully and media packets are being sent. + OS_Error GetMediaPacket( UInt32* outTrackID, Bool16* outIsRTCP, + char** outBuffer, UInt32* outLen); + + // + // If any of the tracks are being interleaved, this puts a media packet to the control + // connection. This function assumes that SendPlay has already completed + // successfully and media packets are being sent. + OS_Error PutMediaPacket( UInt32 inTrackID, Bool16 isRTCP, char* inBuffer, UInt16 inLen); + + // set name and password for authentication in case we are challenged + void SetName(char *name); + void SetPassword(char *password); + + // set control id string default is "trackID" + void SetControlID(char* controlID); + + // level 0, 1, 2, or 3 + void SetVerboseLevel(UInt32 level) { fVerboseLevel = level; } + + //Sets the 3GPP-Link-Char header values; set to all 0 to not send this header + void Set3GPPLinkChars(UInt32 GBW = 0, UInt32 MBW = 0, UInt32 MTD = 0) + { + fGuarenteedBitRate = GBW; + fMaxBitRate = MBW; + fMaxTransferDelay = MTD; + } + //Use 0 for undefined + void SetBandwidth(UInt32 bps = 0) { fBandwidth = bps; } + void Set3GPPRateAdaptation(UInt32 bufferSpace = 0, UInt32 delayTime = 0) + { + fBufferSpace = bufferSpace; + fDelayTime = delayTime; + } + + // ACCESSORS + + StrPtrLen* GetURL() { return &fURL; } + UInt32 GetStatus() { return fStatus; } + StrPtrLen* GetSessionID() { return &fSessionID; } + UInt16 GetServerPort() { return fServerPort; } + UInt32 GetContentLength() { return fContentLength; } + char* GetContentBody() { return fRecvContentBuffer; } + ClientSocket* GetSocket() { return fSocket; } + UInt32 GetTransportMode() { return fTransportMode; } + void SetTransportMode(UInt32 theMode) { fTransportMode = theMode; }; + + char* GetResponse() { return fRecvHeaderBuffer; } + UInt32 GetResponseLen() { return fHeaderLen; } + Bool16 IsTransactionInProgress() { return fState != kInitial; } + Bool16 IsVerbose() { return fVerboseLevel >= 1; } + + // If available, returns the SSRC associated with the track in the PLAY response. + // Returns 0 if SSRC is not available. + UInt32 GetSSRCByTrack(UInt32 inTrackID); + + enum { kPlayMode=0,kPushMode=1,kRecordMode=2}; + + // + // If available, returns the RTP-Meta-Info field ID array for + // a given track. For more details, see RTPMetaInfoPacket.h + RTPMetaInfoPacket::FieldID* GetFieldIDArrayByTrack(UInt32 inTrackID); + + enum + { + kMinNumChannelElements = 5, + kReqBufSize = 4095, + kMethodBuffLen = 24 //buffer for "SETUP" or "PLAY" etc. + }; + + OSMutex* GetMutex() { return &fMutex; } + + private: + static char* sUserAgent; + + + // Helper methods + void ParseInterleaveSubHeader(StrPtrLen* inSubHeader); + void ParseRTPInfoHeader(StrPtrLen* inHeader); + void ParseRTPMetaInfoHeader(StrPtrLen* inHeader); + //Use a inTrackID of kUInt32_Max to turn the Rate-Adaptation header off + void Attach3GPPHeaders(StringFormatter &fmt, UInt32 inTrackID = kUInt32_Max); + + // Call this to receive an RTSP response from the server. + // Returns EAGAIN until a complete response has been received. + OS_Error ReceiveResponse(); + + OSMutex fMutex;//this data structure is shared! + + AuthParser fAuthenticationParser; + Authenticator *fAuthenticator; // only one will be supported + + ClientSocket* fSocket; + UInt32 fVerboseLevel; + + // Information we need to send the request + StrPtrLen fURL; + UInt32 fCSeq; + + // authenticate info + StrPtrLen fName; + StrPtrLen fPassword; + + // Response data we get back + UInt32 fStatus; + StrPtrLen fSessionID; + UInt16 fServerPort; + UInt32 fContentLength; + //StrPtrLen fRTPInfoHeader; + + // Special purpose SETUP params + char* fSetupHeaders; + + // If we are interleaving, this maps channel numbers to track IDs + struct ChannelMapElem + { + UInt32 fTrackID; + Bool16 fIsRTCP; + }; + ChannelMapElem* fChannelTrackMap; + UInt8 fNumChannelElements; + + //Maps between trackID and SSRC number + struct SSRCMapElem + { + UInt32 fTrackID; + UInt32 fSSRC; + SSRCMapElem(UInt32 trackID = kUInt32_Max, UInt32 inSSRC = 0) + : fTrackID(trackID), fSSRC(inSSRC) + {} + }; + SVector fSSRCMap; + + // For storing FieldID arrays + struct FieldIDArrayElem + { + UInt32 fTrackID; + RTPMetaInfoPacket::FieldID fFieldIDs[RTPMetaInfoPacket::kNumFields]; + }; + FieldIDArrayElem* fFieldIDMap; + UInt32 fNumFieldIDElements; + UInt32 fFieldIDMapSize; + + // If we are interleaving, we need this stuff to support the GetMediaPacket function + char* fPacketBuffer; + UInt32 fPacketBufferOffset; + Bool16 fPacketOutstanding; + + + // Data buffers + char fMethod[kMethodBuffLen]; // holds the current method + char fSendBuffer[kReqBufSize + 1]; // for sending requests + char fRecvHeaderBuffer[kReqBufSize + 1];// for receiving response headers + char* fRecvContentBuffer; // for receiving response body + + // Tracking the state of our receives + UInt32 fContentRecvLen; + UInt32 fHeaderRecvLen; + UInt32 fHeaderLen; + UInt32 fSetupTrackID; //is valid during a Setup Transaction + + enum { kInitial, kRequestSending, kResponseReceiving, kHeaderReceived }; + UInt32 fState; + + //UInt32 fEventMask; + Bool16 fAuthAttempted; + UInt32 fTransportMode; + + // + // For tracking media data that got read into the header buffer + UInt32 fPacketDataInHeaderBufferLen; + char* fPacketDataInHeaderBuffer; + + + char* fUserAgent; +static char* sControlID; + char* fControlID; + + //These values are for the wireless links only -- not end-to-end + //For the following values; use 0 for undefined. + UInt32 fGuarenteedBitRate; //kbps + UInt32 fMaxBitRate; //kbps + UInt32 fMaxTransferDelay; //milliseconds + UInt32 fBandwidth; //bps + UInt32 fBufferSpace; //bytes + UInt32 fDelayTime; //milliseconds + + struct InterleavedParams + { + char *extraBytes; + int extraLen; + UInt8 extraChannel; + int extraByteOffset; + }; + static InterleavedParams sInterleavedParams; + +}; + +#endif //__CLIENT_SESSION_H__ diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt new file mode 100644 index 0000000..9482fbf --- /dev/null +++ b/ReleaseNotes.txt @@ -0,0 +1,659 @@ + + RELEASE NOTES + Darwin Streaming Server + +These release notes are for the current development version of Darwin +Streaming Server. + +------------------------------------------------------------------------ +Date: 8/7/2001 +Version: 3.0.1+development [v349] +cvs tag: qtssServer-349 + +* Updated build platforms: Solaris 8, Redhat Linux 7.1, FreeBSD 4.3, + Mac OS X 10.1 +* Retransmit/overbuffering (Skip Protection) algorithms tuned + substantially +* "reliable_udp" attribute defaults to "enabled" +* Various WebAdmin fixes for Windows platform + +------------------------------------------------------------------------ +Date: 8/9/2001 +Version: 3.0.1+development [v350] +cvs tag: qtssServer-350 + +* Minor bug fixes. + +------------------------------------------------------------------------ +Date: 8/14/2001 +Version: 3.0.1+development [v351] +cvs tag: qtssServer-351 + +* QTSSFileModule - enabled caching based on preferences (shared and + private). +* Add buffer delay to on demand movies. +* QTFile - code cleanup. +* Changed some paths for the installer. +* Added support for MP3Broadcaster on Windows. + +------------------------------------------------------------------------ +Date: 8/16/2001 +Version: 3.0.1+development [v352] +cvs tag: qtssServer-352 + +* Added MP3StreamingModule to all builds. +* Fixed gdb script to execute QTSS from build dir on MacOSX. + +------------------------------------------------------------------------ +Date: 8/19/2001 +Version: 3.0.1+development [v353] +cvs tag: qtssServer-353 + +* Added: + add_seconds_to_client_buffer_delay -- Increases client buffer + delay before playing movie. + record_movie_file_sdp -- Records a .sdp file for a .mov file. + Appears as moviename.mov.sdp + enable_movie_file_sdp -- Plays a .mov.sdp file for a .mov movie. +* Fixed server to play .mov.sdp files. +* QTSSFileModule enables both private and shared movie cache buffers. + +------------------------------------------------------------------------ +Date: 8/23/2001 +Version: 3.0.1+development [v354] +cvs tag: qtssServer-354 + +* Added prefs for MP3StreamingModule. +* Default Reliable UDP dir is / on UNIX and \ on NT +* Use Enable Reliable UDP pref to disable Reliable UDP +* Included new Web Admin +* Changed relay preferences to XML format. + Old relay config files will not work. + +------------------------------------------------------------------------ +Date: 08/28/2001 +Version: 3.0.1+development [v355] +cvs tag: qtssServer-355 + +* Added preference: + RTSP_error_message to enable debugging error strings returned in + the content body of RTSP error responses. + +* Changed RTSP code to: + 1) require a Cseq field for OPTIONS requests + 2) allow missing Range headers in a PLAY request (defaults to entire movie) + 3) add content length header to Error responses containing error messages + 4) add SDP o= and t= fields to on-demand movie Describe responses + +* Changed Relay to relay incoming ANNOUNCEd broadcasts + 1) All ANNOUNCEd broadcasts, or + 2) All broadcasts from an IP address + 3) A broadcast URL from an IP address + + (a) Can still configure the relay to relay UDP streams + (b) Can pull an SDP or movie from source and relay it + +* Changed Relay to ANNOUNCE the SDPs to its destinations + 1) for ANNOUNCED sources + 2) for sources that are pulled from the source server + +* Changed Relay config file to be in XML format + 1) relayconfig.xml-Sample will be installed instead of old streamingrelay.conf + +* Added General Settings and Change Password to UI + 1) Require existing password to change to new + 2) Require the user to enter the same password twice + 3) Change the auth cookie so the user won't have to re-authenticate + +------------------------------------------------------------------------------------------------- +Date: 8/31/2001 +Version: 3.0.1+development [v356] +cvs tag: qtssServer-356 + +* Admin Module change + 1) Added an admin module pref called "AdministratorGroup" with + a default value of "admin" + 2) Changed Admin Module to allow all users that belong to the + AdministratorGroup, instead of allowing only one user called + "streamingadmin". + 3) A default qtgroups gets installed from now. The default qtusers + file has user 'streamingadmin' with password 'default'. The default + qtgroups file has the group 'admin' with the user 'streamingadmin'. + 4) The login name for the web admin can be changed by adding the user + to the qtusers file and adding the user to the 'admin' group in the + qtgroups file. + +* Added "RTSP_debug_printfs" server pref to show the incoming and outgoing RTSP. + Fixed Session header response to remove ";timeout=" field if the value is not defined. + Fixed .mov.sdp Describe response to include the Last-Modified and Cache-Control headers. + Fixed RTSP Session header parser to also stop on a ';' + Changes shared file buffer size to default to maximum size if the bit-rate is 0 and no fixed + buffer size is defined. + +------------------------------------------------------------------------------------------------- +Date: 9/04/2001 +Version: 3.0.1+development [v357] +cvs tag: qtssServer-357 + +* more implementation code for Web admin added. +* New XML parser for reading prefs. +* full implementation of MP3 streaming module is included. + +------------------------------------------------------------------------------------------------- +Date: 9/06/2001 +Version: 3.0.1+development [v359] +cvs tag: qtssServer-359 + +* Relay that pulls source from the same server can be setup now + [rtsp_source SOURCE with own IP address as the source_addr] + +------------------------------------------------------------------------------------------------- +Date: 9/10/2001 +Version: 3.0.1+development [v361] +cvs tag: qtssServer-361 + +* UI: play and stop buttons in playlist screen now start/stop playlists immediately +* UI: Start/Stop Playlists button removed +* UI: Login screen now focuses on username field on load +* Bug fixed when starting or stopping playlists other than the top one in the list + +------------------------------------------------------------------------------------------------- +Date: 9/10/2001 +Version: 3.0.1+development [v362] +cvs tag: qtssServer-362 + +* Same as v361 above. + +------------------------------------------------------------------------------------------------- +Date: 9/12/2001 +Version: 3.0.1+development [v363] +cvs tag: qtssServer-363 + +* Better buffer management in mp3 reflector +* Fixed memory leak in mp3 reflector +* UI: changed confirmation to message on-screen instead of dialog +* Icons in playlist builder show up correctly +* MP3 Broadcast password added to general settings +* Secure Admin added to settings + + +------------------------------------------------------------------------------------------------- +Date: 9/17/2001 +Version: 3.0.1+development [v364] +cvs tag: qtssServer-364 + +* Announced source relays can be set up even if the announced + SDP is read-protected ( the relay module asks the server + to automatically authenticate the request it makes since it + is in the same process) + +* The admin server streamingadminserver.pl now listens on a different + port for ssl requests. The default non-ssl port is 1220 (as before) + and the default ssl port is 1240 (for now). The ssl port + can be configured by adding the line sslport=xxxx in the + streamingadminserver.conf config file + +* The admin server redirects the requests dynamically based on the + value of the 'ssl' config parameter. If 'ssl=0', it will redirect + all requests coming in on the ssl port to the non-ssl port and if 'ssl=1' + it will redirect all requests coming in on the non-ssl port to the + ssl port. + For ex: if ssl is ON and the request made is + http://localhost:1220/mypage.html + the server will redirect this request to + https://localhost:1240/mypage.html + Similarly, if ssl is OFF, it will redirect the latter request to the former. + +* The admin server writes its pid to a file (for MacOSX and POSIX platforms only) + pointed to by the config parameter 'pidfile'. There is no default value for + this parameter as of now, so no pid file will be written until this is set. + +* The admin server has a new config parameter 'crtfile' to specify the ssl + certificate file, apart from the parameter 'keyfile' which specifies the ssl + private key file. The default values for both are the same filename, so that + the key and cert could be in the same file (just as before). + +* The admin server supports password protected RSA key for ssl. A new config parameter + 'keypasswordfile' can be set to the file that contains the password. If it + is blank or the parameter isn't set, it is assumed that the key is not encrypted. + The default for 'keypasswordfile' is not set so that the usual case of unencrypted + RSA key works. + +* qtpasswd reports an error if the realm line is missing in the users file. The realm + line should not be hand-edited ever! + +------------------------------------------------------------------------------------------------- +Date: 9/18/2001 +Version: 3.0.1+development [v365] +cvs tag: qtssServer-365 + +* Better handling of client-side flow control issues in the MP3 streaming module. + +* StreamingLoadTool updated. When the destination IP address is bad or the + "streamingloadtool.mov" file can't be found, the tool will continue to work against the + URLs that are valid. + +------------------------------------------------------------------------------------------------- +Date: 9/20/2001 +Version: 3.0.1+development [v367] +cvs tag: qtssServer-367 + +* Added support for Shoutcast-style broadcasters that use the "ICY" protocol. + +* Playlist listings now shows all visible items in webadmin. + +* Fixed access log in web admin. + +* Placeholders for webadmin help added. + +------------------------------------------------------------------------------------------------- +Date: 9/24/2001 +Version: 4.0 Preview [v369] +cvs tag: qtssServer-369 + +* Fixed rebuffering problem in MPStreamingModule +* Admin server changes for Win32 +* Admin UI fixes + +------------------------------------------------------------------------------------------------- +Date: 9/26/2001 +Version: 4.0 Preview [v371] +cvs tag: qtssServer-371 + +* The windows install script (install.bat) installs DSS as a service, stops the old DSS service + if it were running, and then starts the new DSS service. [difference from earlier version: + the install was not stopping the old service nor was it starting the new one] + +------------------------------------------------------------------------------------------------- +Date: 9/27/2001 +Version: 4.0 Preview [v372] +cvs tag: qtssServer-372 + +* Help button fixed or added on all pages. + +* Fixed scrolling by dragging the scroll thumb in playlist detail. + +* Fixed Secure Administration setting in General Settings. + +* Fixed various Windows playlist bugs. + +------------------------------------------------------------------------------------------------- +Date: 9/28/2001 +Version: 4.0 Preview [v373] +cvs tag: qtssServer-373 + +* Relay can be set up on DSS Win32 now. + +------------------------------------------------------------------------------------------------- +Date: 10/1/2001 +Version: 4.0 Preview [v374] +cvs tag: qtssServer-374 + +* Play/stop status should now be more accurate + +* Logging option added to playlists + +------------------------------------------------------------------------------------------------- +Date: 10/2/2001 +Version: 4.0 Preview [v375] +cvs tag: qtssServer-375 + +* Alignment and playlist fixes for IE6. + +* Fixed starting/stopping of playlists on Windows server. + +------------------------------------------------------------------------------------------------- +Date: 10/3/2001 +Version: 4.0 Preview [v376] +cvs tag: qtssServer-376 + +* Fixed movie playlist relay data + +* Can now start QTSS from UI if it's not running. + +* streamingadminserver.pl doesn't listen on the ssl port (default: 1240) + if the Net::SSLeay module isn't running. This is because ssl cannot + work if the module isn't present. Checking/Un-checking ssl checkbox + will not do anything in this case. This doesn't affect ssl on MacOSX because + the perl ssl module (and openssl) are already installed on X. + +------------------------------------------------------------------------------------------------- +Date: 10/4/2001 +Version: 4.0 Preview [v378] +cvs tag: qtssServer-378 + +* Fixed bug where user can't restart server once it's died. + +* Fixed problem with default password. + +* Improved throughput on MP3 Reflector. + +------------------------------------------------------------------------------------------------- +Date: 10/4/2001 +Version: 4.0 Preview [v379] +cvs tag: qtssServer-379 + +* Another fix for qtusers file. + +------------------------------------------------------------------------------------------------- +Date: 10/8/2001 +Version: 4.0 Preview [v380] +cvs tag: qtssServer-380 + +* Posted form data on some screens such as General Settings will now return a redirect page. + This fixes some problems with refreshing and frames. + +* Changed the format of the default relay. + +* PID files are now deleted regardless of whether the PlaylistBroadcaster cleans them up. + +* Fixed some confirmation message problems in Netscape. + +------------------------------------------------------------------------------------------------- +Date: 10/10/2001 +Version: 4.0 Preview [v381] +cvs tag: qtssServer-381 + +* The server can bind to multiple ip addresses now. So the pref "bind_ip_addr" can be made + a list pref that has multiple values + + 10.9.8.0 + 10.9.8.1 + + This is not configurable through the Admin UI. + +* RTSP access logging is now on by default in streamserver.xml + +* MP3 access logging is now implemented. + +* qtpasswd usage has changed. Please run it with no options to get the new usage + before using it + +------------------------------------------------------------------------------------------------- +Date: 10/15/2001 +Version: 4.0 Preview [v382] +cvs tag: qtssServer-382 + +* qtpasswd compiles on windows + +------------------------------------------------------------------------------------------------- +Date: 10/18/2001 +Version: 4.0 Preview [v383] +cvs tag: qtssServer-383 + +* When the user hovers their mouse pointer over an item in a playlist, it will show a tooltip + with the full path. + +* Fixed bug where the user was logged out upon changing password. + +* Added error logging message to relay module. + +* Fixed problem where string overflow occured in some command line params of MP3Broadcaster. + +------------------------------------------------------------------------------------------------- +Date: 10/23/2001 +Version: 4.0 Preview [v384] +cvs tag: qtssServer-384 + +* The Windows Install script adds "C:\Program Files\Darwin Streaming Server" + to the system path so that at the command prompt, one can directly start PLB as follows + C:\>PlaylistBroadcaster.exe -v + qtpasswd.exe and MP3Broadcaster.exe can also be launched this way. + + Caveat: The changes don't get reflected at the command prompt immediately. + A restart of the machine fixed it but there may be a way to get the changes right away. + +* Fixed problems with cookies/setup asisstant on some installations of Windows. + +* Redesigned relay settings page (includes new std. listbox UI) + +* Relay detail now forces user to enter a source IP address. + +------------------------------------------------------------------------------------------------- +Date: 10/25/2001 +Version: 4.0 Preview [v385] +cvs tag: qtssServer-385 + +* Added code to the MP3 reflector to limit the number of MP3 connections to the server's + maximum as indicated in the server's preferences. + +* Added Type column to Connected Users page, which will display movie or music note icon to + describe the type of stream. + +* MP3 sessions are now included in the Connected Users page... one caveat being that at this + point you will only see them if there is more than one. A bug has already been filed against + the admin module regarding this. + +------------------------------------------------------------------------------------------------- +Date: 10/31/01 +Version: 4.0 Preview [v386] +cvs tag: qtssServer-386 + +* Added 'i' parameter to admin protocol to force indexed access to value. + +* Added double-click support to listboxes. + +* User can now click in grey area of scrollbars in playlists to page up/down. + +* User can hold their mouse down on the scroll arrows to progressively scroll down. + +* Support for Netscape 6.2 added. + +* MP3Broadcaster and PlaylistBroadcaster require separate execution for preflight + and broadcast. + +------------------------------------------------------------------------------------------------- +Date: 11/02/01 +Version: 4.0 Preview [v387] +cvs tag: qtssServer-387 + +* Added new code to the access logging module to indicate that times are in GMT or local time. + +* Added new code to the mp3 access log to indicate GMT or local time. + +* Made numerous changes to access logging to clean up code. + +* Fixed MP3 streaming module to only send meta-data when requested. + +* qtpasswd's buffer overflow and long username/realm problems are fixed. + +------------------------------------------------------------------------------------------------- +Date: 11/05/01 +Version: 4.0 [v388] +cvs tag: qtssServer-388 + +* qtaccess has a new keyword "AuthScheme" that can be used to override the + default auth scheme (which comes from the streaming server config file). + The two values that are allowed are "basic" and "digest". + + "AuthScheme basic" will set the auth scheme to basic for the movies protected + by the particular qtaccess file, whereas "AuthScheme digest" will set the + scheme to digest. + + Further documentation on its usage can be found in the sample qtaccess file + +* Fix launching PlaylistBroadcaster from UI. +------------------------------------------------------------------------------------------------- +Date: 11/09/01 +Version: 4.0 [v389] +cvs tag: qtssServer-389 + +* added code to update number of client sessions and byte throughput for MP3 sessions. + +* added new server attributes to contain MP3 session stats. + +------------------------------------------------------------------------------------------------- +Date: 11/12/01 +Version: 4.0 [v390] +cvs tag: qtssServer-390 + +* Playlist mountpoint cannot be longer than 255 chars - admin UI fix +* Added Relay name attribute to the relay session object attributes +* Fix - PLB sends a teardown when SigInt/ctrl-c +* Windows compile fix - no SIGPIPE on windows +* Other bug fixes + +------------------------------------------------------------------------------------------------- +Date: 11/14/01 +Version: 4.0 [v391] +cvs tag: qtssServer-391 + +* Playlist bug on windows - wrong path to the movie/mp3 files was being + written to the playlist file. +* Admin launches PLB/MP3B and shows it as started only if the pid is created + (It waits for a maximum of 10 seconds for the pid to be created before it + assumes that the broadcaster couldn't launch) +* PLB returns an error on preflight if all the movies in the list are bad + - didn't use to happen before +* sample.mp3 gets installed now + +------------------------------------------------------------------------------------------------- +Date: 11/16/01 +Version: 4.0 [v392] +cvs tag: qtssServer-392 + +* Filtering out invalid files in PLB UI. +* New relay stats page. + +------------------------------------------------------------------------------------------------- +Date: 11/25/01 +Version: 4.0 [v393] +cvs tag: qtssServer-393 + +* qtpasswd: Added -h option to display usage + Added -P option; takes in a filename to read the password from + New usage: + qtpasswd 4.0 [v392] built on: Nov 19 2001, 10:20:52 + Usage: qtpasswd [-F] [-f filename] [-c] [-r realm] [-p password] [-d] username + -F Don't ask for confirmation when deleting users or overwriting existing files. + -f Password file to manipulate (Default is "/Library/QuickTimeStreaming/Config/qtusers"). + -c Create new file. + -r The realm name to use when creating a new file via "-c" (Default is "Streaming Server"). + -p Allows entry of password at command line rather than prompting for it. + -P File to read the password from rather than prompting for it. + -d Delete the specified user. + -h Displays usage. + Note: + Usernames cannot be more than 255 characters long and must not include a colon [:]. + Passwords cannot be more than 80 characters long. + If the username/password contains whitespace or characters that may be + interpreted by the shell please enclose it in single quotes, + to prevent it from being interpolated. + +* Can have $, @ etc. in admin username and/or password + +* Added WinNT code to calculate CPU load for admin display. + +* Added c-duration, c-user-agent fields to the mp3 access log. + +* Added server start and stop comments to mp3 access log. + +------------------------------------------------------------------------------------------------- +Date: 11/29/01 +Version: 4.0 [v395] +cvs tag: qtssServer-395 + +* Added localizable strings for Japanese date suffixes. + +* Folders in playlist UI are now named, not numbered. The name is determined by the first title + the playlist is given, and it never changes afterward. + +------------------------------------------------------------------------------------------------- +Date: 11/29/01 +Version: 4.0 [v396] +cvs tag: qtssServer-396 + + +------------------------------------------------------------------------------------------------- +Date: 12/03/01 +Version: 4.0 [v397] +cvs tag: qtssServer-397 + + +------------------------------------------------------------------------------------------------- +Date: 12/05/01 +Version: 4.0 [v398] +cvs tag: qtssServer-398 + +* 5 second timeout added for MP3 retries when stream is flow controlled. + +------------------------------------------------------------------------------------------------- +Date: 12/05/01 +Version: 4.0 [v399] +cvs tag: qtssServer-399 + +* Admin username can no longer include spaces. + +------------------------------------------------------------------------------------------------- +Date: 12/07/01 +Version: 4.0 [v400] +cvs tag: qtssServer-400 + +* Bug fixes + +------------------------------------------------------------------------------------------------- +Date: 12/10/01 +Version: 4.0 [v401] +cvs tag: qtssServer-401 + +* Windows bug (unable to launch PLB/MP3B from Admin) fix - Not clearing out + the environment variable hash in the admin server for each request on Windows; + Otherwise, the launched programs (PLB/MP3B) were unable to locate DLLs, etc. + as the environment variables were not properly set. + +------------------------------------------------------------------------------------------------- +Date: 12/12/01 +Version: 4.0 [v402] +cvs tag: qtssServer-402 + +* Setup assistant bug fix -- can now set the MP3 password correctly from the Setup assistant. + +------------------------------------------------------------------------------------------------- +Date: 12/13/01 +Version: 4.0 [v403] +cvs tag: qtssServer-403 + +* Localization fix - strings with single quotes are now supported. Dialogs also converted to + Unicode automatically. + +------------------------------------------------------------------------------------------------- +Date: 12/14/01 +Version: 4.0 [v404] +cvs tag: qtssServer-404 + +* Fixed localization pop menu issues. + +* Fixed Perl playlist-lib bug. + +------------------------------------------------------------------------------------------------- +Date: 12/14/01 +Version: 4.0 [v405] +cvs tag: qtssServer-405 + +* Fixed PLB segmentation fault bug (if sdp reference movie is not given in config) + +------------------------------------------------------------------------------------------------- +Date: 2/27/01 +Version: 4.0 [v412] +cvs tag: qtssServer-412 + +* General bug fixes. + +------------------------------------------------------------------------------------------------- +Date: 3/21/2002 +Version: 4.0+ [v415] +cvs tag: qtssServer-415 + +* First submission to new build train + +------------------------------------------------------------------------------------------------- +Date: 4/24/2002 +Version: 4.0+ [v419] +cvs tag: qtssServer-419 + +* QTSSHomeDirectoryModule installed in /Library/QuickTimeStreaming/Modules, and enabled. + Supports serving movies from users' home directories + +------------------------------------------------------------------------------------------------- +Date: 3/12/2003 + diff --git a/SafeStdLib/DynamicModuleStdLib.cpp b/SafeStdLib/DynamicModuleStdLib.cpp new file mode 100644 index 0000000..af6cda6 --- /dev/null +++ b/SafeStdLib/DynamicModuleStdLib.cpp @@ -0,0 +1,196 @@ +/* + * + * @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: DynamicModuleStdLib.cpp + + Contains: Thread safe std lib calls for QTSS API dynamic modules + + +*/ + +#include +#include +#include +#include +#include "SafeStdLib.h" +#include "OSMutex.h" +#include "OS.h" +#include "QTSS.h" + + +#ifndef USE_DEFAULT_STD_LIB + +#if __Win32__ + +#define VSNprintf _vsnprintf + +#else + +#define VSNprintf vsnprintf + +#endif + +int qtss_printf(char *fmt, ...) +{ + if (fmt == NULL) + return 1; + + QTSS_LockStdLib(); + va_list args; + va_start(args,fmt); + int result = ::vprintf(fmt, args); + va_end(args); + QTSS_UnlockStdLib(); + + return result; +} + +int qtss_sprintf(char *buffer, const char *fmt, ...) +{ + if (buffer == NULL) + return -1; + + QTSS_LockStdLib(); + va_list args; + va_start(args,fmt); + int result = ::vsprintf(buffer, fmt, args); + va_end(args); + QTSS_UnlockStdLib(); + + return result; +} + +int qtss_fprintf(FILE *file, const char *fmt, ...) +{ + if (file == NULL) + return -1; + + QTSS_LockStdLib(); + va_list args; + va_start(args,fmt); + int result = ::vfprintf(file, fmt, args); + va_end(args); + QTSS_UnlockStdLib(); + + return result; +} + +int qtss_snprintf(char *str, size_t size, const char *fmt, ...) +{ + if (str == NULL) + return -1; + + QTSS_LockStdLib(); + va_list args; + va_start(args,fmt); + int result = ::VSNprintf(str, size, fmt, args); + va_end(args); + QTSS_UnlockStdLib(); + + return result; +} + +size_t qtss_strftime(char *buf, size_t maxsize, const char *format, const struct tm *timeptr) +{ + if (buf == NULL) + return 0; + + QTSS_LockStdLib(); + size_t result = ::strftime(buf, maxsize, format, timeptr); + QTSS_UnlockStdLib(); + + return result; +} + +#endif //USE_DEFAULT_STD_LIB + +char *qtss_strerror(int errnum, char* buffer, int buffLen) +{ + QTSS_LockStdLib(); + (void) ::strncpy( buffer, ::strerror(errnum), buffLen); + buffer[buffLen -1] = 0; //make sure it is null terminated even if truncated. + QTSS_UnlockStdLib(); + + return buffer; +} + +char *qtss_ctime(const time_t *timep, char* buffer, int buffLen) +{ +#if __MacOSX__ + Assert(buffLen >= 26); + return ::ctime_r(timep, buffer); +#else + QTSS_LockStdLib(); + (void) ::strncpy( buffer, ::ctime(timep), buffLen); + buffer[buffLen -1] = 0; //make sure it is null terminated even if truncated. + QTSS_UnlockStdLib(); + + return buffer; +#endif +} + +char *qtss_asctime(const struct tm *timeptr, char* buffer, int buffLen) +{ +#if __MacOSX__ + Assert(buffLen >= 26); + return ::asctime_r(timeptr, buffer); +#else + QTSS_LockStdLib(); + (void) ::strncpy( buffer, ::asctime(timeptr), buffLen); + buffer[buffLen -1] = 0; //make sure it is null terminated even if truncated. + QTSS_UnlockStdLib(); + + return buffer; +#endif +} + +struct tm *qtss_gmtime(const time_t *timep, struct tm *result) +{ +#if __MacOSX__ + return ::gmtime_r(timep, result); +#else + QTSS_LockStdLib(); + struct tm *time_result = ::gmtime(timep); + *result = *time_result; + QTSS_UnlockStdLib(); + + return result; +#endif +} + +struct tm *qtss_localtime(const time_t *timep, struct tm *result) +{ +#if __MacOSX__ + return ::localtime_r(timep, result); +#else + QTSS_LockStdLib(); + struct tm *time_result = ::localtime(timep); + *result = *time_result; + QTSS_UnlockStdLib(); + + return result; +#endif +} + diff --git a/SafeStdLib/InternalStdLib.cpp b/SafeStdLib/InternalStdLib.cpp new file mode 100644 index 0000000..2c5908b --- /dev/null +++ b/SafeStdLib/InternalStdLib.cpp @@ -0,0 +1,237 @@ +/* + * + * @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: InternalStdLib.cpp + + Contains: Thread safe std lib calls for internal modules and apps + + +*/ + + +#include +#include +#include +#include +#include "OSMutex.h" +#include "OS.h" + +#include "OSMemory.h" +#include "SafeStdLib.h" + + +static UInt64 sTotalChars=0; +static UInt32 sMaxTotalCharsInK = 100 * 1000;//100MB default +static int sMaxFileSizeReached = 0; +UInt32 qtss_getmaxprintfcharsinK() +{ + OSMutexLocker locker(OS::GetStdLibMutex()); + return sMaxTotalCharsInK; +} + +void qtss_setmaxprintfcharsinK(UInt32 newMaxCharsInK) +{ + OSMutexLocker locker(OS::GetStdLibMutex()); + sMaxTotalCharsInK = newMaxCharsInK; +} + +int qtss_maxprintf(const char *fmt, ...) +{ + if (fmt == NULL) + return -1; + + OSMutexLocker locker(OS::GetStdLibMutex()); + + if (sTotalChars > ( (UInt64) sMaxTotalCharsInK * 1024) ) + { + if (sMaxFileSizeReached == 0) + printf ("\nReached maximum configured output limit = %"_U32BITARG_"K\n", sMaxTotalCharsInK); + + sMaxFileSizeReached = 1; + + return -1; + } + sMaxFileSizeReached = 0; // in case maximum changes + + va_list args; + va_start(args,fmt); + int result = ::vprintf(fmt, args); + sTotalChars += result; + va_end(args); + + return result; +} + + + +#ifndef USE_DEFAULT_STD_LIB + +#if __Win32__ + +#define VSNprintf _vsnprintf + +#else + +#define VSNprintf vsnprintf + +#endif + + +int qtss_printf(const char *fmt, ...) +{ + if (fmt == NULL) + return -1; + + OSMutexLocker locker(OS::GetStdLibMutex()); + va_list args; + va_start(args,fmt); + int result = ::vprintf(fmt, args); + va_end(args); + + return result; +} + +int qtss_sprintf(char *buffer, const char *fmt, ...) +{ + if (buffer == NULL) + return -1; + + OSMutexLocker locker(OS::GetStdLibMutex()); + va_list args; + va_start(args,fmt); + int result = ::vsprintf(buffer, fmt, args); + va_end(args); + + return result; +} + +int qtss_fprintf(FILE *file, const char *fmt, ...) +{ + if (file == NULL) + return -1; + + OSMutexLocker locker(OS::GetStdLibMutex()); + va_list args; + va_start(args,fmt); + int result = ::vfprintf(file, fmt, args); + va_end(args); + + return result; +} + +int qtss_snprintf(char *str, size_t size, const char *fmt, ...) +{ + if (str == NULL) + return -1; + + OSMutexLocker locker(OS::GetStdLibMutex()); + va_list args; + va_start(args,fmt); + int result = ::VSNprintf(str, size, fmt, args); + va_end(args); + + return result; +} + +size_t qtss_strftime(char *buf, size_t maxsize, const char *format, const struct tm *timeptr) +{ + if (buf == NULL) + return 0; + + OSMutexLocker locker(OS::GetStdLibMutex()); + return ::strftime(buf, maxsize, format, timeptr); + +} + + +#endif //USE_DEFAULT_STD_LIB + + + +char *qtss_strerror(int errnum, char* buffer, int buffLen) +{ + OSMutexLocker locker(OS::GetStdLibMutex()); + (void) ::strncpy( buffer, ::strerror(errnum), buffLen); + buffer[buffLen -1] = 0; //make sure it is null terminated even if truncated. + + return buffer; +} + +char *qtss_ctime(const time_t *timep, char* buffer, int buffLen) +{ +#if __MacOSX__ + Assert(buffLen >= 26); + return ::ctime_r(timep, buffer); +#else + OSMutexLocker locker(OS::GetStdLibMutex()); + ::strncpy( buffer, ::ctime(timep), buffLen);//don't use terminator + buffer[buffLen -1] = 0; //make sure it is null terminated even if truncated. + + return buffer; +#endif +} + +char *qtss_asctime(const struct tm *timeptr, char* buffer, int buffLen) +{ +#if __MacOSX__ + Assert(buffLen >= 26); + return ::asctime_r(timeptr, buffer); +#else + OSMutexLocker locker(OS::GetStdLibMutex()); + ::strncpy( buffer, ::asctime(timeptr), buffLen); + buffer[buffLen -1] = 0; //make sure it is null terminated even if truncated. + + return buffer; +#endif +} + +struct tm *qtss_gmtime(const time_t *timep, struct tm *result) +{ +#if __MacOSX__ + return ::gmtime_r(timep, result); +#else + OSMutexLocker locker(OS::GetStdLibMutex()); + struct tm *time_result = ::gmtime(timep); + *result = *time_result; + + return result; +#endif +} + +struct tm *qtss_localtime(const time_t *timep, struct tm *result) +{ + #if __MacOSX__ + return ::localtime_r(timep, result); +#else + OSMutexLocker locker(OS::GetStdLibMutex()); + struct tm *time_result = ::localtime(timep); + *result = *time_result; + + return result; +#endif +} + + + diff --git a/Server.tproj/GenerateXMLPrefs.cpp b/Server.tproj/GenerateXMLPrefs.cpp new file mode 100644 index 0000000..307a1ee --- /dev/null +++ b/Server.tproj/GenerateXMLPrefs.cpp @@ -0,0 +1,188 @@ +/* + * + * @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: GenerateXMLPrefs.h + + Contains: Routine that updates a QTSS 1.x 2.x PrefsSource to the new XMLPrefsSource. + +*/ + +#include "GenerateXMLPrefs.h" +#include "QTSSDataConverter.h" +#include "QTSS.h" + +struct PrefConversionInfo +{ + char* fPrefName; + char* fModuleName; + QTSS_AttrDataType fPrefType; +}; + +static const PrefConversionInfo kPrefs[] = +{ + { "rtsp_timeout", NULL, qtssAttrDataTypeUInt32 }, + { "real_rtsp_timeout", NULL, qtssAttrDataTypeUInt32 }, + { "rtp_timeout", NULL, qtssAttrDataTypeUInt32 }, + { "maximum_connections", NULL, qtssAttrDataTypeSInt32 }, + { "maximum_bandwidth", NULL, qtssAttrDataTypeSInt32 }, + { "movie_folder", NULL, qtssAttrDataTypeCharArray }, + { "bind_ip_addr", NULL, qtssAttrDataTypeCharArray }, + { "break_on_assert", NULL, qtssAttrDataTypeBool16 }, + { "auto_restart", NULL, qtssAttrDataTypeBool16 }, + { "total_bytes_update", NULL, qtssAttrDataTypeUInt32 }, + { "average_bandwidth_update", NULL, qtssAttrDataTypeUInt32 }, + { "safe_play_duration", NULL, qtssAttrDataTypeUInt32 }, + { "module_folder", NULL, qtssAttrDataTypeCharArray }, + { "error_logfile_name", NULL, qtssAttrDataTypeCharArray }, + { "error_logfile_dir", NULL, qtssAttrDataTypeCharArray }, + { "error_logfile_interval", NULL, qtssAttrDataTypeUInt32 }, + { "error_logfile_size", NULL, qtssAttrDataTypeUInt32 }, + { "error_logfile_verbosity", NULL, qtssAttrDataTypeUInt32 }, + { "screen_logging", NULL, qtssAttrDataTypeBool16 }, + { "error_logging", NULL, qtssAttrDataTypeBool16 }, + { "drop_all_video_delay", NULL, qtssAttrDataTypeSInt32 }, + { "start_thinning_delay", NULL, qtssAttrDataTypeSInt32 }, + { "large_window_size", NULL, qtssAttrDataTypeSInt32 }, + { "window_size_threshold", NULL, qtssAttrDataTypeSInt32 }, + { "min_tcp_buffer_size", NULL, qtssAttrDataTypeUInt32 }, + { "max_tcp_buffer_size", NULL, qtssAttrDataTypeUInt32 }, + { "tcp_seconds_to_buffer", NULL, qtssAttrDataTypeFloat32 }, + { "do_report_http_connection_ip_address", NULL, qtssAttrDataTypeBool16 }, + { "default_authorization_realm", NULL, qtssAttrDataTypeCharArray }, + { "run_user_name", NULL, qtssAttrDataTypeCharArray }, + { "run_group_name", NULL, qtssAttrDataTypeCharArray }, + { "append_source_addr_in_transport", NULL, qtssAttrDataTypeBool16 }, + { "rtsp_port", NULL, qtssAttrDataTypeUInt16 }, + + { "request_logging", "QTSSAccessLogModule", qtssAttrDataTypeBool16 }, + { "request_logfile_name", "QTSSAccessLogModule", qtssAttrDataTypeCharArray }, + { "request_logfile_dir", "QTSSAccessLogModule", qtssAttrDataTypeCharArray }, + { "request_logfile_size", "QTSSAccessLogModule", qtssAttrDataTypeUInt32 }, + { "request_logfile_interval", "QTSSAccessLogModule", qtssAttrDataTypeUInt32 }, + + { "history_update_interval", "QTSSSvrControlModule", qtssAttrDataTypeUInt32 }, + + { "buffer_seconds", "QTSSFileModule", qtssAttrDataTypeUInt32 }, + { "sdp_url", "QTSSFileModule", qtssAttrDataTypeCharArray }, + { "admin_email", "QTSSFileModule", qtssAttrDataTypeCharArray }, + { "max_advance_send_time", "QTSSFileModule", qtssAttrDataTypeUInt32 }, + + { "reflector_delay", "QTSSReflectorModule", qtssAttrDataTypeUInt32 }, + { "reflector_bucket_size", "QTSSReflectorModule", qtssAttrDataTypeUInt32 }, + + { "web_stats_url", "QTSSWebStatsModule", qtssAttrDataTypeCharArray }, + + { "loss_thin_tolerance", "QTSSFlowControlModule", qtssAttrDataTypeUInt32 }, + { "num_losses_to_thin", "QTSSFlowControlModule", qtssAttrDataTypeUInt32 }, + { "loss_thick_tolerance", "QTSSFlowControlModule", qtssAttrDataTypeUInt32 }, + { "num_losses_to_thick", "QTSSFlowControlModule", qtssAttrDataTypeUInt32 }, + { "num_worses_to_thin", "QTSSFlowControlModule", qtssAttrDataTypeUInt32 }, + + { "relay_stats_url", "QTSSRelayModule", qtssAttrDataTypeCharArray }, + { "relay_prefs_file", "QTSSRelayModule", qtssAttrDataTypeCharArray }, + + { "num_conns_per_ip_addr", "QTSSSpamDefenseModule", qtssAttrDataTypeUInt32 }, + + { "modAccess_usersfilepath", "QTSSAccessModule", qtssAttrDataTypeCharArray }, + { "modAccess_groupsfilepath", "QTSSAccessModule", qtssAttrDataTypeCharArray }, + { "modAccess_qtaccessfilename", "QTSSAccessModule", qtssAttrDataTypeCharArray }, + + // + // This element will be used if the pref is something we don't know about. + // Just have unknown prefs default to be server prefs with a type of char + { NULL, NULL, qtssAttrDataTypeCharArray } +}; + +int GenerateAllXMLPrefs(FilePrefsSource* inPrefsSource, XMLPrefsParser* inXMLPrefs) +{ + for (UInt32 x = 0; x < inPrefsSource->GetNumKeys(); x++) + { + // + // Get the name of this pref + char* thePrefName = inPrefsSource->GetKeyAtIndex(x); + + // + // Find the information corresponding to this pref in the above array + UInt32 y = 0; + for ( ; kPrefs[y].fPrefName != NULL; y++) + if (::strcmp(thePrefName, kPrefs[y].fPrefName) == 0) + break; + + char* theTypeString = (char*)QTSSDataConverter::TypeToTypeString(kPrefs[y].fPrefType); + ContainerRef module = inXMLPrefs->GetRefForModule(kPrefs[y].fModuleName); + ContainerRef pref = inXMLPrefs->AddPref(module, thePrefName, theTypeString); + + char* theValue = inPrefsSource->GetValueAtIndex(x); + + static char* kTrue = "true"; + static char* kFalse = "false"; + + // + // If the pref is a Bool16, the new pref format uses "true" & "false", + // the old one uses "enabled" and "disabled", so we have to explicitly convert. + if (kPrefs[y].fPrefType == qtssAttrDataTypeBool16) + { + if (::strcmp(theValue, "enabled") == 0) + theValue = kTrue; + else + theValue = kFalse; + } + inXMLPrefs->AddPrefValue(pref, theValue); + } + + return inXMLPrefs->WritePrefsFile(); +} + +int GenerateStandardXMLPrefs(PrefsSource* inPrefsSource, XMLPrefsParser* inXMLPrefs) +{ + char thePrefBuf[1024]; + + for (UInt32 x = 0; kPrefs[x].fPrefName != NULL; x++) + { + char* theTypeString = (char*)QTSSDataConverter::TypeToTypeString(kPrefs[x].fPrefType); + ContainerRef module = inXMLPrefs->GetRefForModule(kPrefs[x].fModuleName); + ContainerRef pref = inXMLPrefs->AddPref(module, kPrefs[x].fPrefName, theTypeString); + + for (UInt32 y = 0; true; y++) + { + if (inPrefsSource->GetValueByIndex(kPrefs[x].fPrefName, y, thePrefBuf) == 0) + break; + + // + // If the pref is a Bool16, the new pref format uses "true" & "false", + // the old one uses "enabled" and "disabled", so we have to explicitly convert. + if (kPrefs[x].fPrefType == qtssAttrDataTypeBool16) + { + if (::strcmp(thePrefBuf, "enabled") == 0) + ::strcpy(thePrefBuf, "true"); + else + ::strcpy(thePrefBuf, "false"); + } + inXMLPrefs->AddPrefValue(pref, thePrefBuf); + } + } + + return inXMLPrefs->WritePrefsFile(); +} diff --git a/Server.tproj/GenerateXMLPrefs.h b/Server.tproj/GenerateXMLPrefs.h new file mode 100644 index 0000000..51d78a9 --- /dev/null +++ b/Server.tproj/GenerateXMLPrefs.h @@ -0,0 +1,37 @@ +/* + * + * @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: GenerateXMLPrefs.h + + Contains: Routine that updates a QTSS 1.x 2.x PrefsSource to the new XMLPrefsSource. + +*/ + +#include "PrefsSource.h" +#include "FilePrefsSource.h" +#include "XMLPrefsParser.h" + +int GenerateStandardXMLPrefs(PrefsSource* inPrefsSource, XMLPrefsParser* inXMLPrefs); +int GenerateAllXMLPrefs(FilePrefsSource* inPrefsSource, XMLPrefsParser* inXMLPrefs); diff --git a/Server.tproj/QTSSCallbacks.cpp b/Server.tproj/QTSSCallbacks.cpp new file mode 100644 index 0000000..1b44383 --- /dev/null +++ b/Server.tproj/QTSSCallbacks.cpp @@ -0,0 +1,956 @@ +/* + * + * @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: QTSSCallbacks.cpp + + Contains: Implements QTSS Callback functions. + + +*/ + +#include "QTSSCallbacks.h" +#include "QTSSDictionary.h" +#include "QTSSStream.h" +#include "OSMemory.h" +#include "RTSPRequestInterface.h" +#include "RTPSession.h" +#include "OS.h" +#include "EventContext.h" +#include "Socket.h" +#include "QTSSFile.h" +#include "QTSSSocket.h" +#include "QTSServerInterface.h" +#include "QTSSDataConverter.h" +#include "QTSSModule.h" + +#include + +#define __QTSSCALLBACKS_DEBUG__ 0 +#define debug_printf if (__QTSSCALLBACKS_DEBUG__) qtss_printf + + +void* QTSSCallbacks::QTSS_New(FourCharCode /*inMemoryIdentifier*/, UInt32 inSize) +{ + // + // This callback is now deprecated because the server no longer uses FourCharCodes + // for memory debugging. For clients that still use it, the default, non-debugging + // version of New is used. + + //return OSMemory::New(inSize, inMemoryIdentifier, false); + return OSMemory::New(inSize); +} + +void QTSSCallbacks::QTSS_Delete(void* inMemory) +{ + OSMemory::Delete(inMemory); +} + +void QTSSCallbacks::QTSS_Milliseconds(SInt64* outMilliseconds) +{ + if (outMilliseconds != NULL) + *outMilliseconds = OS::Milliseconds(); +} + +void QTSSCallbacks::QTSS_ConvertToUnixTime(SInt64 *inQTSS_MilliSecondsPtr, time_t* outSecondsPtr) +{ + if ( (NULL != outSecondsPtr) && (NULL != inQTSS_MilliSecondsPtr) ) + *outSecondsPtr = OS::TimeMilli_To_UnixTimeSecs(*inQTSS_MilliSecondsPtr); +} + + + +QTSS_Error QTSSCallbacks::QTSS_AddRole(QTSS_Role inRole) +{ + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + // Roles can only be added before modules have had their Initialize role invoked. + if ((theState == NULL) || (theState->curRole != QTSS_Register_Role)) + return QTSS_OutOfState; + + return theState->curModule->AddRole(inRole); +} + + + +QTSS_Error QTSSCallbacks::QTSS_LockObject(QTSS_Object inDictionary) +{ + if (inDictionary == NULL) + return QTSS_BadArgument; + + ((QTSSDictionary*)inDictionary)->GetMutex()->Lock(); + ((QTSSDictionary*)inDictionary)->SetLocked(true); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_UnlockObject(QTSS_Object inDictionary) +{ + if (inDictionary == NULL) + return QTSS_BadArgument; + + ((QTSSDictionary*)inDictionary)->SetLocked(false); + ((QTSSDictionary*)inDictionary)->GetMutex()->Unlock(); + + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_CreateObjectType(QTSS_ObjectType* outType) +{ + QTSS_ObjectType type = QTSSDictionaryMap::CreateNewMap(); + if (type == 0) + return QTSS_RequestFailed; + + *outType = type; + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_AddAttribute(QTSS_ObjectType inType, const char* inName, void* inUnused) +{ + // + // This call is deprecated, make the new call with sensible default arguments + return QTSSCallbacks::QTSS_AddStaticAttribute(inType, inName, inUnused, qtssAttrDataTypeUnknown); +} + +QTSS_Error QTSSCallbacks::QTSS_AddStaticAttribute(QTSS_ObjectType inObjectType, const char* inAttrName, void* inUnused, QTSS_AttrDataType inAttrDataType) +{ + Assert(inUnused == NULL); + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + // Static attributes can only be added before modules have had their Initialize role invoked. + if ((theState == NULL) || (theState->curRole != QTSS_Register_Role)) + return QTSS_OutOfState; + + UInt32 theDictionaryIndex = QTSSDictionaryMap::GetMapIndex(inObjectType); + if (theDictionaryIndex == QTSSDictionaryMap::kIllegalDictionary) + return QTSS_BadArgument; + + QTSSDictionaryMap* theMap = QTSSDictionaryMap::GetMap(theDictionaryIndex); + return theMap->AddAttribute(inAttrName, NULL, inAttrDataType, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe); +} + +QTSS_Error QTSSCallbacks::QTSS_AddInstanceAttribute(QTSS_Object inObject, const char* inAttrName, void* inUnused, QTSS_AttrDataType inAttrDataType) +{ + Assert(inUnused == NULL); + if ((inObject == NULL) || (inAttrName == NULL)) + return QTSS_BadArgument; + + return ((QTSSDictionary*)inObject)->AddInstanceAttribute(inAttrName, NULL, inAttrDataType, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModeDelete | qtssAttrModePreempSafe); +} + +QTSS_Error QTSSCallbacks::QTSS_RemoveInstanceAttribute(QTSS_Object inObject, QTSS_AttributeID inID) +{ + if (inObject == NULL || (inID == qtssIllegalAttrID) ) + return QTSS_BadArgument; + + return ((QTSSDictionary*)inObject)->RemoveInstanceAttribute(inID); +} + + +QTSS_Error QTSSCallbacks::QTSS_IDForAttr(QTSS_ObjectType inType, const char* inName, QTSS_AttributeID* outID) +{ + if (outID == NULL) + return QTSS_BadArgument; + + UInt32 theDictionaryIndex = QTSSDictionaryMap::GetMapIndex(inType); + if (theDictionaryIndex == QTSSDictionaryMap::kIllegalDictionary) + return QTSS_BadArgument; + + return QTSSDictionaryMap::GetMap(theDictionaryIndex)->GetAttrID(inName, outID); +} + +QTSS_Error QTSSCallbacks::QTSS_GetAttrInfoByIndex(QTSS_Object inObject, UInt32 inIndex, QTSS_Object* outAttrInfoObject) +{ + if (inObject == NULL) + return QTSS_BadArgument; + + return ((QTSSDictionary*)inObject)->GetAttrInfoByIndex(inIndex, (QTSSAttrInfoDict**)outAttrInfoObject); +} + +QTSS_Error QTSSCallbacks::QTSS_GetAttrInfoByID(QTSS_Object inObject, QTSS_AttributeID inAttrID, QTSS_Object* outAttrInfoObject) +{ + if (inObject == NULL || (inAttrID == qtssIllegalAttrID) ) + return QTSS_BadArgument; + + return ((QTSSDictionary*)inObject)->GetAttrInfoByID(inAttrID, (QTSSAttrInfoDict**)outAttrInfoObject); +} + +QTSS_Error QTSSCallbacks::QTSS_GetAttrInfoByName(QTSS_Object inObject, const char* inAttrName, QTSS_Object* outAttrInfoObject) +{ + if (inObject == NULL) + return QTSS_BadArgument; + + return ((QTSSDictionary*)inObject)->GetAttrInfoByName(inAttrName, (QTSSAttrInfoDict**)outAttrInfoObject); +} + + +QTSS_Error QTSSCallbacks::QTSS_GetValuePtr (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, void** outBuffer, UInt32* outLen) +{ + if ((inDictionary == NULL) || (outBuffer == NULL) || (outLen == NULL) || (inID == qtssIllegalAttrID) ) + return QTSS_BadArgument; + return ((QTSSDictionary*)inDictionary)->GetValuePtr(inID, inIndex, outBuffer, outLen); +} + + +QTSS_Error QTSSCallbacks::QTSS_GetValue (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, void* ioBuffer, UInt32* ioLen) +{ + if (inDictionary == NULL || (inID == qtssIllegalAttrID) ) + return QTSS_BadArgument; + return ((QTSSDictionary*)inDictionary)->GetValue(inID, inIndex, ioBuffer, ioLen); +} + +QTSS_Error QTSSCallbacks::QTSS_GetValueAsString (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, char** outString) +{ + if (inDictionary == NULL) + return QTSS_BadArgument; + return ((QTSSDictionary*)inDictionary)->GetValueAsString(inID, inIndex, outString); +} + +QTSS_Error QTSSCallbacks::QTSS_TypeToTypeString(const QTSS_AttrDataType inType, char** outTypeString) +{ + if (outTypeString == NULL) + return QTSS_BadArgument; + + *outTypeString = QTSSDataConverter::TypeToTypeString(inType); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_TypeStringToType(char* inTypeString, QTSS_AttrDataType* outType) +{ + if ((inTypeString == NULL) || (outType == NULL)) + return QTSS_BadArgument; + + *outType = QTSSDataConverter::TypeStringToType(inTypeString); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_StringToValue( char* inValueAsString, const QTSS_AttrDataType inType, void* ioBuffer, UInt32* ioBufSize) +{ + return QTSSDataConverter::StringToValue(inValueAsString,inType,ioBuffer,ioBufSize); +} + +QTSS_Error QTSSCallbacks::QTSS_ValueToString( void* inValue, const UInt32 inValueLen, const QTSS_AttrDataType inType, char** outString) +{ + if ((inValue == NULL) || (outString == NULL)) + return QTSS_BadArgument; + + *outString = QTSSDataConverter::ValueToString(inValue,inValueLen,inType); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_SetValue (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, const void* inBuffer, UInt32 inLen) +{ + if ((inDictionary == NULL) || ((inBuffer == NULL) && (inLen > 0)) || (inID == qtssIllegalAttrID) ) + return QTSS_BadArgument; + return ((QTSSDictionary*)inDictionary)->SetValue(inID, inIndex, inBuffer, inLen); +} + +QTSS_Error QTSSCallbacks::QTSS_SetValuePtr (QTSS_Object inDictionary, QTSS_AttributeID inID, const void* inBuffer, UInt32 inLen) +{ + if ((inDictionary == NULL) || ((inBuffer == NULL) && (inLen > 0))) + return QTSS_BadArgument; + return ((QTSSDictionary*)inDictionary)->SetValuePtr(inID, inBuffer, inLen); +} + +QTSS_Error QTSSCallbacks::QTSS_CreateObject (QTSS_Object inDictionary, QTSS_AttributeID inID, QTSS_ObjectType inType, UInt32* outIndex, QTSS_Object* outCreatedObject) +{ + if ((inDictionary == NULL) || (outCreatedObject == NULL) || (outIndex == NULL) || (inID == qtssIllegalAttrID) ) + return QTSS_BadArgument; + + QTSSDictionaryMap* theMap = NULL; + if (inType != qtssDynamicObjectType) + { + UInt32 theDictionaryIndex = QTSSDictionaryMap::GetMapIndex(inType); + if (theDictionaryIndex == QTSSDictionaryMap::kIllegalDictionary) + return QTSS_BadArgument; + + theMap = QTSSDictionaryMap::GetMap(theDictionaryIndex); + } + + return ((QTSSDictionary*)inDictionary)->CreateObjectValue(inID, outIndex, (QTSSDictionary**)outCreatedObject, theMap); +} + +QTSS_Error QTSSCallbacks::QTSS_GetNumValues (QTSS_Object inObject, QTSS_AttributeID inID, UInt32* outNumValues) +{ + if ((inObject == NULL) || (outNumValues == NULL) || (inID == qtssIllegalAttrID) ) + return QTSS_BadArgument; + + *outNumValues = ((QTSSDictionary*)inObject)->GetNumValues(inID); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_GetNumAttributes(QTSS_Object inObject, UInt32* outNumValues) +{ + + if (outNumValues == NULL) + return QTSS_BadArgument; + + if (inObject == NULL) + return QTSS_BadArgument; + + OSMutexLocker locker(((QTSSDictionary*)inObject)->GetMutex()); + + QTSSDictionaryMap* theMap = NULL; + *outNumValues = 0; + + // Get the Static Attribute count + theMap = ((QTSSDictionary*)inObject)->GetDictionaryMap(); + if (theMap != NULL) + *outNumValues += theMap->GetNumNonRemovedAttrs(); + // Get the Instance Attribute count + theMap = ((QTSSDictionary*)inObject)->GetInstanceDictMap(); + if (theMap != NULL) + *outNumValues += theMap->GetNumNonRemovedAttrs(); + + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_RemoveValue (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex) +{ + if (inObject == NULL) + return QTSS_BadArgument; + + return ((QTSSDictionary*)inObject)->RemoveValue(inID, inIndex); +} + + + +QTSS_Error QTSSCallbacks::QTSS_Write(QTSS_StreamRef inStream, void* inBuffer, UInt32 inLen, UInt32* outLenWritten, UInt32 inFlags) +{ + if (inStream == NULL) + return QTSS_BadArgument; + QTSS_Error theErr = ((QTSSStream*)inStream)->Write(inBuffer, inLen, outLenWritten, inFlags); + + // Server internally propogates POSIX errorcodes such as EAGAIN and ENOTCONN up to this + // level. The API guarentees that no POSIX errors get returned, so we have QTSS_Errors + // to replace them. So we have to replace them here. + if (theErr == EAGAIN) + return QTSS_WouldBlock; + else if (theErr > 0) + return QTSS_NotConnected; + else + return theErr; +} + +QTSS_Error QTSSCallbacks::QTSS_WriteV(QTSS_StreamRef inStream, iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, UInt32* outLenWritten) +{ + if (inStream == NULL) + return QTSS_BadArgument; + QTSS_Error theErr = ((QTSSStream*)inStream)->WriteV(inVec, inNumVectors, inTotalLength, outLenWritten); + + // Server internally propogates POSIX errorcodes such as EAGAIN and ENOTCONN up to this + // level. The API guarentees that no POSIX errors get returned, so we have QTSS_Errors + // to replace them. So we have to replace them here. + if (theErr == EAGAIN) + return QTSS_WouldBlock; + else if (theErr > 0) + return QTSS_NotConnected; + else + return theErr; +} + +QTSS_Error QTSSCallbacks::QTSS_Flush(QTSS_StreamRef inStream) +{ + if (inStream == NULL) + return QTSS_BadArgument; + QTSS_Error theErr = ((QTSSStream*)inStream)->Flush(); + + // Server internally propogates POSIX errorcodes such as EAGAIN and ENOTCONN up to this + // level. The API guarentees that no POSIX errors get returned, so we have QTSS_Errors + // to replace them. So we have to replace them here. + if (theErr == EAGAIN) + return QTSS_WouldBlock; + else if (theErr > 0) + return QTSS_NotConnected; + else + return theErr; +} + +QTSS_Error QTSSCallbacks::QTSS_Read(QTSS_StreamRef inStream, void* ioBuffer, UInt32 inBufLen, UInt32* outLengthRead) +{ + if ((inStream == NULL) || (ioBuffer == NULL)) + return QTSS_BadArgument; + QTSS_Error theErr = ((QTSSStream*)inStream)->Read(ioBuffer, inBufLen, outLengthRead); + + // Server internally propogates POSIX errorcodes such as EAGAIN and ENOTCONN up to this + // level. The API guarentees that no POSIX errors get returned, so we have QTSS_Errors + // to replace them. So we have to replace them here. + if (theErr == EAGAIN) + return QTSS_WouldBlock; + else if (theErr > 0) + return QTSS_NotConnected; + else + return theErr; +} + +QTSS_Error QTSSCallbacks::QTSS_Seek(QTSS_StreamRef inStream, UInt64 inNewPosition) +{ + if (inStream == NULL) + return QTSS_BadArgument; + return ((QTSSStream*)inStream)->Seek(inNewPosition); +} + + +QTSS_Error QTSSCallbacks::QTSS_Advise(QTSS_StreamRef inStream, UInt64 inPosition, UInt32 inAdviseSize) +{ + if (inStream == NULL) + return QTSS_BadArgument; + return ((QTSSStream*)inStream)->Advise(inPosition, inAdviseSize); +} + + + +QTSS_Error QTSSCallbacks::QTSS_OpenFileObject(char* inPath, QTSS_OpenFileFlags inFlags, QTSS_Object* outFileObject) +{ + if ((inPath == NULL) || (outFileObject == NULL)) + return QTSS_BadArgument; + + // + // Create a new file object + QTSSFile* theNewFile = NEW QTSSFile(); + QTSS_Error theErr = theNewFile->Open(inPath, inFlags); + + if (theErr != QTSS_NoErr) + delete theNewFile; // No module wanted to open the file. + else + *outFileObject = theNewFile; + + return theErr; +} + +QTSS_Error QTSSCallbacks::QTSS_CloseFileObject(QTSS_Object inFileObject) +{ + if (inFileObject == NULL) + return QTSS_BadArgument; + + QTSSFile* theFile = (QTSSFile*)inFileObject; + + theFile->Close(); + delete theFile; + return QTSS_NoErr; +} + + +QTSS_Error QTSSCallbacks::QTSS_CreateStreamFromSocket(int inFileDesc, QTSS_StreamRef* outStream) +{ + if (outStream == NULL) + return QTSS_BadArgument; + + if (inFileDesc < 0) + return QTSS_BadArgument; + + // + // Create a new socket object + *outStream = (QTSS_StreamRef)NEW QTSSSocket(inFileDesc); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_DestroySocketStream(QTSS_StreamRef inStream) +{ + if (inStream == NULL) + return QTSS_BadArgument; + + // + // Note that the QTSSSocket destructor will call close on its file descriptor. + // Calling module should not also close the file descriptor! (This is noted in the API) + QTSSSocket* theSocket = (QTSSSocket*)inStream; + delete theSocket; + return QTSS_NoErr; +} + + +QTSS_Error QTSSCallbacks::QTSS_AddService(const char* inServiceName, QTSS_ServiceFunctionPtr inFunctionPtr) +{ + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + // This may happen if this callback is occurring on module-created thread + if (theState == NULL) + return QTSS_OutOfState; + + // Roles can only be added before modules have had their Initialize role invoked. + if (theState->curRole != QTSS_Register_Role) + return QTSS_OutOfState; + + return QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kServiceDictIndex)-> + AddAttribute(inServiceName, (QTSS_AttrFunctionPtr)inFunctionPtr, qtssAttrDataTypeUnknown, qtssAttrModeRead); +} + +QTSS_Error QTSSCallbacks::QTSS_IDForService(const char* inTag, QTSS_ServiceID* outID) +{ + return QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kServiceDictIndex)-> + GetAttrID(inTag, outID); +} + +QTSS_Error QTSSCallbacks::QTSS_DoService(QTSS_ServiceID inID, QTSS_ServiceFunctionArgsPtr inArgs) +{ + // Make sure that the service ID is in fact valid + + QTSSDictionaryMap* theMap = QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kServiceDictIndex); + SInt32 theIndex = theMap->ConvertAttrIDToArrayIndex(inID); + if (theIndex < 0) + return QTSS_IllegalService; + + // Get the service function + QTSS_ServiceFunctionPtr theFunction = (QTSS_ServiceFunctionPtr)theMap->GetAttrFunction(theIndex); + + // Invoke it, return the result. + return (theFunction)(inArgs); +} + + +QTSS_Error QTSSCallbacks::QTSS_SendRTSPHeaders(QTSS_RTSPRequestObject inRef) +{ + if (inRef == NULL) + return QTSS_BadArgument; + + ((RTSPRequestInterface*)inRef)->SendHeader(); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_AppendRTSPHeader(QTSS_RTSPRequestObject inRef, + QTSS_RTSPHeader inHeader, + char* inValue, + UInt32 inValueLen) +{ + if ((inRef == NULL) || (inValue == NULL)) + return QTSS_BadArgument; + if (inHeader >= qtssNumHeaders) + return QTSS_BadArgument; + + StrPtrLen theValue(inValue, inValueLen); + ((RTSPRequestInterface*)inRef)->AppendHeader(inHeader, &theValue); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_SendStandardRTSPResponse(QTSS_RTSPRequestObject inRTSPRequest, + QTSS_Object inRTPInfo, + UInt32 inFlags) +{ + if ((inRTSPRequest == NULL) || (inRTPInfo == NULL)) + return QTSS_BadArgument; + + switch (((RTSPRequestInterface*)inRTSPRequest)->GetMethod()) + { + case qtssDescribeMethod: + ((RTPSession*)inRTPInfo)->SendDescribeResponse((RTSPRequestInterface*)inRTSPRequest); + return QTSS_NoErr; + case qtssSetupMethod: + { + // Because QTSS_SendStandardRTSPResponse supports sending a proper 304 Not Modified on a SETUP, + // but a caller typically won't be adding a stream for a 304 response, we have the policy of + // making the caller pass in the QTSS_ClientSessionObject instead. That means we need to do + // different things here depending... + if (((RTSPRequestInterface*)inRTSPRequest)->GetStatus() == qtssRedirectNotModified) + (void)((RTPSession*)inRTPInfo)->DoSessionSetupResponse((RTSPRequestInterface*)inRTSPRequest); + else + { + if (inFlags & qtssSetupRespDontWriteSSRC) + ((RTPStream*)inRTPInfo)->DisableSSRC(); + else + ((RTPStream*)inRTPInfo)->EnableSSRC(); + + ((RTPStream*)inRTPInfo)->SendSetupResponse((RTSPRequestInterface*)inRTSPRequest); + } + + return QTSS_NoErr; + } + case qtssPlayMethod: + case qtssRecordMethod: + ((RTPSession*)inRTPInfo)->SendPlayResponse((RTSPRequestInterface*)inRTSPRequest, inFlags); + return QTSS_NoErr; + case qtssPauseMethod: + ((RTPSession*)inRTPInfo)->SendPauseResponse((RTSPRequestInterface*)inRTSPRequest); + return QTSS_NoErr; + case qtssTeardownMethod: + ((RTPSession*)inRTPInfo)->SendTeardownResponse((RTSPRequestInterface*)inRTSPRequest); + return QTSS_NoErr; + case qtssAnnounceMethod: + ((RTPSession*)inRTPInfo)->SendAnnounceResponse((RTSPRequestInterface*)inRTSPRequest); + return QTSS_NoErr; + } + return QTSS_BadArgument; +} + + + + +QTSS_Error QTSSCallbacks::QTSS_AddRTPStream(QTSS_ClientSessionObject inClientSession, QTSS_RTSPRequestObject inRTSPRequest, QTSS_RTPStreamObject* outStream, QTSS_AddStreamFlags inFlags) +{ + if ((inClientSession == NULL) || (inRTSPRequest == NULL) ||(outStream == NULL)) + return QTSS_BadArgument; + return ((RTPSession*)inClientSession)->AddStream((RTSPRequestInterface*)inRTSPRequest, (RTPStream**)outStream, inFlags); +} + +QTSS_Error QTSSCallbacks::QTSS_Play(QTSS_ClientSessionObject inClientSession, QTSS_RTSPRequestObject inRTSPRequest, QTSS_PlayFlags inPlayFlags) +{ + if (inClientSession == NULL) + return QTSS_BadArgument; + return ((RTPSession*)inClientSession)->Play((RTSPRequestInterface*)inRTSPRequest, inPlayFlags); +} + +QTSS_Error QTSSCallbacks::QTSS_Pause(QTSS_ClientSessionObject inClientSession) +{ + if (inClientSession == NULL) + return QTSS_BadArgument; + ((RTPSession*)inClientSession)->Pause(); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_Teardown(QTSS_ClientSessionObject inClientSession) +{ + if (inClientSession == NULL) + return QTSS_BadArgument; + + ((RTPSession*)inClientSession)->Teardown(); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_RefreshTimeOut(QTSS_ClientSessionObject inClientSession) +{ + if (inClientSession == NULL) + return QTSS_BadArgument; + + ((RTPSession*)inClientSession)->RefreshTimeouts(); + return QTSS_NoErr; +} + + + +QTSS_Error QTSSCallbacks::QTSS_RequestEvent(QTSS_StreamRef inStream, QTSS_EventType inEventMask) +{ + // First thing to do is to alter the thread's module state to reflect the fact + // that an event is outstanding. + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + if (theState == NULL) + return QTSS_RequestFailed; + + if (theState->curTask == NULL) + return QTSS_OutOfState; + + theState->eventRequested = true; + + // Now, tell this stream to be ready for the requested event + QTSSStream* theStream = (QTSSStream*)inStream; + theStream->SetTask(theState->curTask); + theStream->RequestEvent(inEventMask); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_SignalStream(QTSS_StreamRef inStream) +{ + if (inStream == NULL) + return QTSS_BadArgument; + + QTSSStream* theStream = (QTSSStream*)inStream; + if (theStream->GetTask() != NULL) + theStream->GetTask()->Signal(Task::kReadEvent); + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_SetIdleTimer(SInt64 inMsecToWait) +{ + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + // This may happen if this callback is occurring on module-created thread + if (theState == NULL) + return QTSS_RequestFailed; + + if (theState->curTask == NULL) + return QTSS_OutOfState; + + theState->eventRequested = true; + theState->idleTime = inMsecToWait; + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_SetIdleRoleTimer(SInt64 inMsecToWait) +{ + + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + // This may happen if this callback is occurring on module-created thread + if (theState == NULL) + return QTSS_RequestFailed; + + if (theState->curModule == NULL) + return QTSS_RequestFailed; + + + QTSSModule* theModule = theState->curModule; + QTSS_ModuleState* thePrivateModuleState = theModule->GetModuleState(); + thePrivateModuleState->idleTime = inMsecToWait; + theModule->Signal(Task::kUpdateEvent); + + + return QTSS_NoErr; +} + +QTSS_Error QTSSCallbacks::QTSS_RequestLockedCallback() +{ + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + // This may happen if this callback is occurring on module-created thread + if (theState == NULL) + return QTSS_RequestFailed; + + if (theState->curTask == NULL) + return QTSS_OutOfState; + + theState->globalLockRequested = true; //x + + return QTSS_NoErr; +} + +Bool16 QTSSCallbacks::QTSS_IsGlobalLocked() +{ + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + // This may happen if this callback is occurring on module-created thread + if (theState == NULL) + return false; + + if (theState->curTask == NULL) + return false; + + return theState->isGlobalLocked; +} + +QTSS_Error QTSSCallbacks::QTSS_UnlockGlobalLock() +{ + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + // This may happen if this callback is occurring on module-created thread + if (theState == NULL) + return QTSS_RequestFailed; + + if (theState->curTask == NULL) + return QTSS_OutOfState; + + ((Task *)OSThread::GetCurrent())->GlobalUnlock(); + + theState->globalLockRequested = false; + theState->isGlobalLocked = false; + + + return QTSS_NoErr; +} + + +QTSS_Error QTSSCallbacks::QTSS_Authenticate(const char* inAuthUserName, const char* inAuthResourceLocalPath, const char* inAuthMoviesDir, QTSS_ActionFlags inAuthRequestAction, QTSS_AuthScheme inAuthScheme, QTSS_RTSPRequestObject ioAuthRequestObject) +{ + if((inAuthUserName == NULL) || (inAuthResourceLocalPath == NULL) || (inAuthMoviesDir == NULL) || (ioAuthRequestObject == NULL)) + return QTSS_BadArgument; + if(inAuthRequestAction == qtssActionFlagsNoFlags) + return QTSS_BadArgument; + if(inAuthScheme == qtssAuthNone) + return QTSS_BadArgument; + + // First create a RTSPRequestInterface object + // There is no session attached to it, so just pass in NULL for the RTSPSession + RTSPRequestInterface *request = (RTSPRequestInterface *) ioAuthRequestObject; + // Set all the attributes required by the authentication module, using the input values + (void) request->SetValue(qtssRTSPReqUserName, 0, inAuthUserName , ::strlen(inAuthUserName), QTSSDictionary::kDontObeyReadOnly); + (void) request->SetValue(qtssRTSPReqLocalPath, 0, inAuthResourceLocalPath , ::strlen(inAuthResourceLocalPath), QTSSDictionary::kDontObeyReadOnly); + (void) request->SetValue(qtssRTSPReqRootDir, 0, inAuthMoviesDir , ::strlen(inAuthMoviesDir), QTSSDictionary::kNoFlags); + (void) request->SetValue(qtssRTSPReqAction, 0, (const void *)&inAuthRequestAction , sizeof(QTSS_ActionFlags), QTSSDictionary::kNoFlags); + (void) request->SetValue(qtssRTSPReqAuthScheme, 0, (const void *)&inAuthScheme , sizeof(QTSS_AuthScheme), QTSSDictionary::kDontObeyReadOnly); + QTSSUserProfile *profile = request->GetUserProfile(); + (void) profile->SetValue(qtssUserName, 0, inAuthUserName, ::strlen(inAuthUserName), QTSSDictionary::kDontObeyReadOnly); + + + // Because this is a role being executed from inside a callback, we need to + // make sure that QTSS_RequestEvent will not work. + Task* curTask = NULL; + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + if (theState != NULL) + curTask = theState->curTask; + + // Setup the authentication param block + QTSS_RoleParams theAuthenticationParams; + theAuthenticationParams.rtspAthnParams.inRTSPRequest = request; + + QTSS_Error theErr = QTSS_RequestFailed; + + UInt32 x = 0; + UInt32 numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPAthnRole); + QTSSModule* theModulePtr = NULL; + Bool16 allowedDefault = QTSServerInterface::GetServer()->GetPrefs()->GetAllowGuestDefault(); + Bool16 allowed = allowedDefault; //server pref? + Bool16 hasUser = false; + Bool16 handled = false; + + + // Call all the modules that are registered for the RTSP Authorize Role + for ( ; x < numModules; x++) + { + request->SetAllowed(allowedDefault); + request->SetHasUser(false); + request->SetAuthHandled(false); + + debug_printf(" QTSSCallbacks::QTSS_Authenticate calling module module = %lu numModules=%lu\n", x,numModules); + theModulePtr = QTSServerInterface::GetModule(QTSSModule::kRTSPAthnRole, x); + theErr = QTSS_NoErr; + if (theModulePtr) + { + theErr = theModulePtr->CallDispatch(QTSS_RTSPAuthenticate_Role, &theAuthenticationParams); + debug_printf(" QTSSCallbacks::QTSS_Authorize calling module module = %lu numModules=%lu ModuleError=%ld\n", x,numModules, theErr); + } + else + { + debug_printf(" QTSSCallbacks::QTSS_Authorize calling module module = %lu is NULL! numModules=%lu\n", x,numModules); + continue; + } + allowed = request->GetAllowed(); + hasUser = request->GetHasUser(); + handled = request->GetAuthHandled(); + debug_printf("QTSSCallbacks::QTSS_Authenticate allowedDefault =%d allowed= %d hasUser = %d handled=%d \n",allowedDefault, allowed,hasUser, handled); + + + if (hasUser || handled ) //See RTSPSession.cpp::Run state=kAuthenticatingRequest + { + debug_printf(" QTSSCallbacks::QTSS_Authenticate skipping other modules fCurrentModule = %lu numModules=%lu\n", x,numModules); + break; + } + } + + + // Reset the curTask to what it was before this role started + if (theState != NULL) + theState->curTask = curTask; + + return theErr; +} + +QTSS_Error QTSSCallbacks::QTSS_Authorize(QTSS_RTSPRequestObject inAuthRequestObject, char** outAuthRealm, Bool16* outAuthUserAllowed) +{ + RTSPRequestInterface* request = (RTSPRequestInterface *) inAuthRequestObject; + if (request == NULL) + return QTSS_BadArgument; + + // Because this is a role being executed from inside a callback, we need to + // make sure that QTSS_RequestEvent will not work. + Task* curTask = NULL; + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + if (theState != NULL) + curTask = theState->curTask; + + QTSS_RoleParams theParams; + theParams.rtspRequestParams.inRTSPSession = NULL; + theParams.rtspRequestParams.inRTSPRequest = request; + theParams.rtspRequestParams.inClientSession = NULL; + + QTSS_Error theErr = QTSS_RequestFailed; + UInt32 x = 0; + UInt32 numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTSPAuthRole); + QTSSModule* theModulePtr = NULL; + Bool16 allowedDefault = QTSServerInterface::GetServer()->GetPrefs()->GetAllowGuestDefault(); + *outAuthUserAllowed = allowedDefault; + Bool16 allowed = allowedDefault; //server pref? + Bool16 hasUser = false; + Bool16 handled = false; + + + // Call all the modules that are registered for the RTSP Authorize Role + + for ( ; x < numModules; x++) + { + request->SetAllowed(true); + request->SetHasUser(false); + request->SetAuthHandled(false); + + debug_printf(" QTSSCallbacks::QTSS_Authorize calling module module = %lu numModules=%lu\n", x,numModules); + theModulePtr = QTSServerInterface::GetModule(QTSSModule::kRTSPAuthRole, x); + theErr = QTSS_NoErr; + if (theModulePtr) + { + if (__QTSSCALLBACKS_DEBUG__) + theModulePtr->GetValue(qtssModName)->PrintStr("QTSSModule::CallDispatch ENTER module=", "\n"); + + theErr = theModulePtr->CallDispatch(QTSS_RTSPAuthorize_Role, &theParams); + debug_printf(" QTSSCallbacks::QTSS_Authorize calling module module = %lu numModules=%lu ModuleError=%ld\n", x,numModules, theErr); + } + else + { debug_printf(" QTSSCallbacks::QTSS_Authorize calling module module = %lu is NULL! numModules=%lu\n", x,numModules); + continue; + } + + allowed = request->GetAllowed(); + hasUser = request->GetHasUser(); + handled = request->GetAuthHandled(); + debug_printf("QTSSCallbacks::QTSS_Authorize allowedDefault =%d allowed= %d hasUser = %d handled=%d \n",allowedDefault, allowed,hasUser, handled); + + *outAuthUserAllowed = allowed; + //notes: + //if (allowed && !handled) break; //old module + //if (!allowed && handled) /new module handled the request but not authorized keep trying + //if (allowed && handled) //new module allowed but keep trying in case someone denies. + + if (!allowed && !handled) //old module break on !allowed + { + debug_printf("RTSPSession.cpp::Run(kAuthorizingRequest) skipping other modules fCurrentModule = %lu numModules=%lu\n", x,numModules); + break; + } + } + + // outAuthRealm is set to the realm that is given by the module that has denied authentication + StrPtrLen* realm = request->GetValue(qtssRTSPReqURLRealm); + *outAuthRealm = realm->GetAsCString(); + + return theErr; +} + +void QTSSCallbacks::QTSS_LockStdLib() +{ + OS::GetStdLibMutex()->Lock(); +} + +void QTSSCallbacks::QTSS_UnlockStdLib() +{ + OS::GetStdLibMutex()->Unlock(); +} + diff --git a/Server.tproj/QTSSCallbacks.h b/Server.tproj/QTSSCallbacks.h new file mode 100644 index 0000000..a84e720 --- /dev/null +++ b/Server.tproj/QTSSCallbacks.h @@ -0,0 +1,164 @@ +/* + * + * @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: QTSSCallbacks.h + + Contains: All the QTSS callback functions + + +*/ + + +#ifndef __QTSSCALLBACKS_H__ +#define __QTSSCALLBACKS_H__ + +#include "QTSS.h" + +class QTSSCallbacks +{ + public: + + // MEMORY ROUTINES + + static void* QTSS_New(FourCharCode inMemoryIdentifier, UInt32 inSize); + static void QTSS_Delete(void* inMemory); + + + // TIME ROUTINES + static void QTSS_Milliseconds(SInt64* outMilliseconds); + static void QTSS_ConvertToUnixTime(SInt64* inQTSS_MilliSecondsPtr, time_t* outSecondsPtr); + + // STARTUP ROUTINES + + static QTSS_Error QTSS_AddRole(QTSS_Role inRole); + + // DICTIONARY ROUTINES + + // DICTIONARY LOCKING + static QTSS_Error QTSS_LockObject(QTSS_Object inDictionary); + static QTSS_Error QTSS_UnlockObject(QTSS_Object inDictionary); + + // CREATE NEW OBJECT TYPE + static QTSS_Error QTSS_CreateObjectType(QTSS_ObjectType* outType); + + // ADD ATTRIBUTE + + static QTSS_Error QTSS_AddAttribute(QTSS_ObjectType inType, const char* inTag, void* inUnused); + static QTSS_Error QTSS_AddStaticAttribute(QTSS_ObjectType inObjectType, const char* inAttrName, void* inUnused, QTSS_AttrDataType inAttrDataType); + static QTSS_Error QTSS_AddInstanceAttribute(QTSS_Object inObject, const char* inAttrName, void* inUnused, QTSS_AttrDataType inAttrDataType); + + // REMOVE ATTRIBUTE + + static QTSS_Error QTSS_RemoveInstanceAttribute(QTSS_Object inObject, QTSS_AttributeID inID); + + // ATTRIBUTE INFO + static QTSS_Error QTSS_IDForAttr(QTSS_ObjectType inType, const char* inTag, QTSS_AttributeID* outID); + + static QTSS_Error QTSS_GetAttrInfoByName(QTSS_Object inObject, const char* inAttrName, QTSS_Object* outAttrInfoObject); + static QTSS_Error QTSS_GetAttrInfoByID(QTSS_Object inObject, QTSS_AttributeID inAttrID, QTSS_Object* outAttrInfoObject); + static QTSS_Error QTSS_GetAttrInfoByIndex(QTSS_Object inObject, UInt32 inIndex, QTSS_Object* outAttrInfoObject); + + static QTSS_Error QTSS_GetNumAttributes(QTSS_Object inObject, UInt32* outNumValues); + + // TYPE INFO & TYPE CONVERSIONS + + static QTSS_Error QTSS_TypeToTypeString(const QTSS_AttrDataType inType, char** outTypeString); + static QTSS_Error QTSS_TypeStringToType( char* inTypeString, QTSS_AttrDataType* outType); + static QTSS_Error QTSS_StringToValue( char* inValueAsString, const QTSS_AttrDataType inType, void* ioBuffer, UInt32* ioBufSize); + static QTSS_Error QTSS_ValueToString( void* inValue, const UInt32 inValueLen, const QTSS_AttrDataType inType, char** outString); + + // ATTRIBUTE VALUES + + static QTSS_Error QTSS_GetValuePtr (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, void** outBuffer, UInt32* outLen); + static QTSS_Error QTSS_GetValue (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, void* ioBuffer, UInt32* ioLen); + static QTSS_Error QTSS_GetValueAsString (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, char** outString); + + static QTSS_Error QTSS_SetValue (QTSS_Object inDictionary, QTSS_AttributeID inID, UInt32 inIndex, const void* inBuffer, UInt32 inLen); + static QTSS_Error QTSS_SetValuePtr (QTSS_Object inDictionary, QTSS_AttributeID inID, const void* inBuffer, UInt32 inLen); + static QTSS_Error QTSS_CreateObject (QTSS_Object inDictionary, QTSS_AttributeID inID, QTSS_ObjectType inType, UInt32* outIndex, QTSS_Object* outCreatedObject); + static QTSS_Error QTSS_GetNumValues (QTSS_Object inObject, QTSS_AttributeID inID, UInt32* outNumValues); + static QTSS_Error QTSS_RemoveValue (QTSS_Object inObject, QTSS_AttributeID inID, UInt32 inIndex); + + // STREAM ROUTINES + + static QTSS_Error QTSS_Write(QTSS_StreamRef inStream, void* inBuffer, UInt32 inLen, UInt32* outLenWritten, QTSS_WriteFlags inFlags); + static QTSS_Error QTSS_WriteV(QTSS_StreamRef inStream, iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, UInt32* outLenWritten); + static QTSS_Error QTSS_Flush(QTSS_StreamRef inStream); + static QTSS_Error QTSS_Read(QTSS_StreamRef inRef, void* ioBuffer, UInt32 inBufLen, UInt32* outLengthRead); + static QTSS_Error QTSS_Seek(QTSS_StreamRef inRef, UInt64 inNewPosition); + static QTSS_Error QTSS_Advise(QTSS_StreamRef inRef, UInt64 inPosition, UInt32 inAdviseSize); + + // FILE SYSTEM ROUTINES + + static QTSS_Error QTSS_OpenFileObject(char* inPath, QTSS_OpenFileFlags inFlags, QTSS_Object* outFileObject); + static QTSS_Error QTSS_CloseFileObject(QTSS_Object inFileObject); + + // SOCKET ROUTINES + + static QTSS_Error QTSS_CreateStreamFromSocket(int inFileDesc, QTSS_StreamRef* outStream); + static QTSS_Error QTSS_DestroySocketStream(QTSS_StreamRef inStream); + + // SERVICE ROUTINES + + static QTSS_Error QTSS_AddService(const char* inServiceName, QTSS_ServiceFunctionPtr inFunctionPtr); + static QTSS_Error QTSS_IDForService(const char* inTag, QTSS_ServiceID* outID); + static QTSS_Error QTSS_DoService(QTSS_ServiceID inID, QTSS_ServiceFunctionArgsPtr inArgs); + + // RTSP ROUTINES + + static QTSS_Error QTSS_SendRTSPHeaders(QTSS_RTSPRequestObject inRef); + static QTSS_Error QTSS_AppendRTSPHeader(QTSS_RTSPRequestObject inRef, QTSS_RTSPHeader inHeader, char* inValue, UInt32 inValueLen); + static QTSS_Error QTSS_SendStandardRTSPResponse(QTSS_RTSPRequestObject inRTSPRequest, QTSS_Object inRTPInfo, UInt32 inFlags); + + // RTP ROUTINES + + static QTSS_Error QTSS_AddRTPStream(QTSS_ClientSessionObject inClientSession, QTSS_RTSPRequestObject inRTSPRequest, QTSS_RTPStreamObject* outStream, QTSS_AddStreamFlags inFlags); + static QTSS_Error QTSS_Play(QTSS_ClientSessionObject inClientSession, QTSS_RTSPRequestObject inRTSPRequest, QTSS_PlayFlags inPlayFlags); + static QTSS_Error QTSS_Pause(QTSS_ClientSessionObject inClientSession); + static QTSS_Error QTSS_Teardown(QTSS_ClientSessionObject inClientSession); + static QTSS_Error QTSS_RefreshTimeOut(QTSS_ClientSessionObject inClientSession); + + // ASYNC I/O ROUTINES + static QTSS_Error QTSS_RequestEvent(QTSS_StreamRef inStream, QTSS_EventType inEventMask); + static QTSS_Error QTSS_SignalStream(QTSS_StreamRef inStream); + static QTSS_Error QTSS_SetIdleTimer(SInt64 inMsecToWait); + static QTSS_Error QTSS_SetIdleRoleTimer(SInt64 inMsecToWait); + + + static QTSS_Error QTSS_RequestLockedCallback(); + static Bool16 QTSS_IsGlobalLocked(); + static QTSS_Error QTSS_UnlockGlobalLock(); + + // AUTHENTICATION AND AUTHORIZATION ROUTINE + static QTSS_Error QTSS_Authenticate(const char* inAuthUserName, const char* inAuthResourceLocalPath, const char* inAuthMoviesDir, QTSS_ActionFlags inAuthRequestAction, QTSS_AuthScheme inAuthScheme, QTSS_RTSPRequestObject ioAuthRequestObject); + static QTSS_Error QTSS_Authorize(QTSS_RTSPRequestObject inAuthRequestObject, char** outAuthRealm, Bool16* outAuthUserAllowed); + + static void QTSS_LockStdLib(); + static void QTSS_UnlockStdLib(); + private: + +}; + +#endif //__QTSSCALLBACKS_H__ diff --git a/Server.tproj/QTSSDataConverter.cpp b/Server.tproj/QTSSDataConverter.cpp new file mode 100644 index 0000000..84e1a8a --- /dev/null +++ b/Server.tproj/QTSSDataConverter.cpp @@ -0,0 +1,376 @@ +/* + * + * @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: QTSSDataConverter.cpp + + Contains: Utility routines for converting to and from + QTSS_AttrDataTypes and text + + Written By: Denis Serenyi + + Change History (most recent first): + +*/ + +#include "QTSSDataConverter.h" +#include "StrPtrLen.h" +#include "OSMemory.h" +#include +#include + + +static const StrPtrLen kEnabledStr("true"); +static const StrPtrLen kDisabledStr("false"); + +static char* kDataTypeStrings[] = +{ + "Unknown", + "CharArray", + "Bool16", + "SInt16", + "UInt16", + "SInt32", + "UInt32", + "SInt64", + "UInt64", + "QTSS_Object", + "QTSS_StreamRef", + "Float32", + "Float64", + "VoidPointer", + "QTSS_TimeVal" +}; + +static const char* kHEXChars={ "0123456789ABCDEF" }; + +static const UInt8 sCharToNums[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0-9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //10-19 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //30-39 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //40-49 - 48 = '0' + 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, //50-59 + 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, //60-69 A-F + 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, //90-99 a-f + 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + +char* QTSSDataConverter::TypeToTypeString( QTSS_AttrDataType inType) +{ + if (inType < qtssAttrDataTypeNumTypes) + return kDataTypeStrings[inType]; + return kDataTypeStrings[qtssAttrDataTypeUnknown]; +} + +QTSS_AttrDataType QTSSDataConverter::TypeStringToType( char* inTypeString) +{ + for (UInt32 x = 0; x < qtssAttrDataTypeNumTypes; x++) + { + StrPtrLen theTypeStrPtr(inTypeString); + if (theTypeStrPtr.EqualIgnoreCase(kDataTypeStrings[x], ::strlen(kDataTypeStrings[x]))) + return x; + } + return qtssAttrDataTypeUnknown; +} + +QTSS_Error QTSSDataConverter::StringToValue( char* inValueAsString, + QTSS_AttrDataType inType, + void* ioBuffer, + UInt32* ioBufSize) +{ + UInt32 theBufSize = 0; + char* theFormat = NULL; + + if ( inValueAsString == NULL || ioBufSize == NULL) + return QTSS_BadArgument; + + if ( inType == qtssAttrDataTypeCharArray ) + { + // + // If this data type is a string, copy the string into + // the destination buffer + UInt32 theLen = ::strlen(inValueAsString); + + // + // First check to see if the destination is big enough + if ((ioBuffer == NULL) || (*ioBufSize < theLen)) + { + *ioBufSize = theLen; + return QTSS_NotEnoughSpace; + } + + // + // Do the string copy. Use memcpy for speed. + ::memcpy(ioBuffer, inValueAsString, theLen); + *ioBufSize = theLen; + return QTSS_NoErr; + } + + if (inType == qtssAttrDataTypeBool16) + { + // + // The text "enabled" means true, anything else means false + if (*ioBufSize < sizeof(Bool16)) + { + *ioBufSize = sizeof(Bool16); + return QTSS_NotEnoughSpace; + } + + Bool16* it = (Bool16*)ioBuffer; + StrPtrLen theValuePtr(inValueAsString); + if (kEnabledStr.EqualIgnoreCase(inValueAsString, ::strlen(inValueAsString))) + *it = true; + else + *it = false; + + *ioBufSize = sizeof(Bool16); + return QTSS_NoErr; + } + + // + // If this is another type, format the string into that type + switch ( inType ) + { + case qtssAttrDataTypeUInt16: + { + theBufSize = sizeof(UInt16); + theFormat = "%hu"; + } + break; + + case qtssAttrDataTypeSInt16: + { + theBufSize = sizeof(SInt16); + theFormat = "%hd"; + } + break; + + case qtssAttrDataTypeSInt32: + { + theBufSize = sizeof(SInt32); + theFormat = "%d"; + } + break; + + case qtssAttrDataTypeUInt32: + { + theBufSize = sizeof(UInt32); + theFormat = "%u"; + } + break; + + case qtssAttrDataTypeSInt64: + { + theBufSize = sizeof(SInt64); + theFormat = "%"_64BITARG_"d"; + } + break; + + case qtssAttrDataTypeUInt64: + { + theBufSize = sizeof(UInt64); + theFormat = "%"_64BITARG_"u"; + } + break; + + case qtssAttrDataTypeFloat32: + { + theBufSize = sizeof(Float32); + theFormat = "%f"; + } + break; + + case qtssAttrDataTypeFloat64: + { + theBufSize = sizeof(Float64); + theFormat = "%f"; + } + break; + + case qtssAttrDataTypeTimeVal: + { + theBufSize = sizeof(SInt64); + theFormat = "%"_64BITARG_"d"; + } + break; + + default: + return ConvertCHexStringToBytes(inValueAsString,ioBuffer,ioBufSize); + } + + if (( ioBuffer == NULL) || (*ioBufSize < theBufSize )) + { + *ioBufSize = theBufSize; + return QTSS_NotEnoughSpace; + } + *ioBufSize = theBufSize; + ::sscanf(inValueAsString, theFormat, ioBuffer); + + return QTSS_NoErr; +} + +QTSS_Error QTSSDataConverter::ConvertCHexStringToBytes( char* inValueAsString, + void* ioBuffer, + UInt32* ioBufSize) +{ + UInt32 stringLen = ::strlen(inValueAsString) ; + UInt32 dataLen = (stringLen + (stringLen & 1 ? 1 : 0)) / 2; + + // First check to see if the destination is big enough + if ((ioBuffer == NULL) || (*ioBufSize < dataLen)) + { + *ioBufSize = dataLen; + return QTSS_NotEnoughSpace; + } + + UInt8* dataPtr = (UInt8*) ioBuffer; + UInt8 char1, char2; + while (*inValueAsString) + { + char1 = sCharToNums[(UInt8) (*inValueAsString++) ] * 16; + if (*inValueAsString != 0) + char2 = sCharToNums[(UInt8) (*inValueAsString++)]; + else + char2 = 0; + *dataPtr++ = char1 + char2; + } + + *ioBufSize = dataLen; + return QTSS_NoErr; +} + +char* QTSSDataConverter::ConvertBytesToCHexString( void* inValue, const UInt32 inValueLen) +{ + UInt8* theDataPtr = (UInt8*) inValue; + UInt32 len = inValueLen *2; + + char *theString = NEW char[len+1]; + char *resultStr = theString; + if (theString != NULL) + { + UInt8 temp; + UInt32 count = 0; + for (count = 0; count < inValueLen; count++) + { + temp = *theDataPtr++; + *theString++ = kHEXChars[temp >> 4]; + *theString++ = kHEXChars[temp & 0xF]; + } + *theString = 0; + } + return resultStr; +} + +char* QTSSDataConverter::ValueToString( void* inValue, + const UInt32 inValueLen, + const QTSS_AttrDataType inType) +{ + if (inValue == NULL) + return NULL; + + if ( inType == qtssAttrDataTypeCharArray ) + { + StrPtrLen theStringPtr((char*)inValue, inValueLen); + return theStringPtr.GetAsCString(); + } + if (inType == qtssAttrDataTypeBool16) + { + Bool16* theBoolPtr = (Bool16*)inValue; + if (*theBoolPtr) + return kEnabledStr.GetAsCString(); + else + return kDisabledStr.GetAsCString(); + } + + // + // With these other types, its impossible to tell how big they'll + // be, so just allocate some buffer and hope we fit. + char* theString = NEW char[128]; + + // + // If this is another type, format the string into that type + switch ( inType ) + { + case qtssAttrDataTypeUInt16: + qtss_sprintf(theString, "%hu", *( UInt16*)inValue); + break; + + case qtssAttrDataTypeSInt16: + qtss_sprintf(theString, "%hd", *( SInt16*)inValue); + break; + + case qtssAttrDataTypeSInt32: + qtss_sprintf(theString, "%"_S32BITARG_, *( SInt32*)inValue); + break; + + case qtssAttrDataTypeUInt32: + qtss_sprintf(theString, "%"_U32BITARG_, *( UInt32*)inValue); + break; + + case qtssAttrDataTypeSInt64: + qtss_sprintf(theString, "%"_64BITARG_"d", *( SInt64*)inValue); + break; + + case qtssAttrDataTypeUInt64: + qtss_sprintf(theString, "%"_64BITARG_"u", *( UInt64*)inValue); + break; + + case qtssAttrDataTypeFloat32: + qtss_sprintf(theString, "%f", *( Float32*)inValue); + break; + + case qtssAttrDataTypeFloat64: + qtss_sprintf(theString, "%f", *( Float64*)inValue); + break; + + case qtssAttrDataTypeTimeVal: + qtss_sprintf(theString, "%"_64BITARG_"d", *( SInt64*)inValue); + break; + + default: + delete theString; + theString = ConvertBytesToCHexString(inValue, inValueLen); + } + + return theString; +} diff --git a/Server.tproj/QTSSDataConverter.h b/Server.tproj/QTSSDataConverter.h new file mode 100644 index 0000000..5764538 --- /dev/null +++ b/Server.tproj/QTSSDataConverter.h @@ -0,0 +1,78 @@ +/* + * + * @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: QTSSDataConverter.h + + Contains: Utility routines for converting to and from + QTSS_AttrDataTypes and text + + Written By: Denis Serenyi + + Change History (most recent first): + +*/ + +#include "QTSS.h" + +class QTSSDataConverter +{ + public: + + // + // This function converts a type string, eg, "UInt32" to the enum, qtssAttrDataTypeUInt32 + static QTSS_AttrDataType TypeStringToType( char* inTypeString); + + // + // This function does the opposite conversion + static char* TypeToTypeString( QTSS_AttrDataType inType); + + // + // This function converts a text-formatted value of a certain type + // to its type. Returns: QTSS_NotEnoughSpace if the buffer provided + // is not big enough. + + // String must be NULL-terminated. + // If output value is a string, it will not be NULL-terminated + static QTSS_Error StringToValue( char* inValueAsString, + QTSS_AttrDataType inType, + void* ioBuffer, + UInt32* ioBufSize); + + // If value is a string, doesn't have to be NULL-terminated. + // Output string will be NULL terminated. + static char* ValueToString( void* inValue, + const UInt32 inValueLen, + const QTSS_AttrDataType inType); + + + // Takes a pointer to buffer and converts to hex in high to low order + static char* ConvertBytesToCHexString( void* inValue, const UInt32 inValueLen); + + // Takes a string of hex values and converts to bytes in high to low order + static QTSS_Error ConvertCHexStringToBytes( char* inValueAsString, + void* ioBuffer, + UInt32* ioBufSize); +}; + diff --git a/Server.tproj/QTSSDictionary.cpp b/Server.tproj/QTSSDictionary.cpp new file mode 100644 index 0000000..1b493ad --- /dev/null +++ b/Server.tproj/QTSSDictionary.cpp @@ -0,0 +1,1140 @@ +/* + * + * @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: QTSSDictionary.cpp + + Contains: Implementation of object defined in QTSSDictionary.h + + +*/ + + +#include "QTSSDictionary.h" + +#include +#include + +#include "MyAssert.h" +#include "OSMemory.h" +#include "QTSSDataConverter.h" + +#include + + + +QTSSDictionary::QTSSDictionary(QTSSDictionaryMap* inMap, OSMutex* inMutex) +: fAttributes(NULL), fInstanceAttrs(NULL), fInstanceArraySize(0), + fMap(inMap), fInstanceMap(NULL), fMutexP(inMutex), fMyMutex(false), fLocked(false) +{ + if (fMap != NULL) + fAttributes = NEW DictValueElement[inMap->GetNumAttrs()]; + if (fMutexP == NULL) + { + fMyMutex = true; + fMutexP = NEW OSMutex(); + } +} + +QTSSDictionary::~QTSSDictionary() +{ + if (fMap != NULL) + this->DeleteAttributeData(fAttributes, fMap->GetNumAttrs()); + if (fAttributes != NULL) + delete [] fAttributes; + delete fInstanceMap; + this->DeleteAttributeData(fInstanceAttrs, fInstanceArraySize); + delete [] fInstanceAttrs; + if (fMyMutex) + delete fMutexP; +} + +QTSSDictionary* QTSSDictionary::CreateNewDictionary(QTSSDictionaryMap* inMap, OSMutex* inMutex) +{ + return NEW QTSSDictionary(inMap, inMutex); +} + +QTSS_Error QTSSDictionary::GetValuePtr(QTSS_AttributeID inAttrID, UInt32 inIndex, + void** outValueBuffer, UInt32* outValueLen, + Bool16 isInternal) +{ + // Check first to see if this is a static attribute or an instance attribute + QTSSDictionaryMap* theMap = fMap; + DictValueElement* theAttrs = fAttributes; + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + { + theMap = fInstanceMap; + theAttrs = fInstanceAttrs; + } + + if (theMap == NULL) + return QTSS_AttrDoesntExist; + + SInt32 theMapIndex = theMap->ConvertAttrIDToArrayIndex(inAttrID); + + if (theMapIndex < 0) + return QTSS_AttrDoesntExist; + if (theMap->IsRemoved(theMapIndex)) + return QTSS_AttrDoesntExist; + if ((!isInternal) && (!theMap->IsPreemptiveSafe(theMapIndex)) && !this->IsLocked()) + return QTSS_NotPreemptiveSafe; + // An iterated attribute cannot have a param retrieval function + if ((inIndex > 0) && (theMap->GetAttrFunction(theMapIndex) != NULL)) + return QTSS_BadIndex; + // Check to make sure the index parameter is legal + if ((inIndex > 0) && (inIndex >= theAttrs[theMapIndex].fNumAttributes)) + return QTSS_BadIndex; + + + // Retrieve the parameter + char* theBuffer = theAttrs[theMapIndex].fAttributeData.Ptr; + *outValueLen = theAttrs[theMapIndex].fAttributeData.Len; + + Bool16 cacheable = theMap->IsCacheable(theMapIndex); + if ( (theMap->GetAttrFunction(theMapIndex) != NULL) && ((cacheable && (*outValueLen == 0)) || !cacheable) ) + { + // If function is cacheable: + // If the parameter doesn't have a value assigned yet, and there is an attribute + // retrieval function provided, invoke that function now. + // If function is *not* cacheable: + // always call the function + + theBuffer = (char*)theMap->GetAttrFunction(theMapIndex)(this, outValueLen); + + //If the param retrieval function didn't return an explicit value for this attribute, + //refetch the parameter out of the array, in case the function modified it. + + if (theBuffer == NULL) + { + theBuffer = theAttrs[theMapIndex].fAttributeData.Ptr; + *outValueLen = theAttrs[theMapIndex].fAttributeData.Len; + } + + } +#if DEBUG + else + // Make sure we aren't outside the bounds of attribute memory + Assert(theAttrs[theMapIndex].fAllocatedLen >= + (theAttrs[theMapIndex].fAttributeData.Len * (theAttrs[theMapIndex].fNumAttributes))); +#endif + + // Return an error if there is no data for this attribute + if (*outValueLen == 0) + return QTSS_ValueNotFound; + + theBuffer += theAttrs[theMapIndex].fAttributeData.Len * inIndex; + *outValueBuffer = theBuffer; + + // strings need an extra dereference - moved it up + if ((theMap->GetAttrType(theMapIndex) == qtssAttrDataTypeCharArray) && (theAttrs[theMapIndex].fNumAttributes > 1)) + { + char** string = (char**)theBuffer; + *outValueBuffer = *string; + //*outValueLen = strlen(*string) + 1; + *outValueLen = strlen(*string); + } + + return QTSS_NoErr; +} + + + +QTSS_Error QTSSDictionary::GetValue(QTSS_AttributeID inAttrID, UInt32 inIndex, + void* ioValueBuffer, UInt32* ioValueLen) +{ + // If there is a mutex, lock it and get a pointer to the proper attribute + OSMutexLocker locker(fMutexP); + + void* tempValueBuffer = NULL; + UInt32 tempValueLen = 0; + QTSS_Error theErr = this->GetValuePtr(inAttrID, inIndex, &tempValueBuffer, &tempValueLen, true); + if (theErr != QTSS_NoErr) + return theErr; + + if (theErr == QTSS_NoErr) + { + // If caller provided a buffer that's too small for this attribute, report that error + if (tempValueLen > *ioValueLen) + theErr = QTSS_NotEnoughSpace; + + // Only copy out the attribute if the buffer is big enough + if ((ioValueBuffer != NULL) && (theErr == QTSS_NoErr)) + ::memcpy(ioValueBuffer, tempValueBuffer, tempValueLen); + + // Always set the ioValueLen to be the actual length of the attribute. + *ioValueLen = tempValueLen; + } + + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionary::GetValueAsString(QTSS_AttributeID inAttrID, UInt32 inIndex, char** outString) +{ + void* tempValueBuffer; + UInt32 tempValueLen = 0; + + if (outString == NULL) + return QTSS_BadArgument; + + OSMutexLocker locker(fMutexP); + QTSS_Error theErr = this->GetValuePtr(inAttrID, inIndex, &tempValueBuffer, + &tempValueLen, true); + if (theErr != QTSS_NoErr) + return theErr; + + QTSSDictionaryMap* theMap = fMap; + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + theMap = fInstanceMap; + + if (theMap == NULL) + return QTSS_AttrDoesntExist; + + SInt32 theMapIndex = theMap->ConvertAttrIDToArrayIndex(inAttrID); + Assert(theMapIndex >= 0); + + *outString = QTSSDataConverter::ValueToString(tempValueBuffer, tempValueLen, theMap->GetAttrType(theMapIndex)); + return QTSS_NoErr; +} + + + +QTSS_Error QTSSDictionary::CreateObjectValue(QTSS_AttributeID inAttrID, UInt32* outIndex, + QTSSDictionary** newObject, QTSSDictionaryMap* inMap, UInt32 inFlags) +{ + // Check first to see if this is a static attribute or an instance attribute + QTSSDictionaryMap* theMap = fMap; + DictValueElement* theAttrs = fAttributes; + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + { + theMap = fInstanceMap; + theAttrs = fInstanceAttrs; + } + + if (theMap == NULL) + return QTSS_AttrDoesntExist; + + SInt32 theMapIndex = theMap->ConvertAttrIDToArrayIndex(inAttrID); + + // If there is a mutex, make this action atomic. + OSMutexLocker locker(fMutexP); + + if (theMapIndex < 0) + return QTSS_AttrDoesntExist; + if ((!(inFlags & kDontObeyReadOnly)) && (!theMap->IsWriteable(theMapIndex))) + return QTSS_ReadOnly; + if (theMap->IsRemoved(theMapIndex)) + return QTSS_AttrDoesntExist; + if (theMap->GetAttrType(theMapIndex) != qtssAttrDataTypeQTSS_Object) + return QTSS_BadArgument; + + UInt32 numValues = theAttrs[theMapIndex].fNumAttributes; + + // if normal QTSSObjects have been added, then we can't add a dynamic one + if (!theAttrs[theMapIndex].fIsDynamicDictionary && (numValues > 0)) + return QTSS_ReadOnly; + + QTSSDictionary* oldDict = NULL; + *outIndex = numValues; // add the object into the next spot + + UInt32 len = sizeof(QTSSDictionary*); + QTSSDictionary* dict = CreateNewDictionary(inMap, fMutexP); + + // kind of a hack to avoid the check in SetValue + theAttrs[theMapIndex].fIsDynamicDictionary = false; + QTSS_Error err = SetValue(inAttrID, *outIndex, &dict, len, inFlags); + if (err != QTSS_NoErr) + { + delete dict; + return err; + } + + if (oldDict != NULL) + { + delete oldDict; + } + + theAttrs[theMapIndex].fIsDynamicDictionary = true; + *newObject = dict; + + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionary::SetValue(QTSS_AttributeID inAttrID, UInt32 inIndex, + const void* inBuffer, UInt32 inLen, + UInt32 inFlags) +{ + // Check first to see if this is a static attribute or an instance attribute + QTSSDictionaryMap* theMap = fMap; + DictValueElement* theAttrs = fAttributes; + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + { + theMap = fInstanceMap; + theAttrs = fInstanceAttrs; + } + + if (theMap == NULL) + return QTSS_AttrDoesntExist; + + SInt32 theMapIndex = theMap->ConvertAttrIDToArrayIndex(inAttrID); + + // If there is a mutex, make this action atomic. + OSMutexLocker locker(fMutexP); + + if (theMapIndex < 0) + return QTSS_AttrDoesntExist; + if ((!(inFlags & kDontObeyReadOnly)) && (!theMap->IsWriteable(theMapIndex))) + return QTSS_ReadOnly; + if (theMap->IsRemoved(theMapIndex)) + return QTSS_AttrDoesntExist; + if (theAttrs[theMapIndex].fIsDynamicDictionary) + return QTSS_ReadOnly; + + UInt32 numValues = theAttrs[theMapIndex].fNumAttributes; + + QTSS_AttrDataType dataType = theMap->GetAttrType(theMapIndex); + UInt32 attrLen = inLen; + if (dataType == qtssAttrDataTypeCharArray) + { + if (inIndex > 0) + attrLen = sizeof(char*); // value just contains a pointer + + if ((numValues == 1) && (inIndex == 1)) + { + // we're adding a second value, so we need to change the storage from directly + // storing the string to an array of string pointers + + // creating new memory here just to create a null terminated string + // instead of directly using the old storage as the old storage didn't + // have its string null terminated + UInt32 tempStringLen = theAttrs[theMapIndex].fAttributeData.Len; + char* temp = NEW char[tempStringLen + 1]; + ::memcpy(temp, theAttrs[theMapIndex].fAttributeData.Ptr, tempStringLen); + temp[tempStringLen] = '\0'; + delete [] theAttrs[theMapIndex].fAttributeData.Ptr; + + //char* temp = theAttrs[theMapIndex].fAttributeData.Ptr; + + theAttrs[theMapIndex].fAllocatedLen = 16 * sizeof(char*); + theAttrs[theMapIndex].fAttributeData.Ptr = NEW char[theAttrs[theMapIndex].fAllocatedLen]; + theAttrs[theMapIndex].fAttributeData.Len = sizeof(char*); + // store off original string as first value in array + *(char**)theAttrs[theMapIndex].fAttributeData.Ptr = temp; + // question: why isn't theAttrs[theMapIndex].fAllocatedInternally set to true? + } + } + else + { + // If this attribute is iterated, this new value + // must be the same size as all the others. + if (((inIndex > 0) || (numValues > 1)) + &&(theAttrs[theMapIndex].fAttributeData.Len != 0) && (inLen != theAttrs[theMapIndex].fAttributeData.Len)) + return QTSS_BadArgument; + } + + // + // Can't put empty space into the array of values + if (inIndex > numValues) + return QTSS_BadIndex; + + if ((attrLen * (inIndex + 1)) > theAttrs[theMapIndex].fAllocatedLen) + { + // We need to reallocate this buffer. + UInt32 theLen; + + if (inIndex == 0) + theLen = attrLen; // most attributes are single valued, so allocate just enough space + else + theLen = 2 * (attrLen * (inIndex + 1));// Allocate twice as much as we need + char* theNewBuffer = NEW char[theLen]; + if (inIndex > 0) + { + // Copy out the old attribute data + ::memcpy(theNewBuffer, theAttrs[theMapIndex].fAttributeData.Ptr, + theAttrs[theMapIndex].fAllocatedLen); + } + + // Now get rid of the old stuff. Delete the buffer + // if it was already allocated internally + if (theAttrs[theMapIndex].fAllocatedInternally) + delete [] theAttrs[theMapIndex].fAttributeData.Ptr; + + // Finally, update this attribute structure with all the new values. + theAttrs[theMapIndex].fAttributeData.Ptr = theNewBuffer; + theAttrs[theMapIndex].fAllocatedLen = theLen; + theAttrs[theMapIndex].fAllocatedInternally = true; + } + + // At this point, we should always have enough space to write what we want + Assert(theAttrs[theMapIndex].fAllocatedLen >= (attrLen * (inIndex + 1))); + + // Copy the new data to the right place in our data buffer + void *attributeBufferPtr; + if ((dataType != qtssAttrDataTypeCharArray) || ((numValues < 2) && (inIndex == 0))) + { + attributeBufferPtr = theAttrs[theMapIndex].fAttributeData.Ptr + (inLen * inIndex); + theAttrs[theMapIndex].fAttributeData.Len = inLen; + } + else + { + //attributeBufferPtr = NEW char[inLen]; + // allocating one extra so that we can null terminate the string + attributeBufferPtr = NEW char[inLen + 1]; + char* tempBuffer = (char*)attributeBufferPtr; + tempBuffer[inLen] = '\0'; + + //char** valuePtr = (char**)theAttrs[theMapIndex].fAttributeData.Ptr + (inLen * inIndex); + // The offset should be (attrLen * inIndex) and not (inLen * inIndex) + char** valuePtr = (char**)(theAttrs[theMapIndex].fAttributeData.Ptr + (attrLen * inIndex)); + if (inIndex < numValues) // we're replacing an existing string + delete *valuePtr; + *valuePtr = (char*)attributeBufferPtr; + } + + ::memcpy(attributeBufferPtr, inBuffer, inLen); + + + // Set the number of attributes to be proper + if (inIndex >= theAttrs[theMapIndex].fNumAttributes) + { + // + // We should never have to increment num attributes by more than 1 + Assert(theAttrs[theMapIndex].fNumAttributes == inIndex); + theAttrs[theMapIndex].fNumAttributes++; + } + + // + // Call the completion routine + if (((fMap == NULL) || fMap->CompleteFunctionsAllowed()) && !(inFlags & kDontCallCompletionRoutine)) + this->SetValueComplete(theMapIndex, theMap, inIndex, attributeBufferPtr, inLen); + + return QTSS_NoErr; +} + + +QTSS_Error QTSSDictionary::SetValuePtr(QTSS_AttributeID inAttrID, + const void* inBuffer, UInt32 inLen, + UInt32 inFlags) +{ + // Check first to see if this is a static attribute or an instance attribute + QTSSDictionaryMap* theMap = fMap; + DictValueElement* theAttrs = fAttributes; + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + { + theMap = fInstanceMap; + theAttrs = fInstanceAttrs; + } + + if (theMap == NULL) + return QTSS_AttrDoesntExist; + + SInt32 theMapIndex = theMap->ConvertAttrIDToArrayIndex(inAttrID); + + // If there is a mutex, make this action atomic. + OSMutexLocker locker(fMutexP); + + if (theMapIndex < 0) + return QTSS_AttrDoesntExist; + if ((!(inFlags & kDontObeyReadOnly)) && (!theMap->IsWriteable(theMapIndex))) + return QTSS_ReadOnly; + if (theMap->IsRemoved(theMapIndex)) + return QTSS_AttrDoesntExist; + if (theAttrs[theMapIndex].fIsDynamicDictionary) + return QTSS_ReadOnly; + + UInt32 numValues = theAttrs[theMapIndex].fNumAttributes; + if ((numValues > 0) || (theAttrs[theMapIndex].fAttributeData.Ptr != NULL)) + return QTSS_BadArgument; // you can only set the pointer if you haven't done set value + + theAttrs[theMapIndex].fAttributeData.Ptr = (char*) inBuffer; + theAttrs[theMapIndex].fAttributeData.Len = inLen; + theAttrs[theMapIndex].fAllocatedLen = inLen; + + // This function assumes there is only one value and that it isn't allocated internally + theAttrs[theMapIndex].fNumAttributes = 1; + + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionary::RemoveValue(QTSS_AttributeID inAttrID, UInt32 inIndex, UInt32 inFlags) +{ + // Check first to see if this is a static attribute or an instance attribute + QTSSDictionaryMap* theMap = fMap; + DictValueElement* theAttrs = fAttributes; + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + { + theMap = fInstanceMap; + theAttrs = fInstanceAttrs; + } + + if (theMap == NULL) + return QTSS_AttrDoesntExist; + + SInt32 theMapIndex = theMap->ConvertAttrIDToArrayIndex(inAttrID); + + // If there is a mutex, make this action atomic. + OSMutexLocker locker(fMutexP); + + if (theMapIndex < 0) + return QTSS_AttrDoesntExist; + if ((!(inFlags & kDontObeyReadOnly)) && (!theMap->IsWriteable(theMapIndex))) + return QTSS_ReadOnly; + if (theMap->IsRemoved(theMapIndex)) + return QTSS_AttrDoesntExist; + if ((theMap->GetAttrFunction(theMapIndex) != NULL) && (inIndex > 0)) + return QTSS_BadIndex; + + UInt32 numValues = theAttrs[theMapIndex].fNumAttributes; + + UInt32 theValueLen = theAttrs[theMapIndex].fAttributeData.Len; + + if (theAttrs[theMapIndex].fIsDynamicDictionary) + { + // this is an internally allocated dictionary, so we need to desctruct it + Assert(theMap->GetAttrType(theMapIndex) == qtssAttrDataTypeQTSS_Object); + Assert(theValueLen == sizeof(QTSSDictionary*)); + QTSSDictionary* dict = *(QTSSDictionary**)(theAttrs[theMapIndex].fAttributeData.Ptr + (theValueLen * inIndex)); + delete dict; + } + + QTSS_AttrDataType dataType = theMap->GetAttrType(theMapIndex); + if ((dataType == qtssAttrDataTypeCharArray) && (numValues > 1)) + { + // we need to delete the string + char* str = *(char**)(theAttrs[theMapIndex].fAttributeData.Ptr + (theValueLen * inIndex)); + delete str; + } + + // + // If there are values after this one in the array, move them. + if (inIndex + 1 < theAttrs[theMapIndex].fNumAttributes) + { + ::memmove( theAttrs[theMapIndex].fAttributeData.Ptr + (theValueLen * inIndex), + theAttrs[theMapIndex].fAttributeData.Ptr + (theValueLen * (inIndex + 1)), + theValueLen * ( (theAttrs[theMapIndex].fNumAttributes) - inIndex - 1)); + } // else this is the last in the array so just truncate. + // + // Update our number of values + theAttrs[theMapIndex].fNumAttributes--; + if (theAttrs[theMapIndex].fNumAttributes == 0) + theAttrs[theMapIndex].fAttributeData.Len = 0; + + if ((dataType == qtssAttrDataTypeCharArray) && (theAttrs[theMapIndex].fNumAttributes == 1)) + { + // we only have one string left, so we don't need the extra pointer + char* str = *(char**)(theAttrs[theMapIndex].fAttributeData.Ptr); + delete theAttrs[theMapIndex].fAttributeData.Ptr; + theAttrs[theMapIndex].fAttributeData.Ptr = str; + theAttrs[theMapIndex].fAttributeData.Len = strlen(str); + theAttrs[theMapIndex].fAllocatedLen = strlen(str); + } + + // + // Call the completion routine + if (((fMap == NULL) || fMap->CompleteFunctionsAllowed()) && !(inFlags & kDontCallCompletionRoutine)) + this->RemoveValueComplete(theMapIndex, theMap, inIndex); + + return QTSS_NoErr; +} + +UInt32 QTSSDictionary::GetNumValues(QTSS_AttributeID inAttrID) +{ + // Check first to see if this is a static attribute or an instance attribute + QTSSDictionaryMap* theMap = fMap; + DictValueElement* theAttrs = fAttributes; + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + { + theMap = fInstanceMap; + theAttrs = fInstanceAttrs; + } + + if (theMap == NULL) + return 0; + + SInt32 theMapIndex = theMap->ConvertAttrIDToArrayIndex(inAttrID); + if (theMapIndex < 0) + return 0; + + return theAttrs[theMapIndex].fNumAttributes; +} + +void QTSSDictionary::SetNumValues(QTSS_AttributeID inAttrID, UInt32 inNumValues) +{ + // Check first to see if this is a static attribute or an instance attribute + QTSSDictionaryMap* theMap = fMap; + DictValueElement* theAttrs = fAttributes; + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + { + theMap = fInstanceMap; + theAttrs = fInstanceAttrs; + } + + if (theMap == NULL) + return; + + SInt32 theMapIndex = theMap->ConvertAttrIDToArrayIndex(inAttrID); + if (theMapIndex < 0) + return; + + UInt32 numAttributes = theAttrs[theMapIndex].fNumAttributes; + // this routine can only be ever used to reduce the number of values + if (inNumValues >= numAttributes || numAttributes == 0) + return; + + QTSS_AttrDataType dataType = theMap->GetAttrType(theMapIndex); + if (theAttrs[theMapIndex].fIsDynamicDictionary || (dataType == qtssAttrDataTypeCharArray)) + { + // getting rid of dictionaries or strings is tricky, so it's easier to call remove value + for (UInt32 removeCount = numAttributes - inNumValues; removeCount > 0; removeCount--) + { // the delete index passed to RemoveValue is always the last in the array. + this->RemoveValue(inAttrID, theAttrs[theMapIndex].fNumAttributes - 1, kDontObeyReadOnly); + } + } + else + { + theAttrs[theMapIndex].fNumAttributes = inNumValues; + if (inNumValues == 0) + theAttrs[theMapIndex].fAttributeData.Len = 0; + } +} + +void QTSSDictionary::SetVal( QTSS_AttributeID inAttrID, + void* inValueBuffer, + UInt32 inBufferLen) +{ + Assert(inAttrID >= 0); + Assert(fMap); + Assert((UInt32)inAttrID < fMap->GetNumAttrs()); + fAttributes[inAttrID].fAttributeData.Ptr = (char*)inValueBuffer; + fAttributes[inAttrID].fAttributeData.Len = inBufferLen; + fAttributes[inAttrID].fAllocatedLen = inBufferLen; + + // This function assumes there is only one value and that it isn't allocated internally + fAttributes[inAttrID].fNumAttributes = 1; +} + +void QTSSDictionary::SetEmptyVal(QTSS_AttributeID inAttrID, void* inBuf, UInt32 inBufLen) +{ + Assert(inAttrID >= 0); + Assert(fMap); + Assert((UInt32)inAttrID < fMap->GetNumAttrs()); + fAttributes[inAttrID].fAttributeData.Ptr = (char*)inBuf; + fAttributes[inAttrID].fAllocatedLen = inBufLen; + +#if !ALLOW_NON_WORD_ALIGN_ACCESS + //if (((UInt32) inBuf % 4) > 0) + // qtss_printf("bad align by %d\n",((UInt32) inBuf % 4) ); + Assert( ((PointerSizedInt) inBuf % 4) == 0 ); +#endif +} + + +QTSS_Error QTSSDictionary::AddInstanceAttribute( const char* inAttrName, + QTSS_AttrFunctionPtr inFuncPtr, + QTSS_AttrDataType inDataType, + QTSS_AttrPermission inPermission ) +{ + if ((fMap != NULL) && !fMap->InstanceAttrsAllowed()) + return QTSS_InstanceAttrsNotAllowed; + + OSMutexLocker locker(fMutexP); + + // + // Check to see if this attribute exists in the static map. If it does, + // we can't add it as an instance attribute, so return an error + QTSSAttrInfoDict* throwAway = NULL; + QTSS_Error theErr; + if (fMap != NULL) + { + theErr = fMap->GetAttrInfoByName(inAttrName, &throwAway); + if (theErr == QTSS_NoErr) + return QTSS_AttrNameExists; + } + + if (fInstanceMap == NULL) + { + UInt32 theFlags = QTSSDictionaryMap::kAllowRemoval | QTSSDictionaryMap::kIsInstanceMap; + if ((fMap == NULL) || fMap->CompleteFunctionsAllowed()) + theFlags |= QTSSDictionaryMap::kCompleteFunctionsAllowed; + + fInstanceMap = new QTSSDictionaryMap( 0, theFlags ); + } + + // + // Add the attribute into the Dictionary Map. + theErr = fInstanceMap->AddAttribute(inAttrName, inFuncPtr, inDataType, inPermission); + if (theErr != QTSS_NoErr) + return theErr; + + // + // Check to see if our DictValueElement array needs to be reallocated + if (fInstanceMap->GetNumAttrs() >= fInstanceArraySize) + { + UInt32 theNewArraySize = fInstanceArraySize * 2; + if (theNewArraySize == 0) + theNewArraySize = QTSSDictionaryMap::kMinArraySize; + Assert(theNewArraySize > fInstanceMap->GetNumAttrs()); + + DictValueElement* theNewArray = NEW DictValueElement[theNewArraySize]; + if (fInstanceAttrs != NULL) + { + ::memcpy(theNewArray, fInstanceAttrs, sizeof(DictValueElement) * fInstanceArraySize); + + // + // Delete the old instance attr structs, this does not delete the actual attribute memory + delete [] fInstanceAttrs; + } + fInstanceAttrs = theNewArray; + fInstanceArraySize = theNewArraySize; + } + return QTSS_NoErr; +} +QTSS_Error QTSSDictionary::RemoveInstanceAttribute(QTSS_AttributeID inAttr) +{ + OSMutexLocker locker(fMutexP); + + if (fInstanceMap != NULL) + { + QTSS_Error theErr = fInstanceMap->CheckRemovePermission(inAttr); + if (theErr != QTSS_NoErr) + return theErr; + + this->SetNumValues(inAttr,(UInt32) 0); // make sure to set num values to 0 since it is a deleted attribute + fInstanceMap->RemoveAttribute(inAttr); + } + else + return QTSS_BadArgument; + + // + // Call the completion routine + SInt32 theMapIndex = fInstanceMap->ConvertAttrIDToArrayIndex(inAttr); + this->RemoveInstanceAttrComplete(theMapIndex, fInstanceMap); + + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionary::GetAttrInfoByIndex(UInt32 inIndex, QTSSAttrInfoDict** outAttrInfoDict) +{ + if (outAttrInfoDict == NULL) + return QTSS_BadArgument; + + OSMutexLocker locker(fMutexP); + + UInt32 numInstanceValues = 0; + UInt32 numStaticValues = 0; + + if (fMap != NULL) + numStaticValues = fMap->GetNumNonRemovedAttrs(); + + if (fInstanceMap != NULL) + numInstanceValues = fInstanceMap->GetNumNonRemovedAttrs(); + + if (inIndex >= (numStaticValues + numInstanceValues)) + return QTSS_AttrDoesntExist; + + if ( (numStaticValues > 0) && (inIndex < numStaticValues) ) + return fMap->GetAttrInfoByIndex(inIndex, outAttrInfoDict); + + Assert(fInstanceMap != NULL); + return fInstanceMap->GetAttrInfoByIndex(inIndex - numStaticValues, outAttrInfoDict); + +} + +QTSS_Error QTSSDictionary::GetAttrInfoByID(QTSS_AttributeID inAttrID, QTSSAttrInfoDict** outAttrInfoDict) +{ + if (outAttrInfoDict == NULL) + return QTSS_BadArgument; + + if (QTSSDictionaryMap::IsInstanceAttrID(inAttrID)) + { + OSMutexLocker locker(fMutexP); + + if (fInstanceMap != NULL) + return fInstanceMap->GetAttrInfoByID(inAttrID, outAttrInfoDict); + } + else + if (fMap != NULL) return fMap->GetAttrInfoByID(inAttrID, outAttrInfoDict); + + return QTSS_AttrDoesntExist; +} + +QTSS_Error QTSSDictionary::GetAttrInfoByName(const char* inAttrName, QTSSAttrInfoDict** outAttrInfoDict) +{ + QTSS_Error theErr = QTSS_AttrDoesntExist; + if (outAttrInfoDict == NULL) + return QTSS_BadArgument; + + // Retrieve the Dictionary Map for this object type + if (fMap != NULL) + theErr = fMap->GetAttrInfoByName(inAttrName, outAttrInfoDict); + + if (theErr == QTSS_AttrDoesntExist) + { + OSMutexLocker locker(fMutexP); + if (fInstanceMap != NULL) + theErr = fInstanceMap->GetAttrInfoByName(inAttrName, outAttrInfoDict); + } + return theErr; +} + +void QTSSDictionary::DeleteAttributeData(DictValueElement* inDictValues, UInt32 inNumValues) +{ + for (UInt32 x = 0; x < inNumValues; x++) + { + if (inDictValues[x].fAllocatedInternally) + delete [] inDictValues[x].fAttributeData.Ptr; + } +} + + + +QTSSAttrInfoDict::AttrInfo QTSSAttrInfoDict::sAttributes[] = +{ + /* 0 */ { "qtssAttrName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtssAttrID", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 2 */ { "qtssAttrDataType", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 3 */ { "qtssAttrPermissions",NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe } +}; + +QTSSAttrInfoDict::QTSSAttrInfoDict() +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kAttrInfoDictIndex)), fID(qtssIllegalAttrID) +{} + +QTSSAttrInfoDict::~QTSSAttrInfoDict() {} + + + +QTSSDictionaryMap* QTSSDictionaryMap::sDictionaryMaps[kNumDictionaries + kNumDynamicDictionaryTypes]; +UInt32 QTSSDictionaryMap::sNextDynamicMap = kNumDictionaries; + +void QTSSDictionaryMap::Initialize() +{ + // + // Have to do this one first because this dict map is used by all the other + // dict maps. + sDictionaryMaps[kAttrInfoDictIndex] = new QTSSDictionaryMap(qtssAttrInfoNumParams); + + // Setup the Attr Info attributes before constructing any other dictionaries + for (UInt32 x = 0; x < qtssAttrInfoNumParams; x++) + sDictionaryMaps[kAttrInfoDictIndex]->SetAttribute(x, QTSSAttrInfoDict::sAttributes[x].fAttrName, + QTSSAttrInfoDict::sAttributes[x].fFuncPtr, + QTSSAttrInfoDict::sAttributes[x].fAttrDataType, + QTSSAttrInfoDict::sAttributes[x].fAttrPermission); + + sDictionaryMaps[kServerDictIndex] = new QTSSDictionaryMap(qtssSvrNumParams, QTSSDictionaryMap::kCompleteFunctionsAllowed); + sDictionaryMaps[kPrefsDictIndex] = new QTSSDictionaryMap(qtssPrefsNumParams, QTSSDictionaryMap::kInstanceAttrsAllowed | QTSSDictionaryMap::kCompleteFunctionsAllowed); + sDictionaryMaps[kTextMessagesDictIndex] = new QTSSDictionaryMap(qtssMsgNumParams); + sDictionaryMaps[kServiceDictIndex] = new QTSSDictionaryMap(0); + sDictionaryMaps[kRTPStreamDictIndex] = new QTSSDictionaryMap(qtssRTPStrNumParams); + sDictionaryMaps[kClientSessionDictIndex]= new QTSSDictionaryMap(qtssCliSesNumParams, QTSSDictionaryMap::kCompleteFunctionsAllowed); + sDictionaryMaps[kRTSPSessionDictIndex] = new QTSSDictionaryMap(qtssRTSPSesNumParams); + sDictionaryMaps[kRTSPRequestDictIndex] = new QTSSDictionaryMap(qtssRTSPReqNumParams); + sDictionaryMaps[kRTSPHeaderDictIndex] = new QTSSDictionaryMap(qtssNumHeaders); + sDictionaryMaps[kFileDictIndex] = new QTSSDictionaryMap(qtssFlObjNumParams); + sDictionaryMaps[kModuleDictIndex] = new QTSSDictionaryMap(qtssModNumParams); + sDictionaryMaps[kModulePrefsDictIndex] = new QTSSDictionaryMap(0, QTSSDictionaryMap::kInstanceAttrsAllowed | QTSSDictionaryMap::kCompleteFunctionsAllowed); + sDictionaryMaps[kQTSSUserProfileDictIndex] = new QTSSDictionaryMap(qtssUserNumParams); + sDictionaryMaps[kQTSSConnectedUserDictIndex] = new QTSSDictionaryMap(qtssConnectionNumParams); + sDictionaryMaps[k3GPPRequestDictIndex] = new QTSSDictionaryMap(qtss3GPPRequestNumParams); + sDictionaryMaps[k3GPPStreamDictIndex] = new QTSSDictionaryMap(qtss3GPPStreamNumParams); + sDictionaryMaps[k3GPPClientSessionDictIndex] = new QTSSDictionaryMap(qtss3GPPCliSesNumParams); + sDictionaryMaps[k3GPPRTSPSessionDictIndex] = new QTSSDictionaryMap(qtss3GPPRTSPSessNumParams); + +} + +QTSSDictionaryMap::QTSSDictionaryMap(UInt32 inNumReservedAttrs, UInt32 inFlags) +: fNextAvailableID(inNumReservedAttrs), fNumValidAttrs(inNumReservedAttrs),fAttrArraySize(inNumReservedAttrs), fFlags(inFlags) +{ + if (fAttrArraySize < kMinArraySize) + fAttrArraySize = kMinArraySize; + fAttrArray = NEW QTSSAttrInfoDict*[fAttrArraySize]; + ::memset(fAttrArray, 0, sizeof(QTSSAttrInfoDict*) * fAttrArraySize); +} + +QTSS_Error QTSSDictionaryMap::AddAttribute( const char* inAttrName, + QTSS_AttrFunctionPtr inFuncPtr, + QTSS_AttrDataType inDataType, + QTSS_AttrPermission inPermission) +{ + if (inAttrName == NULL || ::strlen(inAttrName) > QTSS_MAX_ATTRIBUTE_NAME_SIZE) + return QTSS_BadArgument; + + for (UInt32 count = 0; count < fNextAvailableID; count++) + { + if (::strcmp(&fAttrArray[count]->fAttrInfo.fAttrName[0], inAttrName) == 0) + { // found the name in the dictionary + if (fAttrArray[count]->fAttrInfo.fAttrPermission & qtssPrivateAttrModeRemoved ) + { // it is a previously removed attribute + if (fAttrArray[count]->fAttrInfo.fAttrDataType == inDataType) + { //same type so reuse the attribute + QTSS_AttributeID attrID = fAttrArray[count]->fID; + this->UnRemoveAttribute(attrID); + fAttrArray[count]->fAttrInfo.fFuncPtr = inFuncPtr; // reset + fAttrArray[count]->fAttrInfo.fAttrPermission = inPermission;// reset + return QTSS_NoErr; // nothing left to do. It is re-added. + } + + // a removed attribute with the same name but different type--so keep checking + continue; + } + // an error, an active attribute with this name exists + return QTSS_AttrNameExists; + } + } + + if (fAttrArraySize == fNextAvailableID) + { + // If there currently isn't an attribute array, or if the current array + // is full, allocate a new array and copy all the old stuff over to the new array. + + UInt32 theNewArraySize = fAttrArraySize * 2; + if (theNewArraySize == 0) + theNewArraySize = kMinArraySize; + + QTSSAttrInfoDict** theNewArray = NEW QTSSAttrInfoDict*[theNewArraySize]; + ::memset(theNewArray, 0, sizeof(QTSSAttrInfoDict*) * theNewArraySize); + if (fAttrArray != NULL) + { + ::memcpy(theNewArray, fAttrArray, sizeof(QTSSAttrInfoDict*) * fAttrArraySize); + delete [] fAttrArray; + } + fAttrArray = theNewArray; + fAttrArraySize = theNewArraySize; + } + + QTSS_AttributeID theID = fNextAvailableID; + fNextAvailableID++; + fNumValidAttrs++; + if (fFlags & kIsInstanceMap) + theID |= 0x80000000; // Set the high order bit to indicate this is an instance attr + + // Copy the information into the first available element + // Currently, all attributes added in this fashion are always writeable + this->SetAttribute(theID, inAttrName, inFuncPtr, inDataType, inPermission); + return QTSS_NoErr; +} + +void QTSSDictionaryMap::SetAttribute( QTSS_AttributeID inID, + const char* inAttrName, + QTSS_AttrFunctionPtr inFuncPtr, + QTSS_AttrDataType inDataType, + QTSS_AttrPermission inPermission ) +{ + UInt32 theIndex = QTSSDictionaryMap::ConvertAttrIDToArrayIndex(inID); + UInt32 theNameLen = ::strlen(inAttrName); + Assert(theNameLen < QTSS_MAX_ATTRIBUTE_NAME_SIZE); + Assert(fAttrArray[theIndex] == NULL); + + fAttrArray[theIndex] = NEW QTSSAttrInfoDict; + + //Copy the information into the first available element + fAttrArray[theIndex]->fID = inID; + + ::strcpy(&fAttrArray[theIndex]->fAttrInfo.fAttrName[0], inAttrName); + fAttrArray[theIndex]->fAttrInfo.fFuncPtr = inFuncPtr; + fAttrArray[theIndex]->fAttrInfo.fAttrDataType = inDataType; + fAttrArray[theIndex]->fAttrInfo.fAttrPermission = inPermission; + + fAttrArray[theIndex]->SetVal(qtssAttrName, &fAttrArray[theIndex]->fAttrInfo.fAttrName[0], theNameLen); + fAttrArray[theIndex]->SetVal(qtssAttrID, &fAttrArray[theIndex]->fID, sizeof(fAttrArray[theIndex]->fID)); + fAttrArray[theIndex]->SetVal(qtssAttrDataType, &fAttrArray[theIndex]->fAttrInfo.fAttrDataType, sizeof(fAttrArray[theIndex]->fAttrInfo.fAttrDataType)); + fAttrArray[theIndex]->SetVal(qtssAttrPermissions, &fAttrArray[theIndex]->fAttrInfo.fAttrPermission, sizeof(fAttrArray[theIndex]->fAttrInfo.fAttrPermission)); +} + +QTSS_Error QTSSDictionaryMap::CheckRemovePermission(QTSS_AttributeID inAttrID) +{ + SInt32 theIndex = this->ConvertAttrIDToArrayIndex(inAttrID); + if (theIndex < 0) + return QTSS_AttrDoesntExist; + + if (0 == (fAttrArray[theIndex]->fAttrInfo.fAttrPermission & qtssAttrModeDelete)) + return QTSS_BadArgument; + + if (!(fFlags & kAllowRemoval)) + return QTSS_BadArgument; + + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionaryMap::RemoveAttribute(QTSS_AttributeID inAttrID) +{ + SInt32 theIndex = this->ConvertAttrIDToArrayIndex(inAttrID); + if (theIndex < 0) + return QTSS_AttrDoesntExist; + + Assert(fFlags & kAllowRemoval); + if (!(fFlags & kAllowRemoval)) + return QTSS_BadArgument; + + //qtss_printf("QTSSDictionaryMap::RemoveAttribute arraySize=%"_U32BITARG_" numNonRemove= %"_U32BITARG_" fAttrArray[%"_U32BITARG_"]->fAttrInfo.fAttrName=%s\n",this->GetNumAttrs(), this->GetNumNonRemovedAttrs(), theIndex,fAttrArray[theIndex]->fAttrInfo.fAttrName); + // + // Don't actually touch the attribute or anything. Just flag the + // it as removed. + fAttrArray[theIndex]->fAttrInfo.fAttrPermission |= qtssPrivateAttrModeRemoved; + fNumValidAttrs--; + Assert(fNumValidAttrs < 1000000); + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionaryMap::UnRemoveAttribute(QTSS_AttributeID inAttrID) +{ + if (this->ConvertAttrIDToArrayIndex(inAttrID) == -1) + return QTSS_AttrDoesntExist; + + SInt32 theIndex = this->ConvertAttrIDToArrayIndex(inAttrID); + if (theIndex < 0) + return QTSS_AttrDoesntExist; + + fAttrArray[theIndex]->fAttrInfo.fAttrPermission &= ~qtssPrivateAttrModeRemoved; + + fNumValidAttrs++; + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionaryMap::GetAttrInfoByName(const char* inAttrName, QTSSAttrInfoDict** outAttrInfoObject, + Bool16 returnRemovedAttr) +{ + if (outAttrInfoObject == NULL) + return QTSS_BadArgument; + + for (UInt32 count = 0; count < fNextAvailableID; count++) + { + if (::strcmp(&fAttrArray[count]->fAttrInfo.fAttrName[0], inAttrName) == 0) + { + if ((fAttrArray[count]->fAttrInfo.fAttrPermission & qtssPrivateAttrModeRemoved) && (!returnRemovedAttr)) + continue; + + *outAttrInfoObject = fAttrArray[count]; + return QTSS_NoErr; + } + } + return QTSS_AttrDoesntExist; +} + +QTSS_Error QTSSDictionaryMap::GetAttrInfoByID(QTSS_AttributeID inID, QTSSAttrInfoDict** outAttrInfoObject) +{ + if (outAttrInfoObject == NULL) + return QTSS_BadArgument; + + SInt32 theIndex = this->ConvertAttrIDToArrayIndex(inID); + if (theIndex < 0) + return QTSS_AttrDoesntExist; + + if (fAttrArray[theIndex]->fAttrInfo.fAttrPermission & qtssPrivateAttrModeRemoved) + return QTSS_AttrDoesntExist; + + *outAttrInfoObject = fAttrArray[theIndex]; + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionaryMap::GetAttrInfoByIndex(UInt32 inIndex, QTSSAttrInfoDict** outAttrInfoObject) +{ + if (outAttrInfoObject == NULL) + return QTSS_BadArgument; + if (inIndex >= this->GetNumNonRemovedAttrs()) + return QTSS_AttrDoesntExist; + + UInt32 actualIndex = inIndex; + UInt32 max = this->GetNumAttrs(); + if (fFlags & kAllowRemoval) + { + // If this dictionary map allows attributes to be removed, then + // the iteration index and array indexes won't line up exactly, so + // we have to iterate over the whole map all the time + actualIndex = 0; + for (UInt32 x = 0; x < max; x++) + { if (fAttrArray[x] && (fAttrArray[x]->fAttrInfo.fAttrPermission & qtssPrivateAttrModeRemoved) ) + { continue; + } + + if (actualIndex == inIndex) + { actualIndex = x; + break; + } + actualIndex++; + } + } + //qtss_printf("QTSSDictionaryMap::GetAttrInfoByIndex arraySize=%"_U32BITARG_" numNonRemove= %"_U32BITARG_" fAttrArray[%"_U32BITARG_"]->fAttrInfo.fAttrName=%s\n",this->GetNumAttrs(), this->GetNumNonRemovedAttrs(), actualIndex,fAttrArray[actualIndex]->fAttrInfo.fAttrName); + Assert(actualIndex < fNextAvailableID); + Assert(!(fAttrArray[actualIndex]->fAttrInfo.fAttrPermission & qtssPrivateAttrModeRemoved)); + *outAttrInfoObject = fAttrArray[actualIndex]; + return QTSS_NoErr; +} + +QTSS_Error QTSSDictionaryMap::GetAttrID(const char* inAttrName, QTSS_AttributeID* outID) +{ + if (outID == NULL) + return QTSS_BadArgument; + + QTSSAttrInfoDict* theAttrInfo = NULL; + QTSS_Error theErr = this->GetAttrInfoByName(inAttrName, &theAttrInfo); + if (theErr == QTSS_NoErr) + *outID = theAttrInfo->fID; + + return theErr; +} + +UInt32 QTSSDictionaryMap::GetMapIndex(QTSS_ObjectType inType) +{ + if (inType < sNextDynamicMap) + return inType; + + switch (inType) + { + case qtssRTPStreamObjectType: return kRTPStreamDictIndex; + case qtssClientSessionObjectType: return kClientSessionDictIndex; + case qtssRTSPSessionObjectType: return kRTSPSessionDictIndex; + case qtssRTSPRequestObjectType: return kRTSPRequestDictIndex; + case qtssRTSPHeaderObjectType: return kRTSPHeaderDictIndex; + case qtssServerObjectType: return kServerDictIndex; + case qtssPrefsObjectType: return kPrefsDictIndex; + case qtssTextMessagesObjectType: return kTextMessagesDictIndex; + case qtssFileObjectType: return kFileDictIndex; + case qtssModuleObjectType: return kModuleDictIndex; + case qtssModulePrefsObjectType: return kModulePrefsDictIndex; + case qtssAttrInfoObjectType: return kAttrInfoDictIndex; + case qtssUserProfileObjectType: return kQTSSUserProfileDictIndex; + case qtssConnectedUserObjectType: return kQTSSConnectedUserDictIndex; + + case qtss3GPPStreamObjectType: return k3GPPStreamDictIndex; + case qtss3GPPClientSessionObjectType: return k3GPPClientSessionDictIndex; + case qtss3GPPRTSPObjectType: return k3GPPRTSPSessionDictIndex; + case qtss3GPPRequestObjectType: return k3GPPRequestDictIndex; + + + default: return kIllegalDictionary; + } + return kIllegalDictionary; +} + +QTSS_ObjectType QTSSDictionaryMap::CreateNewMap() +{ + if (sNextDynamicMap == kNumDictionaries + kNumDynamicDictionaryTypes) + return 0; + + sDictionaryMaps[sNextDynamicMap] = new QTSSDictionaryMap(0); + QTSS_ObjectType result = (QTSS_ObjectType)sNextDynamicMap; + sNextDynamicMap++; + + return result; +} diff --git a/Server.tproj/QTSSDictionary.h b/Server.tproj/QTSSDictionary.h new file mode 100644 index 0000000..39f2161 --- /dev/null +++ b/Server.tproj/QTSSDictionary.h @@ -0,0 +1,435 @@ +/* + * + * @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: QTSSDictionary.h + + Contains: Definitions of two classes: QTSSDictionary and QTSSDictionaryMap. + Collectively, these classes implement the "dictionary" APIs in QTSS + API. A QTSSDictionary corresponds to a QTSS_Object, + a QTSSDictionaryMap corresponds to a QTSS_ObjectType. + + Created: Tue, Mar 2, 1999 @ 4:23 PM +*/ + + + +#ifndef _QTSSDICTIONARY_H_ +#define _QTSSDICTIONARY_H_ + +#include +#include "SafeStdLib.h" +#include "QTSS.h" +#include "OSHeaders.h" +#include "OSMutex.h" +#include "StrPtrLen.h" +#include "MyAssert.h" +#include "QTSSStream.h" + +class QTSSDictionary; +class QTSSDictionaryMap; +class QTSSAttrInfoDict; + +#define __DICTIONARY_TESTING__ 0 + +// +// Function prototype for attr functions +typedef void* (*QTSS_AttrFunctionPtr)(QTSSDictionary* , UInt32* ); + +class QTSSDictionary : public QTSSStream +{ + public: + + // + // CONSTRUCTOR / DESTRUCTOR + + QTSSDictionary(QTSSDictionaryMap* inMap, OSMutex* inMutex = NULL); + virtual ~QTSSDictionary(); + + // + // QTSS API CALLS + + // Flags used by internal callers of these routines + enum + { + kNoFlags = 0, + kDontObeyReadOnly = 1, + kDontCallCompletionRoutine = 2 + }; + + // This version of GetValue copies the element into a buffer provided by the caller + // Returns: QTSS_BadArgument, QTSS_NotPreemptiveSafe (if attribute is not preemptive safe), + // QTSS_BadIndex (if inIndex is bad) + QTSS_Error GetValue(QTSS_AttributeID inAttrID, UInt32 inIndex, void* ioValueBuffer, UInt32* ioValueLen); + + + //This version of GetValue returns a pointer to the internal buffer for the attribute. + //Only usable if the attribute is preemptive safe. + // + // Returns: Same as above, but also QTSS_NotEnoughSpace, if value is too big for buffer. + QTSS_Error GetValuePtr(QTSS_AttributeID inAttrID, UInt32 inIndex, void** outValueBuffer, UInt32* outValueLen) + { return GetValuePtr(inAttrID, inIndex, outValueBuffer, outValueLen, false); } + + // This version of GetValue converts the value to a string before returning it. Memory for + // the string is allocated internally. + // + // Returns: QTSS_BadArgument, QTSS_BadIndex, QTSS_ValueNotFound + QTSS_Error GetValueAsString(QTSS_AttributeID inAttrID, UInt32 inIndex, char** outString); + + // Returns: QTSS_BadArgument, QTSS_ReadOnly (if attribute is read only), + // QTSS_BadIndex (attempt to set indexed parameter with param retrieval) + QTSS_Error SetValue(QTSS_AttributeID inAttrID, UInt32 inIndex, + const void* inBuffer, UInt32 inLen, UInt32 inFlags = kNoFlags); + + // Returns: QTSS_BadArgument, QTSS_ReadOnly (if attribute is read only), + QTSS_Error SetValuePtr(QTSS_AttributeID inAttrID, + const void* inBuffer, UInt32 inLen, UInt32 inFlags = kNoFlags); + + // Returns: QTSS_BadArgument, QTSS_ReadOnly (if attribute is read only), + QTSS_Error CreateObjectValue(QTSS_AttributeID inAttrID, UInt32* outIndex, + QTSSDictionary** newObject, QTSSDictionaryMap* inMap = NULL, + UInt32 inFlags = kNoFlags); + + // Returns: QTSS_BadArgument, QTSS_ReadOnly, QTSS_BadIndex + QTSS_Error RemoveValue(QTSS_AttributeID inAttrID, UInt32 inIndex, UInt32 inFlags = kNoFlags); + + // Utility routine used by the two external flavors of GetValue + QTSS_Error GetValuePtr(QTSS_AttributeID inAttrID, UInt32 inIndex, + void** outValueBuffer, UInt32* outValueLen, + Bool16 isInternal); + + // + // ACCESSORS + + QTSSDictionaryMap* GetDictionaryMap() { return fMap; } + + // Returns the Instance dictionary map for this dictionary. This may return NULL + // if there are no instance attributes in this dictionary + QTSSDictionaryMap* GetInstanceDictMap() { return fInstanceMap; } + + // Returns the number of values associated with a given attribute + UInt32 GetNumValues(QTSS_AttributeID inAttrID); + void SetNumValues(QTSS_AttributeID inAttrID, UInt32 inNumValues); + + // Meant only for internal server use. Does no error checking, + // doesn't invoke the param retrieval function. + StrPtrLen* GetValue(QTSS_AttributeID inAttrID) + { return &fAttributes[inAttrID].fAttributeData; } + + OSMutex* GetMutex() { return fMutexP; } + + void SetLocked(Bool16 inLocked) { fLocked = inLocked; } + Bool16 IsLocked() { return fLocked; } + + // + // GETTING ATTRIBUTE INFO + QTSS_Error GetAttrInfoByIndex(UInt32 inIndex, QTSSAttrInfoDict** outAttrInfoDict); + QTSS_Error GetAttrInfoByName(const char* inAttrName, QTSSAttrInfoDict** outAttrInfoDict); + QTSS_Error GetAttrInfoByID(QTSS_AttributeID inAttrID, QTSSAttrInfoDict** outAttrInfoDict); + + + // + // INSTANCE ATTRIBUTES + + QTSS_Error AddInstanceAttribute( const char* inAttrName, + QTSS_AttrFunctionPtr inFuncPtr, + QTSS_AttrDataType inDataType, + QTSS_AttrPermission inPermission ); + + QTSS_Error RemoveInstanceAttribute(QTSS_AttributeID inAttr); + // + // MODIFIERS + + // These functions are meant to be used by the server when it is setting up the + // dictionary attributes. They do no error checking. + + // They don't set fNumAttributes & fAllocatedInternally. + void SetVal(QTSS_AttributeID inAttrID, void* inValueBuffer, UInt32 inBufferLen); + void SetVal(QTSS_AttributeID inAttrID, StrPtrLen* inNewValue) + { this->SetVal(inAttrID, inNewValue->Ptr, inNewValue->Len); } + + // Call this if you want to assign empty storage to an attribute + void SetEmptyVal(QTSS_AttributeID inAttrID, void* inBuf, UInt32 inBufLen); + +#if __DICTIONARY_TESTING__ + static void Test(); // API test for these objects +#endif + + protected: + + // Derived classes can provide a completion routine for some dictionary functions + virtual void RemoveValueComplete(UInt32 /*inAttrIndex*/, QTSSDictionaryMap* /*inMap*/, UInt32 /*inValueIndex*/) {} + + virtual void SetValueComplete(UInt32 /*inAttrIndex*/, QTSSDictionaryMap* /*inMap*/, + UInt32 /*inValueIndex*/, void* /*inNewValue*/, UInt32 /*inNewValueLen*/) {} + virtual void RemoveInstanceAttrComplete(UInt32 /*inAttrindex*/, QTSSDictionaryMap* /*inMap*/) {} + + virtual QTSSDictionary* CreateNewDictionary(QTSSDictionaryMap* inMap, OSMutex* inMutex); + + private: + + struct DictValueElement + { + // This stores all necessary information for each attribute value. + + DictValueElement() : fAllocatedLen(0), fNumAttributes(0), + fAllocatedInternally(false), fIsDynamicDictionary(false) {} + + // Does not delete! You Must call DeleteAttributeData for that + ~DictValueElement() {} + + StrPtrLen fAttributeData; // The data + UInt32 fAllocatedLen; // How much space do we have allocated? + UInt32 fNumAttributes; // If this is an iterated attribute, how many? + Bool16 fAllocatedInternally; //Should we delete this memory? + Bool16 fIsDynamicDictionary; //is this a dictionary object? + }; + + DictValueElement* fAttributes; + DictValueElement* fInstanceAttrs; + UInt32 fInstanceArraySize; + QTSSDictionaryMap* fMap; + QTSSDictionaryMap* fInstanceMap; + OSMutex* fMutexP; + Bool16 fMyMutex; + Bool16 fLocked; + + void DeleteAttributeData(DictValueElement* inDictValues, UInt32 inNumValues); +}; + + +class QTSSAttrInfoDict : public QTSSDictionary +{ + public: + + struct AttrInfo + { + // This is all the relevent information for each dictionary + // attribute. + char fAttrName[QTSS_MAX_ATTRIBUTE_NAME_SIZE + 1]; + QTSS_AttrFunctionPtr fFuncPtr; + QTSS_AttrDataType fAttrDataType; + QTSS_AttrPermission fAttrPermission; + }; + + QTSSAttrInfoDict(); + virtual ~QTSSAttrInfoDict(); + + private: + + AttrInfo fAttrInfo; + QTSS_AttributeID fID; + + static AttrInfo sAttributes[]; + + friend class QTSSDictionaryMap; + +}; + +class QTSSDictionaryMap +{ + public: + + // + // This must be called before using any QTSSDictionary or QTSSDictionaryMap functionality + static void Initialize(); + + // Stores all meta-information for attributes + + // CONSTRUCTOR FLAGS + enum + { + kNoFlags = 0, + kAllowRemoval = 1, + kIsInstanceMap = 2, + kInstanceAttrsAllowed = 4, + kCompleteFunctionsAllowed = 8 + }; + + // + // CONSTRUCTOR / DESTRUCTOR + + QTSSDictionaryMap(UInt32 inNumReservedAttrs, UInt32 inFlags = kNoFlags); + ~QTSSDictionaryMap(){ delete fAttrArray; } + + // + // QTSS API CALLS + + // All functions either return QTSS_BadArgument or QTSS_NoErr + QTSS_Error AddAttribute( const char* inAttrName, + QTSS_AttrFunctionPtr inFuncPtr, + QTSS_AttrDataType inDataType, + QTSS_AttrPermission inPermission ); + + // + // Marks this attribute as removed + QTSS_Error RemoveAttribute(QTSS_AttributeID inAttrID); + QTSS_Error UnRemoveAttribute(QTSS_AttributeID inAttrID); + QTSS_Error CheckRemovePermission(QTSS_AttributeID inAttrID); + + // + // Searching / Iteration. These never return removed attributes + QTSS_Error GetAttrInfoByName(const char* inAttrName, QTSSAttrInfoDict** outAttrInfoDict, Bool16 returnRemovedAttr = false); + QTSS_Error GetAttrInfoByID(QTSS_AttributeID inID, QTSSAttrInfoDict** outAttrInfoDict); + QTSS_Error GetAttrInfoByIndex(UInt32 inIndex, QTSSAttrInfoDict** outAttrInfoDict); + QTSS_Error GetAttrID(const char* inAttrName, QTSS_AttributeID* outID); + + // + // PRIVATE ATTR PERMISSIONS + enum + { + qtssPrivateAttrModeRemoved = 0x80000000 + }; + + // + // CONVERTING attribute IDs to array indexes. Returns -1 if inAttrID doesn't exist + inline SInt32 ConvertAttrIDToArrayIndex(QTSS_AttributeID inAttrID); + + static Bool16 IsInstanceAttrID(QTSS_AttributeID inAttrID) + { return (inAttrID & 0x80000000) != 0; } + + // ACCESSORS + + // These functions do no error checking. Be careful. + + // Includes removed attributes + UInt32 GetNumAttrs() { return fNextAvailableID; } + UInt32 GetNumNonRemovedAttrs() { return fNumValidAttrs; } + + Bool16 IsPreemptiveSafe(UInt32 inIndex) + { Assert(inIndex < fNextAvailableID); return (Bool16) (fAttrArray[inIndex]->fAttrInfo.fAttrPermission & qtssAttrModePreempSafe); } + + Bool16 IsWriteable(UInt32 inIndex) + { Assert(inIndex < fNextAvailableID); return (Bool16) (fAttrArray[inIndex]->fAttrInfo.fAttrPermission & qtssAttrModeWrite); } + + Bool16 IsCacheable(UInt32 inIndex) + { Assert(inIndex < fNextAvailableID); return (Bool16) (fAttrArray[inIndex]->fAttrInfo.fAttrPermission & qtssAttrModeCacheable); } + + Bool16 IsRemoved(UInt32 inIndex) + { Assert(inIndex < fNextAvailableID); return (Bool16) (fAttrArray[inIndex]->fAttrInfo.fAttrPermission & qtssPrivateAttrModeRemoved) ; } + + QTSS_AttrFunctionPtr GetAttrFunction(UInt32 inIndex) + { Assert(inIndex < fNextAvailableID); return fAttrArray[inIndex]->fAttrInfo.fFuncPtr; } + + char* GetAttrName(UInt32 inIndex) + { Assert(inIndex < fNextAvailableID); return fAttrArray[inIndex]->fAttrInfo.fAttrName; } + + QTSS_AttributeID GetAttrID(UInt32 inIndex) + { Assert(inIndex < fNextAvailableID); return fAttrArray[inIndex]->fID; } + + QTSS_AttrDataType GetAttrType(UInt32 inIndex) + { Assert(inIndex < fNextAvailableID); return fAttrArray[inIndex]->fAttrInfo.fAttrDataType; } + + Bool16 InstanceAttrsAllowed() { return (Bool16) (fFlags & kInstanceAttrsAllowed); } + Bool16 CompleteFunctionsAllowed() { return (Bool16) (fFlags & kCompleteFunctionsAllowed) ; } + + // MODIFIERS + + // Sets this attribute ID to have this information + + void SetAttribute( QTSS_AttributeID inID, + const char* inAttrName, + QTSS_AttrFunctionPtr inFuncPtr, + QTSS_AttrDataType inDataType, + QTSS_AttrPermission inPermission ); + + + // + // DICTIONARY MAPS + + // All dictionary maps are stored here, and are accessable + // through these routines + + // This enum allows all QTSSDictionaryMaps to be stored in an array + enum + { + kServerDictIndex = 0, + kPrefsDictIndex = 1, + kTextMessagesDictIndex = 2, + kServiceDictIndex = 3, + + kRTPStreamDictIndex = 4, + kClientSessionDictIndex = 5, + kRTSPSessionDictIndex = 6, + kRTSPRequestDictIndex = 7, + kRTSPHeaderDictIndex = 8, + kFileDictIndex = 9, + kModuleDictIndex = 10, + kModulePrefsDictIndex = 11, + kAttrInfoDictIndex = 12, + kQTSSUserProfileDictIndex = 13, + kQTSSConnectedUserDictIndex = 14, + k3GPPRequestDictIndex = 15, + k3GPPStreamDictIndex = 16, + k3GPPClientSessionDictIndex = 17, + k3GPPRTSPSessionDictIndex = 18, + + kNumDictionaries = 19, + + kNumDynamicDictionaryTypes = 500, + kIllegalDictionary = kNumDynamicDictionaryTypes + kNumDictionaries + }; + + // This function converts a QTSS_ObjectType to an index + static UInt32 GetMapIndex(QTSS_ObjectType inType); + + // Using one of the above predefined indexes, this returns the corresponding map + static QTSSDictionaryMap* GetMap(UInt32 inIndex) + { Assert(inIndex < kNumDynamicDictionaryTypes + kNumDictionaries); return sDictionaryMaps[inIndex]; } + + static QTSS_ObjectType CreateNewMap(); + + private: + + // + // Repository for dictionary maps + + static QTSSDictionaryMap* sDictionaryMaps[kNumDictionaries + kNumDynamicDictionaryTypes]; + static UInt32 sNextDynamicMap; + + enum + { + kMinArraySize = 20 + }; + + UInt32 fNextAvailableID; + UInt32 fNumValidAttrs; + UInt32 fAttrArraySize; + QTSSAttrInfoDict** fAttrArray; + UInt32 fFlags; + + friend class QTSSDictionary; +}; + +inline SInt32 QTSSDictionaryMap::ConvertAttrIDToArrayIndex(QTSS_AttributeID inAttrID) +{ + SInt32 theIndex = inAttrID & 0x7FFFFFFF; + if ((theIndex < 0) || (theIndex >= (SInt32)fNextAvailableID)) + return -1; + else + return theIndex; +} + + +#endif diff --git a/Server.tproj/QTSSErrorLogModule.cpp b/Server.tproj/QTSSErrorLogModule.cpp new file mode 100644 index 0000000..86cd79c --- /dev/null +++ b/Server.tproj/QTSSErrorLogModule.cpp @@ -0,0 +1,400 @@ +/* + * + * @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: QTSSErrorLogModule.cpp + + Contains: Implementation of object defined in .h file. + + + +*/ + +#include +#include "QTSSErrorLogModule.h" +#include "QTSSMessages.h" +#include "QTSSRollingLog.h" +#include "QTSServerInterface.h" +#include "QTSSExpirationDate.h" +#include "OSMemory.h" +#include "OS.h" +#include "Task.h" + +// STATIC FUNCTIONS + +// The dispatch function for this module +static QTSS_Error QTSSErrorLogModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock); + +// A service routine allowing other modules to roll the log +static QTSS_Error RollErrorLog(QTSS_ServiceFunctionArgsPtr inArgs); + +static QTSS_Error Register(QTSS_Register_Params* inParams); +static QTSS_Error Shutdown(); + +static QTSS_Error LogError(QTSS_RoleParamPtr inParamBlock); +static void CheckErrorLogState(); + +static QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams); +static void WriteStartupMessage(); +static void WriteShutdownMessage(); + +typedef char* LevelMsg; + +static LevelMsg sErrorLevel[] = { + "FATAL:", + "WARNING:", + "INFO:", + "ASSERT:", + "DEBUG:" +}; + +// QTSSERRORLOG CLASS DEFINITION + +class QTSSErrorLog : public QTSSRollingLog +{ + public: + + QTSSErrorLog() : QTSSRollingLog() {this->SetTaskName("QTSSErrorLog");} + virtual ~QTSSErrorLog() {} + + virtual char* GetLogName() { return QTSServerInterface::GetServer()->GetPrefs()->GetErrorLogName();} + + virtual char* GetLogDir() { return QTSServerInterface::GetServer()->GetPrefs()->GetErrorLogDir();} + + virtual UInt32 GetRollIntervalInDays() { return QTSServerInterface::GetServer()->GetPrefs()->GetErrorRollIntervalInDays();} + + virtual UInt32 GetMaxLogBytes() { return QTSServerInterface::GetServer()->GetPrefs()->GetMaxErrorLogBytes();} + +}; + +//ERRORLOGCHECKTASK CLASS DEFINITION + +class ErrorLogCheckTask : public Task +{ + public: + ErrorLogCheckTask() : Task() {this->SetTaskName("ErrorLogCheckTask"); this->Signal(Task::kStartEvent); } + virtual ~ErrorLogCheckTask() {} + + private: + virtual SInt64 Run(); +}; + +const UInt32 kMaxLogStringLen = 2172; + +// STATIC DATA + +static OSMutex* sLogMutex = NULL;//Log module isn't reentrant +static QTSSErrorLog* sErrorLog = NULL; +static char sLastErrorString[kMaxLogStringLen] = ""; +static int sDupErrorStringCount = 0; +static Bool16 sStartedUp = false; +static ErrorLogCheckTask* sErrorLogCheckTask = NULL; + + + +// FUNCTION IMPLEMENTATIONS + +QTSS_Error QTSSErrorLogModule_Main(void* inPrivateArgs) +{ + return _stublibrary_main(inPrivateArgs, QTSSErrorLogModuleDispatch); +} + + +QTSS_Error QTSSErrorLogModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock) +{ + switch (inRole) + { + case QTSS_Register_Role: + return Register(&inParamBlock->regParams); + case QTSS_StateChange_Role: + return StateChange(&inParamBlock->stateChangeParams); + case QTSS_ErrorLog_Role: + return LogError(inParamBlock); + case QTSS_Shutdown_Role: + return Shutdown(); + } + return QTSS_NoErr; +} + + +// ROLE METHODS + +QTSS_Error Register(QTSS_Register_Params* inParams) +{ + sLogMutex = NEW OSMutex(); + + // Do role & service setup + + (void)QTSS_AddRole(QTSS_ErrorLog_Role); + (void)QTSS_AddRole(QTSS_Shutdown_Role); + (void)QTSS_AddRole(QTSS_StateChange_Role); + + (void)QTSS_AddService("RollErrorLog", &RollErrorLog); + + // Unlike most modules, all initialization for this module happens in + // the register role. This is so that this error log can begin logging + // errors ASAP. + + CheckErrorLogState(); + WriteStartupMessage(); + + // Tell the server our name! + static char* sModuleName = "QTSSErrorLogModule"; + ::strcpy(inParams->outModuleName, sModuleName); + + sErrorLogCheckTask = NEW ErrorLogCheckTask(); + + return QTSS_NoErr; +} + +QTSS_Error Shutdown() +{ + WriteShutdownMessage(); + if (sErrorLogCheckTask != NULL) + { + // sErrorLogCheckTask is a task object, so don't delete it directly + // instead we signal it to kill itself. + sErrorLogCheckTask->Signal(Task::kKillEvent); + sErrorLogCheckTask = NULL; + } + return QTSS_NoErr; +} + +QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams) +{ + if (stateChangeParams->inNewState == qtssIdleState) + { + WriteShutdownMessage(); + } + else if (stateChangeParams->inNewState == qtssRunningState) + { + // Always force our preferences to be reread when we change + // the server's state back to the start -- [sfu] + QTSS_ServiceID id; + (void) QTSS_IDForService(QTSS_REREAD_PREFS_SERVICE, &id); + (void) QTSS_DoService(id, NULL); + WriteStartupMessage(); + } + + return QTSS_NoErr; +} + + +QTSS_Error LogError(QTSS_RoleParamPtr inParamBlock) +{ + Assert(NULL != inParamBlock->errorParams.inBuffer); + if (inParamBlock->errorParams.inBuffer == NULL) + return QTSS_NoErr; + + UInt16 verbLvl = (UInt16) inParamBlock->errorParams.inVerbosity; + if (verbLvl >= qtssIllegalVerbosity) + verbLvl = qtssFatalVerbosity; + + QTSServerPrefs* thePrefs = QTSServerInterface::GetServer()->GetPrefs(); + + OSMutexLocker locker(sLogMutex); + if (thePrefs->GetErrorLogVerbosity() >= inParamBlock->errorParams.inVerbosity) + { + size_t inStringLen = ::strlen(inParamBlock->errorParams.inBuffer); + size_t lastStringLen = ::strlen(sLastErrorString); + Bool16 isDuplicate = true; + + if (inStringLen > sizeof(sLastErrorString) -1) //truncate to max char buffer subtract \0 terminator + inStringLen = sizeof(sLastErrorString) -1; + + if (lastStringLen != inStringLen) //same size? + isDuplicate = false; // different sizes + else if (::strncmp(inParamBlock->errorParams.inBuffer, sLastErrorString, lastStringLen ) != 0 ) //same chars? + isDuplicate = false; //different chars + + //is this error message the same as the last one we received? + if ( isDuplicate ) + { //yes? increment count and bail if it's not the first time we've seen this message (otherwise fall thourhg and write it to the log) + sDupErrorStringCount++; + return QTSS_NoErr; + } + else + { + //we have a new error message, write a "previous line" message before writing the new log entry + if ( sDupErrorStringCount >= 1 ) + { + /*** clean this up - lots of duplicate code ***/ + + //The error logger is the bottleneck for any and all messages printed by the server. + //For debugging purposes, these messages can be printed to stdout as well. + if (thePrefs->IsScreenLoggingEnabled()) + qtss_printf("--last message repeated %d times\n", sDupErrorStringCount); + + CheckErrorLogState(); + + if (sErrorLog == NULL) + return QTSS_NoErr; + + //timestamp the error + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + //for now, just ignore the error. + if (!result) + theDateBuffer[0] = '\0'; + + char tempBuffer[kMaxLogStringLen]; + qtss_snprintf(tempBuffer,sizeof(tempBuffer), "%s: --last message repeated %d times\n", theDateBuffer, sDupErrorStringCount); + + sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); + + sDupErrorStringCount = 0; + } + ::strlcpy(sLastErrorString, inParamBlock->errorParams.inBuffer, sizeof(sLastErrorString)); + + } + + //The error logger is the bottleneck for any and all messages printed by the server. + //For debugging purposes, these messages can be printed to stdout as well. + if (thePrefs->IsScreenLoggingEnabled()) + qtss_printf("%s %s\n", sErrorLevel[verbLvl], inParamBlock->errorParams.inBuffer); + + CheckErrorLogState(); + + if (sErrorLog == NULL) + return QTSS_NoErr; + + //timestamp the error + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + //for now, just ignore the error. + if (!result) + theDateBuffer[0] = '\0'; + + char tempBuffer[kMaxLogStringLen]; + qtss_snprintf(tempBuffer,sizeof(tempBuffer), "%s: %s %s\n", theDateBuffer, sErrorLevel[verbLvl], inParamBlock->errorParams.inBuffer); + tempBuffer[sizeof(tempBuffer)-2] = '\n'; //make sure the entry has a line feed before the \0 terminator + tempBuffer[sizeof(tempBuffer)-1] = '\0'; //make sure it is 0 terminated. + + sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); + } + return QTSS_NoErr; +} + + +void CheckErrorLogState() +{ + //this function makes sure the logging state is in synch with the preferences. + //extern variable declared in QTSSPreferences.h + + QTSServerPrefs* thePrefs = QTSServerInterface::GetServer()->GetPrefs(); + + //check error log. + if ((NULL == sErrorLog) && (thePrefs->IsErrorLogEnabled())) + { + sErrorLog = NEW QTSSErrorLog(); + sErrorLog->EnableLog(); + } + + if ((NULL != sErrorLog) && (!thePrefs->IsErrorLogEnabled())) + { + sErrorLog->Delete(); //sErrorLog is a task object, so don't delete it directly + sErrorLog = NULL; + } +} + +// SERVICE ROUTINES + +QTSS_Error RollErrorLog(QTSS_ServiceFunctionArgsPtr /*inArgs*/) +{ + OSMutexLocker locker(sLogMutex); + if (sErrorLog != NULL) + sErrorLog->RollLog(); + return QTSS_NoErr; +} + +void WriteStartupMessage() +{ + if (sStartedUp) + return; + + sStartedUp = true; + + //format a date for the startup time + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + char tempBuffer[kMaxLogStringLen]; + if (result) + qtss_snprintf(tempBuffer,sizeof(tempBuffer), "# Streaming STARTUP %s\n", theDateBuffer); + + // log startup message to error log as well. + if ((result) && (sErrorLog != NULL)) + sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); + + //write the expire date to the log + if ( QTSSExpirationDate::WillSoftwareExpire() && sErrorLog != NULL ) + { + QTSSExpirationDate::sPrintExpirationDate(tempBuffer); + sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); + } +} + +void WriteShutdownMessage() +{ + if (!sStartedUp) + return; + + sStartedUp = false; + + //log shutdown message + //format a date for the shutdown time + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false); + + char tempBuffer[kMaxLogStringLen]; + if (result) + qtss_snprintf(tempBuffer, sizeof(tempBuffer), "# Streaming SHUTDOWN %s\n", theDateBuffer); + + if ( result && sErrorLog != NULL ) + sErrorLog->WriteToLog(tempBuffer, kAllowLogToRoll); +} + +// This task runs once an hour to check and see if the log needs to roll. +SInt64 ErrorLogCheckTask::Run() +{ + static Bool16 firstTime = true; + + // don't check the log for rolling the first time we run. + if (firstTime) + { + firstTime = false; + } + else + { + Bool16 success = false; + + if (sErrorLog != NULL && sErrorLog->IsLogEnabled()) + success = sErrorLog->CheckRollLog(); + Assert(success); + } + // execute this task again in one hour. + return (60*60*1000); +} + + diff --git a/Server.tproj/QTSSErrorLogModule.h b/Server.tproj/QTSSErrorLogModule.h new file mode 100644 index 0000000..00d9060 --- /dev/null +++ b/Server.tproj/QTSSErrorLogModule.h @@ -0,0 +1,41 @@ +/* + * + * @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: QTSSErrorLogModule.h + + Contains: A module that uses QTSSRollingLog to write error messages to a file + + + +*/ + +#ifndef __QTSS_ERROR_LOG_MODULE_H__ +#define __QTSS_ERROR_LOG_MODULE_H__ + +#include "QTSS.h" + +QTSS_Error QTSSErrorLogModule_Main(void* inPrivateArgs); + +#endif // __QTSS_ERROR_LOG_MODULE_H__ diff --git a/Server.tproj/QTSSExpirationDate.cpp b/Server.tproj/QTSSExpirationDate.cpp new file mode 100644 index 0000000..e05cd0e --- /dev/null +++ b/Server.tproj/QTSSExpirationDate.cpp @@ -0,0 +1,115 @@ +/* + * + * @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: QTSSExpirationDate.cpp + + Contains: Implementation of class defined in QTSSExpirationDate.h + + Written by: Denis Serenyi + + Copyright: © 1998 by Apple Computer, Inc., all rights reserved. + + + + +*/ + +#include "QTSSExpirationDate.h" + +#include "MyAssert.h" +#include "OSHeaders.h" +#include "SafeStdLib.h" +#include + + +Bool16 QTSSExpirationDate::sIsExpirationEnabled = false; +//must be in "5/12/1998" format, "m/d/4digityear" +char* QTSSExpirationDate::sExpirationDate = "3/15/2002"; + +void QTSSExpirationDate::PrintExpirationDate() +{ + if (sIsExpirationEnabled) + qtss_printf("Software expires on: %s\n", sExpirationDate); +} + +void QTSSExpirationDate::sPrintExpirationDate(char* ioExpireMessage) +{ + if (sIsExpirationEnabled) + qtss_sprintf(ioExpireMessage, "Software expires on: %s\n", sExpirationDate); +} + + +Bool16 QTSSExpirationDate::IsSoftwareExpired() +{ + if (!sIsExpirationEnabled) + return false; + + SInt32 expMonth, expDay, expYear; + if (EOF == ::sscanf(sExpirationDate, "%"_S32BITARG_"/%"_S32BITARG_"/%"_S32BITARG_"", &expMonth, &expDay, &expYear)) + { + Assert(false); + return true; + } + + //sanity checks + Assert((expMonth > 0) && (expMonth <= 12)); + if ((expMonth <= 0) || (expMonth > 12)) + return true; + + Assert((expDay > 0) && (expDay <= 31)); + if ((expDay <= 0) || (expDay > 31)) + return true; + + Assert(expYear >= 1998); + if (expYear < 1998) + return true; + + time_t theCurrentTime = ::time(NULL); + Assert(theCurrentTime != -1); + if (theCurrentTime == -1) + return true; + + struct tm timeResult; + struct tm* theLocalTime = qtss_localtime(&theCurrentTime, &timeResult); + Assert(theLocalTime != NULL); + if (theLocalTime == NULL) + return true; + + if (expYear > (theLocalTime->tm_year + 1900)) + return false;//ok + if (expYear < (theLocalTime->tm_year + 1900)) + return true;//expired + + if (expMonth > (theLocalTime->tm_mon + 1)) + return false;//ok + if (expMonth < (theLocalTime->tm_mon + 1)) + return true;//expired + + if (expDay > theLocalTime->tm_mday) + return false;//ok + else + return true;//expired +} + diff --git a/Server.tproj/QTSSExpirationDate.h b/Server.tproj/QTSSExpirationDate.h new file mode 100644 index 0000000..2dfcfd5 --- /dev/null +++ b/Server.tproj/QTSSExpirationDate.h @@ -0,0 +1,59 @@ +/* + * + * @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: QTSSExpirationDate.h + + Contains: Routine that checks to see if software is expired. + + Written by: Denis Serenyi + + Copyright: © 1998 by Apple Computer, Inc., all rights reserved. + + +*/ + +#ifndef __QTSS_EXPIRATION_DATE_H__ +#define __QTSS_EXPIRATION_DATE_H__ + +#include "OSHeaders.h" + +class QTSSExpirationDate +{ + public: + + //checks current time vs. hard coded time constant. + static Bool16 WillSoftwareExpire(){return sIsExpirationEnabled;} + static Bool16 IsSoftwareExpired(); + static void PrintExpirationDate(); + static void sPrintExpirationDate(char* ioExpireMessage); + + private: + + static Bool16 sIsExpirationEnabled; + static char* sExpirationDate; + +}; + +#endif //__QTSS_EXPIRATION_DATE_H__ diff --git a/Server.tproj/QTSSFile.cpp b/Server.tproj/QTSSFile.cpp new file mode 100644 index 0000000..9429767 --- /dev/null +++ b/Server.tproj/QTSSFile.cpp @@ -0,0 +1,194 @@ +/* + * + * @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: QTSSFile.h + + Contains: + + + +*/ + +#include "QTSSFile.h" +#include "QTSServerInterface.h" + +QTSSAttrInfoDict::AttrInfo QTSSFile::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssFlObjStream", NULL, qtssAttrDataTypeQTSS_StreamRef, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtssFlObjFileSysModuleName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 2 */ { "qtssFlObjLength", NULL, qtssAttrDataTypeUInt64, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 3 */ { "qtssFlObjPosition", NULL, qtssAttrDataTypeUInt64, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 4 */ { "qtssFlObjModDate", NULL, qtssAttrDataTypeUInt64, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite } +}; + +void QTSSFile::Initialize() +{ + for (UInt32 x = 0; x < qtssFlObjNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kFileDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); +} + +QTSSFile::QTSSFile() +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kFileDictIndex)), + fModule(NULL), + fPosition(0), + fLength(0), + fModDate(0) +{ + fThisPtr = this; + // + // The stream is just a pointer to this thing + this->SetVal(qtssFlObjStream, &fThisPtr, sizeof(fThisPtr)); + this->SetVal(qtssFlObjLength, &fLength, sizeof(fLength)); + this->SetVal(qtssFlObjPosition, &fPosition, sizeof(fPosition)); + this->SetVal(qtssFlObjModDate, &fModDate, sizeof(fModDate)); +} + +QTSS_Error QTSSFile::Open(char* inPath, QTSS_OpenFileFlags inFlags) +{ + // + // Because this is a role being executed from inside a callback, we need to + // make sure that QTSS_RequestEvent will not work. + Task* curTask = NULL; + QTSS_ModuleState* theState = (QTSS_ModuleState*)OSThread::GetMainThreadData(); + if (OSThread::GetCurrent() != NULL) + theState = (QTSS_ModuleState*)OSThread::GetCurrent()->GetThreadData(); + + if (theState != NULL) + curTask = theState->curTask; + + QTSS_RoleParams theParams; + theParams.openFileParams.inPath = inPath; + theParams.openFileParams.inFlags = inFlags; + theParams.openFileParams.inFileObject = this; + + QTSS_Error theErr = QTSS_FileNotFound; + UInt32 x = 0; + + for ( ; x < QTSServerInterface::GetNumModulesInRole(QTSSModule::kOpenFilePreProcessRole); x++) + { + theErr = QTSServerInterface::GetModule(QTSSModule::kOpenFilePreProcessRole, x)->CallDispatch(QTSS_OpenFilePreProcess_Role, &theParams); + if (theErr != QTSS_FileNotFound) + { + fModule = QTSServerInterface::GetModule(QTSSModule::kOpenFilePreProcessRole, x); + break; + } + } + + if (theErr == QTSS_FileNotFound) + { + // None of the prepreprocessors claimed this file. Invoke the default file handler + if (QTSServerInterface::GetNumModulesInRole(QTSSModule::kOpenFileRole) > 0) + { + fModule = QTSServerInterface::GetModule(QTSSModule::kOpenFileRole, 0); + theErr = QTSServerInterface::GetModule(QTSSModule::kOpenFileRole, 0)->CallDispatch(QTSS_OpenFile_Role, &theParams); + + } + } + + // + // Reset the curTask to what it was before this role started + if (theState != NULL) + theState->curTask = curTask; + + return theErr; +} + +void QTSSFile::Close() +{ + Assert(fModule != NULL); + + QTSS_RoleParams theParams; + theParams.closeFileParams.inFileObject = this; + (void)fModule->CallDispatch(QTSS_CloseFile_Role, &theParams); +} + + +QTSS_Error QTSSFile::Read(void* ioBuffer, UInt32 inBufLen, UInt32* outLengthRead) +{ + Assert(fModule != NULL); + UInt32 theLenRead = 0; + + // + // Invoke the owning QTSS API module. Setup a param block to do so. + QTSS_RoleParams theParams; + theParams.readFileParams.inFileObject = this; + theParams.readFileParams.inFilePosition = fPosition; + theParams.readFileParams.ioBuffer = ioBuffer; + theParams.readFileParams.inBufLen = inBufLen; + theParams.readFileParams.outLenRead = &theLenRead; + + QTSS_Error theErr = fModule->CallDispatch(QTSS_ReadFile_Role, &theParams); + + fPosition += theLenRead; + if (outLengthRead != NULL) + *outLengthRead = theLenRead; + + return theErr; +} + +QTSS_Error QTSSFile::Seek(UInt64 inNewPosition) +{ + UInt64* theFileLength = NULL; + UInt32 theParamLength = 0; + + (void)this->GetValuePtr(qtssFlObjLength, 0, (void**)&theFileLength, &theParamLength); + + if (theParamLength != sizeof(UInt64)) + return QTSS_RequestFailed; + + if (inNewPosition > *theFileLength) + return QTSS_RequestFailed; + + fPosition = inNewPosition; + return QTSS_NoErr; +} + +QTSS_Error QTSSFile::Advise(UInt64 inPosition, UInt32 inAdviseSize) +{ + Assert(fModule != NULL); + + // + // Invoke the owning QTSS API module. Setup a param block to do so. + QTSS_RoleParams theParams; + theParams.adviseFileParams.inFileObject = this; + theParams.adviseFileParams.inPosition = inPosition; + theParams.adviseFileParams.inSize = inAdviseSize; + + return fModule->CallDispatch(QTSS_AdviseFile_Role, &theParams); +} + +QTSS_Error QTSSFile::RequestEvent(QTSS_EventType inEventMask) +{ + Assert(fModule != NULL); + + // + // Invoke the owning QTSS API module. Setup a param block to do so. + QTSS_RoleParams theParams; + theParams.reqEventFileParams.inFileObject = this; + theParams.reqEventFileParams.inEventMask = inEventMask; + + return fModule->CallDispatch(QTSS_RequestEventFile_Role, &theParams); +} diff --git a/Server.tproj/QTSSFile.h b/Server.tproj/QTSSFile.h new file mode 100644 index 0000000..2ee5b50 --- /dev/null +++ b/Server.tproj/QTSSFile.h @@ -0,0 +1,78 @@ +/* + * + * @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: QTSSFile.h + + Contains: + + + + +*/ + +#include "QTSSDictionary.h" +#include "QTSSModule.h" + +#include "OSFileSource.h" +#include "EventContext.h" + +class QTSSFile : public QTSSDictionary +{ + public: + + QTSSFile(); + virtual ~QTSSFile() {} + + static void Initialize(); + + // + // Opening & Closing + QTSS_Error Open(char* inPath, QTSS_OpenFileFlags inFlags); + void Close(); + + // + // Implementation of stream functions. + virtual QTSS_Error Read(void* ioBuffer, UInt32 inLen, UInt32* outLen); + + virtual QTSS_Error Seek(UInt64 inNewPosition); + + virtual QTSS_Error Advise(UInt64 inPosition, UInt32 inAdviseSize); + + virtual QTSS_Error RequestEvent(QTSS_EventType inEventMask); + + private: + + QTSSModule* fModule; + UInt64 fPosition; + QTSSFile* fThisPtr; + + // + // File attributes + UInt64 fLength; + time_t fModDate; + + static QTSSAttrInfoDict::AttrInfo sAttributes[]; +}; + diff --git a/Server.tproj/QTSSMessages.cpp b/Server.tproj/QTSSMessages.cpp new file mode 100644 index 0000000..03f6f52 --- /dev/null +++ b/Server.tproj/QTSSMessages.cpp @@ -0,0 +1,256 @@ +/* + * + * @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: QTSSMessages.cpp + + Contains: Implementation of object defined in .h + + + +*/ + +#include "QTSSMessages.h" +#include "OSMemory.h" + +// see QTSS.h (QTSS_TextMessagesObject) for list of enums to map these strings + +char* QTSSMessages::sMessagesKeyStrings[] = +{ /* index */ +/* 0 */ "qtssMsgNoMessage", +/* 1 */ "qtssMsgNoURLInRequest", +/* 2 */ "qtssMsgBadRTSPMethod", +/* 3 */ "qtssMsgNoRTSPVersion", +/* 4 */ "qtssMsgNoRTSPInURL", +/* 5 */ "qtssMsgURLTooLong", +/* 6 */ "qtssMsgURLInBadFormat", +/* 7 */ "qtssMsgNoColonAfterHeader", +/* 8 */ "qtssMsgNoEOLAfterHeader", +/* 9 */ "qtssMsgRequestTooLong", +/* 10*/ "qtssMsgNoModuleFolder", +/* 11*/ "qtssMsgCouldntListen", +/* 12*/ "qtssMsgInitFailed", +/* 13*/ "qtssMsgNotConfiguredForIP", +/* 14*/ "qtssMsgDefaultRTSPAddrUnavail", +/* 15*/ "qtssMsgBadModule", +/* 16*/ "qtssMsgRegFailed", +/* 17*/ "qtssMsgRefusingConnections", +/* 18*/ "qtssMsgTooManyClients", +/* 19*/ "qtssMsgTooMuchThruput", +/* 20*/ "qtssMsgNoSessionID", +/* 21*/ "qtssMsgFileNameTooLong", +/* 22*/ "qtssMsgNoClientPortInTransport", +/* 23*/ "qtssMsgRTPPortMustBeEven", +/* 24*/ "qtssMsgRTCPPortMustBeOneBigger", +/* 25*/ "qtssMsgOutOfPorts", +/* 26*/ "qtssMsgNoModuleForRequest", +/* 27*/ "qtssMsgAltDestNotAllowed", +/* 28*/ "qtssMsgCantSetupMulticast", +/* 29*/ "qtssListenPortInUse", +/* 30*/ "qtssListenPortAccessDenied", +/* 31*/ "qtssListenPortError", +/* 32*/ "qtssMsgBadBase64", +/* 33*/ "qtssMsgSomePortsFailed", +/* 34*/ "qtssMsgNoPortsSucceeded", +/* 35*/ "qtssMsgCannotCreatePidFile", +/* 36*/ "qtssMsgCannotSetRunUser", +/* 37*/ "qtssMsgCannotSetRunGroup", +/* 38*/ "qtssMsgNoSesIDOnDescribe", +/* 39*/ "qtssServerPrefMissing", +/* 40*/ "qtssServerPrefWrongType", +/* 41*/ "qtssMsgCantWriteFile", +/* 42*/ "qtssMsgSockBufSizesTooLarge", +/* 43*/ "qtssMsgBadFormat", + + // module specific messages follow (these are dynamically numbered) + +/* 44*/ "QTSSvrControlModuleCantRegisterMachPort", +/* 45*/ "QTSSvrControlModuleServerControlFatalErr", +/* 46*/ "QTSSReflectorModuleCantBindReflectorSocket", +/* 47*/ "QTSSReflectorModuleCantJoinMulticastGroup", +/* 48*/ "QTSSFileModuleSeekToNonExistentTime", +/* 49*/ "QTSSFileModuleNoSDPFileFound", +/* 50*/ "QTSSFileModuleBadQTFile", +/* 51*/ "QTSSFileModuleFileIsNotHinted", +/* 52*/ "QTSSFileModuleExpectedDigitFilename", +/* 53*/ "QTSSFileModuleTrackDoesntExist", +/* 54*/ "QTSSReflectorModuleExpectedDigitFilename", +/* 55*/ "QTSSSpamDefenseModuleTooManyConnections", +/* 56*/ "QTSSReflectorModuleBadTrackID", +/* 57*/ "QTSSAccessModuleBadAccessFileName", +/* 58*/ "QTSSReflectorModuleNoRelaySources", +/* 59*/ "QTSSReflectorModuleNoRelayDests", +/* 60*/ "QTSSReflectorModuleNoRelayStreams", +/* 61*/ "QTSSReflectorModuleNoRelayConfig", +/* 62*/ "QTSSReflectorModuleDuplicateBroadcastStream", +/* 63*/ "QTSSAccessModuleUsersFileNotFound", +/* 64*/ "QTSSAccessModuleGroupsFileNotFound", +/* 65*/ "QTSSAccessModuleBadUsersFile", +/* 66*/ "QTSSAccessModuleBadGroupsFile", +/* 67*/ "QTSSReflectorModuleAnnounceRequiresSDPSuffix", +/* 68*/ "QTSSReflectorModuleAnnounceDisabled", +/* 69*/ "QTSSReflectorModuleSDPPortMinimumPort", +/* 70*/ "QTSSReflectorModuleSDPPortMaximumPort", +/* 71*/ "QTSSReflectorModuleStaticPortsConflict", +/* 72*/ "QTSSReflectorModuleStaticPortPrefsBadRange", +/* 73*/ "QTSSRelayModulePrefParseError" +}; + +// see QTSS.h (QTSS_TextMessagesObject) for list of enums to map these strings + +char* QTSSMessages::sMessages[] = +{ +/* 0 */ "%s%s", +/* 1 */ "There was no URL contained in the following request: %s", +/* 2 */ "The following RTSP method: %s, was not understood by the server", +/* 3 */ "There is no RTSP version in the following request: %s", +/* 4 */ "Server expected 'rtsp://' and instead received: %s", +/* 5 */ "The following URL is too long to be processed by the server: %s", +/* 6 */ "The following URL is not in proper URL format: %s", +/* 7 */ "There was no colon after a header in the following request: %s", +/* 8 */ "There was no EOL after a header in the following request: %s", +/* 9 */ "That request is too long to be processed by the server.", +/* 10*/ "No module folder exists.", +/* 11*/ "Streaming Server couldn't listen on a specified RTSP port. Quitting.", +/* 12*/ "The module %s failed to Initialize.", +/* 13*/ "This machine is currently not configured for IP.", +/* 14*/ "The specified RTSP listening IP address doesn't exist.", +/* 15*/ "The module %s is not a compatible QTSS API module.", +/* 16*/ "The module %s failed to Register.", +/* 17*/ "Streaming Server is currently refusing new connections", +/* 18*/ "Too many clients connected", +/* 19*/ "Too much bandwidth being served", +/* 20*/ "No active RTP session for that RTSP session ID", +/* 21*/ "Specified file name is too long to be handled by the server.", +/* 22*/ "No client port pair found in transport header", +/* 23*/ "Reported client RTP port is not an even number", +/* 24*/ "Reported client RTCP port is not one greater than RTP port", +/* 25*/ "Streaming Server couldn't find any available UDP ports", +/* 26*/ "There is no QTSS API module installed to process this request.", +/* 27*/ "Not allowed to specify an alternate destination address", +/* 28*/ "Can't setup multicast.", +/* 29*/ "Another process is already using the following RTSP port: %s", +/* 30*/ "You must be root to use the following RTSP port: %s", +/* 31*/ "An error occurred when attempting to listen on the following RTSP port: %s", +/* 32*/ "The base64 you just sent to the server is corrupt!", +/* 33*/ "Streaming Server failed to listen on all requested RTSP port(s).", +/* 34*/ "Streaming Server is not listening for RTSP on any ports.", +/* 35*/ "Error creating pid file %s: %s", +/* 36*/ "Error switching to user %s: %s", +/* 37*/ "Error switching to group %s: %s", +/* 38*/ "A DESCRIBE request cannot contain the Session header", +/* 39*/ "The following pref, %s, wasn't found. Using a default value of: %s", +/* 40*/ "The following pref, %s, has the wrong type. Using a default value of: %s", +/* 41*/ "Couldn't re-write server prefs file", +/* 42*/ "Couldn't set desired UDP receive socket buffer size. Using size of %s K", +/* 43*/ "The request is incorrectly formatted.", + +// module specific messages follow (these are dynamically numbered) + +/* 44*/ "Fatal error: Can't register Mach Ports.", +/* 45*/ "A fatal error occcured while starting up server control module", +/* 46*/ "Can't bind reflector sockets", +/* 47*/ "Reflector sockets couldn't join multicast", +/* 48*/ "Couldn't seek to specified time.", +/* 49*/ "No SDP file found for the following URL: %s", +/* 50*/ "The requested file is not a movie file.", +/* 51*/ "Requested movie hasn't been hinted.", +/* 52*/ "Expected a digit at the end of the following URL: %s", +/* 53*/ "Specified trackID doesn't exist in the movie", +/* 54*/ "Expected a digit at the end of the following URL: %s", +/* 55*/ "Too many connections from your IP address!", +/* 56*/ "TrackID doesn't match any trackID for this ReflectorSession", +/* 57*/ "Invalid config value for qtaccessfilename: name contains %s. Now using default name:%s", +/* 58*/ "The relay configuration file at: %s has no relay_source lines", +/* 59*/ "Could not find any relay_destination lines for one of the relay_source lines of the relay configuration file", +/* 60*/ "Could not find any input stream information for one of the relay_source lines of the relay configuration file", +/* 61*/ "Found an empty relay configuration file at %s", +/* 62*/ "A broadcast stream is already setup for this URL", +/* 63*/ "No users file found at %s.", +/* 64*/ "No groups file found at %s.", +/* 65*/ "Unable to read the users file at %s. It may be corrupted.", +/* 66*/ "Unable to read the groups file at %s. It may be corrupted.", +/* 67*/ "The Announced file does not end with .sdp", +/* 68*/ "The Announce feature is disabled. Your request is denied.", +/* 69*/ "The SDP file's static port %s is less than the QTSSReflectorModule's minimum_static_sdp_port preference.", +/* 70*/ "The SDP file's static port %s is greater than the QTSSReflectorModule's maximum_static_sdp_port preference.", +/* 71*/ "The QTSSReflectorModule's minimum_static_sdp_port and maximum_static_sdp_port preferences conflict with the client and dynamic broadcast port range= %s to %s.", +/* 72*/ "The QTSSReflectorModule's minimum_static_sdp_port and maximum_static_sdp_port preferences define an invalid range (min=%s > max=%s).", +/* 73*/ "The QTSSRelayModule encountered an error while parsing the relay config file. No relays setup in relayconfig.xml." +}; + +// need to maintain numbers to update kNumMessages in QTSSMessages.h. + +void QTSSMessages::Initialize() +{ + QTSSDictionaryMap* theMap = QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kTextMessagesDictIndex); + Assert(theMap != NULL); + + for (UInt32 x = 0; x < qtssMsgNumParams; x++) + theMap->SetAttribute(x, sMessagesKeyStrings[x], NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe); +} + +QTSSMessages::QTSSMessages(PrefsSource* inMessages) +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kTextMessagesDictIndex)) +{ + static const UInt32 kMaxMessageSize = 2048; + char theMessage[kMaxMessageSize]; + + // Use the names of the attributes in the attribute map as the key values for + // finding preferences in the config file. + + for (UInt32 x = 0; x < this->GetDictionaryMap()->GetNumAttrs(); x++) + { + theMessage[0] = '\0'; + (void)inMessages->GetValue(this->GetDictionaryMap()->GetAttrName(x), &theMessage[0]); + + if (theMessage[0] == '\0') + { + // If a message doesn't exist in the file, check to see if this attribute + // name matches one of the compiled-in strings. If so, use that instead + for (UInt32 y = 0; y < kNumMessages; y++) + { + if (::strcmp(this->GetDictionaryMap()->GetAttrName(x), sMessagesKeyStrings[y]) == 0) + { + ::strcpy(theMessage, sMessages[y]); + break; + } + } + // If we didn't find a match, just copy in this last-resort message + if (theMessage[0] == '\0') + ::strcpy(theMessage, "No Message"); + } + + // Add this preference into the dictionary. + + // If there is a message, allocate some new memory for + // the new attribute, and copy the data into the newly allocated buffer + if (theMessage[0] != '\0') + { + char* attrBuffer = NEW char[::strlen(theMessage) + 2]; + ::strcpy(attrBuffer, theMessage); + this->SetVal(this->GetDictionaryMap()->GetAttrID(x), attrBuffer, ::strlen(attrBuffer)); + } + } +} diff --git a/Server.tproj/QTSSMessages.h b/Server.tproj/QTSSMessages.h new file mode 100644 index 0000000..72a31e2 --- /dev/null +++ b/Server.tproj/QTSSMessages.h @@ -0,0 +1,73 @@ +/* + * + * @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: QTSSMessages.h + + Contains: This global dictionary provides a central mapping from message + names to actual text messages, stored in the provided prefs source. + + This allows the whole server to be easily localizeable. + + + +*/ + +#ifndef __QTSSMESSAGES_H__ +#define __QTSSMESSAGES_H__ + +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "PrefsSource.h" + +class QTSSMessages : public QTSSDictionary +{ + public: + + // INITIALIZE + // + // This function sets up the dictionary map. Must be called before instantiating + // the first RTSPMessages object. + + static void Initialize(); + + QTSSMessages(PrefsSource* inMessages); + virtual ~QTSSMessages() {} + + + //Use the standard GetAttribute method in QTSSDictionary to retrieve messages + + private: + + enum + { + kNumMessages = 74 // 0 based count so it is one more than last message index number + }; + + static char* sMessagesKeyStrings[]; + static char* sMessages[]; +}; + + +#endif // __QTSSMESSAGES_H__ diff --git a/Server.tproj/QTSSModule.cpp b/Server.tproj/QTSSModule.cpp new file mode 100644 index 0000000..5cf035a --- /dev/null +++ b/Server.tproj/QTSSModule.cpp @@ -0,0 +1,364 @@ +/* + * + * @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: QTSSModule.cpp + + Contains: Implements object defined in QTSSModule.h + + +*/ + +#include + +#include "QTSSModule.h" +#include "OSArrayObjectDeleter.h" +#include "OSMemory.h" +#include "StringParser.h" +#include "Socket.h" +#include "QTSServerInterface.h" + + +Bool16 QTSSModule::sHasRTSPRequestModule = false; +Bool16 QTSSModule::sHasOpenFileModule = false; +Bool16 QTSSModule::sHasRTSPAuthenticateModule = false; + +QTSSAttrInfoDict::AttrInfo QTSSModule::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssModName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtssModDesc", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 2 */ { "qtssModVersion", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 3 */ { "qtssModRoles", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 4 */ { "qtssModPrefs", NULL, qtssAttrDataTypeQTSS_Object,qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeInstanceAttrAllowed }, + /* 5 */ { "qtssModAttributes", NULL, qtssAttrDataTypeQTSS_Object, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeInstanceAttrAllowed } +}; + +char* QTSSModule::sRoleNames[] = +{ + "InitializeRole" , + "ShutdownRole" , + "RTSPFilterRole" , + "RTSPRouteRole" , + "RTSPAthnRole" , + "RTSPAuthRole" , + "RTSPPreProcessorRole" , + "RTSPRequestRole" , + "RTSPPostProcessorRole" , + "RTSPSessionClosingRole" , + "RTPSendPacketsRole" , + "ClientSessionClosingRole" , + "RTCPProcessRole" , + "ErrorLogRole" , + "RereadPrefsRole" , + "OpenFileRole" , + "OpenFilePreProcessRole" , + "AdviseFileRole" , + "ReadFileRole" , + "CloseFileRole" , + "RequestEventFileRole" , + "RTSPIncomingDataRole" , + "StateChangeRole" , + "TimedIntervalRole" , + "" +}; + + +void QTSSModule::Initialize() +{ + //Setup all the dictionary stuff + for (UInt32 x = 0; x < qtssModNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kModuleDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); +} + +QTSSModule::QTSSModule(char* inName, char* inPath) +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kModuleDictIndex)), + fQueueElem(NULL), + fPath(NULL), + fFragment(NULL), + fDispatchFunc(NULL), + fPrefs(NULL), + fAttributes(NULL) +{ + + fQueueElem.SetEnclosingObject(this); + this->SetTaskName("QTSSModule"); + if ((inPath != NULL) && (inPath[0] != '\0')) + { + // Create a code fragment if this module is being loaded from disk + + fFragment = NEW OSCodeFragment(inPath); + fPath = NEW char[::strlen(inPath) + 2]; + ::strcpy(fPath, inPath); + } + + fAttributes = NEW QTSSDictionary( NULL, &fAttributesMutex ); + + this->SetVal(qtssModPrefs, &fPrefs, sizeof(fPrefs)); + this->SetVal(qtssModAttributes, &fAttributes, sizeof(fAttributes)); + + // If there is a name, copy it into the module object's internal buffer + if (inName != NULL) + this->SetValue(qtssModName, 0, inName, ::strlen(inName), QTSSDictionary::kDontObeyReadOnly); + + ::memset(fRoleArray, 0, sizeof(fRoleArray)); + ::memset(&fModuleState, 0, sizeof(fModuleState)); + +} + +QTSS_Error QTSSModule::SetupModule(QTSS_CallbacksPtr inCallbacks, QTSS_MainEntryPointPtr inEntrypoint) +{ + QTSS_Error theErr = QTSS_NoErr; + + // Load fragment from disk if necessary + + if ((fFragment != NULL) && (inEntrypoint == NULL)) + theErr = this->LoadFromDisk(&inEntrypoint); + if (theErr != QTSS_NoErr) + return theErr; + + // At this point, we must have an entrypoint + if (inEntrypoint == NULL) + return QTSS_NotAModule; + + // Invoke the private initialization routine + QTSS_PrivateArgs thePrivateArgs; + thePrivateArgs.inServerAPIVersion = QTSS_API_VERSION; + thePrivateArgs.inCallbacks = inCallbacks; + thePrivateArgs.outStubLibraryVersion = 0; + thePrivateArgs.outDispatchFunction = NULL; + + theErr = (inEntrypoint)(&thePrivateArgs); + if (theErr != QTSS_NoErr) + return theErr; + + if (thePrivateArgs.outStubLibraryVersion > thePrivateArgs.inServerAPIVersion) + return QTSS_WrongVersion; + + // Set the dispatch function so we'll be able to invoke this module later on + + fDispatchFunc = thePrivateArgs.outDispatchFunction; + + //Log + char msgStr[2048]; + char* moduleName = NULL; + (void)this->GetValueAsString (qtssModName, 0, &moduleName); + qtss_snprintf(msgStr, sizeof(msgStr), "Module Loaded...%s [%s]", moduleName, (fFragment==NULL)?"static":"dynamic"); + delete moduleName; + QTSServerInterface::LogError(qtssMessageVerbosity, msgStr); + + return QTSS_NoErr; +} + +QTSS_Error QTSSModule::LoadFromDisk(QTSS_MainEntryPointPtr* outEntrypoint) +{ + static StrPtrLen sMainEntrypointName("_Main"); + + Assert(outEntrypoint != NULL); + + // Modules only need to be initialized if they reside on disk. + if (fFragment == NULL) + return QTSS_NoErr; + + if (!fFragment->IsValid()) + return QTSS_NotAModule; + + // fPath is actually a path. Extract the file name. + + StrPtrLen theFileName(fPath); + StringParser thePathParser(&theFileName); + + while (thePathParser.GetThru(&theFileName, kPathDelimiterChar)) + ; + Assert(theFileName.Len > 0); + Assert(theFileName.Ptr != NULL); + +#ifdef __Win32__ + StringParser theDLLTruncator(&theFileName); + theDLLTruncator.ConsumeUntil(&theFileName, '.'); // strip off the ".DLL" +#endif + + /** 08/16/01 quellish **/ + +#if __MacOSX__ + StringParser theBundleTruncator(&theFileName); + theBundleTruncator.ConsumeUntil(&theFileName, '.'); // strip off the ".bundle" +#endif + + // At this point, theFileName points to the file name. Make this the module name. + this->SetValue(qtssModName, 0, theFileName.Ptr, theFileName.Len, QTSSDictionary::kDontObeyReadOnly); + + // + // The main entrypoint symbol name is the file name plus that _Main__ string up there. + OSCharArrayDeleter theSymbolName(NEW char[theFileName.Len + sMainEntrypointName.Len + 2]); + ::memcpy(theSymbolName, theFileName.Ptr, theFileName.Len); + theSymbolName[theFileName.Len] = '\0'; + + ::strcat(theSymbolName, sMainEntrypointName.Ptr); + *outEntrypoint = (QTSS_MainEntryPointPtr)fFragment->GetSymbol(theSymbolName.GetObject()); + return QTSS_NoErr; +} + + +SInt32 QTSSModule::GetPrivateRoleIndex(QTSS_Role apiRole) +{ + + switch (apiRole) + { + // Map actual QTSS Role names to our private enum values. Turn on the proper one + // in the role array + case QTSS_Initialize_Role: return kInitializeRole ; + case QTSS_Shutdown_Role: return kShutdownRole ; + case QTSS_RTSPFilter_Role: return kRTSPFilterRole ; + case QTSS_RTSPRoute_Role: return kRTSPRouteRole ; + case QTSS_RTSPAuthenticate_Role: return kRTSPAthnRole ; + case QTSS_RTSPAuthorize_Role: return kRTSPAuthRole ; + case QTSS_RTSPPreProcessor_Role: return kRTSPPreProcessorRole ; + case QTSS_RTSPRequest_Role: return kRTSPRequestRole ; + case QTSS_RTSPPostProcessor_Role: return kRTSPPostProcessorRole ; + case QTSS_RTSPSessionClosing_Role: return kRTSPSessionClosingRole ; + case QTSS_RTPSendPackets_Role: return kRTPSendPacketsRole ; + case QTSS_ClientSessionClosing_Role:return kClientSessionClosingRole; + case QTSS_RTCPProcess_Role: return kRTCPProcessRole ; + case QTSS_ErrorLog_Role: return kErrorLogRole ; + case QTSS_RereadPrefs_Role: return kRereadPrefsRole ; + case QTSS_OpenFile_Role: return kOpenFileRole ; + case QTSS_OpenFilePreProcess_Role: return kOpenFilePreProcessRole ; + case QTSS_AdviseFile_Role: return kAdviseFileRole ; + case QTSS_ReadFile_Role: return kReadFileRole ; + case QTSS_CloseFile_Role: return kCloseFileRole ; + case QTSS_RequestEventFile_Role: return kRequestEventFileRole ; + case QTSS_RTSPIncomingData_Role: return kRTSPIncomingDataRole ; + case QTSS_StateChange_Role: return kStateChangeRole ; + case QTSS_Interval_Role: return kTimedIntervalRole ; + default: + return -1; + } +} + + +QTSS_Error QTSSModule::AddRole(QTSS_Role inRole) +{ + // There can only be one QTSS_RTSPRequest processing module + if ((inRole == QTSS_RTSPRequest_Role) && (sHasRTSPRequestModule)) + return QTSS_RequestFailed; + if ((inRole == QTSS_OpenFilePreProcess_Role) && (sHasOpenFileModule)) + return QTSS_RequestFailed; + +#if 0// Allow multiple modules in QTSS v6.0. Enabling forces the first auth module There can be only one module registered for QTSS_RTSPAuthenticate_Role + if ((inRole == QTSS_RTSPAuthenticate_Role) && (sHasRTSPAuthenticateModule)) + return QTSS_RequestFailed; +#endif + + + SInt32 arrayID = GetPrivateRoleIndex(inRole); + if (arrayID < 0) + return QTSS_BadArgument; + + fRoleArray[arrayID] = true; + +/* + switch (inRole) + { + // Map actual QTSS Role names to our private enum values. Turn on the proper one + // in the role array + case QTSS_Initialize_Role: fRoleArray[kInitializeRole] = true; break; + case QTSS_Shutdown_Role: fRoleArray[kShutdownRole] = true; break; + case QTSS_RTSPFilter_Role: fRoleArray[kRTSPFilterRole] = true; break; + case QTSS_RTSPRoute_Role: fRoleArray[kRTSPRouteRole] = true; break; + case QTSS_RTSPAuthenticate_Role: fRoleArray[kRTSPAthnRole] = true; break; + case QTSS_RTSPAuthorize_Role: fRoleArray[kRTSPAuthRole] = true; break; + case QTSS_RTSPPreProcessor_Role: fRoleArray[kRTSPPreProcessorRole] = true; break; + case QTSS_RTSPRequest_Role: fRoleArray[kRTSPRequestRole] = true; break; + case QTSS_RTSPPostProcessor_Role: fRoleArray[kRTSPPostProcessorRole] = true; break; + case QTSS_RTSPSessionClosing_Role: fRoleArray[kRTSPSessionClosingRole] = true; break; + case QTSS_RTPSendPackets_Role: fRoleArray[kRTPSendPacketsRole] = true; break; + case QTSS_ClientSessionClosing_Role:fRoleArray[kClientSessionClosingRole] = true;break; + case QTSS_RTCPProcess_Role: fRoleArray[kRTCPProcessRole] = true; break; + case QTSS_ErrorLog_Role: fRoleArray[kErrorLogRole] = true; break; + case QTSS_RereadPrefs_Role: fRoleArray[kRereadPrefsRole] = true; break; + case QTSS_OpenFile_Role: fRoleArray[kOpenFileRole] = true; break; + case QTSS_OpenFilePreProcess_Role: fRoleArray[kOpenFilePreProcessRole] = true; break; + case QTSS_AdviseFile_Role: fRoleArray[kAdviseFileRole] = true; break; + case QTSS_ReadFile_Role: fRoleArray[kReadFileRole] = true; break; + case QTSS_CloseFile_Role: fRoleArray[kCloseFileRole] = true; break; + case QTSS_RequestEventFile_Role: fRoleArray[kRequestEventFileRole] = true; break; + case QTSS_RTSPIncomingData_Role: fRoleArray[kRTSPIncomingDataRole] = true; break; + case QTSS_StateChange_Role: fRoleArray[kStateChangeRole] = true; break; + case QTSS_Interval_Role: fRoleArray[kTimedIntervalRole] = true; break; + default: + return QTSS_BadArgument; + } +*/ + + if (inRole == QTSS_RTSPRequest_Role) + sHasRTSPRequestModule = true; + if (inRole == QTSS_OpenFile_Role) + sHasOpenFileModule = true; + if (inRole == QTSS_RTSPAuthenticate_Role) + sHasRTSPAuthenticateModule = true; + + // + // Add this role to the array of roles attribute + QTSS_Error theErr = this->SetValue(qtssModRoles, this->GetNumValues(qtssModRoles), &inRole, sizeof(inRole), QTSSDictionary::kDontObeyReadOnly); + Assert(theErr == QTSS_NoErr); + return QTSS_NoErr; +} + +SInt64 QTSSModule::Run() +{ + EventFlags events = this->GetEvents(); + + OSThreadDataSetter theSetter(&fModuleState, NULL); + if (events & Task::kUpdateEvent) + { // force us to update to a new idle time + return fModuleState.idleTime;// If the module has requested idle time... + } + + if (fRoleArray[kTimedIntervalRole]) + { + if (events & Task::kIdleEvent || fModuleState.globalLockRequested) + { + fModuleState.curModule = this; // this structure is setup in each thread + fModuleState.curRole = QTSS_Interval_Role; // before invoking a module in a role. Sometimes + fModuleState.eventRequested = false; + fModuleState.curTask = this; + if (fModuleState.globalLockRequested ) + { fModuleState.globalLockRequested = false; + fModuleState.isGlobalLocked = true; + } + + (void)this->CallDispatch(QTSS_Interval_Role, NULL); + fModuleState.isGlobalLocked = false; + + if (fModuleState.globalLockRequested) // call this request back locked + return this->CallLocked(); + + return fModuleState.idleTime; // If the module has requested idle time... + } + } + + return 0; + } diff --git a/Server.tproj/QTSSModule.h b/Server.tproj/QTSSModule.h new file mode 100644 index 0000000..c7a2f5e --- /dev/null +++ b/Server.tproj/QTSSModule.h @@ -0,0 +1,189 @@ +/* + * + * @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@ + * + */ + /* + Contains: This object represents a single QTSS API compliant module. + A module may either be compiled directly into the server, + or loaded from a code fragment residing on the disk. + + Object does the loading and initialization of a module, and + stores all per-module data. + + +*/ + +#ifndef __QTSSMODULE_H__ +#define __QTSSMODULE_H__ + +#include "QTSS.h" +#include "QTSS_Private.h" +#include "QTSSDictionary.h" +#include "Task.h" +#include "QTSSPrefs.h" + +#include "OSCodeFragment.h" +#include "OSQueue.h" +#include "StrPtrLen.h" + +#define MODULE_DEBUG 0 + +class QTSSModule : public QTSSDictionary, public Task +{ + public: + + // + // INITIALIZE + static void Initialize(); + + // CONSTRUCTOR / SETUP / DESTRUCTOR + + // Specify the path to the code fragment if this module + // is to be loaded from disk. If it is loaded from disk, the + // name of the module will be its file name. Otherwise, the + // inName parameter will set it. + + QTSSModule(char* inName, char* inPath = NULL); + + // This function does all the module setup. If the module is being + // loaded from disk, you need not pass in a main entrypoint (as + // it will be grabbed from the fragment). Otherwise, you must pass + // in a main entrypoint. + // + // Note that this function does not invoke any public module roles. + QTSS_Error SetupModule(QTSS_CallbacksPtr inCallbacks, QTSS_MainEntryPointPtr inEntrypoint = NULL); + + // Doesn't free up internally allocated stuff + virtual ~QTSSModule(){} + + // + // MODIFIERS + void SetPrefsDict(QTSSPrefs* inPrefs) { fPrefs = inPrefs; } + void SetAttributesDict(QTSSDictionary* inAttributes) { fAttributes = inAttributes; } + // + // ACCESSORS + + OSQueueElem* GetQueueElem() { return &fQueueElem; } + Bool16 IsInitialized() { return fDispatchFunc != NULL; } + QTSSPrefs* GetPrefsDict() { return fPrefs; } + QTSSDictionary* GetAttributesDict() { return fAttributes; } + OSMutex* GetAttributesMutex() { return &fAttributesMutex; } + + //convert QTSS.h 4 char id roles to private role index + SInt32 GetPrivateRoleIndex(QTSS_Role apiRole); + + + // This calls into the module. + QTSS_Error CallDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) + { + SInt32 theRoleIndex = -1; + + if (MODULE_DEBUG) + { this->GetValue(qtssModName)->PrintStr("QTSSModule::CallDispatch ENTER module=", " role="); + theRoleIndex = GetPrivateRoleIndex(inRole); + if (theRoleIndex != -1) + qtss_printf(" %s ENTR\n", sRoleNames[theRoleIndex]); + + } + QTSS_Error theError = (fDispatchFunc)(inRole, inParams); + + if (MODULE_DEBUG) + { this->GetValue(qtssModName)->PrintStr("QTSSModule::CallDispatch EXIT module=", " role="); + if (theRoleIndex != -1) + qtss_printf(" %s EXIT\n", sRoleNames[theRoleIndex]); + } + + return theError; + } + + + // These enums allow roles to be stored in a more optimized way + // add new RoleNames to sRoleNames in QTSSModule.cpp for debugging + enum + { + kInitializeRole = 0, + kShutdownRole = 1, + kRTSPFilterRole = 2, + kRTSPRouteRole = 3, + kRTSPAthnRole = 4, + kRTSPAuthRole = 5, + kRTSPPreProcessorRole = 6, + kRTSPRequestRole = 7, + kRTSPPostProcessorRole = 8, + kRTSPSessionClosingRole = 9, + kRTPSendPacketsRole = 10, + kClientSessionClosingRole = 11, + kRTCPProcessRole = 12, + kErrorLogRole = 13, + kRereadPrefsRole = 14, + kOpenFileRole = 15, + kOpenFilePreProcessRole = 16, + kAdviseFileRole = 17, + kReadFileRole = 18, + kCloseFileRole = 19, + kRequestEventFileRole = 20, + kRTSPIncomingDataRole = 21, + kStateChangeRole = 22, + kTimedIntervalRole = 23, + + kNumRoles = 24 + }; + typedef UInt32 RoleIndex; + + // Call this to activate this module in the specified role. + QTSS_Error AddRole(QTSS_Role inRole); + + // This returns true if this module is supposed to run in the specified role. + Bool16 RunsInRole(RoleIndex inIndex) { Assert(inIndex < kNumRoles); return fRoleArray[inIndex]; } + + SInt64 Run(); + + QTSS_ModuleState* GetModuleState() { return &fModuleState;} + + private: + + QTSS_Error LoadFromDisk(QTSS_MainEntryPointPtr* outEntrypoint); + + OSQueueElem fQueueElem; + char* fPath; + OSCodeFragment* fFragment; + QTSS_DispatchFuncPtr fDispatchFunc; + Bool16 fRoleArray[kNumRoles]; + QTSSPrefs* fPrefs; + QTSSDictionary* fAttributes; + OSMutex fAttributesMutex; + + static Bool16 sHasRTSPRequestModule; + static Bool16 sHasOpenFileModule; + static Bool16 sHasRTSPAuthenticateModule; + + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + static char* sRoleNames[]; + + QTSS_ModuleState fModuleState; + +}; + + + +#endif //__QTSSMODULE_H__ diff --git a/Server.tproj/QTSSPrefs.cpp b/Server.tproj/QTSSPrefs.cpp new file mode 100644 index 0000000..e0bbd37 --- /dev/null +++ b/Server.tproj/QTSSPrefs.cpp @@ -0,0 +1,424 @@ +/* + * + * @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: QTSSPrefs.cpp + + Contains: Implements class defined in QTSSPrefs.h. + + Change History (most recent first): + +*/ + +#include "QTSSPrefs.h" +#include "MyAssert.h" +#include "OSMemory.h" +#include "QTSSDataConverter.h" +#include "OSArrayObjectDeleter.h" + + +QTSSPrefs::QTSSPrefs(XMLPrefsParser* inPrefsSource, StrPtrLen* inModuleName, QTSSDictionaryMap* inMap, + Bool16 areInstanceAttrsAllowed, QTSSPrefs* parentDictionary ) +: QTSSDictionary(inMap, &fPrefsMutex), + fPrefsSource(inPrefsSource), + fPrefName(NULL), + fParentDictionary(parentDictionary) +{ + if (inModuleName != NULL) + fPrefName = inModuleName->GetAsCString(); +} + +QTSSDictionary* QTSSPrefs::CreateNewDictionary(QTSSDictionaryMap* inMap, OSMutex* /* inMutex */) +{ + return NEW QTSSPrefs(fPrefsSource, NULL, inMap, true, this ); +} + +void QTSSPrefs::RereadPreferences() +{ + RereadObjectPreferences(GetContainerRef()); +} + +void QTSSPrefs::RereadObjectPreferences(ContainerRef container) +{ + QTSS_Error theErr = QTSS_NoErr; + + // + // Keep track of which pref attributes should remain. All others + // will be removed. + // This routine uses names because it adds and deletes attributes. This means attribute indexes,positions and counts are constantly changing. + UInt32 initialNumAttrs = 0; + if (this->GetInstanceDictMap() != NULL) + { + initialNumAttrs = this->GetInstanceDictMap()->GetNumAttrs(); + }; + + char** modulePrefInServer; + if (initialNumAttrs > 0) + { + modulePrefInServer = NEW char*[initialNumAttrs ]; + ::memset(modulePrefInServer, 0, sizeof(char*) * initialNumAttrs); + } + else + { + modulePrefInServer = NULL; + } + + OSMutexLocker locker(&fPrefsMutex); + UInt32 theNumPrefs = fPrefsSource->GetNumPrefsByContainer(container); + + for (UInt32 i = 0; i < initialNumAttrs;i++) // pull out all the names in the server + { + QTSSAttrInfoDict* theAttrInfoPtr = NULL; + theErr = this->GetInstanceDictMap()->GetAttrInfoByIndex(i, &theAttrInfoPtr); + if (theErr != QTSS_NoErr) + continue; + + UInt32 nameLen = 0; + theErr = theAttrInfoPtr->GetValuePtr(qtssAttrName,0, (void **) &modulePrefInServer[i], &nameLen); + Assert(theErr == QTSS_NoErr); + //qtss_printf("QTSSPrefs::RereadPreferences modulePrefInServer in server=%s\n",modulePrefInServer[i]); + } + + // Use the names of the attributes in the attribute map as the key values for + // finding preferences in the config file. + + for (UInt32 x = 0; x < theNumPrefs; x++) + { + char* thePrefTypeStr = NULL; + char* thePrefName = NULL; + (void)fPrefsSource->GetPrefValueByIndex(container, x, 0, &thePrefName, &thePrefTypeStr); + + // What type is this data type? + QTSS_AttrDataType thePrefType = QTSSDataConverter::TypeStringToType(thePrefTypeStr); + + // + // Check to see if there is an attribute with this name already in the + // instance map. If one matches, then we don't need to add this attribute. + QTSSAttrInfoDict* theAttrInfo = NULL; + if (this->GetInstanceDictMap() != NULL) + (void)this->GetInstanceDictMap()->GetAttrInfoByName(thePrefName, + &theAttrInfo, + false ); // false=don't return info on deleted attributes + UInt32 theLen = sizeof(QTSS_AttrDataType); + QTSS_AttributeID theAttrID = qtssIllegalAttrID; + + for (UInt32 i = 0; i < initialNumAttrs;i++) // see if this name is in the server + { if (modulePrefInServer[i] != NULL && thePrefName != NULL && 0 == ::strcmp(modulePrefInServer[i],thePrefName)) + { modulePrefInServer[i] = NULL; // in the server so don't delete later + //qtss_printf("QTSSPrefs::RereadPreferences modulePrefInServer in file and in server=%s\n",thePrefName); + } + } + + if ( theAttrInfo == NULL ) + { + theAttrID = this->AddPrefAttribute(thePrefName, thePrefType); // not present or deleted + this->SetPrefValuesFromFile(container, x, theAttrID, 0); // will add another or replace a deleted attribute + } + else + { + QTSS_AttrDataType theAttrType = qtssAttrDataTypeUnknown; + theErr = theAttrInfo->GetValue(qtssAttrDataType, 0, &theAttrType, &theLen); + Assert(theErr == QTSS_NoErr); + + theLen = sizeof(theAttrID); + theErr = theAttrInfo->GetValue(qtssAttrID, 0, &theAttrID, &theLen); + Assert(theErr == QTSS_NoErr); + + if (theAttrType != thePrefType) + { + // + // This is not the same pref as before, because the data types + // are different. Remove the old one from the map, add the new one. + (void)this->RemoveInstanceAttribute(theAttrID); + theAttrID = this->AddPrefAttribute(thePrefName, thePrefType); + } + else + { + // + // This pref already exists + } + + // + // Set the values + this->SetPrefValuesFromFile(container, x, theAttrID, 0); + + // Mark this pref as found. + SInt32 theIndex = this->GetInstanceDictMap()->ConvertAttrIDToArrayIndex(theAttrID); + Assert(theIndex >= 0); + } + } + + // Remove all attributes that no longer apply + if (this->GetInstanceDictMap() != NULL && initialNumAttrs > 0) + { + for (UInt32 a = 0; a < initialNumAttrs; a++) + { + if (NULL != modulePrefInServer[a]) // found a pref in the server that wasn't in the file + { + QTSSAttrInfoDict* theAttrInfoPtr = NULL; + theErr = this->GetInstanceDictMap()->GetAttrInfoByName(modulePrefInServer[a], &theAttrInfoPtr); + Assert(theErr == QTSS_NoErr); + if (theErr != QTSS_NoErr) continue; + + QTSS_AttributeID theAttrID = qtssIllegalAttrID; + UInt32 theLen = sizeof(theAttrID); + theErr = theAttrInfoPtr->GetValue(qtssAttrID, 0, &theAttrID, &theLen); + Assert(theErr == QTSS_NoErr); + if (theErr != QTSS_NoErr) continue; + + if (0) + { char* theName = NULL; + UInt32 nameLen = 0; + theAttrInfoPtr->GetValuePtr(qtssAttrName,0, (void **) &theName, &nameLen); + qtss_printf("QTSSPrefs::RereadPreferences about to delete modulePrefInServer=%s attr=%s id=%"_U32BITARG_"\n",modulePrefInServer[a], theName,theAttrID); + } + + + this->GetInstanceDictMap()->RemoveAttribute(theAttrID); + modulePrefInServer[a] = NULL; + } + } + } + + delete modulePrefInServer; +} + +void QTSSPrefs::SetPrefValuesFromFile(ContainerRef container, UInt32 inPrefIndex, QTSS_AttributeID inAttrID, UInt32 inNumValues) +{ + ContainerRef pref = fPrefsSource->GetPrefRefByIndex(container, inPrefIndex); + SetPrefValuesFromFileWithRef(pref, inAttrID, inNumValues); +} + +void QTSSPrefs::SetPrefValuesFromFileWithRef(ContainerRef pref, QTSS_AttributeID inAttrID, UInt32 inNumValues) +{ + // + // We have an attribute ID for this pref, it is in the map and everything. + // Now, let's add all the values that are in the pref file. + if (pref == 0) + return; + + UInt32 numPrefValues = inNumValues; + if (inNumValues == 0) + numPrefValues = fPrefsSource->GetNumPrefValues(pref); + + char* thePrefName = NULL; + char* thePrefValue = NULL; + char* thePrefTypeStr = NULL; + QTSS_AttrDataType thePrefType = qtssAttrDataTypeUnknown; + + // find the type. If this is a QTSSObject, then we need to call a different routine + thePrefValue = fPrefsSource->GetPrefValueByRef( pref, 0, &thePrefName, &thePrefTypeStr); + thePrefType = QTSSDataConverter::TypeStringToType(thePrefTypeStr); + if (thePrefType == qtssAttrDataTypeQTSS_Object) + { + SetObjectValuesFromFile(pref, inAttrID, numPrefValues, thePrefName); + return; + } + + UInt32 maxPrefValueSize = 0; + QTSS_Error theErr = QTSS_NoErr; + + // + // We have to loop through all the values associated with this pref twice: + // first, to figure out the length (in bytes) of the longest value, secondly + // to actually copy these values into the dictionary. + for (UInt32 y = 0; y < numPrefValues; y++) + { + UInt32 tempMaxPrefValueSize = 0; + + thePrefValue = fPrefsSource->GetPrefValueByRef( pref, y, &thePrefName, &thePrefTypeStr); + + theErr = QTSSDataConverter::StringToValue( thePrefValue, thePrefType, + NULL, &tempMaxPrefValueSize ); + Assert(theErr == QTSS_NotEnoughSpace); + + if (tempMaxPrefValueSize > maxPrefValueSize) + maxPrefValueSize = tempMaxPrefValueSize; + } + + + for (UInt32 z = 0; z < numPrefValues; z++) + { + thePrefValue = fPrefsSource->GetPrefValueByRef( pref, z, &thePrefName, &thePrefTypeStr); + this->SetPrefValue(inAttrID, z, thePrefValue, thePrefType, maxPrefValueSize); + } + + // + // Make sure the dictionary knows exactly how many values are associated with + // this pref + this->SetNumValues(inAttrID, numPrefValues); +} + +void QTSSPrefs::SetObjectValuesFromFile(ContainerRef pref, QTSS_AttributeID inAttrID, UInt32 inNumValues, char* prefName) +{ + for (UInt32 z = 0; z < inNumValues; z++) + { + ContainerRef object = fPrefsSource->GetObjectValue( pref, z ); + QTSSPrefs* prefObject; + UInt32 len = sizeof(QTSSPrefs*); + QTSS_Error err = this->GetValue(inAttrID, z, &prefObject, &len); + if (err != QTSS_NoErr) + { + UInt32 tempIndex; + err = CreateObjectValue(inAttrID, &tempIndex, (QTSSDictionary**)&prefObject, NULL, QTSSDictionary::kDontObeyReadOnly | QTSSDictionary::kDontCallCompletionRoutine); + Assert(err == QTSS_NoErr); + Assert(tempIndex == z); + if (err != QTSS_NoErr) // this shouldn't happen + return; + StrPtrLen temp(prefName); + prefObject->fPrefName = temp.GetAsCString(); + } + prefObject->RereadObjectPreferences(object); + } + + // + // Make sure the dictionary knows exactly how many values are associated with + // this pref + this->SetNumValues(inAttrID, inNumValues); +} + +void QTSSPrefs::SetPrefValue(QTSS_AttributeID inAttrID, UInt32 inAttrIndex, + char* inPrefValue, QTSS_AttrDataType inPrefType, UInt32 inValueSize) + +{ + static const UInt32 kMaxPrefValueSize = 1024; + char convertedPrefValue[kMaxPrefValueSize]; + ::memset(convertedPrefValue, 0, kMaxPrefValueSize); + Assert(inValueSize < kMaxPrefValueSize); + + UInt32 convertedBufSize = kMaxPrefValueSize; + QTSS_Error theErr = QTSSDataConverter::StringToValue + (inPrefValue, inPrefType, convertedPrefValue, &convertedBufSize ); + Assert(theErr == QTSS_NoErr); + + if (inValueSize == 0) + inValueSize = convertedBufSize; + + this->SetValue(inAttrID, inAttrIndex, convertedPrefValue, inValueSize, QTSSDictionary::kDontObeyReadOnly | QTSSDictionary::kDontCallCompletionRoutine); + +} + +QTSS_AttributeID QTSSPrefs::AddPrefAttribute(const char* inAttrName, QTSS_AttrDataType inDataType) +{ + QTSS_Error theErr = this->AddInstanceAttribute( inAttrName, NULL, inDataType, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModeDelete); + Assert(theErr == QTSS_NoErr); + + QTSS_AttributeID theID = qtssIllegalAttrID; + theErr = this->GetInstanceDictMap()->GetAttrID( inAttrName, &theID); + Assert(theErr == QTSS_NoErr); + + return theID; +} + +void QTSSPrefs::RemoveValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap, + UInt32 inValueIndex) +{ + ContainerRef objectRef = GetContainerRef(); + ContainerRef pref = fPrefsSource->GetPrefRefByName( objectRef, inMap->GetAttrName(inAttrIndex)); + Assert(pref != NULL); + if (pref != NULL) + fPrefsSource->RemovePrefValue( pref, inValueIndex); + + if (fPrefsSource->WritePrefsFile()) + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgCantWriteFile, 0); +} + +void QTSSPrefs::RemoveInstanceAttrComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap) +{ + ContainerRef objectRef = GetContainerRef(); + ContainerRef pref = fPrefsSource->GetPrefRefByName( objectRef, inMap->GetAttrName(inAttrIndex)); + Assert(pref != NULL); + if (pref != NULL) + { + fPrefsSource->RemovePref(pref); + } + + if (fPrefsSource->WritePrefsFile()) + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgCantWriteFile, 0); +} + +void QTSSPrefs::SetValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap, + UInt32 inValueIndex, void* inNewValue, UInt32 inNewValueLen) +{ + ContainerRef objectRef = GetContainerRef(); + ContainerRef pref = fPrefsSource->AddPref(objectRef, inMap->GetAttrName(inAttrIndex), QTSSDataConverter::TypeToTypeString(inMap->GetAttrType(inAttrIndex))); + if (inMap->GetAttrType(inAttrIndex) == qtssAttrDataTypeQTSS_Object) + { + QTSSPrefs* object = *(QTSSPrefs**)inNewValue; // value is a pointer to a QTSSPrefs object + StrPtrLen temp(inMap->GetAttrName(inAttrIndex)); + object->fPrefName = temp.GetAsCString(); + if (inValueIndex == fPrefsSource->GetNumPrefValues(pref)) + fPrefsSource->AddNewObject(pref); + } + else + { + OSCharArrayDeleter theValueAsString(QTSSDataConverter::ValueToString(inNewValue, inNewValueLen, inMap->GetAttrType(inAttrIndex))); + fPrefsSource->SetPrefValue(pref, inValueIndex, theValueAsString.GetObject()); + } + + if (fPrefsSource->WritePrefsFile()) + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgCantWriteFile, 0); +} + +ContainerRef QTSSPrefs::GetContainerRefForObject(QTSSPrefs* object) +{ + ContainerRef thisContainer = GetContainerRef(); + ContainerRef pref = fPrefsSource->GetPrefRefByName(thisContainer, object->fPrefName); + if (pref == NULL) + return NULL; + + if (fPrefsSource->GetNumPrefValues(pref) <= 1) + return fPrefsSource->GetObjectValue(pref, 0); + + QTSSAttrInfoDict* theAttrInfoPtr = NULL; + QTSS_Error theErr = this->GetInstanceDictMap()->GetAttrInfoByName(object->fPrefName, &theAttrInfoPtr); + Assert(theErr == QTSS_NoErr); + if (theErr != QTSS_NoErr) return NULL; + QTSS_AttributeID theAttrID = qtssIllegalAttrID; + UInt32 len = sizeof(theAttrID); + theErr = theAttrInfoPtr->GetValue(qtssAttrID, 0, &theAttrID, &len); + Assert(theErr == QTSS_NoErr); + if (theErr != QTSS_NoErr) return NULL; + + UInt32 index = 0; + QTSSPrefs* prefObject; + len = sizeof(prefObject); + while (this->GetValue(theAttrID, index, &prefObject, &len) == QTSS_NoErr) + { + if (prefObject == object) + { + return fPrefsSource->GetObjectValue(pref, index); + } + } + + return NULL; +} + +ContainerRef QTSSPrefs::GetContainerRef() +{ + if (fParentDictionary == NULL) // this is a top level Pref, so it must be a module + return fPrefsSource->GetRefForModule(fPrefName); + else + return fParentDictionary->GetContainerRefForObject(this); +} diff --git a/Server.tproj/QTSSPrefs.h b/Server.tproj/QTSSPrefs.h new file mode 100644 index 0000000..12c4462 --- /dev/null +++ b/Server.tproj/QTSSPrefs.h @@ -0,0 +1,113 @@ +/* + * + * @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@ + * + */ + /* + Contains: Object store for module preferences. + + + +*/ + +#ifndef __QTSSMODULEPREFS_H__ +#define __QTSSMODULEPREFS_H__ + +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "QTSSModuleUtils.h" + +#include "StrPtrLen.h" +#include "XMLPrefsParser.h" + +class QTSSPrefs : public QTSSDictionary +{ + public: + + QTSSPrefs( XMLPrefsParser* inPrefsSource, + StrPtrLen* inModuleName, + QTSSDictionaryMap* inMap, + Bool16 areInstanceAttrsAllowed, + QTSSPrefs* parentDictionary = NULL ); + virtual ~QTSSPrefs() { if (fPrefName != NULL) delete [] fPrefName; } + + //This is callable at any time, and is thread safe wrt to the accessors + void RereadPreferences(); + void RereadObjectPreferences(ContainerRef container); + + // + // ACCESSORS + OSMutex* GetMutex() { return &fPrefsMutex; } + + ContainerRef GetContainerRefForObject(QTSSPrefs* object); + ContainerRef GetContainerRef(); + + protected: + + XMLPrefsParser* fPrefsSource; + OSMutex fPrefsMutex; + char* fPrefName; + QTSSPrefs* fParentDictionary; + + // + // SET PREF VALUES FROM FILE + // + // Places all the values at inPrefIndex of the prefs file into the attribute + // with the specified ID. This attribute must already exist. + // + // Specify inNumValues if you wish to restrict the number of values retrieved + // from the text file to a certain number, otherwise specify 0. + void SetPrefValuesFromFile(ContainerRef container, UInt32 inPrefIndex, QTSS_AttributeID inAttrID, UInt32 inNumValues = 0); + void SetPrefValuesFromFileWithRef(ContainerRef pref, QTSS_AttributeID inAttrID, UInt32 inNumValues = 0); + void SetObjectValuesFromFile(ContainerRef pref, QTSS_AttributeID inAttrID, UInt32 inNumValues, char* prefName); + + // + // SET PREF VALUE + // + // Places the specified value into the attribute with inAttrID, at inAttrIndex + // index. This function does the conversion, and uses the converted size of the + // value when setting the value. If you wish to override this size, specify inValueSize, + // otherwise it can be 0. + void SetPrefValue(QTSS_AttributeID inAttrID, UInt32 inAttrIndex, + char* inPrefValue, QTSS_AttrDataType inPrefType, UInt32 inValueSize = 0); + + + protected: + + // + // Completion routines for SetValue and RemoveValue write back to the config source + virtual void RemoveValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap, + UInt32 inValueIndex); + + virtual void SetValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap, + UInt32 inValueIndex, void* inNewValue, UInt32 inNewValueLen); + + virtual void RemoveInstanceAttrComplete(UInt32 inAttrindex, QTSSDictionaryMap* inMap); + + virtual QTSSDictionary* CreateNewDictionary(QTSSDictionaryMap* inMap, OSMutex* inMutex); + + private: + + QTSS_AttributeID AddPrefAttribute(const char* inAttrName, QTSS_AttrDataType inDataType); + +}; +#endif //__QTSSMODULEPREFS_H__ diff --git a/Server.tproj/QTSSSocket.cpp b/Server.tproj/QTSSSocket.cpp new file mode 100644 index 0000000..b30af5a --- /dev/null +++ b/Server.tproj/QTSSSocket.cpp @@ -0,0 +1,50 @@ +/* + * + * @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: QTSSSocket.cpp + + Contains: A QTSS Stream object for a generic socket + + Written By: Denis Serenyi + + + +*/ + +#include "QTSSSocket.h" + +QTSS_Error QTSSSocket::RequestEvent(QTSS_EventType inEventMask) +{ + int theMask = 0; + + if (inEventMask & QTSS_ReadableEvent) + theMask |= EV_RE; + if (inEventMask & QTSS_WriteableEvent) + theMask |= EV_WR; + + fEventContext.SetTask(this->GetTask()); + fEventContext.RequestEvent(theMask); + return QTSS_NoErr; +} diff --git a/Server.tproj/QTSSSocket.h b/Server.tproj/QTSSSocket.h new file mode 100644 index 0000000..f806625 --- /dev/null +++ b/Server.tproj/QTSSSocket.h @@ -0,0 +1,62 @@ +/* + * + * @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: QTSSSocket.h + + Contains: A QTSS Stream object for a generic socket + + Written By: Denis Serenyi + + + + +*/ + +#ifndef __QTSS_SOCKET_H__ +#define __QTSS_SOCKET_H__ + +#include "QTSS.h" +#include "EventContext.h" +#include "QTSSStream.h" +#include "Socket.h" + +class QTSSSocket : public QTSSStream +{ + public: + + QTSSSocket(int inFileDesc) : fEventContext(inFileDesc, Socket::GetEventThread()) {} + virtual ~QTSSSocket() {} + + // + // The only operation this stream supports is the requesting of events. + virtual QTSS_Error RequestEvent(QTSS_EventType inEventMask); + + private: + + EventContext fEventContext; +}; + +#endif //__QTSS_SOCKET_H__ + diff --git a/Server.tproj/QTSSStream.h b/Server.tproj/QTSSStream.h new file mode 100644 index 0000000..7b5a10e --- /dev/null +++ b/Server.tproj/QTSSStream.h @@ -0,0 +1,80 @@ +/* + * + * @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: QTSSStream.h + + Contains: Abstract base class containing the prototypes for generalized + stream functions. + + Any server object that wants to act as a QTSS_StreamRef should + derive off of this and implement one or more of the stream APIs. + +*/ + + +#ifndef __QTSS_STREAM_H__ +#define __QTSS_STREAM_H__ + +#include "QTSS.h" +#include "Task.h" + +class QTSSStream +{ + public: + + QTSSStream() : fTask(NULL) {} + virtual ~QTSSStream() {} + + // + // A stream can have a task associated with it. If this stream supports + // async I/O, the task is needed to know what to wakeup when there is an event + void SetTask(Task* inTask) { fTask = inTask; } + Task* GetTask() { return fTask; } + + virtual QTSS_Error Read(void* /*ioBuffer*/, UInt32 /*inLen*/, UInt32* /*outLen*/) + { return QTSS_Unimplemented; } + + virtual QTSS_Error Write(void* /*inBuffer*/, UInt32 /*inLen*/, UInt32* /*outLenWritten*/, UInt32 /*inFlags*/) + { return QTSS_Unimplemented; } + + virtual QTSS_Error WriteV(iovec* /*inVec*/, UInt32 /*inNumVectors*/, UInt32 /*inTotalLength*/, UInt32* /*outLenWritten*/) + { return QTSS_Unimplemented; } + + virtual QTSS_Error Flush() { return QTSS_Unimplemented; } + + virtual QTSS_Error Seek(UInt64 /*inNewPosition*/) { return QTSS_Unimplemented; } + + virtual QTSS_Error Advise(UInt64 /*inPosition*/, UInt32 /*inAdviseSize*/) + { return QTSS_Unimplemented; } + + virtual QTSS_Error RequestEvent(QTSS_EventType /*inEventMask*/) + { return QTSS_Unimplemented; } + + private: + + Task* fTask; +}; + +#endif //__QTSS_STREAM_H__ diff --git a/Server.tproj/QTSSUserProfile.cpp b/Server.tproj/QTSSUserProfile.cpp new file mode 100644 index 0000000..c02f550 --- /dev/null +++ b/Server.tproj/QTSSUserProfile.cpp @@ -0,0 +1,70 @@ +/* + * + * @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: QTSSUserProfile.cp + + Contains: Implementation of class defined in QTSSUserProfile.h + + + + +*/ + + +//INCLUDES: +#include "QTSSUserProfile.h" + +QTSSAttrInfoDict::AttrInfo QTSSUserProfile::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssUserName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtssUserPassword", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite}, + /* 2 */ { "qtssUserGroups", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite}, + /* 3 */ { "qtssUserRealm", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite}, + /* 4 */ { "qtssUserRights", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite}, + /* 5 */ { "qtssUserExtendedRights", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite}, + /* 6 */ { "qtssUserQTSSExtendedRights", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite} +}; + +void QTSSUserProfile::Initialize() +{ + //Setup all the dictionary stuff + for (UInt32 x = 0; x < qtssUserNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kQTSSUserProfileDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); + + +} + +//CONSTRUCTOR / DESTRUCTOR: very simple stuff +QTSSUserProfile::QTSSUserProfile() +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kQTSSUserProfileDictIndex)) +{ + this->SetEmptyVal(qtssUserName, &fUserNameBuf[0], kMaxUserProfileNameLen); + this->SetEmptyVal(qtssUserPassword, &fUserPasswordBuf[0], kMaxUserProfilePasswordLen); + this->SetVal(qtssUserRights, &fUserRights, sizeof(fUserRights)); + this->fUserRights = qtssAttrRightNone; +} + diff --git a/Server.tproj/QTSSUserProfile.h b/Server.tproj/QTSSUserProfile.h new file mode 100644 index 0000000..dca662c --- /dev/null +++ b/Server.tproj/QTSSUserProfile.h @@ -0,0 +1,74 @@ +/* + * + * @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: QTSSUserProfile.h + + Contains: An object to store User Profile, for authentication + and authorization + + Implements the RTSP Request dictionary for QTSS API. + + +*/ + + +#ifndef __QTSSUSERPROFILE_H__ +#define __QTSSUSERPROFILE_H__ + +//INCLUDES: +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "StrPtrLen.h" + +class QTSSUserProfile : public QTSSDictionary +{ + public: + + //Initialize + //Call initialize before instantiating this class. For maximum performance, this class builds + //any response header it can at startup time. + static void Initialize(); + + //CONSTRUCTOR & DESTRUCTOR + QTSSUserProfile(); + virtual ~QTSSUserProfile() {} + + protected: + + enum + { + kMaxUserProfileNameLen = 32, + kMaxUserProfilePasswordLen = 32 + }; + + char fUserNameBuf[kMaxUserProfileNameLen]; // Set by RTSPRequest object + char fUserPasswordBuf[kMaxUserProfilePasswordLen];// Set by authentication module through API + + UInt32 fUserRights; //Set by authorization module. + //Dictionary support + static QTSSAttrInfoDict::AttrInfo sAttributes[]; +}; +#endif // __QTSSUSERPROFILE_H__ + diff --git a/Server.tproj/QTSServer.cpp b/Server.tproj/QTSServer.cpp new file mode 100644 index 0000000..0f913f9 --- /dev/null +++ b/Server.tproj/QTSServer.cpp @@ -0,0 +1,1247 @@ +/* + * + * @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: QTSServer.cpp + + Contains: Implements object defined in QTSServer.h + + + +*/ + + +#ifndef __Win32__ +#include +#include +#endif +#include + +#ifndef __Win32__ +#include +#include +#include +#endif + +#include "QTSServer.h" + +#include "OSMemory.h" +#include "OSArrayObjectDeleter.h" +#include "SocketUtils.h" +#include "TCPListenerSocket.h" +#include "Task.h" + +#include "QTSS_Private.h" +#include "QTSSCallbacks.h" +#include "QTSSModuleUtils.h" + +//Compile time modules +#include "QTSSErrorLogModule.h" +#include "QTSSFileModule.h" +#include "QTSSAccessLogModule.h" +#include "QTSSFlowControlModule.h" +#include "QTSSReflectorModule.h" +#ifdef PROXYSERVER +#include "QTSSProxyModule.h" +#endif +#include "QTSSRelayModule.h" +#include "QTSSPosixFileSysModule.h" +#include "QTSSAdminModule.h" +#include "QTSSAccessModule.h" +#include "QTSSMP3StreamingModule.h" +#if __MacOSX__ +#include "QTSSDSAuthModule.h" +#endif +#if MEMORY_DEBUGGING +#include "QTSSWebDebugModule.h" +#endif + + + +#include "RTSPRequestInterface.h" +#include "RTSPSessionInterface.h" +#include "RTPSessionInterface.h" +#include "RTSPSession.h" +#include "RTPStream.h" +#include "RTCPTask.h" +#include "QTSSFile.h" + +#include "RTPStream3GPP.h" +#include "RTSPRequest3GPP.h" + +// CLASS DEFINITIONS + +class RTSPListenerSocket : public TCPListenerSocket +{ + public: + + RTSPListenerSocket() {} + virtual ~RTSPListenerSocket() {} + + //sole job of this object is to implement this function + virtual Task* GetSessionTask(TCPSocket** outSocket); + + //check whether the Listener should be idling + Bool16 OverMaxConnections(UInt32 buffer); + +}; + +class RTPSocketPool : public UDPSocketPool +{ + public: + + // Pool of UDP sockets for use by the RTP server + + RTPSocketPool() {} + ~RTPSocketPool() {} + + virtual UDPSocketPair* ConstructUDPSocketPair(); + virtual void DestructUDPSocketPair(UDPSocketPair* inPair); + + virtual void SetUDPSocketOptions(UDPSocketPair* inPair); +}; + + + +char* QTSServer::sPortPrefString = "rtsp_port"; +QTSS_Callbacks QTSServer::sCallbacks; +XMLPrefsParser* QTSServer::sPrefsSource = NULL; +PrefsSource* QTSServer::sMessagesSource = NULL; + + +QTSServer::~QTSServer() +{ + // + // Grab the server mutex. This is to make sure all gets & set values on this + // object complete before we start deleting stuff + OSMutexLocker serverlocker(this->GetServerObjectMutex()); + + // + // Grab the prefs mutex. This is to make sure we can't reread prefs + // WHILE shutting down, which would cause some weirdness for QTSS API + // (some modules could get QTSS_RereadPrefs_Role after QTSS_Shutdown, which would be bad) + OSMutexLocker locker(this->GetPrefs()->GetMutex()); + + QTSS_ModuleState theModuleState; + theModuleState.curRole = QTSS_Shutdown_Role; + theModuleState.curTask = NULL; + OSThread::SetMainThreadData(&theModuleState); + + for (UInt32 x = 0; x < QTSServerInterface::GetNumModulesInRole(QTSSModule::kShutdownRole); x++) + (void)QTSServerInterface::GetModule(QTSSModule::kShutdownRole, x)->CallDispatch(QTSS_Shutdown_Role, NULL); + + OSThread::SetMainThreadData(NULL); +} + +Bool16 QTSServer::Initialize(XMLPrefsParser* inPrefsSource, PrefsSource* inMessagesSource, UInt16 inPortOverride, Bool16 createListeners) +{ + static const UInt32 kRTPSessionMapSize = 577; + fServerState = qtssFatalErrorState; + sPrefsSource = inPrefsSource; + sMessagesSource = inMessagesSource; + this->InitCallbacks(); + + // + // DICTIONARY INITIALIZATION + + QTSSModule::Initialize(); + QTSServerPrefs::Initialize(); + QTSSMessages::Initialize(); + RTSPRequestInterface::Initialize(); + RTSPSessionInterface::Initialize(); + RTPSessionInterface::Initialize(); + RTPStream::Initialize(); + RTSPSession::Initialize(); + QTSSFile::Initialize(); + QTSSUserProfile::Initialize(); + + RTSPRequest3GPP::Initialize(); + RTPStream3GPP::Initialize(); + RTPSession3GPP::Initialize(); + RTSPSession3GPP::Initialize(); + + // + // STUB SERVER INITIALIZATION + // + // Construct stub versions of the prefs and messages dictionaries. We need + // both of these to initialize the server, but they have to be stubs because + // their QTSSDictionaryMaps will presumably be modified when modules get loaded. + + fSrvrPrefs = new QTSServerPrefs(inPrefsSource, false); // First time, don't write changes to the prefs file + fSrvrMessages = new QTSSMessages(inMessagesSource); + QTSSModuleUtils::Initialize(fSrvrMessages, this, QTSServerInterface::GetErrorLogStream()); + + // + // SETUP ASSERT BEHAVIOR + // + // Depending on the server preference, we will either break when we hit an + // assert, or log the assert to the error log + if (!fSrvrPrefs->ShouldServerBreakOnAssert()) + SetAssertLogger(this->GetErrorLogStream());// the error log stream is our assert logger + + // + // CREATE GLOBAL OBJECTS + fSocketPool = new RTPSocketPool(); + fRTPMap = new OSRefTable(kRTPSessionMapSize); + + // + // Load ERROR LOG module only. This is good in case there is a startup error. + + QTSSModule* theLoggingModule = new QTSSModule("QTSSErrorLogModule"); + (void)theLoggingModule->SetupModule(&sCallbacks, &QTSSErrorLogModule_Main); + (void)AddModule(theLoggingModule); + this->BuildModuleRoleArrays(); + + // + // DEFAULT IP ADDRESS & DNS NAME + if (!this->SetDefaultIPAddr()) + return false; + + // + // STARTUP TIME - record it + fStartupTime_UnixMilli = OS::Milliseconds(); + fGMTOffset = OS::GetGMTOffset(); + + // + // BEGIN LISTENING + if (createListeners) + { + if ( !this->CreateListeners(false, fSrvrPrefs, inPortOverride) ) + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgSomePortsFailed, 0); + } + + if ( fNumListeners == 0 ) + { if (createListeners) + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgNoPortsSucceeded, 0); + return false; + } + + + fServerState = qtssStartingUpState; + return true; +} + +void QTSServer::InitModules(QTSS_ServerState inEndState) +{ + // + // LOAD AND INITIALIZE ALL MODULES + + // temporarily set the verbosity on missing prefs when starting up to debug level + // This keeps all the pref messages being written to the config file from being logged. + // don't exit until the verbosity level is reset back to the initial prefs. + + LoadModules(fSrvrPrefs); + LoadCompiledInModules(); + this->BuildModuleRoleArrays(); + + fSrvrPrefs->SetErrorLogVerbosity(qtssWarningVerbosity); // turn off info messages while initializing compiled in modules. + // + // CREATE MODULE OBJECTS AND READ IN MODULE PREFS + + // Finish setting up modules. Create our final prefs & messages objects, + // register all global dictionaries, and invoke the modules in their Init roles. + fStubSrvrPrefs = fSrvrPrefs; + fStubSrvrMessages = fSrvrMessages; + + fSrvrPrefs = new QTSServerPrefs(sPrefsSource, true); // Now write changes to the prefs file. First time, we don't because the error messages won't get printed. + QTSS_ErrorVerbosity serverLevel = fSrvrPrefs->GetErrorLogVerbosity(); // get the real prefs verbosity and save it. + fSrvrPrefs->SetErrorLogVerbosity(qtssWarningVerbosity); // turn off info messages while loading dynamic modules + + + fSrvrMessages = new QTSSMessages(sMessagesSource); + QTSSModuleUtils::Initialize(fSrvrMessages, this, QTSServerInterface::GetErrorLogStream()); + + this->SetVal(qtssSvrMessages, &fSrvrMessages, sizeof(fSrvrMessages)); + this->SetVal(qtssSvrPreferences, &fSrvrPrefs, sizeof(fSrvrPrefs)); + + // + // ADD REREAD PREFERENCES SERVICE + (void)QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kServiceDictIndex)-> + AddAttribute(QTSS_REREAD_PREFS_SERVICE, (QTSS_AttrFunctionPtr)QTSServer::RereadPrefsService, qtssAttrDataTypeUnknown, qtssAttrModeRead); + + // + // INVOKE INITIALIZE ROLE + this->DoInitRole(); + + if (fServerState != qtssFatalErrorState) + fServerState = inEndState; // Server is done starting up! + + + fSrvrPrefs->SetErrorLogVerbosity(serverLevel); // reset the server's verbosity back to the original prefs level. +} + +void QTSServer::StartTasks() +{ + fRTCPTask = new RTCPTask(); + fStatsTask = new RTPStatsUpdaterTask(); + + // + // Start listening + for (UInt32 x = 0; x < fNumListeners; x++) + fListeners[x]->RequestEvent(EV_RE); +} + +Bool16 QTSServer::SetDefaultIPAddr() +{ + //check to make sure there is an available ip interface + if (SocketUtils::GetNumIPAddrs() == 0) + { + QTSSModuleUtils::LogError(qtssFatalVerbosity, qtssMsgNotConfiguredForIP, 0); + return false; + } + + //find out what our default IP addr is & dns name + UInt32 theNumAddrs = 0; + UInt32* theIPAddrs = this->GetRTSPIPAddrs(fSrvrPrefs, &theNumAddrs); + if (theNumAddrs == 1) + fDefaultIPAddr = SocketUtils::GetIPAddr(0); + else + fDefaultIPAddr = theIPAddrs[0]; + delete [] theIPAddrs; + + for (UInt32 ipAddrIter = 0; ipAddrIter < SocketUtils::GetNumIPAddrs(); ipAddrIter++) + { + if (SocketUtils::GetIPAddr(ipAddrIter) == fDefaultIPAddr) + { + this->SetVal(qtssSvrDefaultDNSName, SocketUtils::GetDNSNameStr(ipAddrIter)); + Assert(this->GetValue(qtssSvrDefaultDNSName)->Ptr != NULL); + this->SetVal(qtssSvrDefaultIPAddrStr, SocketUtils::GetIPAddrStr(ipAddrIter)); + Assert(this->GetValue(qtssSvrDefaultDNSName)->Ptr != NULL); + break; + } + } + if (this->GetValue(qtssSvrDefaultDNSName)->Ptr == NULL) + { + //If we've gotten here, what has probably happened is the IP address (explicitly + //entered as a preference) doesn't exist + QTSSModuleUtils::LogError(qtssFatalVerbosity, qtssMsgDefaultRTSPAddrUnavail, 0); + return false; + } + return true; +} + + +Bool16 QTSServer::CreateListeners(Bool16 startListeningNow, QTSServerPrefs* inPrefs, UInt16 inPortOverride) +{ + struct PortTracking + { + PortTracking() : fPort(0), fIPAddr(0), fNeedsCreating(true) {} + + UInt16 fPort; + UInt32 fIPAddr; + Bool16 fNeedsCreating; + }; + + PortTracking* thePortTrackers = NULL; + UInt32 theTotalPortTrackers = 0; + + // Get the IP addresses from the pref + UInt32 theNumAddrs = 0; + UInt32* theIPAddrs = this->GetRTSPIPAddrs(inPrefs, &theNumAddrs); + UInt32 index = 0; + + if ( inPortOverride != 0) + { + theTotalPortTrackers = theNumAddrs; // one port tracking struct for each IP addr + thePortTrackers = NEW PortTracking[theTotalPortTrackers]; + for (index = 0; index < theNumAddrs; index++) + { + thePortTrackers[index].fPort = inPortOverride; + thePortTrackers[index].fIPAddr = theIPAddrs[index]; + } + } + else + { + UInt32 theNumPorts = 0; + UInt16* thePorts = GetRTSPPorts(inPrefs, &theNumPorts); + theTotalPortTrackers = theNumAddrs * theNumPorts; + thePortTrackers = NEW PortTracking[theTotalPortTrackers]; + + UInt32 currentIndex = 0; + + for (index = 0; index < theNumAddrs; index++) + { + for (UInt32 portIndex = 0; portIndex < theNumPorts; portIndex++) + { + currentIndex = (theNumPorts * index) + portIndex; + + thePortTrackers[currentIndex].fPort = thePorts[portIndex]; + thePortTrackers[currentIndex].fIPAddr = theIPAddrs[index]; + } + } + + delete [] thePorts; + } + + delete [] theIPAddrs; + // + // Now figure out which of these ports we are *already* listening on. + // If we already are listening on that port, just move the pointer to the + // listener over to the new array + TCPListenerSocket** newListenerArray = NEW TCPListenerSocket*[theTotalPortTrackers]; + UInt32 curPortIndex = 0; + + for (UInt32 count = 0; count < theTotalPortTrackers; count++) + { + for (UInt32 count2 = 0; count2 < fNumListeners; count2++) + { + if ((fListeners[count2]->GetLocalPort() == thePortTrackers[count].fPort) && + (fListeners[count2]->GetLocalAddr() == thePortTrackers[count].fIPAddr)) + { + thePortTrackers[count].fNeedsCreating = false; + newListenerArray[curPortIndex++] = fListeners[count2]; + Assert(curPortIndex <= theTotalPortTrackers); + break; + } + } + } + + // + // Create any new listeners we need + for (UInt32 count3 = 0; count3 < theTotalPortTrackers; count3++) + { + if (thePortTrackers[count3].fNeedsCreating) + { + newListenerArray[curPortIndex] = NEW RTSPListenerSocket(); + QTSS_Error err = newListenerArray[curPortIndex]->Initialize(thePortTrackers[count3].fIPAddr, thePortTrackers[count3].fPort); + + char thePortStr[20]; + qtss_sprintf(thePortStr, "%hu", thePortTrackers[count3].fPort); + + // + // If there was an error creating this listener, destroy it and log an error + if ((startListeningNow) && (err != QTSS_NoErr)) + delete newListenerArray[curPortIndex]; + + if (err == EADDRINUSE) + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssListenPortInUse, 0, thePortStr); + else if (err == EACCES) + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssListenPortAccessDenied, 0, thePortStr); + else if (err != QTSS_NoErr) + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssListenPortError, 0, thePortStr); + else + { + // + // This listener was successfully created. + if (startListeningNow) + newListenerArray[curPortIndex]->RequestEvent(EV_RE); + curPortIndex++; + } + } + } + + // + // Kill any listeners that we no longer need + for (UInt32 count4 = 0; count4 < fNumListeners; count4++) + { + Bool16 deleteThisOne = true; + + for (UInt32 count5 = 0; count5 < curPortIndex; count5++) + { + if (newListenerArray[count5] == fListeners[count4]) + deleteThisOne = false; + } + + if (deleteThisOne) + fListeners[count4]->Signal(Task::kKillEvent); + } + + // + // Finally, make our server attributes and fListener privy to the new... + fListeners = newListenerArray; + fNumListeners = curPortIndex; + UInt32 portIndex = 0; + + for (UInt32 count6 = 0; count6 < fNumListeners; count6++) + { + if (fListeners[count6]->GetLocalAddr() != INADDR_LOOPBACK) + { + UInt16 thePort = fListeners[count6]->GetLocalPort(); + (void)this->SetValue(qtssSvrRTSPPorts, portIndex, &thePort, sizeof(thePort), QTSSDictionary::kDontObeyReadOnly); + portIndex++; + } + } + this->SetNumValues(qtssSvrRTSPPorts, portIndex); + + delete [] thePortTrackers; + return (fNumListeners > 0); +} + +UInt32* QTSServer::GetRTSPIPAddrs(QTSServerPrefs* inPrefs, UInt32* outNumAddrsPtr) +{ + UInt32 numAddrs = inPrefs->GetNumValues(qtssPrefsRTSPIPAddr); + UInt32* theIPAddrArray = NULL; + + if (numAddrs == 0) + { + *outNumAddrsPtr = 1; + theIPAddrArray = NEW UInt32[1]; + theIPAddrArray[0] = INADDR_ANY; + } + else + { + theIPAddrArray = NEW UInt32[numAddrs + 1]; + UInt32 arrIndex = 0; + + for (UInt32 theIndex = 0; theIndex < numAddrs; theIndex++) + { + // Get the ip addr out of the prefs dictionary + QTSS_Error theErr = QTSS_NoErr; + + char* theIPAddrStr = NULL; + theErr = inPrefs->GetValueAsString(qtssPrefsRTSPIPAddr, theIndex, &theIPAddrStr); + if (theErr != QTSS_NoErr) + { + delete [] theIPAddrStr; + break; + } + + + UInt32 theIPAddr = 0; + if (theIPAddrStr != NULL) + { + theIPAddr = SocketUtils::ConvertStringToAddr(theIPAddrStr); + delete [] theIPAddrStr; + + if (theIPAddr != 0) + theIPAddrArray[arrIndex++] = theIPAddr; + } + } + + if ((numAddrs == 1) && (arrIndex == 0)) + theIPAddrArray[arrIndex++] = INADDR_ANY; + else + theIPAddrArray[arrIndex++] = INADDR_LOOPBACK; + + *outNumAddrsPtr = arrIndex; + } + + return theIPAddrArray; +} + +UInt16* QTSServer::GetRTSPPorts(QTSServerPrefs* inPrefs, UInt32* outNumPortsPtr) +{ + *outNumPortsPtr = inPrefs->GetNumValues(qtssPrefsRTSPPorts); + + if (*outNumPortsPtr == 0) + return NULL; + + UInt16* thePortArray = NEW UInt16[*outNumPortsPtr]; + + for (UInt32 theIndex = 0; theIndex < *outNumPortsPtr; theIndex++) + { + // Get the ip addr out of the prefs dictionary + UInt32 theLen = sizeof(UInt16); + QTSS_Error theErr = QTSS_NoErr; + theErr = inPrefs->GetValue(qtssPrefsRTSPPorts, theIndex, &thePortArray[theIndex], &theLen); + Assert(theErr == QTSS_NoErr); + } + + return thePortArray; +} + +Bool16 QTSServer::SetupUDPSockets() +{ + //function finds all IP addresses on this machine, and binds 1 RTP / RTCP + //socket pair to a port pair on each address. + + UInt32 theNumAllocatedPairs = 0; + for (UInt32 theNumPairs = 0; theNumPairs < SocketUtils::GetNumIPAddrs(); theNumPairs++) + { + UDPSocketPair* thePair = fSocketPool->CreateUDPSocketPair(SocketUtils::GetIPAddr(theNumPairs), 0); + if (thePair != NULL) + { + theNumAllocatedPairs++; + thePair->GetSocketA()->RequestEvent(EV_RE); + thePair->GetSocketB()->RequestEvent(EV_RE); + } + } + //only return an error if we couldn't allocate ANY pairs of sockets + if (theNumAllocatedPairs == 0) + { + fServerState = qtssFatalErrorState; // also set the state to fatal error + return false; + } + return true; +} + +Bool16 QTSServer::SwitchPersonality() +{ +#ifndef __Win32__ //not supported + OSCharArrayDeleter runGroupName(fSrvrPrefs->GetRunGroupName()); + OSCharArrayDeleter runUserName(fSrvrPrefs->GetRunUserName()); + + int groupID = 0; + + if (::strlen(runGroupName.GetObject()) > 0) + { + struct group* gr = ::getgrnam(runGroupName.GetObject()); + if (gr == NULL || ::setgid(gr->gr_gid) == -1) + { + char buffer[kErrorStrSize]; + + QTSSModuleUtils::LogError(qtssFatalVerbosity, qtssMsgCannotSetRunGroup, 0, + runGroupName.GetObject(), qtss_strerror(OSThread::GetErrno(), buffer, sizeof(buffer))); + return false; + } + groupID = gr->gr_gid; + } + + if (::strlen(runUserName.GetObject()) > 0) + { + struct passwd* pw = ::getpwnam(runUserName.GetObject()); + +#if __MacOSX__ + if (pw != NULL && groupID != 0) //call initgroups before doing a setuid + (void) initgroups(runUserName.GetObject(),groupID); +#endif + + if (pw == NULL || ::setuid(pw->pw_uid) == -1) + { + QTSSModuleUtils::LogError(qtssFatalVerbosity, qtssMsgCannotSetRunUser, 0, + runUserName.GetObject(), strerror(OSThread::GetErrno())); + return false; + } + } + +#endif + return true; +} + + + + +void QTSServer::LoadCompiledInModules() +{ +#ifndef DSS_DYNAMIC_MODULES_ONLY + // MODULE DEVELOPERS SHOULD ADD THE FOLLOWING THREE LINES OF CODE TO THIS + // FUNCTION IF THEIR MODULE IS BEING COMPILED INTO THE SERVER. + // + // QTSSModule* myModule = new QTSSModule("__MODULE_NAME__"); + // (void)myModule->Initialize(&sCallbacks, &__MODULE_MAIN_ROUTINE__); + // (void)AddModule(myModule); + // + // The following modules are all compiled into the server. + + QTSSModule* theFileModule = new QTSSModule("QTSSFileModule"); + (void)theFileModule->SetupModule(&sCallbacks, &QTSSFileModule_Main); + (void)AddModule(theFileModule); + + QTSSModule* theReflectorModule = new QTSSModule("QTSSReflectorModule"); + (void)theReflectorModule->SetupModule(&sCallbacks, &QTSSReflectorModule_Main); + (void)AddModule(theReflectorModule); + + QTSSModule* theRelayModule = new QTSSModule("QTSSRelayModule"); + (void)theRelayModule->SetupModule(&sCallbacks, &QTSSRelayModule_Main); + (void)AddModule(theRelayModule); + + QTSSModule* theAccessLog = new QTSSModule("QTSSAccessLogModule"); + (void)theAccessLog->SetupModule(&sCallbacks, &QTSSAccessLogModule_Main); + (void)AddModule(theAccessLog); + + QTSSModule* theFlowControl = new QTSSModule("QTSSFlowControlModule"); + (void)theFlowControl->SetupModule(&sCallbacks, &QTSSFlowControlModule_Main); + (void)AddModule(theFlowControl); + + QTSSModule* theFileSysModule = new QTSSModule("QTSSPosixFileSysModule"); + (void)theFileSysModule->SetupModule(&sCallbacks, &QTSSPosixFileSysModule_Main); + (void)AddModule(theFileSysModule); + + QTSSModule* theAdminModule = new QTSSModule("QTSSAdminModule"); + (void)theAdminModule->SetupModule(&sCallbacks, &QTSSAdminModule_Main); + (void)AddModule(theAdminModule); + + QTSSModule* theMP3StreamingModule = new QTSSModule("QTSSMP3StreamingModule"); + (void)theMP3StreamingModule->SetupModule(&sCallbacks, &QTSSMP3StreamingModule_Main); + (void)AddModule(theMP3StreamingModule); + +#if MEMORY_DEBUGGING + QTSSModule* theWebDebug = new QTSSModule("QTSSWebDebugModule"); + (void)theWebDebug->SetupModule(&sCallbacks, &QTSSWebDebugModule_Main); + (void)AddModule(theWebDebug); +#endif + +#if __MacOSX__ + QTSSModule* theQTSSDSAuthModule = new QTSSModule("QTSSDSAuthModule"); + (void)theQTSSDSAuthModule->SetupModule(&sCallbacks, &QTSSDSAuthModule_Main); + (void)AddModule(theQTSSDSAuthModule); +#endif + + QTSSModule* theQTACCESSmodule = new QTSSModule("QTSSAccessModule"); + (void)theQTACCESSmodule->SetupModule(&sCallbacks, &QTSSAccessModule_Main); + (void)AddModule(theQTACCESSmodule); + + +#endif //DSS_DYNAMIC_MODULES_ONLY + +#ifdef PROXYSERVER + QTSSModule* theProxyModule = new QTSSModule("QTSSProxyModule"); + (void)theProxyModule->SetupModule(&sCallbacks, &QTSSProxyModule_Main); + (void)AddModule(theProxyModule); +#endif + +} + + + +void QTSServer::InitCallbacks() +{ + sCallbacks.addr[kNewCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_New; + sCallbacks.addr[kDeleteCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Delete; + sCallbacks.addr[kMillisecondsCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Milliseconds; + sCallbacks.addr[kConvertToUnixTimeCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_ConvertToUnixTime; + + sCallbacks.addr[kAddRoleCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_AddRole; + sCallbacks.addr[kCreateObjectTypeCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_CreateObjectType; + sCallbacks.addr[kAddAttributeCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_AddAttribute; + sCallbacks.addr[kIDForTagCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_IDForAttr; + sCallbacks.addr[kGetAttributePtrByIDCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_GetValuePtr; + sCallbacks.addr[kGetAttributeByIDCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_GetValue; + sCallbacks.addr[kSetAttributeByIDCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_SetValue; + sCallbacks.addr[kCreateObjectValueCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_CreateObject; + sCallbacks.addr[kGetNumValuesCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_GetNumValues; + + sCallbacks.addr[kWriteCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Write; + sCallbacks.addr[kWriteVCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_WriteV; + sCallbacks.addr[kFlushCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Flush; + sCallbacks.addr[kReadCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Read; + sCallbacks.addr[kSeekCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Seek; + sCallbacks.addr[kAdviseCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Advise; + + sCallbacks.addr[kAddServiceCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_AddService; + sCallbacks.addr[kIDForServiceCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_IDForService; + sCallbacks.addr[kDoServiceCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_DoService; + + sCallbacks.addr[kSendRTSPHeadersCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_SendRTSPHeaders; + sCallbacks.addr[kAppendRTSPHeadersCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_AppendRTSPHeader; + sCallbacks.addr[kSendStandardRTSPCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_SendStandardRTSPResponse; + + sCallbacks.addr[kAddRTPStreamCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_AddRTPStream; + sCallbacks.addr[kPlayCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Play; + sCallbacks.addr[kPauseCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Pause; + sCallbacks.addr[kTeardownCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Teardown; + sCallbacks.addr[kRefreshTimeOutCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_RefreshTimeOut; + + sCallbacks.addr[kRequestEventCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_RequestEvent; + sCallbacks.addr[kSetIdleTimerCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_SetIdleTimer; + sCallbacks.addr[kSignalStreamCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_SignalStream; + + sCallbacks.addr[kOpenFileObjectCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_OpenFileObject; + sCallbacks.addr[kCloseFileObjectCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_CloseFileObject; + + sCallbacks.addr[kCreateSocketStreamCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_CreateStreamFromSocket; + sCallbacks.addr[kDestroySocketStreamCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_DestroySocketStream; + + sCallbacks.addr[kAddStaticAttributeCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_AddStaticAttribute; + sCallbacks.addr[kAddInstanceAttributeCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_AddInstanceAttribute; + sCallbacks.addr[kRemoveInstanceAttributeCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_RemoveInstanceAttribute; + + sCallbacks.addr[kGetAttrInfoByIndexCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_GetAttrInfoByIndex; + sCallbacks.addr[kGetAttrInfoByNameCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_GetAttrInfoByName; + sCallbacks.addr[kGetAttrInfoByIDCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_GetAttrInfoByID; + sCallbacks.addr[kGetNumAttributesCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_GetNumAttributes; + + + sCallbacks.addr[kGetValueAsStringCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_GetValueAsString; + sCallbacks.addr[kTypeToTypeStringCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_TypeToTypeString; + sCallbacks.addr[kTypeStringToTypeCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_TypeStringToType; + sCallbacks.addr[kStringToValueCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_StringToValue; + sCallbacks.addr[kValueToStringCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_ValueToString; + + sCallbacks.addr[kRemoveValueCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_RemoveValue; + + sCallbacks.addr[kRequestGlobalLockCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_RequestLockedCallback; + sCallbacks.addr[kIsGlobalLockedCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_IsGlobalLocked; + sCallbacks.addr[kUnlockGlobalLock] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_UnlockGlobalLock; + + sCallbacks.addr[kAuthenticateCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Authenticate; + sCallbacks.addr[kAuthorizeCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_Authorize; + + sCallbacks.addr[kLockObjectCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_LockObject; + sCallbacks.addr[kUnlockObjectCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_UnlockObject; + sCallbacks.addr[kSetAttributePtrCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_SetValuePtr; + + sCallbacks.addr[kSetIntervalRoleTimerCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_SetIdleRoleTimer; + + sCallbacks.addr[kLockStdLibCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_LockStdLib; + sCallbacks.addr[kUnlockStdLibCallback] = (QTSS_CallbackProcPtr)QTSSCallbacks::QTSS_UnlockStdLib; +} + +void QTSServer::LoadModules(QTSServerPrefs* inPrefs) +{ + // Fetch the name of the module directory and open it. + OSCharArrayDeleter theModDirName(inPrefs->GetModuleDirectory()); + +#ifdef __Win32__ + // NT doesn't seem to have support for the POSIX directory parsing APIs. + OSCharArrayDeleter theLargeModDirName(NEW char[::strlen(theModDirName.GetObject()) + 3]); + ::strcpy(theLargeModDirName.GetObject(), theModDirName.GetObject()); + ::strcat(theLargeModDirName.GetObject(), "\\*"); + + WIN32_FIND_DATA theFindData; + HANDLE theSearchHandle = ::FindFirstFile(theLargeModDirName.GetObject(), &theFindData); + + if (theSearchHandle == INVALID_HANDLE_VALUE) + { + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgNoModuleFolder, 0); + return; + } + + while (theSearchHandle != INVALID_HANDLE_VALUE) + { + this->CreateModule(theModDirName.GetObject(), theFindData.cFileName); + + if (!::FindNextFile(theSearchHandle, &theFindData)) + { + ::FindClose(theSearchHandle); + theSearchHandle = INVALID_HANDLE_VALUE; + } + } +#else + + // POSIX version + // opendir mallocs memory for DIR* so call closedir to free the allocated memory + DIR* theDir = ::opendir(theModDirName.GetObject()); + if (theDir == NULL) + { + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgNoModuleFolder, 0); + return; + } + + while (true) + { + // Iterate over each file in the directory, attempting to construct + // a module object from that file. + + struct dirent* theFile = ::readdir(theDir); + if (theFile == NULL) + break; + + this->CreateModule(theModDirName.GetObject(), theFile->d_name); + } + + (void)::closedir(theDir); + +#endif +} + +void QTSServer::CreateModule(char* inModuleFolderPath, char* inModuleName) +{ + // Ignore these silly directory names + + if (::strcmp(inModuleName, ".") == 0) + return; + if (::strcmp(inModuleName, "..") == 0) + return; + if (::strlen(inModuleName) == 0) + return; + if (*inModuleName == '.') + return; // Fix 2572248. Do not attempt to load '.' files as modules at all + + // + // Construct a full path to this module + UInt32 totPathLen = ::strlen(inModuleFolderPath) + ::strlen(inModuleName); + OSCharArrayDeleter theModPath(NEW char[totPathLen + 4]); + ::strcpy(theModPath.GetObject(), inModuleFolderPath); + ::strcat(theModPath.GetObject(), kPathDelimiterString); + ::strcat(theModPath.GetObject(), inModuleName); + + // + // Construct a QTSSModule object, and attempt to initialize the module + QTSSModule* theNewModule = NEW QTSSModule(inModuleName, theModPath.GetObject()); + QTSS_Error theErr = theNewModule->SetupModule(&sCallbacks); + + if (theErr != QTSS_NoErr) + { + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgBadModule, theErr, + inModuleName); + delete theNewModule; + } + // + // If the module was successfully initialized, add it to our module queue + else if (!this->AddModule(theNewModule)) + { + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgRegFailed, theErr, + inModuleName); + delete theNewModule; + } +} + +Bool16 QTSServer::AddModule(QTSSModule* inModule) +{ + Assert(inModule->IsInitialized()); + + // Prepare to invoke the module's Register role. Setup the Register param block + QTSS_ModuleState theModuleState; + + theModuleState.curModule = inModule; + theModuleState.curRole = QTSS_Register_Role; + theModuleState.curTask = NULL; + OSThread::SetMainThreadData(&theModuleState); + + // Currently we do nothing with the module name + QTSS_RoleParams theRegParams; + theRegParams.regParams.outModuleName[0] = 0; + + // If the module returns an error from the QTSS_Register role, don't put it anywhere + if (inModule->CallDispatch(QTSS_Register_Role, &theRegParams) != QTSS_NoErr) + return false; + + OSThread::SetMainThreadData(NULL); + + // + // Update the module name to reflect what was returned from the register role + theRegParams.regParams.outModuleName[QTSS_MAX_MODULE_NAME_LENGTH - 1] = 0; + if (theRegParams.regParams.outModuleName[0] != 0) + inModule->SetValue(qtssModName, 0, theRegParams.regParams.outModuleName, ::strlen(theRegParams.regParams.outModuleName), false); + + // + // Give the module object a prefs dictionary. Instance attributes are allowed for these objects. + QTSSPrefs* thePrefs = NEW QTSSPrefs( sPrefsSource, inModule->GetValue(qtssModName), QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kModulePrefsDictIndex), true); + thePrefs->RereadPreferences(); + inModule->SetPrefsDict(thePrefs); + + // + // Add this module to the array of module (dictionaries) + UInt32 theNumModules = this->GetNumValues(qtssSvrModuleObjects); + QTSS_Error theErr = this->SetValue(qtssSvrModuleObjects, theNumModules, &inModule, sizeof(QTSSModule*), QTSSDictionary::kDontObeyReadOnly); + Assert(theErr == QTSS_NoErr); + + // + // Add this module to the module queue + sModuleQueue.EnQueue(inModule->GetQueueElem()); + + return true; +} + +void QTSServer::BuildModuleRoleArrays() +{ + OSQueueIter theIter(&sModuleQueue); + QTSSModule* theModule = NULL; + + // Make sure these variables are cleaned out in case they've already been inited. + + DestroyModuleRoleArrays(); + + // Loop through all the roles of all the modules, recording the number of + // modules in each role, and also recording which modules are doing what. + + for (UInt32 x = 0; x < QTSSModule::kNumRoles; x++) + { + sNumModulesInRole[x] = 0; + for (theIter.Reset(); !theIter.IsDone(); theIter.Next()) + { + theModule = (QTSSModule*)theIter.GetCurrent()->GetEnclosingObject(); + if (theModule->RunsInRole(x)) + sNumModulesInRole[x] += 1; + } + + if (sNumModulesInRole[x] > 0) + { + UInt32 moduleIndex = 0; + sModuleArray[x] = new QTSSModule*[sNumModulesInRole[x] + 1]; + for (theIter.Reset(); !theIter.IsDone(); theIter.Next()) + { + theModule = (QTSSModule*)theIter.GetCurrent()->GetEnclosingObject(); + if (theModule->RunsInRole(x)) + { + sModuleArray[x][moduleIndex] = theModule; + moduleIndex++; + } + } + } + } +} + +void QTSServer::DestroyModuleRoleArrays() +{ + for (UInt32 x = 0; x < QTSSModule::kNumRoles; x++) + { + sNumModulesInRole[x] = 0; + if (sModuleArray[x] != NULL) + delete [] sModuleArray[x]; + sModuleArray[x] = NULL; + } +} + +void QTSServer::DoInitRole() +{ + QTSS_RoleParams theInitParams; + theInitParams.initParams.inServer = this; + theInitParams.initParams.inPrefs = fSrvrPrefs; + theInitParams.initParams.inMessages = fSrvrMessages; + theInitParams.initParams.inErrorLogStream = &sErrorLogStream; + + QTSS_ModuleState theModuleState; + theModuleState.curRole = QTSS_Initialize_Role; + theModuleState.curTask = NULL; + OSThread::SetMainThreadData(&theModuleState); + + // + // Add the OPTIONS method as the one method the server handles by default (it handles + // it internally). Modules that handle other RTSP methods will add + QTSS_RTSPMethod theOptionsMethod = qtssOptionsMethod; + (void)this->SetValue(qtssSvrHandledMethods, 0, &theOptionsMethod, sizeof(theOptionsMethod)); + + +// For now just disable the SetParameter to be compatible with Real. It should really be removed only for clients that have problems with their SetParameter implementations like (Real Players). +// At the moment it isn't necesary to add the option. +// QTSS_RTSPMethod theSetParameterMethod = qtssSetParameterMethod; +// (void)this->SetValue(qtssSvrHandledMethods, 0, &theSetParameterMethod, sizeof(theSetParameterMethod)); + + for (UInt32 x = 0; x < QTSServerInterface::GetNumModulesInRole(QTSSModule::kInitializeRole); x++) + { + QTSSModule* theModule = QTSServerInterface::GetModule(QTSSModule::kInitializeRole, x); + theInitParams.initParams.inModule = theModule; + theModuleState.curModule = theModule; + QTSS_Error theErr = theModule->CallDispatch(QTSS_Initialize_Role, &theInitParams); + + if (theErr != QTSS_NoErr) + { + // If the module reports an error when initializing itself, + // delete the module and pretend it was never there. + QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgInitFailed, theErr, + theModule->GetValue(qtssModName)->Ptr); + + sModuleQueue.Remove(theModule->GetQueueElem()); + delete theModule; + } + } + this->SetupPublicHeader(); + + OSThread::SetMainThreadData(NULL); +} + +void QTSServer::SetupPublicHeader() +{ + // + // After the Init role, all the modules have reported the methods that they handle. + // So, we can prune this attribute for duplicates, and construct a string to use in the + // Public: header of the OPTIONS response + QTSS_RTSPMethod* theMethod = NULL; + UInt32 theLen = 0; + + Bool16 theUniqueMethods[qtssNumMethods + 1]; + ::memset(theUniqueMethods, 0, sizeof(theUniqueMethods)); + + for (UInt32 y = 0; this->GetValuePtr(qtssSvrHandledMethods, y, (void**)&theMethod, &theLen) == QTSS_NoErr; y++) + theUniqueMethods[*theMethod] = true; + + // Rewrite the qtssSvrHandledMethods, eliminating any duplicates that modules may have introduced + UInt32 uniqueMethodCount = 0; + for (QTSS_RTSPMethod z = 0; z < qtssNumMethods; z++) + { + if (theUniqueMethods[z]) + this->SetValue(qtssSvrHandledMethods, uniqueMethodCount++, &z, sizeof(QTSS_RTSPMethod)); + } + this->SetNumValues(qtssSvrHandledMethods, uniqueMethodCount); + + // Format a text string for the Public: header + ResizeableStringFormatter theFormatter(NULL, 0); + + for (UInt32 a = 0; this->GetValuePtr(qtssSvrHandledMethods, a, (void**)&theMethod, &theLen) == QTSS_NoErr; a++) + { + sPublicHeaderFormatter.Put(RTSPProtocol::GetMethodString(*theMethod)); + sPublicHeaderFormatter.Put(", "); + } + sPublicHeaderStr.Ptr = sPublicHeaderFormatter.GetBufPtr(); + sPublicHeaderStr.Len = sPublicHeaderFormatter.GetBytesWritten() - 2; //trunc the last ", " +} + + +Task* RTSPListenerSocket::GetSessionTask(TCPSocket** outSocket) +{ + Assert(outSocket != NULL); + + // when the server is behing a round robin DNS, the client needs to knwo the IP address ot the server + // so that it can direct the "POST" half of the connection to the same machine when tunnelling RTSP thru HTTP + Bool16 doReportHTTPConnectionAddress = QTSServerInterface::GetServer()->GetPrefs()->GetDoReportHTTPConnectionAddress(); + + RTSPSession* theTask = NEW RTSPSession(doReportHTTPConnectionAddress); + *outSocket = theTask->GetSocket(); // out socket is not attached to a unix socket yet. + + if (this->OverMaxConnections(0)) + this->SlowDown(); + else + this->RunNormal(); + + return theTask; +} + + +Bool16 RTSPListenerSocket::OverMaxConnections(UInt32 buffer) +{ + QTSServerInterface* theServer = QTSServerInterface::GetServer(); + SInt32 maxConns = theServer->GetPrefs()->GetMaxConnections(); + Bool16 overLimit = false; + + if (maxConns > -1) // limit connections + { + maxConns += buffer; + if ( (theServer->GetNumRTPSessions() > (UInt32) maxConns) + || + ( theServer->GetNumRTSPSessions() + theServer->GetNumRTSPHTTPSessions() > (UInt32) maxConns ) + ) + { + overLimit = true; + } + } + return overLimit; + +} + + +UDPSocketPair* RTPSocketPool::ConstructUDPSocketPair() +{ + Task* theTask = ((QTSServer*)QTSServerInterface::GetServer())->fRTCPTask; + + //construct a pair of UDP sockets, the lower one for RTP data (outgoing only, no demuxer + //necessary), and one for RTCP data (incoming, so definitely need a demuxer). + //These are nonblocking sockets that DON'T receive events (we are going to poll for data) + // They do receive events - we don't poll from them anymore + return NEW + UDPSocketPair( NEW UDPSocket(theTask, Socket::kNonBlockingSocketType), + NEW UDPSocket(theTask, UDPSocket::kWantsDemuxer | Socket::kNonBlockingSocketType)); +} + +void RTPSocketPool::DestructUDPSocketPair(UDPSocketPair* inPair) +{ + delete inPair->GetSocketA(); + delete inPair->GetSocketB(); + delete inPair; +} + +void RTPSocketPool::SetUDPSocketOptions(UDPSocketPair* inPair) +{ + // Apparently the socket buffer size matters even though this is UDP and being + // used for sending... on UNIX typically the socket buffer size doesn't matter because the + // packet goes right down to the driver. On Win32 and linux, unless this is really big, we get packet loss. + inPair->GetSocketA()->SetSocketBufSize(256 * 1024); + + // + // Always set the Rcv buf size for the RTCP sockets. This is important because the + // server is going to be getting many many acks. + UInt32 theRcvBufSize = QTSServerInterface::GetServer()->GetPrefs()->GetRTCPSocketRcvBufSizeinK(); + + // + // In case the rcv buf size is too big for the system, retry, dividing the requested size by 2. + // Until it works, or until some minimum value is reached. + OS_Error theErr = EINVAL; + while ((theErr != OS_NoErr) && (theRcvBufSize > 32)) + { + theErr = inPair->GetSocketB()->SetSocketRcvBufSize(theRcvBufSize * 1024); + if (theErr != OS_NoErr) + theRcvBufSize >>= 1; + } + + // + // Report an error if we couldn't set the socket buffer size the user requested + if (theRcvBufSize != QTSServerInterface::GetServer()->GetPrefs()->GetRTCPSocketRcvBufSizeinK()) + { + char theRcvBufSizeStr[20]; + qtss_sprintf(theRcvBufSizeStr, "%"_U32BITARG_"", theRcvBufSize); + // + // For now, do not log an error, though we should enable this in the future. + //QTSSModuleUtils::LogError(qtssWarningVerbosity, qtssMsgSockBufSizesTooLarge, theRcvBufSizeStr); + } +} + + + +QTSS_Error QTSServer::RereadPrefsService(QTSS_ServiceFunctionArgsPtr /*inArgs*/) +{ + // + // This function can only be called safely when the server is completely running. + // Ensuring this is a bit complicated because of preemption. Here's how it's done... + + QTSServerInterface* theServer = QTSServerInterface::GetServer(); + + // This is to make sure this function isn't being called before the server is + // completely started up. + if ((theServer == NULL) || (theServer->GetServerState() != qtssRunningState)) + return QTSS_OutOfState; + + // Because the server must have started up, and because this object always stays + // around (until the process dies), we can now safely get this object. + QTSServerPrefs* thePrefs = theServer->GetPrefs(); + + // Grab the prefs mutex. We want to make sure that calls to RereadPrefsService + // are serialized. This also prevents the server from shutting down while in + // this function, because the QTSServer destructor grabs this mutex as well. + OSMutexLocker locker(thePrefs->GetMutex()); + + // Finally, check the server state again. The state may have changed + // to qtssShuttingDownState or qtssFatalErrorState in this time, though + // at this point we have the prefs mutex, so we are guarenteed that the + // server can't actually shut down anymore + if (theServer->GetServerState() != qtssRunningState) + return QTSS_OutOfState; + + // Ok, we're ready to reread preferences now. + + // + // Reread preferences + sPrefsSource->Parse(); + thePrefs->RereadServerPreferences(true); + + { + // + // Update listeners, ports, and IP addrs. + OSMutexLocker locker(theServer->GetServerObjectMutex()); + (void)((QTSServer*)theServer)->SetDefaultIPAddr(); + (void)((QTSServer*)theServer)->CreateListeners(true, thePrefs, 0); + } + + // Delete all the streams + QTSSModule** theModule = NULL; + UInt32 theLen = 0; + + for (int y = 0; QTSServerInterface::GetServer()->GetValuePtr(qtssSvrModuleObjects, y, (void**)&theModule, &theLen) == QTSS_NoErr; y++) + { + Assert(theModule != NULL); + Assert(theLen == sizeof(QTSSModule*)); + + (*theModule)->GetPrefsDict()->RereadPreferences(); + +#if DEBUG + theModule = NULL; + theLen = 0; +#endif + } + + // + // Go through each module's prefs object and have those reread as well + + // + // Now that we are done rereading the prefs, invoke all modules in the RereadPrefs + // role so they can update their internal prefs caches. + for (UInt32 x = 0; x < QTSServerInterface::GetNumModulesInRole(QTSSModule::kRereadPrefsRole); x++) + { + QTSSModule* theModule = QTSServerInterface::GetModule(QTSSModule::kRereadPrefsRole, x); + (void)theModule->CallDispatch(QTSS_RereadPrefs_Role, NULL); + } + return QTSS_NoErr; +} + + diff --git a/Server.tproj/QTSServer.h b/Server.tproj/QTSServer.h new file mode 100644 index 0000000..31b24eb --- /dev/null +++ b/Server.tproj/QTSServer.h @@ -0,0 +1,173 @@ +/* + * + * @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: QTSServer.h + + Contains: This object is responsible for bringing up & shutting down + the server. It also loads & initializes all modules. + + + +*/ + +#ifndef __QTSSERVER_H__ +#define __QTSSERVER_H__ + +#include "QTSServerInterface.h" +#include "Task.h" + +class RTCPTask; +class RTSPListenerSocket; +class RTPSocketPool; +class SessionTimeoutTask; + +class QTSServer : public QTSServerInterface +{ + public: + + QTSServer() {} + virtual ~QTSServer(); + + // + // Initialize + // + // This function starts the server. If it returns true, the server has + // started up sucessfully. If it returns false, a fatal error occurred + // while attempting to start the server. + // + // This function *must* be called before the server creates any threads, + // because one of its actions is to change the server to the right UID / GID. + // Threads will only inherit these if they are created afterwards. + Bool16 Initialize(XMLPrefsParser* inPrefsSource, PrefsSource* inMessagesSource, + UInt16 inPortOverride, Bool16 createListeners); + + // + // InitModules + // + // Initialize *does not* do much of the module initialization tasks. This + // function may be called after the server has created threads, but the + // server must not be in a state where it can do real work. In other words, + // call this function right after calling Initialize. + void InitModules(QTSS_ServerState inEndState); + + // + // StartTasks + // + // The server has certain global tasks that it runs for things like stats + // updating and RTCP processing. This function must be called to start those + // going, and it must be called after Initialize + void StartTasks(); + + + // + // RereadPrefsService + // + // This service is registered by the server (calling "RereadPreferences"). + // It rereads the preferences. Anyone can call this to reread the preferences, + // and it may be called safely at any time, though it will fail with a + // QTSS_OutOfState if the server isn't in the qtssRunningState. + + static QTSS_Error RereadPrefsService(QTSS_ServiceFunctionArgsPtr inArgs); + + // + // CreateListeners + // + // This function may be called multiple times & at any time. + // It updates the server's listeners to reflect what the preferences say. + // Returns false if server couldn't listen on one or more of the ports, true otherwise + Bool16 CreateListeners(Bool16 startListeningNow, QTSServerPrefs* inPrefs, UInt16 inPortOverride); + + // + // SetDefaultIPAddr + // + // Sets the IP address related attributes of the server. + Bool16 SetDefaultIPAddr(); + + Bool16 SetupUDPSockets(); + + Bool16 SwitchPersonality(); + + private: + + // + // GLOBAL TASKS + RTCPTask* fRTCPTask; + RTPStatsUpdaterTask*fStatsTask; + SessionTimeoutTask *fSessionTimeoutTask; + static char* sPortPrefString; + static XMLPrefsParser* sPrefsSource; + static PrefsSource* sMessagesSource; + + // + // Module loading & unloading routines + + static QTSS_Callbacks sCallbacks; + + // Sets up QTSS API callback routines + void InitCallbacks(); + + // Loads compiled-in modules + void LoadCompiledInModules(); + + // Loads modules from disk + void LoadModules(QTSServerPrefs* inPrefs); + void CreateModule(char* inModuleFolderPath, char* inModuleName); + + // Adds a module to the module array + Bool16 AddModule(QTSSModule* inModule); + + // Call module init roles + void DoInitRole(); + void SetupPublicHeader(); + UInt32* GetRTSPIPAddrs(QTSServerPrefs* inPrefs, UInt32* outNumAddrsPtr); + UInt16* GetRTSPPorts(QTSServerPrefs* inPrefs, UInt32* outNumPortsPtr); + + // Build & destroy the optimized role / module arrays for invoking modules + void BuildModuleRoleArrays(); + void DestroyModuleRoleArrays(); + + +#ifndef __Win32__ + static pid_t sMainPid; +#endif + + + friend class RTPSocketPool; +}; + +class RereadPrefsTask : public Task +{ +public: + virtual SInt64 Run() + { + QTSServer::RereadPrefsService(NULL); + return -1; + } +}; + + +#endif // __QTSSERVER_H__ + + diff --git a/Server.tproj/QTSServerInterface.cpp b/Server.tproj/QTSServerInterface.cpp new file mode 100644 index 0000000..68b3ce5 --- /dev/null +++ b/Server.tproj/QTSServerInterface.cpp @@ -0,0 +1,636 @@ +/* + * + * @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: QTSServerInterface.cpp + + Contains: Implementation of object defined in QTSServerInterface.h. + + + +*/ + +//INCLUDES: + +#ifndef kVersionString +#include "../revision.h" +#endif +#include "QTSServerInterface.h" + +#include "RTPSessionInterface.h" +#include "OSRef.h" +#include "UDPSocketPool.h" +#include "RTSPProtocol.h" +#include "RTPPacketResender.h" +#ifndef __MacOSX__ +#include "../revision.h" +#endif + +// STATIC DATA + +UInt32 QTSServerInterface::sServerAPIVersion = QTSS_API_VERSION; +QTSServerInterface* QTSServerInterface::sServer = NULL; +#if __MacOSX__ +StrPtrLen QTSServerInterface::sServerNameStr("QTSS"); +#else +StrPtrLen QTSServerInterface::sServerNameStr("DSS"); +#endif + +// kVersionString from revision.h, include with -i at project level +StrPtrLen QTSServerInterface::sServerVersionStr(kVersionString); +StrPtrLen QTSServerInterface::sServerBuildStr(kBuildString); +StrPtrLen QTSServerInterface::sServerCommentStr(kCommentString); + +StrPtrLen QTSServerInterface::sServerPlatformStr(kPlatformNameString); +StrPtrLen QTSServerInterface::sServerBuildDateStr(__DATE__ ", "__TIME__); +char QTSServerInterface::sServerHeader[kMaxServerHeaderLen]; +StrPtrLen QTSServerInterface::sServerHeaderPtr(sServerHeader, kMaxServerHeaderLen); + +ResizeableStringFormatter QTSServerInterface::sPublicHeaderFormatter(NULL, 0); +StrPtrLen QTSServerInterface::sPublicHeaderStr; + +QTSSModule** QTSServerInterface::sModuleArray[QTSSModule::kNumRoles]; +UInt32 QTSServerInterface::sNumModulesInRole[QTSSModule::kNumRoles]; +OSQueue QTSServerInterface::sModuleQueue; +QTSSErrorLogStream QTSServerInterface::sErrorLogStream; + + +QTSSAttrInfoDict::AttrInfo QTSServerInterface::sConnectedUserAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssConnectionType", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 1 */ { "qtssConnectionCreateTimeInMsec", NULL, qtssAttrDataTypeTimeVal, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 2 */ { "qtssConnectionTimeConnectedInMsec", TimeConnected, qtssAttrDataTypeTimeVal, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 3 */ { "qtssConnectionBytesSent", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 4 */ { "qtssConnectionMountPoint", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 5 */ { "qtssConnectionHostName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe } , + + /* 6 */ { "qtssConnectionSessRemoteAddrStr", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 7 */ { "qtssConnectionSessLocalAddrStr", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + + /* 8 */ { "qtssConnectionCurrentBitRate", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 9 */ { "qtssConnectionPacketLossPercent", NULL, qtssAttrDataTypeFloat32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + // this last parameter is a workaround for the current dictionary implementation. For qtssConnectionTimeConnectedInMsec above we have a param + // retrieval function. This needs storage to keep the value returned, but if it sets its own param then the function no longer gets called. + /* 10 */ { "qtssConnectionTimeStorage", NULL, qtssAttrDataTypeTimeVal, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, +}; + + +QTSSAttrInfoDict::AttrInfo QTSServerInterface::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssServerAPIVersion", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtssSvrDefaultDNSName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead }, + /* 2 */ { "qtssSvrDefaultIPAddr", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 3 */ { "qtssSvrServerName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 4 */ { "qtssRTSPSvrServerVersion", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 5 */ { "qtssRTSPSvrServerBuildDate", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 6 */ { "qtssSvrRTSPPorts", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead }, + /* 7 */ { "qtssSvrRTSPServerHeader", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 8 */ { "qtssSvrState", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 9 */ { "qtssSvrIsOutOfDescriptors", IsOutOfDescriptors, qtssAttrDataTypeBool16, qtssAttrModeRead }, + /* 10 */ { "qtssRTSPCurrentSessionCount", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 11 */ { "qtssRTSPHTTPCurrentSessionCount",NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 12 */ { "qtssRTPSvrNumUDPSockets", GetTotalUDPSockets, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 13 */ { "qtssRTPSvrCurConn", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 14 */ { "qtssRTPSvrTotalConn", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 15 */ { "qtssRTPSvrCurBandwidth", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 16 */ { "qtssRTPSvrTotalBytes", NULL, qtssAttrDataTypeUInt64, qtssAttrModeRead }, + /* 17 */ { "qtssRTPSvrAvgBandwidth", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 18 */ { "qtssRTPSvrCurPackets", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 19 */ { "qtssRTPSvrTotalPackets", NULL, qtssAttrDataTypeUInt64, qtssAttrModeRead }, + /* 20 */ { "qtssSvrHandledMethods", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 21 */ { "qtssSvrModuleObjects", NULL, qtssAttrDataTypeQTSS_Object,qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 22 */ { "qtssSvrStartupTime", NULL, qtssAttrDataTypeTimeVal, qtssAttrModeRead }, + /* 23 */ { "qtssSvrGMTOffsetInHrs", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead }, + /* 24 */ { "qtssSvrDefaultIPAddrStr", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead }, + /* 25 */ { "qtssSvrPreferences", NULL, qtssAttrDataTypeQTSS_Object,qtssAttrModeRead | qtssAttrModeInstanceAttrAllowed}, + /* 26 */ { "qtssSvrMessages", NULL, qtssAttrDataTypeQTSS_Object,qtssAttrModeRead }, + /* 27 */ { "qtssSvrClientSessions", NULL, qtssAttrDataTypeQTSS_Object,qtssAttrModeRead }, + /* 28 */ { "qtssSvrCurrentTimeMilliseconds",CurrentUnixTimeMilli, qtssAttrDataTypeTimeVal,qtssAttrModeRead}, + /* 29 */ { "qtssSvrCPULoadPercent", NULL, qtssAttrDataTypeFloat32, qtssAttrModeRead}, + /* 30 */ { "qtssSvrNumReliableUDPBuffers", GetNumUDPBuffers, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 31 */ { "qtssSvrReliableUDPWastageInBytes",GetNumWastedBytes, qtssAttrDataTypeUInt32, qtssAttrModeRead }, + /* 32 */ { "qtssSvrConnectedUsers", NULL, qtssAttrDataTypeQTSS_Object, qtssAttrModeRead | qtssAttrModeWrite }, + /* 33 */ { "qtssMP3SvrCurConn", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 34 */ { "qtssMP3SvrTotalConn", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 35 */ { "qtssMP3SvrCurBandwidth", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 36 */ { "qtssMP3SvrTotalBytes", NULL, qtssAttrDataTypeUInt64, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 37 */ { "qtssMP3SvrAvgBandwidth", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + + /* 38 */ { "qtssSvrServerBuild", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 39 */ { "qtssSvrServerPlatform", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 40 */ { "qtssSvrRTSPServerComment", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 41 */ { "qtssSvrNumThinned", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 42 */ { "qtssSvrNumThreads", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe } + +}; + +void QTSServerInterface::Initialize() +{ + for (UInt32 x = 0; x < qtssSvrNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kServerDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); + + for (UInt32 y = 0; y < qtssConnectionNumParams; y++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kQTSSConnectedUserDictIndex)-> + SetAttribute(y, sConnectedUserAttributes[y].fAttrName, sConnectedUserAttributes[y].fFuncPtr, + sConnectedUserAttributes[y].fAttrDataType, sConnectedUserAttributes[y].fAttrPermission); + + //Write out a premade server header + StringFormatter serverFormatter(sServerHeaderPtr.Ptr, kMaxServerHeaderLen); + serverFormatter.Put(RTSPProtocol::GetHeaderString(qtssServerHeader)); + serverFormatter.Put(": "); + serverFormatter.Put(sServerNameStr); + serverFormatter.PutChar('/'); + serverFormatter.Put(sServerVersionStr); + serverFormatter.PutChar(' '); + + serverFormatter.PutChar('('); + serverFormatter.Put("Build/"); + serverFormatter.Put(sServerBuildStr); + serverFormatter.Put("; "); + serverFormatter.Put("Platform/"); + serverFormatter.Put(sServerPlatformStr); + serverFormatter.PutChar(';'); + + if (sServerCommentStr.Len > 0) + { + serverFormatter.PutChar(' '); + serverFormatter.Put(sServerCommentStr); + } + serverFormatter.PutChar(')'); + + + sServerHeaderPtr.Len = serverFormatter.GetCurrentOffset(); + Assert(sServerHeaderPtr.Len < kMaxServerHeaderLen); +} + +QTSServerInterface::QTSServerInterface() + : QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kServerDictIndex), &fMutex), + fSocketPool(NULL), + fRTPMap(NULL), + fSrvrPrefs(NULL), + fSrvrMessages(NULL), + fServerState(qtssStartingUpState), + fDefaultIPAddr(0), + fListeners(NULL), + fNumListeners(0), + fStartupTime_UnixMilli(0), + fGMTOffset(0), + fNumRTSPSessions(0), + fNumRTSPHTTPSessions(0), + fNumRTPSessions(0), + fNumRTPPlayingSessions(0), + fTotalRTPSessions(0), + fTotalRTPBytes(0), + fTotalRTPPackets(0), + fTotalRTPPacketsLost(0), + fPeriodicRTPBytes(0), + fPeriodicRTPPacketsLost(0), + fPeriodicRTPPackets(0), + fCurrentRTPBandwidthInBits(0), + fAvgRTPBandwidthInBits(0), + fRTPPacketsPerSecond(0), + fCPUPercent(0), + fCPUTimeUsedInSec(0), + fUDPWastageInBytes(0), + fNumUDPBuffers(0), + fNumMP3Sessions(0), + fTotalMP3Sessions(0), + fCurrentMP3BandwidthInBits(0), + fTotalMP3Bytes(0), + fAvgMP3BandwidthInBits(0), + fSigInt(false), + fSigTerm(false), + fDebugLevel(0), + fDebugOptions(0), + fMaxLate(0), + fTotalLate(0), + fCurrentMaxLate(0), + fTotalQuality(0), + fNumThinned(0), + fNumThreads(0) +{ + for (UInt32 y = 0; y < QTSSModule::kNumRoles; y++) + { + sModuleArray[y] = NULL; + sNumModulesInRole[y] = 0; + } + + this->SetVal(qtssSvrState, &fServerState, sizeof(fServerState)); + this->SetVal(qtssServerAPIVersion, &sServerAPIVersion, sizeof(sServerAPIVersion)); + this->SetVal(qtssSvrDefaultIPAddr, &fDefaultIPAddr, sizeof(fDefaultIPAddr)); + this->SetVal(qtssSvrServerName, sServerNameStr.Ptr, sServerNameStr.Len); + this->SetVal(qtssSvrServerVersion, sServerVersionStr.Ptr, sServerVersionStr.Len); + this->SetVal(qtssSvrServerBuildDate, sServerBuildDateStr.Ptr, sServerBuildDateStr.Len); + this->SetVal(qtssSvrRTSPServerHeader, sServerHeaderPtr.Ptr, sServerHeaderPtr.Len); + this->SetVal(qtssRTSPCurrentSessionCount, &fNumRTSPSessions, sizeof(fNumRTSPSessions)); + this->SetVal(qtssRTSPHTTPCurrentSessionCount, &fNumRTSPHTTPSessions,sizeof(fNumRTSPHTTPSessions)); + this->SetVal(qtssRTPSvrCurConn, &fNumRTPSessions, sizeof(fNumRTPSessions)); + this->SetVal(qtssRTPSvrTotalConn, &fTotalRTPSessions, sizeof(fTotalRTPSessions)); + this->SetVal(qtssRTPSvrCurBandwidth, &fCurrentRTPBandwidthInBits,sizeof(fCurrentRTPBandwidthInBits)); + this->SetVal(qtssRTPSvrTotalBytes, &fTotalRTPBytes, sizeof(fTotalRTPBytes)); + this->SetVal(qtssRTPSvrAvgBandwidth, &fAvgRTPBandwidthInBits, sizeof(fAvgRTPBandwidthInBits)); + this->SetVal(qtssRTPSvrCurPackets, &fRTPPacketsPerSecond, sizeof(fRTPPacketsPerSecond)); + this->SetVal(qtssRTPSvrTotalPackets, &fTotalRTPPackets, sizeof(fTotalRTPPackets)); + this->SetVal(qtssSvrStartupTime, &fStartupTime_UnixMilli, sizeof(fStartupTime_UnixMilli)); + this->SetVal(qtssSvrGMTOffsetInHrs, &fGMTOffset, sizeof(fGMTOffset)); + this->SetVal(qtssSvrCPULoadPercent, &fCPUPercent, sizeof(fCPUPercent)); + this->SetVal(qtssMP3SvrCurConn, &fNumMP3Sessions, sizeof(fNumMP3Sessions)); + this->SetVal(qtssMP3SvrTotalConn, &fTotalMP3Sessions, sizeof(fTotalMP3Sessions)); + this->SetVal(qtssMP3SvrCurBandwidth, &fCurrentMP3BandwidthInBits,sizeof(fCurrentMP3BandwidthInBits)); + this->SetVal(qtssMP3SvrTotalBytes, &fTotalMP3Bytes, sizeof(fTotalMP3Bytes)); + this->SetVal(qtssMP3SvrAvgBandwidth, &fAvgMP3BandwidthInBits, sizeof(fAvgMP3BandwidthInBits)); + + this->SetVal(qtssSvrServerBuild, sServerBuildStr.Ptr, sServerBuildStr.Len); + this->SetVal(qtssSvrRTSPServerComment, sServerCommentStr.Ptr, sServerCommentStr.Len); + this->SetVal(qtssSvrServerPlatform, sServerPlatformStr.Ptr, sServerPlatformStr.Len); + + this->SetVal(qtssSvrNumThinned, &fNumThinned, sizeof(fNumThinned)); + this->SetVal(qtssSvrNumThreads, &fNumThreads, sizeof(fNumThreads)); + + + sServer = this; +} + + +void QTSServerInterface::LogError(QTSS_ErrorVerbosity inVerbosity, char* inBuffer) +{ + QTSS_RoleParams theParams; + theParams.errorParams.inVerbosity = inVerbosity; + theParams.errorParams.inBuffer = inBuffer; + + for (UInt32 x = 0; x < QTSServerInterface::GetNumModulesInRole(QTSSModule::kErrorLogRole); x++) + (void)QTSServerInterface::GetModule(QTSSModule::kErrorLogRole, x)->CallDispatch(QTSS_ErrorLog_Role, &theParams); + + // If this is a fatal error, set the proper attribute in the RTSPServer dictionary + if ((inVerbosity == qtssFatalVerbosity) && (sServer != NULL)) + { + QTSS_ServerState theState = qtssFatalErrorState; + (void)sServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState)); + } +} + +void QTSServerInterface::KillAllRTPSessions() +{ + OSMutexLocker locker(fRTPMap->GetMutex()); + for (OSRefHashTableIter theIter(fRTPMap->GetHashTable()); !theIter.IsDone(); theIter.Next()) + { + OSRef* theRef = theIter.GetCurrent(); + RTPSessionInterface* theSession = (RTPSessionInterface*)theRef->GetObject(); + theSession->Signal(Task::kKillEvent); + } +} + +void QTSServerInterface::SetValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap, + UInt32 inValueIndex, void* inNewValue, UInt32 inNewValueLen) +{ + if (inAttrIndex == qtssSvrState) + { + Assert(inNewValueLen == sizeof(QTSS_ServerState)); + + // + // Invoke the server state change role + QTSS_RoleParams theParams; + theParams.stateChangeParams.inNewState = *(QTSS_ServerState*)inNewValue; + + static QTSS_ModuleState sStateChangeState = { NULL, 0, NULL, false }; + if (OSThread::GetCurrent() == NULL) + OSThread::SetMainThreadData(&sStateChangeState); + else + OSThread::GetCurrent()->SetThreadData(&sStateChangeState); + + UInt32 numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kStateChangeRole); + { + for (UInt32 theCurrentModule = 0; theCurrentModule < numModules; theCurrentModule++) + { + QTSSModule* theModule = QTSServerInterface::GetModule(QTSSModule::kStateChangeRole, theCurrentModule); + (void)theModule->CallDispatch(QTSS_StateChange_Role, &theParams); + } + } + + // + // Make sure to clear out the thread data + if (OSThread::GetCurrent() == NULL) + OSThread::SetMainThreadData(NULL); + else + OSThread::GetCurrent()->SetThreadData(NULL); + } +} + + +RTPStatsUpdaterTask::RTPStatsUpdaterTask() +: Task(), fLastBandwidthTime(0), fLastBandwidthAvg(0), fLastBytesSent(0), fLastTotalMP3Bytes(0) +{ + this->SetTaskName("RTPStatsUpdaterTask"); + this->Signal(Task::kStartEvent); +} + +Float32 RTPStatsUpdaterTask::GetCPUTimeInSeconds() +{ + // This function returns the total number of seconds that the + // process running RTPStatsUpdaterTask() has been executing as + // a user process. + Float32 cpuTimeInSec = 0.0; +#ifdef __Win32__ + // The Win32 way of getting the time for this process + HANDLE hProcess = GetCurrentProcess(); + SInt64 createTime, exitTime, kernelTime, userTime; + if(GetProcessTimes(hProcess, (LPFILETIME)&createTime, (LPFILETIME)&exitTime, (LPFILETIME)&kernelTime, (LPFILETIME)&userTime)) + { + // userTime is in 10**-7 seconds since Jan.1, 1607. + // (What type of computers did they use in 1607?) + cpuTimeInSec = (Float32) (userTime/10000000.0); + } + else + { + // This should never happen!!! + Assert(0); + cpuTimeInSec = 0.0; + } +#else + // The UNIX way of getting the time for this process + clock_t cpuTime = clock(); + cpuTimeInSec = (Float32) cpuTime / CLOCKS_PER_SEC; +#endif + return cpuTimeInSec; +} + +SInt64 RTPStatsUpdaterTask::Run() +{ + + QTSServerInterface* theServer = QTSServerInterface::sServer; + + // All of this must happen atomically wrt dictionary values we are manipulating + OSMutexLocker locker(&theServer->fMutex); + + //First update total bytes. This must be done because total bytes is a 64 bit number, + //so no atomic functions can apply. + // + // NOTE: The line below is not thread safe on non-PowerPC platforms. This is + // because the fPeriodicRTPBytes variable is being manipulated from within an + // atomic_add. On PowerPC, assignments are atomic, so the assignment below is ok. + // On a non-PowerPC platform, the following would be thread safe: + //unsigned int periodicBytes = atomic_add(&theServer->fPeriodicRTPBytes, 0); + unsigned int periodicBytes = theServer->fPeriodicRTPBytes; + (void)atomic_sub(&theServer->fPeriodicRTPBytes, periodicBytes); + theServer->fTotalRTPBytes += periodicBytes; + + // Same deal for packet totals + unsigned int periodicPackets = theServer->fPeriodicRTPPackets; + (void)atomic_sub(&theServer->fPeriodicRTPPackets, periodicPackets); + theServer->fTotalRTPPackets += periodicPackets; + + // ..and for lost packet totals + unsigned int periodicPacketsLost = theServer->fPeriodicRTPPacketsLost; + (void)atomic_sub(&theServer->fPeriodicRTPPacketsLost, periodicPacketsLost); + theServer->fTotalRTPPacketsLost += periodicPacketsLost; + + SInt64 curTime = OS::Milliseconds(); + + //for cpu percent + Float32 cpuTimeInSec = GetCPUTimeInSeconds(); + + //also update current bandwidth statistic + if (fLastBandwidthTime != 0) + { + Assert(curTime > fLastBandwidthTime); + UInt32 delta = (UInt32)(curTime - fLastBandwidthTime); + // Prevent divide by zero errror + if (delta < 1000) { + WarnV(delta >= 1000, "delta < 1000"); + (void)this->GetEvents();//we must clear the event mask! + return theServer->GetPrefs()->GetTotalBytesUpdateTimeInSecs() * 1000; + } + + UInt32 packetsPerSecond = periodicPackets; + UInt32 theTime = delta / 1000; + + packetsPerSecond /= theTime; + Assert(packetsPerSecond >= 0); + theServer->fRTPPacketsPerSecond = packetsPerSecond; + UInt32 additionalBytes = 28 * packetsPerSecond; // IP headers = 20 + UDP headers = 8 + UInt32 headerBits = 8 * additionalBytes; + headerBits /= theTime; + + Float32 bits = periodicBytes * 8; + bits /= theTime; + theServer->fCurrentRTPBandwidthInBits = (UInt32) (bits + headerBits); + + // okay let's do it for MP3 bytes now + bits = (Float32)(((SInt64)theServer->fTotalMP3Bytes - fLastTotalMP3Bytes) * 8); + bits /= theTime; + theServer->fCurrentMP3BandwidthInBits = (UInt32)bits; + + + //do the computation for cpu percent + Float32 diffTime = cpuTimeInSec - theServer->fCPUTimeUsedInSec; + theServer->fCPUPercent = (diffTime/theTime) * 100; + + UInt32 numProcessors = OS::GetNumProcessors(); + + if (numProcessors > 1) + theServer->fCPUPercent /= numProcessors; + } + + fLastTotalMP3Bytes = (SInt64)theServer->fTotalMP3Bytes; + fLastBandwidthTime = curTime; + // We use a running average for avg. bandwidth calculations + theServer->fAvgMP3BandwidthInBits = (theServer->fAvgMP3BandwidthInBits + + theServer->fCurrentMP3BandwidthInBits)/2; + + //for cpu percent + theServer->fCPUTimeUsedInSec = cpuTimeInSec; + + //also compute average bandwidth, a much more smooth value. This is done with + //the fLastBandwidthAvg, a timestamp of the last time we did an average, and + //fLastBytesSent, the number of bytes sent when we last did an average. + if ((fLastBandwidthAvg != 0) && (curTime > (fLastBandwidthAvg + + (theServer->GetPrefs()->GetAvgBandwidthUpdateTimeInSecs() * 1000)))) + { + UInt32 delta = (UInt32)(curTime - fLastBandwidthAvg); + SInt64 bytesSent = theServer->fTotalRTPBytes - fLastBytesSent; + Assert(bytesSent >= 0); + + //do the bandwidth computation using floating point divides + //for accuracy and speed. + Float32 bits = (Float32)(bytesSent * 8); + Float32 theAvgTime = (Float32)delta; + theAvgTime /= 1000; + bits /= theAvgTime; + Assert(bits >= 0); + theServer->fAvgRTPBandwidthInBits = (UInt32)bits; + + fLastBandwidthAvg = curTime; + fLastBytesSent = theServer->fTotalRTPBytes; + + //if the bandwidth is above the bandwidth setting, disconnect 1 user by sending them + //a BYE RTCP packet. + SInt32 maxKBits = theServer->GetPrefs()->GetMaxKBitsBandwidth(); + if ((maxKBits > -1) && (theServer->fAvgRTPBandwidthInBits > ((UInt32)maxKBits * 1024))) + { + //we need to make sure that all of this happens atomically wrt the session map + OSMutexLocker locker(theServer->GetRTPSessionMap()->GetMutex()); + RTPSessionInterface* theSession = this->GetNewestSession(theServer->fRTPMap); + if (theSession != NULL) + if ((curTime - theSession->GetSessionCreateTime()) < + theServer->GetPrefs()->GetSafePlayDurationInSecs() * 1000) + theSession->Signal(Task::kKillEvent); + } + } + else if (fLastBandwidthAvg == 0) + { + fLastBandwidthAvg = curTime; + fLastBytesSent = theServer->fTotalRTPBytes; + } + + (void)this->GetEvents();//we must clear the event mask! + return theServer->GetPrefs()->GetTotalBytesUpdateTimeInSecs() * 1000; +} + +RTPSessionInterface* RTPStatsUpdaterTask::GetNewestSession(OSRefTable* inRTPSessionMap) +{ + //Caller must lock down the RTP session map + SInt64 theNewestPlayTime = 0; + RTPSessionInterface* theNewestSession = NULL; + + //use the session map to iterate through all the sessions, finding the most + //recently connected client + for (OSRefHashTableIter theIter(inRTPSessionMap->GetHashTable()); !theIter.IsDone(); theIter.Next()) + { + OSRef* theRef = theIter.GetCurrent(); + RTPSessionInterface* theSession = (RTPSessionInterface*)theRef->GetObject(); + Assert(theSession->GetSessionCreateTime() > 0); + if (theSession->GetSessionCreateTime() > theNewestPlayTime) + { + theNewestPlayTime = theSession->GetSessionCreateTime(); + theNewestSession = theSession; + } + } + return theNewestSession; +} + + + +void* QTSServerInterface::CurrentUnixTimeMilli(QTSSDictionary* inServer, UInt32* outLen) +{ + QTSServerInterface* theServer = (QTSServerInterface*)inServer; + theServer->fCurrentTime_UnixMilli = OS::TimeMilli_To_UnixTimeMilli(OS::Milliseconds()); + + // Return the result + *outLen = sizeof(theServer->fCurrentTime_UnixMilli); + return &theServer->fCurrentTime_UnixMilli; +} + +void* QTSServerInterface::GetTotalUDPSockets(QTSSDictionary* inServer, UInt32* outLen) +{ + QTSServerInterface* theServer = (QTSServerInterface*)inServer; + // Multiply by 2 because this is returning the number of socket *pairs* + theServer->fTotalUDPSockets = theServer->fSocketPool->GetSocketQueue()->GetLength() * 2; + + // Return the result + *outLen = sizeof(theServer->fTotalUDPSockets); + return &theServer->fTotalUDPSockets; +} + +void* QTSServerInterface::IsOutOfDescriptors(QTSSDictionary* inServer, UInt32* outLen) +{ + QTSServerInterface* theServer = (QTSServerInterface*)inServer; + + theServer->fIsOutOfDescriptors = false; + for (UInt32 x = 0; x < theServer->fNumListeners; x++) + { + if (theServer->fListeners[x]->IsOutOfDescriptors()) + { + theServer->fIsOutOfDescriptors = true; + break; + } + } + // Return the result + *outLen = sizeof(theServer->fIsOutOfDescriptors); + return &theServer->fIsOutOfDescriptors; +} + +void* QTSServerInterface::GetNumUDPBuffers(QTSSDictionary* inServer, UInt32* outLen) +{ + // This param retrieval function must be invoked each time it is called, + // because whether we are out of descriptors or not is continually changing + QTSServerInterface* theServer = (QTSServerInterface*)inServer; + + theServer->fNumUDPBuffers = RTPPacketResender::GetNumRetransmitBuffers(); + + // Return the result + *outLen = sizeof(theServer->fNumUDPBuffers); + return &theServer->fNumUDPBuffers; +} + +void* QTSServerInterface::GetNumWastedBytes(QTSSDictionary* inServer, UInt32* outLen) +{ + // This param retrieval function must be invoked each time it is called, + // because whether we are out of descriptors or not is continually changing + QTSServerInterface* theServer = (QTSServerInterface*)inServer; + + theServer->fUDPWastageInBytes = RTPPacketResender::GetWastedBufferBytes(); + + // Return the result + *outLen = sizeof(theServer->fUDPWastageInBytes); + return &theServer->fUDPWastageInBytes; +} + +void* QTSServerInterface::TimeConnected(QTSSDictionary* inConnection, UInt32* outLen) +{ + SInt64 connectTime; + void* result; + UInt32 len = sizeof(connectTime); + inConnection->GetValue(qtssConnectionCreateTimeInMsec, 0, &connectTime, &len); + SInt64 timeConnected = OS::Milliseconds() - connectTime; + *outLen = sizeof(timeConnected); + inConnection->SetValue(qtssConnectionTimeStorage, 0, &timeConnected, sizeof(connectTime)); + inConnection->GetValuePtr(qtssConnectionTimeStorage, 0, &result, outLen); + + // Return the result + return result; +} + + +QTSS_Error QTSSErrorLogStream::Write(void* inBuffer, UInt32 inLen, UInt32* outLenWritten, UInt32 inFlags) +{ + // For the error log stream, the flags are considered to be the verbosity + // of the error. + if (inFlags >= qtssIllegalVerbosity) + inFlags = qtssMessageVerbosity; + + QTSServerInterface::LogError(inFlags, (char*)inBuffer); + if (outLenWritten != NULL) + *outLenWritten = inLen; + + return QTSS_NoErr; +} + +void QTSSErrorLogStream::LogAssert(char* inMessage) +{ + QTSServerInterface::LogError(qtssAssertVerbosity, inMessage); +} + + diff --git a/Server.tproj/QTSServerInterface.h b/Server.tproj/QTSServerInterface.h new file mode 100644 index 0000000..97a3fc1 --- /dev/null +++ b/Server.tproj/QTSServerInterface.h @@ -0,0 +1,433 @@ +/* + * + * @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: QTSServerInterface.h + + Contains: This object defines an interface for getting and setting server-wide + attributes, and storing global server resources. + + There can be only one of these objects per process, so there + is a static accessor. + + +*/ + + +#ifndef __QTSSERVERINTERFACE_H__ +#define __QTSSERVERINTERFACE_H__ + +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "QTSServerPrefs.h" +#include "QTSSMessages.h" +#include "QTSSModule.h" +#include "atomic.h" + +#include "OSMutex.h" +#include "Task.h" +#include "TCPListenerSocket.h" +#include "ResizeableStringFormatter.h" + +// OSRefTable; +class UDPSocketPool; +class QTSServerPrefs; +class QTSSMessages; +//class RTPStatsUpdaterTask; +class RTPSessionInterface; + +// This object also functions as our assert logger +class QTSSErrorLogStream : public QTSSStream, public AssertLogger +{ + public: + + // This QTSSStream is used by modules to write to the error log + + QTSSErrorLogStream() {} + virtual ~QTSSErrorLogStream() {} + + virtual QTSS_Error Write(void* inBuffer, UInt32 inLen, UInt32* outLenWritten, UInt32 inFlags); + virtual void LogAssert(char* inMessage); +}; + +class QTSServerInterface : public QTSSDictionary +{ + public: + + //Initialize must be called right off the bat to initialize dictionary resources + static void Initialize(); + + // + // CONSTRUCTOR / DESTRUCTOR + + QTSServerInterface(); + virtual ~QTSServerInterface() {} + + // + // + // STATISTICS MANIPULATION + // These functions are how the server keeps its statistics current + + void AlterCurrentRTSPSessionCount(SInt32 inDifference) + { OSMutexLocker locker(&fMutex); fNumRTSPSessions += inDifference; } + void AlterCurrentRTSPHTTPSessionCount(SInt32 inDifference) + { OSMutexLocker locker(&fMutex); fNumRTSPHTTPSessions += inDifference; } + void SwapFromRTSPToHTTP() + { OSMutexLocker locker(&fMutex); fNumRTSPSessions--; fNumRTSPHTTPSessions++; } + + //total rtp bytes sent by the server + void IncrementTotalRTPBytes(UInt32 bytes) + { (void)atomic_add(&fPeriodicRTPBytes, bytes); } + //total rtp packets sent by the server + void IncrementTotalPackets() + { (void)atomic_add(&fPeriodicRTPPackets, 1); } + //total rtp bytes reported as lost by the clients + void IncrementTotalRTPPacketsLost(UInt32 packets) + { (void)atomic_add(&fPeriodicRTPPacketsLost, packets); } + + // Also increments current RTP session count + void IncrementTotalRTPSessions() + { OSMutexLocker locker(&fMutex); fNumRTPSessions++; fTotalRTPSessions++; } + void AlterCurrentRTPSessionCount(SInt32 inDifference) + { OSMutexLocker locker(&fMutex); fNumRTPSessions += inDifference; } + + //track how many sessions are playing + void AlterRTPPlayingSessions(SInt32 inDifference) + { OSMutexLocker locker(&fMutex); fNumRTPPlayingSessions += inDifference; } + + + void IncrementTotalLate(SInt64 milliseconds) + { OSMutexLocker locker(&fMutex); + fTotalLate += milliseconds; + if (milliseconds > fCurrentMaxLate) fCurrentMaxLate = milliseconds; + if (milliseconds > fMaxLate) fMaxLate = milliseconds; + } + + void IncrementTotalQuality(SInt32 level) + { OSMutexLocker locker(&fMutex); fTotalQuality += level; } + + + void IncrementNumThinned(SInt32 inDifference) + { OSMutexLocker locker(&fMutex); fNumThinned += inDifference; } + + void ClearTotalLate() + { OSMutexLocker locker(&fMutex); fTotalLate = 0; } + void ClearCurrentMaxLate() + { OSMutexLocker locker(&fMutex); fCurrentMaxLate = 0; } + void ClearTotalQuality() + { OSMutexLocker locker(&fMutex); fTotalQuality = 0; } + + + void InitNumThreads(UInt32 numThreads) { fNumThreads = numThreads; } + // + // ACCESSORS + + QTSS_ServerState GetServerState() { return fServerState; } + UInt32 GetNumRTPSessions() { return fNumRTPSessions; } + UInt32 GetNumRTSPSessions() { return fNumRTSPSessions; } + UInt32 GetNumRTSPHTTPSessions(){ return fNumRTSPHTTPSessions; } + + UInt32 GetTotalRTPSessions() { return fTotalRTPSessions; } + UInt32 GetNumRTPPlayingSessions() { return fNumRTPPlayingSessions; } + + UInt32 GetCurBandwidthInBits() { return fCurrentRTPBandwidthInBits; } + UInt32 GetAvgBandwidthInBits() { return fAvgRTPBandwidthInBits; } + UInt32 GetRTPPacketsPerSec() { return fRTPPacketsPerSecond; } + UInt64 GetTotalRTPBytes() { return fTotalRTPBytes; } + UInt64 GetTotalRTPPacketsLost(){ return fTotalRTPPacketsLost; } + UInt64 GetTotalRTPPackets() { return fTotalRTPPackets; } + Float32 GetCPUPercent() { return fCPUPercent; } + Bool16 SigIntSet() { return fSigInt; } + Bool16 SigTermSet() { return fSigTerm; } + + UInt32 GetNumMP3Sessions() { return fNumMP3Sessions; } + UInt32 GetTotalMP3Sessions() { return fTotalMP3Sessions; } + UInt64 GetTotalMP3Bytes() { return fTotalMP3Bytes; } + + UInt32 GetDebugLevel() { return fDebugLevel; } + UInt32 GetDebugOptions() { return fDebugOptions; } + void SetDebugLevel(UInt32 debugLevel) { fDebugLevel = debugLevel; } + void SetDebugOptions(UInt32 debugOptions){ fDebugOptions = debugOptions; } + + SInt64 GetMaxLate() { return fMaxLate; }; + SInt64 GetTotalLate() { return fTotalLate; }; + SInt64 GetCurrentMaxLate() { return fCurrentMaxLate; }; + SInt64 GetTotalQuality() { return fTotalQuality; }; + SInt32 GetNumThinned() { return fNumThinned; }; + UInt32 GetNumThreads() { return fNumThreads; }; + + // + // + // GLOBAL OBJECTS REPOSITORY + // This object is in fact global, so there is an accessor for it as well. + + static QTSServerInterface* GetServer() { return sServer; } + + //Allows you to map RTP session IDs (strings) to actual RTP session objects + OSRefTable* GetRTPSessionMap() { return fRTPMap; } + + //Server provides a statically created & bound UDPSocket / Demuxer pair + //for each IP address setup to serve RTP. You access those pairs through + //this function. This returns a pair pre-bound to the IPAddr specified. + UDPSocketPool* GetSocketPool() { return fSocketPool; } + + QTSServerPrefs* GetPrefs() { return fSrvrPrefs; } + QTSSMessages* GetMessages() { return fSrvrMessages; } + + // + // + // SERVER NAME & VERSION + + static StrPtrLen& GetServerName() { return sServerNameStr; } + static StrPtrLen& GetServerVersion() { return sServerVersionStr; } + static StrPtrLen& GetServerPlatform() { return sServerPlatformStr; } + static StrPtrLen& GetServerBuildDate() { return sServerBuildDateStr; } + static StrPtrLen& GetServerHeader() { return sServerHeaderPtr; } + static StrPtrLen& GetServerBuild() { return sServerBuildStr; } + static StrPtrLen& GetServerComment() { return sServerCommentStr; } + + // + // PUBLIC HEADER + static StrPtrLen* GetPublicHeader() { return &sPublicHeaderStr; } + + // + // KILL ALL + void KillAllRTPSessions(); + + // + // SIGINT - to interrupt the server, set this flag and the server will shut down + void SetSigInt() { fSigInt = true; } + + // SIGTERM - to kill the server, set this flag and the server will shut down + void SetSigTerm() { fSigTerm = true; } + + // + // MODULE STORAGE + + // All module objects are stored here, and are accessable through + // these routines. + + // Returns the number of modules that act in a given role + static UInt32 GetNumModulesInRole(QTSSModule::RoleIndex inRole) + { Assert(inRole < QTSSModule::kNumRoles); return sNumModulesInRole[inRole]; } + + // Allows the caller to iterate over all modules that act in a given role + static QTSSModule* GetModule(QTSSModule::RoleIndex inRole, UInt32 inIndex) + { Assert(inRole < QTSSModule::kNumRoles); + Assert(inIndex < sNumModulesInRole[inRole]); + if (inRole >= QTSSModule::kNumRoles) //index out of bounds, shouldn't happen + { return NULL; + } + if (inIndex >= sNumModulesInRole[inRole]) //index out of bounds, shouldn't happen + { return NULL; + } + return sModuleArray[inRole][inIndex]; + } + + // + // We need to override this. This is how we implement the QTSS_StateChange_Role + virtual void SetValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap, + UInt32 inValueIndex, void* inNewValue, UInt32 inNewValueLen); + + // + // ERROR LOGGING + + // Invokes the error logging modules with some data + static void LogError(QTSS_ErrorVerbosity inVerbosity, char* inBuffer); + + // Returns the error log stream + static QTSSErrorLogStream* GetErrorLogStream() { return &sErrorLogStream; } + + // + // LOCKING DOWN THE SERVER OBJECT + OSMutex* GetServerObjectMutex() { return &fMutex; } + + + + protected: + + // Setup by the derived RTSPServer object + + //Sockets are allocated global to the server, and arbitrated through this pool here. + //RTCP data is processed completely within the following task. + UDPSocketPool* fSocketPool; + + // All RTP sessions are put into this map + OSRefTable* fRTPMap; + + QTSServerPrefs* fSrvrPrefs; + QTSSMessages* fSrvrMessages; + + QTSServerPrefs* fStubSrvrPrefs; + QTSSMessages* fStubSrvrMessages; + + QTSS_ServerState fServerState; + UInt32 fDefaultIPAddr; + + // Array of pointers to TCPListenerSockets. + TCPListenerSocket** fListeners; + UInt32 fNumListeners; // Number of elements in the array + + // startup time + SInt64 fStartupTime_UnixMilli; + SInt32 fGMTOffset; + + static ResizeableStringFormatter sPublicHeaderFormatter; + static StrPtrLen sPublicHeaderStr; + + // + // MODULE DATA + + static QTSSModule** sModuleArray[QTSSModule::kNumRoles]; + static UInt32 sNumModulesInRole[QTSSModule::kNumRoles]; + static OSQueue sModuleQueue; + static QTSSErrorLogStream sErrorLogStream; + + private: + + enum + { + kMaxServerHeaderLen = 1000 + }; + + static void* TimeConnected(QTSSDictionary* inConnection, UInt32* outLen); + + static UInt32 sServerAPIVersion; + static StrPtrLen sServerNameStr; + static StrPtrLen sServerVersionStr; + static StrPtrLen sServerBuildStr; + static StrPtrLen sServerCommentStr; + static StrPtrLen sServerPlatformStr; + static StrPtrLen sServerBuildDateStr; + static char sServerHeader[kMaxServerHeaderLen]; + static StrPtrLen sServerHeaderPtr; + + OSMutex fMutex; + + UInt32 fNumRTSPSessions; + UInt32 fNumRTSPHTTPSessions; + UInt32 fNumRTPSessions; + + //stores the current number of playing connections. + UInt32 fNumRTPPlayingSessions; + + //stores the total number of connections since startup. + UInt32 fTotalRTPSessions; + //stores the total number of bytes served since startup + UInt64 fTotalRTPBytes; + //total number of rtp packets sent since startup + UInt64 fTotalRTPPackets; + //stores the total number of bytes lost (as reported by clients) since startup + UInt64 fTotalRTPPacketsLost; + + //because there is no 64 bit atomic add (for obvious reasons), we efficiently + //implement total byte counting by atomic adding to this variable, then every + //once in awhile updating the sTotalBytes. + unsigned int fPeriodicRTPBytes; + unsigned int fPeriodicRTPPacketsLost; + unsigned int fPeriodicRTPPackets; + + //stores the current served bandwidth in BITS per second + UInt32 fCurrentRTPBandwidthInBits; + UInt32 fAvgRTPBandwidthInBits; + UInt32 fRTPPacketsPerSecond; + + Float32 fCPUPercent; + Float32 fCPUTimeUsedInSec; + + // stores # of UDP sockets in the server currently (gets updated lazily via. + // param retrieval function) + UInt32 fTotalUDPSockets; + + // are we out of descriptors? + Bool16 fIsOutOfDescriptors; + + // Storage for current time attribute + SInt64 fCurrentTime_UnixMilli; + + // Stats for UDP retransmits + UInt32 fUDPWastageInBytes; + UInt32 fNumUDPBuffers; + + // MP3 Client Session params + UInt32 fNumMP3Sessions; + UInt32 fTotalMP3Sessions; + UInt32 fCurrentMP3BandwidthInBits; + UInt64 fTotalMP3Bytes; + UInt32 fAvgMP3BandwidthInBits; + + Bool16 fSigInt; + Bool16 fSigTerm; + + UInt32 fDebugLevel; + UInt32 fDebugOptions; + + + SInt64 fMaxLate; + SInt64 fTotalLate; + SInt64 fCurrentMaxLate; + SInt64 fTotalQuality; + SInt32 fNumThinned; + UInt32 fNumThreads; + + // Param retrieval functions + static void* CurrentUnixTimeMilli(QTSSDictionary* inServer, UInt32* outLen); + static void* GetTotalUDPSockets(QTSSDictionary* inServer, UInt32* outLen); + static void* IsOutOfDescriptors(QTSSDictionary* inServer, UInt32* outLen); + static void* GetNumUDPBuffers(QTSSDictionary* inServer, UInt32* outLen); + static void* GetNumWastedBytes(QTSSDictionary* inServer, UInt32* outLen); + + static QTSServerInterface* sServer; + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + static QTSSAttrInfoDict::AttrInfo sConnectedUserAttributes[]; + + friend class RTPStatsUpdaterTask; + friend class SessionTimeoutTask; +}; + + +class RTPStatsUpdaterTask : public Task +{ + public: + + // This class runs periodically to compute current totals & averages + RTPStatsUpdaterTask(); + virtual ~RTPStatsUpdaterTask() {} + + private: + + virtual SInt64 Run(); + RTPSessionInterface* GetNewestSession(OSRefTable* inRTPSessionMap); + Float32 GetCPUTimeInSeconds(); + + SInt64 fLastBandwidthTime; + SInt64 fLastBandwidthAvg; + SInt64 fLastBytesSent; + SInt64 fLastTotalMP3Bytes; +}; + + + +#endif // __QTSSERVERINTERFACE_H__ + diff --git a/Server.tproj/QTSServerPrefs.cpp b/Server.tproj/QTSServerPrefs.cpp new file mode 100644 index 0000000..bb2148f --- /dev/null +++ b/Server.tproj/QTSServerPrefs.cpp @@ -0,0 +1,721 @@ +/* + * + * @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: QTSSPrefs.cpp + + Contains: Implements class defined in QTSSPrefs.h. + + Change History (most recent first): + +*/ + +#include "QTSServerPrefs.h" +#include "MyAssert.h" +#include "OSMemory.h" +#include "QTSSDataConverter.h" +#include "../defaultPaths.h" +#include "QTSSRollingLog.h" + +#ifndef __Win32__ +#include +#include +#include +#endif + + +char* QTSServerPrefs::sAdditionalDefaultPorts[] = +{ + "7070", + "8000", + "8001", + NULL +}; + +char* QTSServerPrefs::sRTP_Header_Players[] = +{ + "Real", + NULL +}; + +char* QTSServerPrefs::sAdjust_Bandwidth_Players[] = +{ + "Real", + NULL +}; + +char* QTSServerPrefs::sNo_Pause_Time_Adjustment_Players[] = +{ + "Real", + "PVPlayer", + NULL +}; + +char* QTSServerPrefs::sRTP_Start_Time_Players[] = +{ + NULL +}; + +char* QTSServerPrefs::sDisable_Rate_Adapt_Players[] = +{ + NULL +}; + +char* QTSServerPrefs::sFixed_Target_Time_Players[] = +{ + NULL +}; + +char* QTSServerPrefs::sDisable_Thinning_Players[] = +{ + NULL +}; + +QTSServerPrefs::PrefInfo QTSServerPrefs::sPrefInfo[] = +{ + { kDontAllowMultipleValues, "0", NULL }, //rtsp_timeout + { kDontAllowMultipleValues, "180", NULL }, //real_rtsp_timeout + { kDontAllowMultipleValues, "120", NULL }, //rtp_timeout + { kDontAllowMultipleValues, "1000", NULL }, //maximum_connections + { kDontAllowMultipleValues, "102400", NULL }, //maximum_bandwidth + { kDontAllowMultipleValues, DEFAULTPATHS_MOVIES_DIR, NULL }, //movie_folder + { kAllowMultipleValues, "0", NULL }, //bind_ip_addr + { kDontAllowMultipleValues, "false", NULL }, //break_on_assert + { kDontAllowMultipleValues, "true", NULL }, //auto_restart + { kDontAllowMultipleValues, "1", NULL }, //total_bytes_update + { kDontAllowMultipleValues, "60", NULL }, //average_bandwidth_update + { kDontAllowMultipleValues, "600", NULL }, //safe_play_duration + { kDontAllowMultipleValues, DEFAULTPATHS_SSM_DIR, NULL }, //module_folder + { kDontAllowMultipleValues, "Error", NULL }, //error_logfile_name + { kDontAllowMultipleValues, DEFAULTPATHS_LOG_DIR, NULL }, //error_logfile_dir + { kDontAllowMultipleValues, "7", NULL }, //error_logfile_interval + { kDontAllowMultipleValues, "256000", NULL }, //error_logfile_size + { kDontAllowMultipleValues, "2", NULL }, //error_logfile_verbosity + { kDontAllowMultipleValues, "true", NULL }, //screen_logging + { kDontAllowMultipleValues, "true", NULL }, //error_logging + { kDontAllowMultipleValues, "1750", NULL }, //drop_all_video_delay + { kDontAllowMultipleValues, "0", NULL }, //start_thinning_delay + { kDontAllowMultipleValues, "64", NULL }, //large_window_size + { kDontAllowMultipleValues, "200", NULL }, //window_size_threshold + { kDontAllowMultipleValues, "8192", NULL }, //min_tcp_buffer_size + { kDontAllowMultipleValues, "200000", NULL }, //max_tcp_buffer_size + { kDontAllowMultipleValues, ".5", NULL }, //tcp_seconds_to_buffer + { kDontAllowMultipleValues, "false", NULL }, //do_report_http_connection_ip_address + { kDontAllowMultipleValues, "Streaming Server", NULL }, //default_authorization_realm +#ifndef __Win32__ + { kDontAllowMultipleValues, "qtss", NULL }, //run_user_name + { kDontAllowMultipleValues, "qtss", NULL }, //run_group_name +#else + { kDontAllowMultipleValues, "", NULL }, //run_user_name + { kDontAllowMultipleValues, "", NULL }, //run_group_name +#endif + { kDontAllowMultipleValues, "false", NULL }, //append_source_addr_in_transport + { kAllowMultipleValues, "554", sAdditionalDefaultPorts }, //rtsp_ports + { kDontAllowMultipleValues, "500", NULL }, //max_retransmit_delay + { kDontAllowMultipleValues, "24", NULL }, //small_window_size + { kDontAllowMultipleValues, "false", NULL }, //ack_logging_enabled + { kDontAllowMultipleValues, "100", NULL }, //rtcp_poll_interval + { kDontAllowMultipleValues, "768", NULL }, //rtcp_rcv_buf_size + { kDontAllowMultipleValues, "50", NULL }, //send_interval + { kDontAllowMultipleValues, "-2000", NULL }, //thick_all_the_way_delay + { kDontAllowMultipleValues, "", NULL }, //alt_transport_src_ipaddr + { kDontAllowMultipleValues, "25", NULL }, //max_send_ahead_time + { kDontAllowMultipleValues, "true", NULL }, //reliable_udp_slow_start + { kDontAllowMultipleValues, "false", NULL }, //auto_delete_sdp_files + { kDontAllowMultipleValues, "digest", NULL }, //authentication_scheme + { kDontAllowMultipleValues, "10", NULL }, //sdp_file_delete_interval_seconds + { kDontAllowMultipleValues, "false", NULL }, //auto_start + { kDontAllowMultipleValues, "true", NULL }, //reliable_udp + { kAllowMultipleValues, DEFAULTPATHS_DIRECTORY_SEPARATOR, NULL}, //reliable_udp_dirs (set all dirs) + { kDontAllowMultipleValues, "false", NULL }, //reliable_udp_printfs + { kDontAllowMultipleValues, "2500", NULL }, //drop_all_packets_delay + { kDontAllowMultipleValues, "1500", NULL }, //thin_all_the_way_delay + { kDontAllowMultipleValues, "750", NULL }, //always_thin_delay + { kDontAllowMultipleValues, "250", NULL }, //start_thicking_delay + { kDontAllowMultipleValues, "1000", NULL }, //quality_check_interval + { kDontAllowMultipleValues, "false", NULL }, //RTSP_error_message + { kDontAllowMultipleValues, "false", NULL }, //RTSP_debug_printfs +#if __MacOSX__ + { kDontAllowMultipleValues, "false", NULL }, //enable_monitor_stats_file +#else + { kDontAllowMultipleValues, "false", NULL }, //enable_monitor_stats_file +#endif + { kDontAllowMultipleValues, "10", NULL }, //monitor_stats_file_interval_seconds + { kDontAllowMultipleValues, "server_status", NULL }, //monitor_stats_file_name + { kDontAllowMultipleValues, "false", NULL }, //enable_packet_header_printfs + { kDontAllowMultipleValues, "rtp;rr;sr;app;ack;",NULL }, //packet_header_printf_options + { kDontAllowMultipleValues, "2.0", NULL }, //overbuffer_rate + { kDontAllowMultipleValues, "48", NULL }, //medium_window_size + { kDontAllowMultipleValues, "1000", NULL }, //window_size_max_threshold + { kDontAllowMultipleValues, "true", NULL }, //RTSP_server_info + { kDontAllowMultipleValues, "0", NULL }, //run_num_threads + { kDontAllowMultipleValues, DEFAULTPATHS_PID_DIR PLATFORM_SERVER_BIN_NAME ".pid", NULL }, //pid_file + { kDontAllowMultipleValues, "false", NULL }, //force_logs_close_on_write + { kDontAllowMultipleValues, "false", NULL }, //disable_thinning + { kAllowMultipleValues, "Nokia", sRTP_Header_Players }, //player_requires_rtp_header_info + { kAllowMultipleValues, "Nokia", sAdjust_Bandwidth_Players }, //player_requires_bandwidth_adjustment + { kAllowMultipleValues, "Nokia", sNo_Pause_Time_Adjustment_Players }, //player_requires_no_pause_time_adjustment + { kDontAllowMultipleValues, "true", NULL }, //enable_3gpp_protocol + { kDontAllowMultipleValues, "true", NULL }, //enable_3gpp_protocol_rate_adaptation + { kDontAllowMultipleValues, "1", NULL }, //3gpp_protocol_rate_adaptation_report_frequency + { kDontAllowMultipleValues, "0", NULL }, //default_stream_quality + { kAllowMultipleValues, "Real", sRTP_Start_Time_Players }, //player_requires_rtp_start_time_adjust + { kDontAllowMultipleValues, "false", NULL }, //enable_3gpp_debug_printfs + { kDontAllowMultipleValues, "false", NULL }, //enable_udp_monitor_stream + { kDontAllowMultipleValues, "5002", NULL }, //udp_monitor_video_stream + { kDontAllowMultipleValues, "5004", NULL }, //udp_monitor_audio_stream + { kDontAllowMultipleValues, "127.0.0.1",NULL }, //udp_monitor_dest_ip + { kDontAllowMultipleValues, "0.0.0.0", NULL }, //udp_monitor_src_ip + { kDontAllowMultipleValues, "true", NULL }, //enable_allow_guest_default + { kDontAllowMultipleValues, "1", NULL }, //run_num_rtsp_threads + { kAllowMultipleValues, "", sDisable_Rate_Adapt_Players }, //player_requires_disable_3gpp_rate_adapt + { kAllowMultipleValues, "", sFixed_Target_Time_Players }, //player_requires_3gpp_target_time + { kDontAllowMultipleValues, "3000", NULL }, //3gpp_target_time_milliseconds + { kAllowMultipleValues, "", sDisable_Thinning_Players } //player_requires_disable_thinning + + + + + +}; + + + +QTSSAttrInfoDict::AttrInfo QTSServerPrefs::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "rtsp_timeout", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 1 */ { "real_rtsp_timeout", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 2 */ { "rtp_timeout", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 3 */ { "maximum_connections", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 4 */ { "maximum_bandwidth", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 5 */ { "movie_folder", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 6 */ { "bind_ip_addr", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 7 */ { "break_on_assert", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 8 */ { "auto_restart", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 9 */ { "total_bytes_update", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 10 */ { "average_bandwidth_update", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 11 */ { "safe_play_duration", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 12 */ { "module_folder", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 13 */ { "error_logfile_name", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 14 */ { "error_logfile_dir", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 15 */ { "error_logfile_interval", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 16 */ { "error_logfile_size", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 17 */ { "error_logfile_verbosity", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 18 */ { "screen_logging", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 19 */ { "error_logging", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 20 */ { "drop_all_video_delay", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 21 */ { "start_thinning_delay", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 22 */ { "large_window_size", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 23 */ { "window_size_threshold", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 24 */ { "min_tcp_buffer_size", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 25 */ { "max_tcp_buffer_size", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 26 */ { "tcp_seconds_to_buffer", NULL, qtssAttrDataTypeFloat32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 27 */ { "do_report_http_connection_ip_address", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 28 */ { "default_authorization_realm", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 29 */ { "run_user_name", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 30 */ { "run_group_name", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 31 */ { "append_source_addr_in_transport", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 32 */ { "rtsp_port", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 33 */ { "max_retransmit_delay", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 34 */ { "small_window_size", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 35 */ { "ack_logging_enabled", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 36 */ { "rtcp_poll_interval", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 37 */ { "rtcp_rcv_buf_size", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 38 */ { "send_interval", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 39 */ { "thick_all_the_way_delay", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 40 */ { "alt_transport_src_ipaddr", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 41 */ { "max_send_ahead_time", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 42 */ { "reliable_udp_slow_start", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 43 */ { "auto_delete_sdp_files", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 44 */ { "authentication_scheme", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 45 */ { "sdp_file_delete_interval_seconds", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 46 */ { "auto_start", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 47 */ { "reliable_udp", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 48 */ { "reliable_udp_dirs", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 49 */ { "reliable_udp_printfs", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 50 */ { "drop_all_packets_delay", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 51 */ { "thin_all_the_way_delay", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 52 */ { "always_thin_delay", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 53 */ { "start_thicking_delay", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 54 */ { "quality_check_interval", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 55 */ { "RTSP_error_message", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 56 */ { "RTSP_debug_printfs", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 57 */ { "enable_monitor_stats_file", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 58 */ { "monitor_stats_file_interval_seconds", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 59 */ { "monitor_stats_file_name", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 60 */ { "enable_packet_header_printfs", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 61 */ { "packet_header_printf_options", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 62 */ { "overbuffer_rate", NULL, qtssAttrDataTypeFloat32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 63 */ { "medium_window_size", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 64 */ { "window_size_max_threshold", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 65 */ { "RTSP_server_info", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 66 */ { "run_num_threads", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 67 */ { "pid_file", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 68 */ { "force_logs_close_on_write", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 69 */ { "disable_thinning", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 70 */ { "player_requires_rtp_header_info", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 71 */ { "player_requires_bandwidth_adjustment", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 72 */ { "player_requires_no_pause_time_adjustment", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 73 */ { "enable_3gpp_protocol", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 74 */ { "enable_3gpp_protocol_rate_adaptation", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 75 */ { "3gpp_protocol_rate_adaptation_report_frequency", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 76 */ { "default_stream_quality", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 77 */ { "player_requires_rtp_start_time_adjust", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 78 */ { "enable_3gpp_debug_printfs", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 79 */ { "enable_udp_monitor_stream", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 80 */ { "udp_monitor_video_port", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 81 */ { "udp_monitor_audio_port", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 82 */ { "udp_monitor_dest_ip", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 83 */ { "udp_monitor_src_ip", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 84 */ { "enable_allow_guest_default", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite }, + /* 85 */ { "run_num_rtsp_threads", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 86 */ { "player_requires_disable_3gpp_rate_adapt", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 87 */ { "player_requires_3gpp_target_time", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 88 */ { "3gpp_target_time_milliseconds", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite }, + /* 89 */ { "player_requires_disable_thinning", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite } + + +}; + + +QTSServerPrefs::QTSServerPrefs(XMLPrefsParser* inPrefsSource, Bool16 inWriteMissingPrefs) +: QTSSPrefs(inPrefsSource, NULL, QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kPrefsDictIndex), false), + fRTSPTimeoutInSecs(0), + fRTSPTimeoutString(fRTSPTimeoutBuf, 0), + fRealRTSPTimeoutInSecs(0), + fRTPTimeoutInSecs(0), + fMaximumConnections(0), + fMaxBandwidthInKBits(0), + fBreakOnAssert(false), + fAutoRestart(false), + fTBUpdateTimeInSecs(0), + fABUpdateTimeInSecs(0), + fSafePlayDurationInSecs(0), + fErrorRollIntervalInDays(0), + fErrorLogBytes(0), + fErrorLogVerbosity(0), + fScreenLoggingEnabled(true), + fErrorLogEnabled(false), + fDropAllPacketsTimeInMsec(0), + fDropAllVideoPacketsTimeInMsec(0), + fThinAllTheWayTimeInMsec(0), + fAlwaysThinTimeInMsec(0), + fStartThinningTimeInMsec(0), + fStartThickingTimeInMsec(0), + fThickAllTheWayTimeInMsec(0), + fQualityCheckIntervalInMsec(0), + fMinTCPBufferSizeInBytes(0), + fMaxTCPBufferSizeInBytes(0), + fTCPSecondsToBuffer(0), + fDoReportHTTPConnectionAddress(false), + fAppendSrcAddrInTransport(false), + fSmallWindowSizeInK(0), + fMediumWindowSizeInK(0), + fLargeWindowSizeInK(0), + fWindowSizeThreshold(0), + fWindowSizeMaxThreshold(0), + fMaxRetransDelayInMsec(0), + fIsAckLoggingEnabled(false), + fRTCPPollIntervalInMsec(0), + fRTCPSocketRcvBufSizeInK(0), + fIsSlowStartEnabled(false), + fSendIntervalInMsec(0), + fMaxSendAheadTimeInSecs(0), + fauto_delete_sdp_files(false), + fAuthScheme(qtssAuthDigest), + fsdp_file_delete_interval_seconds(10), + fAutoStart(false), + fReliableUDP(true), + fReliableUDPPrintfs(false), + fEnableRTSPErrMsg(false), + fEnableRTSPDebugPrintfs(false), + fEnableRTSPServerInfo(true), + fNumThreads(0), + fNumRTSPThreads(0), +#if __MacOSX__ + fEnableMonitorStatsFile(false), +#else + fEnableMonitorStatsFile(false), +#endif + fStatsFileIntervalSeconds(10), + fOverbufferRate(0.0), + fEnablePacketHeaderPrintfs(false), + fPacketHeaderPrintfOptions(kRTPALL | kRTCPSR | kRTCPRR | kRTCPAPP | kRTCPACK), + fCloseLogsOnWrite(false), + fDisableThinning(false), + // + f3gppProtocolEnabled(true), + f3gppProtocolRateAdaptationEnabled(true), + f3gppProtocolRateAdaptationReportFrequency(1), + fDefaultStreamQuality(0), + f3gppDebugPrintfsEnabled(false), + fUDPMonitorEnabled(false), + fUDPMonitorVideoPort(0), + fUDPMonitorAudioPort(0), + fAllowGuestAuthorizeDefault(true), + f3GPPRateAdaptTargetTime(0) + + +{ + SetupAttributes(); + RereadServerPreferences(inWriteMissingPrefs); +} + +void QTSServerPrefs::Initialize() +{ + for (UInt32 x = 0; x < qtssPrefsNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kPrefsDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); +} + + +void QTSServerPrefs::SetupAttributes() +{ + this->SetVal(qtssPrefsRTSPTimeout, &fRTSPTimeoutInSecs, sizeof(fRTSPTimeoutInSecs)); + this->SetVal(qtssPrefsRealRTSPTimeout, &fRealRTSPTimeoutInSecs, sizeof(fRealRTSPTimeoutInSecs)); + this->SetVal(qtssPrefsRTPTimeout, &fRTPTimeoutInSecs, sizeof(fRTPTimeoutInSecs)); + this->SetVal(qtssPrefsMaximumConnections,&fMaximumConnections, sizeof(fMaximumConnections)); + this->SetVal(qtssPrefsMaximumBandwidth, &fMaxBandwidthInKBits, sizeof(fMaxBandwidthInKBits)); + this->SetVal(qtssPrefsBreakOnAssert, &fBreakOnAssert, sizeof(fBreakOnAssert)); + this->SetVal(qtssPrefsAutoRestart, &fAutoRestart, sizeof(fAutoRestart)); + this->SetVal(qtssPrefsTotalBytesUpdate, &fTBUpdateTimeInSecs, sizeof(fTBUpdateTimeInSecs)); + this->SetVal(qtssPrefsAvgBandwidthUpdate,&fABUpdateTimeInSecs, sizeof(fABUpdateTimeInSecs)); + this->SetVal(qtssPrefsSafePlayDuration, &fSafePlayDurationInSecs, sizeof(fSafePlayDurationInSecs)); + + this->SetVal(qtssPrefsErrorRollInterval, &fErrorRollIntervalInDays, sizeof(fErrorRollIntervalInDays)); + this->SetVal(qtssPrefsMaxErrorLogSize, &fErrorLogBytes, sizeof(fErrorLogBytes)); + this->SetVal(qtssPrefsErrorLogVerbosity, &fErrorLogVerbosity, sizeof(fErrorLogVerbosity)); + this->SetVal(qtssPrefsScreenLogging, &fScreenLoggingEnabled, sizeof(fScreenLoggingEnabled)); + this->SetVal(qtssPrefsErrorLogEnabled, &fErrorLogEnabled, sizeof(fErrorLogEnabled)); + + this->SetVal(qtssPrefsMinTCPBufferSizeInBytes, &fMinTCPBufferSizeInBytes, sizeof(fMinTCPBufferSizeInBytes)); + this->SetVal(qtssPrefsMaxTCPBufferSizeInBytes, &fMaxTCPBufferSizeInBytes, sizeof(fMaxTCPBufferSizeInBytes)); + this->SetVal(qtssPrefsTCPSecondsToBuffer, &fTCPSecondsToBuffer, sizeof(fTCPSecondsToBuffer)); + + this->SetVal(qtssPrefsDoReportHTTPConnectionAddress, &fDoReportHTTPConnectionAddress, sizeof(fDoReportHTTPConnectionAddress)); + this->SetVal(qtssPrefsSrcAddrInTransport, &fAppendSrcAddrInTransport, sizeof(fAppendSrcAddrInTransport)); + + this->SetVal(qtssPrefsSmallWindowSizeInK, &fSmallWindowSizeInK, sizeof(fSmallWindowSizeInK)); + this->SetVal(qtssPrefsMediumWindowSizeInK, &fMediumWindowSizeInK, sizeof(fMediumWindowSizeInK)); + this->SetVal(qtssPrefsLargeWindowSizeInK, &fLargeWindowSizeInK, sizeof(fLargeWindowSizeInK)); + this->SetVal(qtssPrefsWindowSizeThreshold, &fWindowSizeThreshold, sizeof(fWindowSizeThreshold)); + this->SetVal(qtssPrefsWindowSizeMaxThreshold, &fWindowSizeMaxThreshold, sizeof(fWindowSizeMaxThreshold)); + + this->SetVal(qtssPrefsMaxRetransDelayInMsec, &fMaxRetransDelayInMsec, sizeof(fMaxRetransDelayInMsec)); + this->SetVal(qtssPrefsAckLoggingEnabled, &fIsAckLoggingEnabled, sizeof(fIsAckLoggingEnabled)); + this->SetVal(qtssPrefsRTCPPollIntervalInMsec, &fRTCPPollIntervalInMsec, sizeof(fRTCPPollIntervalInMsec)); + this->SetVal(qtssPrefsRTCPSockRcvBufSizeInK, &fRTCPSocketRcvBufSizeInK, sizeof(fRTCPSocketRcvBufSizeInK)); + this->SetVal(qtssPrefsSendInterval, &fSendIntervalInMsec, sizeof(fSendIntervalInMsec)); + this->SetVal(qtssPrefsMaxAdvanceSendTimeInSec, &fMaxSendAheadTimeInSecs, sizeof(fMaxSendAheadTimeInSecs)); + this->SetVal(qtssPrefsReliableUDPSlowStart, &fIsSlowStartEnabled, sizeof(fIsSlowStartEnabled)); + this->SetVal(qtssPrefsAutoDeleteSDPFiles, &fauto_delete_sdp_files, sizeof(fauto_delete_sdp_files)); + this->SetVal(qtssPrefsDeleteSDPFilesInterval, &fsdp_file_delete_interval_seconds, sizeof(fsdp_file_delete_interval_seconds)); + this->SetVal(qtssPrefsAutoStart, &fAutoStart, sizeof(fAutoStart)); + this->SetVal(qtssPrefsReliableUDP, &fReliableUDP, sizeof(fReliableUDP)); + this->SetVal(qtssPrefsReliableUDPPrintfs, &fReliableUDPPrintfs, sizeof(fReliableUDPPrintfs)); + + this->SetVal(qtssPrefsDropAllPacketsDelayInMsec, &fDropAllPacketsTimeInMsec, sizeof(fDropAllPacketsTimeInMsec)); + this->SetVal(qtssPrefsDropVideoAllPacketsDelayInMsec, &fDropAllVideoPacketsTimeInMsec, sizeof(fDropAllVideoPacketsTimeInMsec)); + this->SetVal(qtssPrefsThinAllTheWayDelayInMsec, &fThinAllTheWayTimeInMsec, sizeof(fThinAllTheWayTimeInMsec)); + this->SetVal(qtssPrefsAlwaysThinDelayInMsec, &fAlwaysThinTimeInMsec, sizeof(fAlwaysThinTimeInMsec)); + this->SetVal(qtssPrefsStartThinningDelayInMsec, &fStartThinningTimeInMsec, sizeof(fStartThinningTimeInMsec)); + this->SetVal(qtssPrefsStartThickingDelayInMsec, &fStartThickingTimeInMsec, sizeof(fStartThickingTimeInMsec)); + this->SetVal(qtssPrefsThickAllTheWayDelayInMsec, &fThickAllTheWayTimeInMsec, sizeof(fThickAllTheWayTimeInMsec)); + this->SetVal(qtssPrefsQualityCheckIntervalInMsec, &fQualityCheckIntervalInMsec, sizeof(fQualityCheckIntervalInMsec)); + this->SetVal(qtssPrefsEnableRTSPErrorMessage, &fEnableRTSPErrMsg, sizeof(fEnableRTSPErrMsg)); + this->SetVal(qtssPrefsEnableRTSPDebugPrintfs, &fEnableRTSPDebugPrintfs, sizeof(fEnableRTSPDebugPrintfs)); + this->SetVal(qtssPrefsEnableRTSPServerInfo, &fEnableRTSPServerInfo, sizeof(fEnableRTSPServerInfo)); + this->SetVal(qtssPrefsRunNumThreads, &fNumThreads, sizeof(fNumThreads)); + this->SetVal(qtssPrefsEnableMonitorStatsFile, &fEnableMonitorStatsFile, sizeof(fEnableMonitorStatsFile)); + this->SetVal(qtssPrefsMonitorStatsFileIntervalSec, &fStatsFileIntervalSeconds, sizeof(fStatsFileIntervalSeconds)); + + this->SetVal(qtssPrefsEnablePacketHeaderPrintfs, &fEnablePacketHeaderPrintfs, sizeof(fEnablePacketHeaderPrintfs)); + this->SetVal(qtssPrefsCloseLogsOnWrite, &fCloseLogsOnWrite, sizeof(fCloseLogsOnWrite)); + this->SetVal(qtssPrefsOverbufferRate, &fOverbufferRate, sizeof(fOverbufferRate)); + this->SetVal(qtssPrefsDisableThinning, &fDisableThinning, sizeof(fDisableThinning)); + + this->SetVal(qtssPrefsEnable3gppProtocol, &f3gppProtocolEnabled, sizeof(f3gppProtocolEnabled)); + this->SetVal(qtssPrefsEnable3gppProtocolRateAdapt, &f3gppProtocolRateAdaptationEnabled, sizeof(f3gppProtocolRateAdaptationEnabled)); + this->SetVal(qtssPrefs3gppRateAdaptReportFrequency, &f3gppProtocolRateAdaptationReportFrequency, sizeof(f3gppProtocolRateAdaptationReportFrequency)); //3gpp_protocol_rate_adaptation_report_frequency + this->SetVal(qtssPrefsDefaultStreamQuality, &fDefaultStreamQuality, sizeof(fDefaultStreamQuality)); //default_stream_quality + this->SetVal(qtssPrefsEnable3gppDebugPrintfs, &f3gppDebugPrintfsEnabled, sizeof(f3gppDebugPrintfsEnabled)); + this->SetVal(qtssPrefsEnableUDPMonitor, &fUDPMonitorEnabled, sizeof(fUDPMonitorEnabled)); + this->SetVal(qtssPrefsUDPMonitorAudioPort, &fUDPMonitorVideoPort, sizeof(fUDPMonitorVideoPort)); + this->SetVal(qtssPrefsUDPMonitorVideoPort, &fUDPMonitorAudioPort, sizeof(fUDPMonitorAudioPort)); + this->SetVal(qtssPrefsUDPMonitorDestIPAddr, &fUDPMonitorDestAddr, sizeof(fUDPMonitorDestAddr)); + this->SetVal(qtssPrefsUDPMonitorSourceIPAddr, &fUDPMonitorSrcAddr, sizeof(fUDPMonitorSrcAddr)); + this->SetVal(qtssPrefsEnableAllowGuestDefault, &fAllowGuestAuthorizeDefault, sizeof(fAllowGuestAuthorizeDefault)); //enable_allow_guest_authorize_default + this->SetVal(qtssPrefsNumRTSPThreads, &fNumRTSPThreads, sizeof(fNumRTSPThreads)); + this->SetVal(qtssPrefs3GPPTargetTime, &f3GPPRateAdaptTargetTime, sizeof(f3GPPRateAdaptTargetTime)); + + + + +} + + + +void QTSServerPrefs::RereadServerPreferences(Bool16 inWriteMissingPrefs) +{ + OSMutexLocker locker(&fPrefsMutex); + QTSSDictionaryMap* theMap = QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kPrefsDictIndex); + + for (UInt32 x = 0; x < theMap->GetNumAttrs(); x++) + { + // + // Look for a pref in the file that matches each pref in the dictionary + char* thePrefTypeStr = NULL; + char* thePrefName = NULL; + + ContainerRef server = fPrefsSource->GetRefForServer(); + ContainerRef pref = fPrefsSource->GetPrefRefByName( server, theMap->GetAttrName(x) ); + char* thePrefValue = NULL; + if (pref != NULL) + thePrefValue = fPrefsSource->GetPrefValueByRef( pref, 0, &thePrefName, + (char**)&thePrefTypeStr); + + if ((thePrefValue == NULL) && (x < qtssPrefsNumParams)) // Only generate errors for server prefs + { + // + // There is no pref, use the default and log an error + if (::strlen(sPrefInfo[x].fDefaultValue) > 0) + { + // + // Only log this as an error if there is a default (an empty string + // doesn't count). If there is no default, we will constantly print + // out an error message... + QTSSModuleUtils::LogError( QTSSModuleUtils::GetMisingPrefLogVerbosity(), + qtssServerPrefMissing, + 0, + sAttributes[x].fAttrName, + sPrefInfo[x].fDefaultValue); + } + + this->SetPrefValue(x, 0, sPrefInfo[x].fDefaultValue, sAttributes[x].fAttrDataType); + if (sPrefInfo[x].fAdditionalDefVals != NULL) + { + // + // Add additional default values if they exist + for (UInt32 y = 0; sPrefInfo[x].fAdditionalDefVals[y] != NULL; y++) + this->SetPrefValue(x, y+1, sPrefInfo[x].fAdditionalDefVals[y], sAttributes[x].fAttrDataType); + } + + if (inWriteMissingPrefs) + { + // + // Add this value into the file, cuz we need it. + pref = fPrefsSource->AddPref( server, sAttributes[x].fAttrName, QTSSDataConverter::TypeToTypeString(sAttributes[x].fAttrDataType)); + fPrefsSource->AddPrefValue(pref, sPrefInfo[x].fDefaultValue); + + if (sPrefInfo[x].fAdditionalDefVals != NULL) + { + for (UInt32 a = 0; sPrefInfo[x].fAdditionalDefVals[a] != NULL; a++) + fPrefsSource->AddPrefValue(pref, sPrefInfo[x].fAdditionalDefVals[a]); + } + } + continue; + } + + QTSS_AttrDataType theType = QTSSDataConverter::TypeStringToType(thePrefTypeStr); + + if ((x < qtssPrefsNumParams) && (theType != sAttributes[x].fAttrDataType)) // Only generate errors for server prefs + { + // + // The pref in the file has the wrong type, use the default and log an error + + if (::strlen(sPrefInfo[x].fDefaultValue) > 0) + { + // + // Only log this as an error if there is a default (an empty string + // doesn't count). If there is no default, we will constantly print + // out an error message... + QTSSModuleUtils::LogError( qtssWarningVerbosity, + qtssServerPrefWrongType, + 0, + sAttributes[x].fAttrName, + sPrefInfo[x].fDefaultValue); + } + + this->SetPrefValue(x, 0, sPrefInfo[x].fDefaultValue, sAttributes[x].fAttrDataType); + if (sPrefInfo[x].fAdditionalDefVals != NULL) + { + // + // Add additional default values if they exist + for (UInt32 z = 0; sPrefInfo[x].fAdditionalDefVals[z] != NULL; z++) + this->SetPrefValue(x, z+1, sPrefInfo[x].fAdditionalDefVals[z], sAttributes[x].fAttrDataType); + } + + if (inWriteMissingPrefs) + { + // + // Remove it out of the file and add in the default. + fPrefsSource->RemovePref(pref); + pref = fPrefsSource->AddPref( server, sAttributes[x].fAttrName, QTSSDataConverter::TypeToTypeString(sAttributes[x].fAttrDataType)); + fPrefsSource->AddPrefValue(pref, sPrefInfo[x].fDefaultValue); + if (sPrefInfo[x].fAdditionalDefVals != NULL) + { + for (UInt32 b = 0; sPrefInfo[x].fAdditionalDefVals[b] != NULL; b++) + fPrefsSource->AddPrefValue(pref, sPrefInfo[x].fAdditionalDefVals[b]); + } + } + continue; + } + + UInt32 theNumValues = 0; + if ((x < qtssPrefsNumParams) && (!sPrefInfo[x].fAllowMultipleValues)) + theNumValues = 1; + + this->SetPrefValuesFromFileWithRef(pref, x, theNumValues); + } + + // + // Do any special pref post-processing + this->UpdateAuthScheme(); + this->UpdatePrintfOptions(); + QTSSModuleUtils::SetEnableRTSPErrorMsg(fEnableRTSPErrMsg); + + QTSSRollingLog::SetCloseOnWrite(fCloseLogsOnWrite); + // + // In case we made any changes, write out the prefs file + (void)fPrefsSource->WritePrefsFile(); +} + +void QTSServerPrefs::UpdateAuthScheme() +{ + static StrPtrLen sNoAuthScheme("none"); + static StrPtrLen sBasicAuthScheme("basic"); + static StrPtrLen sDigestAuthScheme("digest"); + + // Get the auth scheme attribute + StrPtrLen* theAuthScheme = this->GetValue(qtssPrefsAuthenticationScheme); + + if (theAuthScheme->Equal(sNoAuthScheme)) + fAuthScheme = qtssAuthNone; + else if (theAuthScheme->Equal(sBasicAuthScheme)) + fAuthScheme = qtssAuthBasic; + else if (theAuthScheme->Equal(sDigestAuthScheme)) + fAuthScheme = qtssAuthDigest; +} + +char* QTSServerPrefs::GetMovieFolder(char* inBuffer, UInt32* ioLen) +{ + OSMutexLocker locker(&fPrefsMutex); + + // Get the movie folder attribute + StrPtrLen* theMovieFolder = this->GetValue(qtssPrefsMovieFolder); + + // If the movie folder path fits inside the provided buffer, copy it there + if (theMovieFolder->Len < *ioLen) + ::memcpy(inBuffer, theMovieFolder->Ptr, theMovieFolder->Len); + else + { + // Otherwise, allocate a buffer to store the path + inBuffer = NEW char[theMovieFolder->Len + 2]; + ::memcpy(inBuffer, theMovieFolder->Ptr, theMovieFolder->Len); + } + inBuffer[theMovieFolder->Len] = 0; + *ioLen = theMovieFolder->Len; + return inBuffer; +} + +Bool16 QTSServerPrefs::IsPathInsideReliableUDPDir(StrPtrLen* inPath) +{ + OSMutexLocker locker(&fPrefsMutex); + + QTSS_Error theErr = QTSS_NoErr; + for (UInt32 x = 0; theErr == QTSS_NoErr; x++) + { + StrPtrLen theReliableUDPDir; + theErr = this->GetValuePtr(qtssPrefsReliableUDPDirs, x, (void**)&theReliableUDPDir.Ptr, &theReliableUDPDir.Len, true); + + if (theErr != QTSS_NoErr) + return false; + + if (inPath->NumEqualIgnoreCase(theReliableUDPDir.Ptr, theReliableUDPDir.Len)) + return true; + } + Assert(0); + return false; +} + +void QTSServerPrefs::UpdatePrintfOptions() +{ + StrPtrLen* theOptions = this->GetValue(qtssPrefsPacketHeaderPrintfOptions); + if (theOptions == NULL || theOptions->Len == 0) + return; + + fPacketHeaderPrintfOptions = 0; + if (theOptions->FindStringIgnoreCase("rtp")) + fPacketHeaderPrintfOptions |= kRTPALL; + if (theOptions->FindStringIgnoreCase("sr")) + fPacketHeaderPrintfOptions |= kRTCPSR; + if (theOptions->FindStringIgnoreCase("rr")) + fPacketHeaderPrintfOptions |= kRTCPRR; + if (theOptions->FindStringIgnoreCase("app")) + fPacketHeaderPrintfOptions |= kRTCPAPP; + if (theOptions->FindStringIgnoreCase("ack")) + fPacketHeaderPrintfOptions |= kRTCPACK; + +} + +void QTSServerPrefs::GetTransportSrcAddr(StrPtrLen* ioBuf) +{ + OSMutexLocker locker(&fPrefsMutex); + + // Get the movie folder attribute + StrPtrLen* theTransportAddr = this->GetValue(qtssPrefsAltTransportIPAddr); + + // If the movie folder path fits inside the provided buffer, copy it there + if ((theTransportAddr->Len > 0) && (theTransportAddr->Len < ioBuf->Len)) + { + ::memcpy(ioBuf->Ptr, theTransportAddr->Ptr, theTransportAddr->Len); + ioBuf->Len = theTransportAddr->Len; + } + else + ioBuf->Len = 0; +} + +char* QTSServerPrefs::GetStringPref(QTSS_AttributeID inAttrID) +{ + StrPtrLen theBuffer; + (void)this->GetValue(inAttrID, 0, NULL, &theBuffer.Len); + theBuffer.Ptr = NEW char[theBuffer.Len + 1]; + theBuffer.Ptr[0] = '\0'; + + if (theBuffer.Len > 0) + { + QTSS_Error theErr = this->GetValue(inAttrID, 0, theBuffer.Ptr, &theBuffer.Len); + if (theErr == QTSS_NoErr) + theBuffer.Ptr[theBuffer.Len] = 0; + } + return theBuffer.Ptr; +} + +void QTSServerPrefs::SetCloseLogsOnWrite(Bool16 closeLogsOnWrite) +{ + QTSSRollingLog::SetCloseOnWrite(closeLogsOnWrite); + fCloseLogsOnWrite = closeLogsOnWrite; +} + diff --git a/Server.tproj/QTSServerPrefs.h b/Server.tproj/QTSServerPrefs.h new file mode 100644 index 0000000..193cba7 --- /dev/null +++ b/Server.tproj/QTSServerPrefs.h @@ -0,0 +1,350 @@ +/* + * + * @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@ + * + */ + /* + Contains: Object store for RTSP server preferences. + + + +*/ + +#ifndef __QTSSERVERPREFS_H__ +#define __QTSSERVERPREFS_H__ + +#include "StrPtrLen.h" +#include "QTSSPrefs.h" +#include "XMLPrefsParser.h" + +class QTSServerPrefs : public QTSSPrefs +{ + public: + + // INITIALIZE + // + // This function sets up the dictionary map. Must be called before instantiating + // the first RTSPPrefs object. + + static void Initialize(); + + QTSServerPrefs(XMLPrefsParser* inPrefsSource, Bool16 inWriteMissingPrefs); + virtual ~QTSServerPrefs() {} + + //This is callable at any time, and is thread safe wrt to the accessors. + //Pass in true if you want this function to update the prefs file if + //any defaults need to be used. False otherwise + void RereadServerPreferences(Bool16 inWriteMissingPrefs); + + //Individual accessor methods for preferences. + + //Amount of idle time after which respective protocol sessions are timed out + //(stored in seconds) + + //This is the value we advertise to clients (lower than the real one) + UInt32 GetRTSPTimeoutInSecs() { return fRTSPTimeoutInSecs; } + UInt32 GetRTPTimeoutInSecs() { return fRTPTimeoutInSecs; } + StrPtrLen* GetRTSPTimeoutAsString() { return &fRTSPTimeoutString; } + + //This is the real timeout + UInt32 GetRealRTSPTimeoutInSecs(){ return fRealRTSPTimeoutInSecs; } + + //-1 means unlimited + SInt32 GetMaxConnections() { return fMaximumConnections; } + SInt32 GetMaxKBitsBandwidth() { return fMaxBandwidthInKBits; } + + // Thinning algorithm parameters + SInt32 GetDropAllPacketsTimeInMsec() { return fDropAllPacketsTimeInMsec; } + SInt32 GetDropAllVideoPacketsTimeInMsec() { return fDropAllVideoPacketsTimeInMsec; } + SInt32 GetThinAllTheWayTimeInMsec() { return fThinAllTheWayTimeInMsec; } + SInt32 GetAlwaysThinTimeInMsec() { return fAlwaysThinTimeInMsec; } + SInt32 GetStartThinningTimeInMsec() { return fStartThinningTimeInMsec; } + SInt32 GetStartThickingTimeInMsec() { return fStartThickingTimeInMsec; } + SInt32 GetThickAllTheWayTimeInMsec() { return fThickAllTheWayTimeInMsec; } + UInt32 GetQualityCheckIntervalInMsec() { return fQualityCheckIntervalInMsec; } + + // for tcp buffer size scaling + UInt32 GetMinTCPBufferSizeInBytes() { return fMinTCPBufferSizeInBytes; } + UInt32 GetMaxTCPBufferSizeInBytes() { return fMaxTCPBufferSizeInBytes; } + Float32 GetTCPSecondsToBuffer() { return fTCPSecondsToBuffer; } + + //for joining HTTP sessions from behind a round-robin DNS + Bool16 GetDoReportHTTPConnectionAddress() { return fDoReportHTTPConnectionAddress; } + + //for debugging, mainly + Bool16 ShouldServerBreakOnAssert() { return fBreakOnAssert; } + Bool16 IsAutoRestartEnabled() { return fAutoRestart; } + + UInt32 GetTotalBytesUpdateTimeInSecs() { return fTBUpdateTimeInSecs; } + UInt32 GetAvgBandwidthUpdateTimeInSecs() { return fABUpdateTimeInSecs; } + UInt32 GetSafePlayDurationInSecs() { return fSafePlayDurationInSecs; } + + // For the compiled-in error logging module + + Bool16 IsErrorLogEnabled() { return fErrorLogEnabled; } + Bool16 IsScreenLoggingEnabled() { return fScreenLoggingEnabled; } + + UInt32 GetMaxErrorLogBytes() { return fErrorLogBytes; } + UInt32 GetErrorRollIntervalInDays() { return fErrorRollIntervalInDays; } + UInt32 GetErrorLogVerbosity() { return fErrorLogVerbosity; } + void SetErrorLogVerbosity(UInt32 verbosity) { fErrorLogVerbosity = verbosity; } + Bool16 GetAppendSrcAddrInTransport() { return fAppendSrcAddrInTransport; } + + // + // For UDP retransmits + UInt32 IsReliableUDPEnabled() { return fReliableUDP; } + UInt32 GetMaxRetransmitDelayInMsec() { return fMaxRetransDelayInMsec; } + Bool16 IsAckLoggingEnabled() { return fIsAckLoggingEnabled; } + UInt32 GetRTCPPollIntervalInMsec() { return fRTCPPollIntervalInMsec; } + UInt32 GetRTCPSocketRcvBufSizeinK() { return fRTCPSocketRcvBufSizeInK; } + UInt32 GetSendIntervalInMsec() { return fSendIntervalInMsec; } + UInt32 GetMaxSendAheadTimeInSecs() { return fMaxSendAheadTimeInSecs; } + Bool16 IsSlowStartEnabled() { return fIsSlowStartEnabled; } + Bool16 GetReliableUDPPrintfsEnabled() { return fReliableUDPPrintfs; } + Bool16 GetRTSPDebugPrintfs() { return fEnableRTSPDebugPrintfs; } + Bool16 GetRTSPServerInfoEnabled() { return fEnableRTSPServerInfo; } + + Float32 GetOverbufferRate() { return fOverbufferRate; } + + // RUDP window size + UInt32 GetSmallWindowSizeInK() { return fSmallWindowSizeInK; } + UInt32 GetMediumWindowSizeInK() { return fMediumWindowSizeInK; } + UInt32 GetLargeWindowSizeInK() { return fLargeWindowSizeInK; } + UInt32 GetWindowSizeThreshold() { return fWindowSizeThreshold; } + UInt32 GetWindowSizeMaxThreshold() { return fWindowSizeMaxThreshold; } + + // + // force logs to close after each write (true or false) + Bool16 GetCloseLogsOnWrite() { return fCloseLogsOnWrite; } + void SetCloseLogsOnWrite(Bool16 closeLogsOnWrite); + + // + // Optionally require that reliable UDP content be in certain folders + Bool16 IsPathInsideReliableUDPDir(StrPtrLen* inPath); + + // Movie folder pref. If the path fits inside the buffer provided, + // the path is copied into that buffer. Otherwise, a new buffer is allocated + // and returned. + char* GetMovieFolder(char* inBuffer, UInt32* ioLen); + + // + // Transport addr pref. Caller must provide a buffer big enough for an IP addr + void GetTransportSrcAddr(StrPtrLen* ioBuf); + + // String preferences. Note that the pointers returned here is allocated + // memory that you must delete! + + char* GetErrorLogDir() + { return this->GetStringPref(qtssPrefsErrorLogDir); } + char* GetErrorLogName() + { return this->GetStringPref(qtssPrefsErrorLogName); } + + char* GetModuleDirectory() + { return this->GetStringPref(qtssPrefsModuleFolder); } + + char* GetAuthorizationRealm() + { return this->GetStringPref(qtssPrefsDefaultAuthorizationRealm); } + + char* GetRunUserName() + { return this->GetStringPref(qtssPrefsRunUserName); } + char* GetRunGroupName() + { return this->GetStringPref(qtssPrefsRunGroupName); } + + char* GetPidFilePath() + { return this->GetStringPref(qtssPrefsPidFile); } + + char* GetStatsMonitorFileName() + { return this->GetStringPref(qtssPrefsMonitorStatsFileName); } + + Bool16 ServerStatFileEnabled() { return fEnableMonitorStatsFile; } + UInt32 GetStatFileIntervalSec() { return fStatsFileIntervalSeconds; } + Bool16 AutoDeleteSDPFiles() { return fauto_delete_sdp_files; } + QTSS_AuthScheme GetAuthScheme() { return fAuthScheme; } + + Bool16 PacketHeaderPrintfsEnabled() { return fEnablePacketHeaderPrintfs; } + Bool16 PrintRTPHeaders() { return (Bool16) (fPacketHeaderPrintfOptions & kRTPALL); } + Bool16 PrintSRHeaders() { return (Bool16) (fPacketHeaderPrintfOptions & kRTCPSR); } + Bool16 PrintRRHeaders() { return (Bool16) (fPacketHeaderPrintfOptions & kRTCPRR); } + Bool16 PrintAPPHeaders() { return (Bool16) (fPacketHeaderPrintfOptions & kRTCPAPP); } + Bool16 PrintACKHeaders() { return (Bool16) (fPacketHeaderPrintfOptions & kRTCPACK); } + + UInt32 DeleteSDPFilesInterval() { return fsdp_file_delete_interval_seconds; } + + UInt32 GetNumThreads() { return fNumThreads; } //short tasks threads + UInt32 GetNumBlockingThreads() { return fNumRTSPThreads; } //return the number of threads that long tasks will be scheduled on -- RTSP processing for example. + + Bool16 GetDisableThinning() { return fDisableThinning; } + + Bool16 Get3GPPEnabled() { return f3gppProtocolEnabled; } + Bool16 Get3GPPRateAdaptationEnabled() { return f3gppProtocolRateAdaptationEnabled; } + UInt16 Get3GPPRateAdaptReportFrequency() { return f3gppProtocolRateAdaptationReportFrequency; } + UInt16 GetDefaultStreamQuality() { return fDefaultStreamQuality; } + Bool16 Get3GPPDebugPrintfs() { return f3gppDebugPrintfsEnabled; } + Bool16 GetUDPMonitorEnabled() { return fUDPMonitorEnabled; } + UInt16 GetUDPMonitorVideoPort() { return fUDPMonitorVideoPort; } + UInt16 GetUDPMonitorAudioPort() { return fUDPMonitorAudioPort; } + + char* GetMonitorDestIP() { return this->GetStringPref(qtssPrefsUDPMonitorDestIPAddr); } + + char* GetMonitorSrcIP() { return this->GetStringPref(qtssPrefsUDPMonitorSourceIPAddr); } + + Bool16 GetAllowGuestDefault() { return fAllowGuestAuthorizeDefault; } + + UInt32 Get3GPPForcedTargetTime() {return f3GPPRateAdaptTargetTime; } + + private: + + UInt32 fRTSPTimeoutInSecs; + char fRTSPTimeoutBuf[20]; + StrPtrLen fRTSPTimeoutString; + UInt32 fRealRTSPTimeoutInSecs; + UInt32 fRTPTimeoutInSecs; + + SInt32 fMaximumConnections; + SInt32 fMaxBandwidthInKBits; + + Bool16 fBreakOnAssert; + Bool16 fAutoRestart; + UInt32 fTBUpdateTimeInSecs; + UInt32 fABUpdateTimeInSecs; + UInt32 fSafePlayDurationInSecs; + + UInt32 fErrorRollIntervalInDays; + UInt32 fErrorLogBytes; + UInt32 fErrorLogVerbosity; + Bool16 fScreenLoggingEnabled; + Bool16 fErrorLogEnabled; + + SInt32 fDropAllPacketsTimeInMsec; + SInt32 fDropAllVideoPacketsTimeInMsec; + SInt32 fThinAllTheWayTimeInMsec; + SInt32 fAlwaysThinTimeInMsec; + SInt32 fStartThinningTimeInMsec; + SInt32 fStartThickingTimeInMsec; + SInt32 fThickAllTheWayTimeInMsec; + UInt32 fQualityCheckIntervalInMsec; + + UInt32 fMinTCPBufferSizeInBytes; + UInt32 fMaxTCPBufferSizeInBytes; + Float32 fTCPSecondsToBuffer; + + Bool16 fDoReportHTTPConnectionAddress; + Bool16 fAppendSrcAddrInTransport; + + UInt32 fSmallWindowSizeInK; + UInt32 fMediumWindowSizeInK; + UInt32 fLargeWindowSizeInK; + UInt32 fWindowSizeThreshold; + UInt32 fWindowSizeMaxThreshold; + + UInt32 fMaxRetransDelayInMsec; + Bool16 fIsAckLoggingEnabled; + UInt32 fRTCPPollIntervalInMsec; + UInt32 fRTCPSocketRcvBufSizeInK; + Bool16 fIsSlowStartEnabled; + UInt32 fSendIntervalInMsec; + UInt32 fMaxSendAheadTimeInSecs; + Bool16 fauto_delete_sdp_files; + QTSS_AuthScheme fAuthScheme; + UInt32 fsdp_file_delete_interval_seconds; + Bool16 fAutoStart; + Bool16 fReliableUDP; + Bool16 fReliableUDPPrintfs; + Bool16 fEnableRTSPErrMsg; + Bool16 fEnableRTSPDebugPrintfs; + Bool16 fEnableRTSPServerInfo; + UInt32 fNumThreads; + UInt32 fNumRTSPThreads; + UInt32 f3GPPRateAdaptTargetTime; + + Bool16 fEnableMonitorStatsFile; + UInt32 fStatsFileIntervalSeconds; + + Float32 fOverbufferRate; + + Bool16 fEnablePacketHeaderPrintfs; + UInt32 fPacketHeaderPrintfOptions; + Bool16 fCloseLogsOnWrite; + + Bool16 fDisableThinning; + + Bool16 f3gppProtocolEnabled; + Bool16 f3gppProtocolRateAdaptationEnabled; + UInt16 f3gppProtocolRateAdaptationReportFrequency; + UInt16 fDefaultStreamQuality; + Bool16 f3gppDebugPrintfsEnabled; + Bool16 fUDPMonitorEnabled; + UInt16 fUDPMonitorVideoPort; + UInt16 fUDPMonitorAudioPort; + char fUDPMonitorDestAddr[20]; + char fUDPMonitorSrcAddr[20]; + Bool16 fAllowGuestAuthorizeDefault; + + enum //fPacketHeaderPrintfOptions + { + kRTPALL = 1 << 0, + kRTCPSR = 1 << 1, + kRTCPRR = 1 << 2, + kRTCPAPP = 1<< 3, + kRTCPACK = 1<< 4 + }; + + enum + { + kAllowMultipleValues = 1, + kDontAllowMultipleValues = 0 + }; + + struct PrefInfo + { + UInt32 fAllowMultipleValues; + char* fDefaultValue; + char** fAdditionalDefVals; // For prefs with multiple default values + }; + + void SetupAttributes(); + void UpdateAuthScheme(); + void UpdatePrintfOptions(); + // + // Returns the string preference with the specified ID. If there + // was any problem, this will return an empty string. + char* GetStringPref(QTSS_AttributeID inAttrID); + + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + static PrefInfo sPrefInfo[]; + + // Prefs that have multiple default values (rtsp_ports) have + // to be dealt with specially + static char* sAdditionalDefaultPorts[]; + + // Player prefs + static char* sRTP_Header_Players[]; + static char* sAdjust_Bandwidth_Players[]; + static char* sNo_Adjust_Pause_Time_Players[]; + static char* sNo_Pause_Time_Adjustment_Players[]; + static char* sRTP_Start_Time_Players[]; + static char* sDisable_Rate_Adapt_Players[]; + static char* sFixed_Target_Time_Players[]; + static char* sDisable_Thinning_Players[]; + +}; +#endif //__QTSSPREFS_H__ diff --git a/Server.tproj/RTCPTask.cpp b/Server.tproj/RTCPTask.cpp new file mode 100644 index 0000000..3e7e979 --- /dev/null +++ b/Server.tproj/RTCPTask.cpp @@ -0,0 +1,114 @@ +/* + * + * @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: RTCPTask.cpp + + Contains: Implementation of class defined in RTCPTask.h + + + + + +*/ + +#include "RTCPTask.h" +#include "QTSServerInterface.h" +#include "UDPSocketPool.h" +#include "RTPStream.h" + +SInt64 RTCPTask::Run() +{ + const UInt32 kMaxRTCPPacketSize = 2048; + char thePacketBuffer[kMaxRTCPPacketSize]; + StrPtrLen thePacket(thePacketBuffer, 0); + QTSServerInterface* theServer = QTSServerInterface::GetServer(); + + //This task goes through all the UDPSockets in the RTPSocketPool, checking to see + //if they have data. If they do, it demuxes the packets and sends the packet onto + //the proper RTP session. + EventFlags events = this->GetEvents(); // get and clear events + + if ( (events & Task::kReadEvent) || (events & Task::kIdleEvent) ) + { + //Must be done atomically wrt the socket pool. + + OSMutexLocker locker(theServer->GetSocketPool()->GetMutex()); + for (OSQueueIter iter(theServer->GetSocketPool()->GetSocketQueue()); + !iter.IsDone(); iter.Next()) + { + UInt32 theRemoteAddr = 0; + UInt16 theRemotePort = 0; + + UDPSocketPair* thePair = (UDPSocketPair*)iter.GetCurrent()->GetEnclosingObject(); + Assert(thePair != NULL); + + for (UInt32 x = 0; x < 2; x++) + { + UDPSocket* theSocket = NULL; + if (x == 0) + theSocket = thePair->GetSocketA(); + else + theSocket = thePair->GetSocketB(); + + UDPDemuxer* theDemuxer = theSocket->GetDemuxer(); + if (theDemuxer == NULL) + continue; + else + { + theDemuxer->GetMutex()->Lock(); + while (true) //get all the outstanding packets for this socket + { + thePacket.Len = 0; + theSocket->RecvFrom(&theRemoteAddr, &theRemotePort, thePacket.Ptr, + kMaxRTCPPacketSize, &thePacket.Len); + if (thePacket.Len == 0) + { + theSocket->RequestEvent(EV_RE); + break;//no more packets on this socket! + } + + //if this socket has a demuxer, find the target RTPStream + if (theDemuxer != NULL) + { + RTPStream* theStream = (RTPStream*)theDemuxer->GetTask(theRemoteAddr, theRemotePort); + if (theStream != NULL) + theStream->ProcessIncomingRTCPPacket(&thePacket); + } + } + theDemuxer->GetMutex()->Unlock(); + } + } + } + } + + return 0; /* Fix for 4004432 */ + /* + SInt64 result = 0; + if (theServer->GetNumRTPSessions() > 0) + result = theServer->GetPrefs()->GetRTCPPollIntervalInMsec(); + + return result; + */ +} diff --git a/Server.tproj/RTCPTask.h b/Server.tproj/RTCPTask.h new file mode 100644 index 0000000..02d914a --- /dev/null +++ b/Server.tproj/RTCPTask.h @@ -0,0 +1,51 @@ +/* + * + * @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: RTCPTask.h + + Contains: A task object that processes all incoming RTCP packets + for the server, and passes each one onto the task for + which it belongs. + +*/ + +#ifndef __RTCP_TASK_H__ +#define __RTCP_TASK_H__ + +#include "Task.h" + +class RTCPTask : public Task +{ + public: + //This task handles all incoming RTCP data. It just polls, so make sure + //to start the polling process by signalling a start event. + RTCPTask() : Task() {this->SetTaskName("RTCPTask"); this->Signal(Task::kStartEvent); } + virtual ~RTCPTask() {} + + private: + virtual SInt64 Run(); +}; + +#endif //__RTCP_TASK_H__ diff --git a/Server.tproj/RTPBandwidthTracker.cpp b/Server.tproj/RTPBandwidthTracker.cpp new file mode 100644 index 0000000..7bae0c3 --- /dev/null +++ b/Server.tproj/RTPBandwidthTracker.cpp @@ -0,0 +1,258 @@ +/* + * + * @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: RTPBandwidthTracker.cpp + + Contains: Implementation of class decribed in .h file + +*/ + +#include "RTPBandwidthTracker.h" +#include "MyAssert.h" +#include "OS.h" + +void RTPBandwidthTracker::SetWindowSize( SInt32 clientWindowSize ) +{ + // + // Currently we only allow this info to be set once + if (fClientWindow > 0) + return; + + // call SetWindowSize once the clients buffer size is known + // since this occurs before the stream starts to send + + fClientWindow = clientWindowSize; + fLastCongestionAdjust = 0; + +#if RTP_PACKET_RESENDER_DEBUGGING + //€ test to see what happens w/o slow start at beginning + //if ( initSlowStart ) + // qtss_printf( "ack list initializing with slow start.\n" ); + //else + // qtss_printf( "ack list initializing at full speed.\n" ); +#endif + + if ( fUseSlowStart ) + { + fSlowStartThreshold = clientWindowSize * 3 / 4; + + // + // This is a change to the standard TCP slow start algorithm. What + // we found was that on high bitrate high latency networks (a DSL connection, perhaps), + // it took just too long for the ACKs to come in and for the window size to + // grow enough. So we cheat a bit. + fCongestionWindow = clientWindowSize / 2; + //fCongestionWindow = kMaximumSegmentSize; + } + else + { + fSlowStartThreshold = clientWindowSize; + fCongestionWindow = clientWindowSize; + } + + if ( fSlowStartThreshold < kMaximumSegmentSize ) + fSlowStartThreshold = kMaximumSegmentSize; +} + +void RTPBandwidthTracker::EmptyWindow( UInt32 bytesIncreased, Bool16 updateBytesInList ) +{ + if (bytesIncreased == 0) + return; + + Assert(fClientWindow > 0 && fCongestionWindow > 0); + + if(fBytesInList < bytesIncreased) + bytesIncreased = fBytesInList; + + if (updateBytesInList) + fBytesInList -= bytesIncreased; + + // this assert hits + Assert(fBytesInList < ((UInt32)fClientWindow + 2000)); //mainly just to catch fBytesInList wrapping below 0 + + // update the congestion window by the number of bytes just acknowledged. + + if ( fCongestionWindow >= fSlowStartThreshold ) + { + // when we hit the slow start threshold, only increase the + // window for each window full of acks. + fCongestionWindow += bytesIncreased * bytesIncreased / fCongestionWindow; + } + else + // + // This is a change to the standard TCP slow start algorithm. What + // we found was that on high bitrate high latency networks (a DSL connection, perhaps), + // it took just too long for the ACKs to come in and for the window size to + // grow enough. So we cheat a bit. + fCongestionWindow += bytesIncreased; + + + if ( fCongestionWindow > fClientWindow ) + fCongestionWindow = fClientWindow; + +// qtss_printf("Window = %d, %d left\n", fCongestionWindow, fCongestionWindow-fBytesInList); +} + +void RTPBandwidthTracker::AdjustWindowForRetransmit() +{ + // this assert hits + Assert(fBytesInList < ((UInt32)fClientWindow + 2000)); //mainly just to catch fBytesInList wrapping below 0 + + // slow start says that we should reduce the new ss threshold to 1/2 + // of where started getting errors ( the current congestion window size ) + + // so, we get a burst of re-tx becuase our RTO was mis-estimated + // it doesn't seem like we should lower the threshold for each one. + // it seems like we should just lower it when we first enter + // the re-transmit "state" +// if ( !fIsRetransmitting ) +// fSlowStartThreshold = fCongestionWindow/2; + + // make sure that it is at least 1 packet + if ( fSlowStartThreshold < kMaximumSegmentSize ) + fSlowStartThreshold = kMaximumSegmentSize; + + // start the full window segemnt counter over again. + fSlowStartByteCount = 0; + + // tcp resets to one (max segment size) mss, but i'm experimenting a bit + // with not being so brutal. + + //curAckList->fCongestionWindow = kMaximumSegmentSize; + +// fCongestionWindow = kMaximumSegmentSize; +// fCongestionWindow = fCongestionWindow / 2; // half the congestion window size + SInt64 theTime = OS::Milliseconds(); + if (theTime - fLastCongestionAdjust > 250) + { + fSlowStartThreshold = fCongestionWindow * 3 / 4; + fCongestionWindow = fCongestionWindow / 2; + fLastCongestionAdjust = theTime; + } + +/* + if ( fSlowStartThreshold < fCongestionWindow ) + fCongestionWindow = fSlowStartThreshold/2; + else + fCongestionWindow = fCongestionWindow /2; +*/ + + if ( fCongestionWindow < kMaximumSegmentSize ) + fCongestionWindow = kMaximumSegmentSize; + + // qtss_printf("Congestion window now %d\n", fCongestionWindow); + fIsRetransmitting = true; +} + +void RTPBandwidthTracker::AddToRTTEstimate( SInt32 rttSampleMSecs ) +{ +// qtss_printf("%d ", rttSampleMSecs); +// static int count = 0; +// if ((count++ % 10) == 0) qtss_printf("\n"); + + // this assert hits + Assert(fBytesInList < ((UInt32)fClientWindow + 2000)); //mainly just to catch fBytesInList wrapping below 0 + + if ( fRunningAverageMSecs == 0 ) + fRunningAverageMSecs = rttSampleMSecs * 8; // init avg to cur sample, scaled by 2**3 + + SInt32 delta = rttSampleMSecs - fRunningAverageMSecs / 8; // scale average back to get cur delta from sample + + // add 1/8 the delta back to the smooth running average + fRunningAverageMSecs = fRunningAverageMSecs + delta; // same as: rt avg = rt avg + delta / 8, but scaled + + if ( delta < 0 ) + delta = -1*delta; // absolute value + + /* + + fRunningMeanDevationMSecs is kept scaled by 4 + + + so this is the same as + + fRunningMeanDevationMSecs = fRunningMeanDevationMSecs + ( |delta| - fRunningMeanDevationMSecs ) /4; + */ + + fRunningMeanDevationMSecs += delta - fRunningMeanDevationMSecs / 4; + + + fUnadjustedRTO = fCurRetransmitTimeout = fRunningAverageMSecs / 8 + fRunningMeanDevationMSecs; + + // rto should not be too low.. + if ( fCurRetransmitTimeout < kMinRetransmitIntervalMSecs ) + fCurRetransmitTimeout = kMinRetransmitIntervalMSecs; + + // or too high... + if ( fCurRetransmitTimeout > kMaxRetransmitIntervalMSecs ) + fCurRetransmitTimeout = kMaxRetransmitIntervalMSecs; +// qtss_printf("CurTimeout == %d\n", fCurRetransmitTimeout); +} + +void RTPBandwidthTracker::UpdateStats() +{ + fNumStatsSamples++; + + if (fMaxCongestionWindowSize < fCongestionWindow) + fMaxCongestionWindowSize = fCongestionWindow; + if (fMinCongestionWindowSize > fCongestionWindow) + fMinCongestionWindowSize = fCongestionWindow; + + if (fMaxRTO < fUnadjustedRTO) + fMaxRTO = fUnadjustedRTO; + if (fMinRTO > fUnadjustedRTO) + fMinRTO = fUnadjustedRTO; + + fTotalCongestionWindowSize += fCongestionWindow; + fTotalRTO += fUnadjustedRTO; +} + +void RTPBandwidthTracker::UpdateAckTimeout(UInt32 bitsSentInInterval, SInt64 intervalLengthInMsec) +{ + // + // First figure out how long it will take us to fill up our window, based on + // the movie's current bit rate + UInt32 unadjustedTimeout = 0; + if (bitsSentInInterval > 0) + unadjustedTimeout = (UInt32) ((intervalLengthInMsec * fCongestionWindow) / bitsSentInInterval); + + // + // If we wait that long, that's too long because we need to actually wait for the ack to arrive. + // So, subtract 1/2 the rto - the last ack timeout + UInt32 rto = (UInt32)fUnadjustedRTO; + if (rto < fAckTimeout) + rto = fAckTimeout; + UInt32 adjustment = (rto - fAckTimeout) / 2; + //qtss_printf("UnadjustedTimeout = %"_U32BITARG_". rto: %"_S32BITARG_". Last ack timeout: %"_U32BITARG_". Adjustment = %"_U32BITARG_".", unadjustedTimeout, fUnadjustedRTO, fAckTimeout, adjustment); + if (adjustment > unadjustedTimeout) + adjustment = unadjustedTimeout; + fAckTimeout = unadjustedTimeout - adjustment; + + //qtss_printf("AckTimeout: %"_U32BITARG_"\n",fAckTimeout); + if (fAckTimeout > kMaxAckTimeout) + fAckTimeout = kMaxAckTimeout; + else if (fAckTimeout < kMinAckTimeout) + fAckTimeout = kMinAckTimeout; +} diff --git a/Server.tproj/RTPBandwidthTracker.h b/Server.tproj/RTPBandwidthTracker.h new file mode 100644 index 0000000..f0d64f1 --- /dev/null +++ b/Server.tproj/RTPBandwidthTracker.h @@ -0,0 +1,173 @@ +/* + * + * @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: RTPBandwidthTracker.h + + Contains: Uses Karns Algorithm to measure round trip times. This also + tracks the current window size based on input from the caller. + + ref: + + Improving Round-Trip Time Estimates in Reliable Transport Protocols, ACM SIGCOMM, ??? + + Congestion Avoidance and Control - Van Jacobson, November 1988 -- Preoceedings of SIGCOMM '88 + + Internetworking with TCP/IP Comer and Stevens, Chap 14, prentice hall 1991 +*/ + +#ifndef __RTP_BANDWIDTH_TRACKER_H__ +#define __RTP_BANDWIDTH_TRACKER_H__ + +#include "OSHeaders.h" + +class RTPBandwidthTracker +{ + public: + RTPBandwidthTracker(Bool16 inUseSlowStart) + : fRunningAverageMSecs(0), + fRunningMeanDevationMSecs(0), + fCurRetransmitTimeout( kMinRetransmitIntervalMSecs ), + fUnadjustedRTO( kMinRetransmitIntervalMSecs ), + fCongestionWindow(kMaximumSegmentSize), + fSlowStartThreshold(0), + fSlowStartByteCount(0), + fClientWindow(0), + fBytesInList(0), + fAckTimeout(kMinAckTimeout), + fUseSlowStart(inUseSlowStart), + fMaxCongestionWindowSize(0), + fMinCongestionWindowSize(1000000), + fMaxRTO(0), + fMinRTO(24000), + fTotalCongestionWindowSize(0), + fTotalRTO(0), + fNumStatsSamples(0) + {} + + ~RTPBandwidthTracker() {} + + // + // Initialization - give the client's window size. + void SetWindowSize(SInt32 clientWindowSize); + + // + // Each RTT sample you get, let the tracker know what it is + // so it can keep a good running average. + void AddToRTTEstimate( SInt32 rttSampleMSecs ); + + // + // Before sending new data, let the tracker know + // how much data you are sending so it can adjust the window. + void FillWindow(UInt32 inNumBytes) + { fBytesInList += inNumBytes; fIsRetransmitting = false; } + + // + // When data is acked, let the tracker know how much + // data was acked so it can adjust the window + void EmptyWindow(UInt32 inNumBytes, Bool16 updateBytesInList = true); + + // + // When retransmitting a packet, call this function so + // the tracker can adjust the window sizes and back off. + void AdjustWindowForRetransmit(); + + // + // ACCESSORS + const Bool16 ReadyForAckProcessing() { return (fClientWindow > 0 && fCongestionWindow > 0); } // see RTPBandwidthTracker::EmptyWindow for requirements + const Bool16 IsFlowControlled() { return ( (SInt32)fBytesInList >= fCongestionWindow ); } + const SInt32 ClientWindowSize() { return fClientWindow; } + const UInt32 BytesInList() { return fBytesInList; } + const SInt32 CongestionWindow() { return fCongestionWindow; } + const SInt32 SlowStartThreshold() { return fSlowStartThreshold; } + const SInt32 RunningAverageMSecs() { return fRunningAverageMSecs / 8; } // fRunningAverageMSecs is stored scaled up 8x + const SInt32 RunningMeanDevationMSecs() { return fRunningMeanDevationMSecs/ 4; } // fRunningMeanDevationMSecs is stored scaled up 4x + const SInt32 CurRetransmitTimeout() { return fCurRetransmitTimeout; } + const SInt32 GetCurrentBandwidthInBps() + { return (fUnadjustedRTO > 0) ? (fCongestionWindow * 1000) / fUnadjustedRTO : 0; } + inline const UInt32 RecommendedClientAckTimeout() { return fAckTimeout; } + void UpdateAckTimeout(UInt32 bitsSentInInterval, SInt64 intervalLengthInMsec); + void UpdateStats(); + + // + // Stats + SInt32 GetMaxCongestionWindowSize() { return fMaxCongestionWindowSize; } + SInt32 GetMinCongestionWindowSize() { return fMinCongestionWindowSize; } + SInt32 GetAvgCongestionWindowSize() { return (SInt32)(fTotalCongestionWindowSize / (SInt64)fNumStatsSamples); } + SInt32 GetMaxRTO() { return fMaxRTO; } + SInt32 GetMinRTO() { return fMinRTO; } + SInt32 GetAvgRTO() { return (SInt32)(fTotalRTO / (SInt64)fNumStatsSamples); } + + enum + { + kMaximumSegmentSize = 1466, // enet - just a guess! + + // + // Our algorithm for telling the client what the ack timeout + // is currently not too sophisticated. This could probably be made + // better. During slow start, we just use 20, and afterwards, just use 100 + kMinAckTimeout = 20, + kMaxAckTimeout = 100 + }; + + private: + + // + // For computing the round-trip estimate using Karn's algorithm + SInt32 fRunningAverageMSecs; + SInt32 fRunningMeanDevationMSecs; + SInt32 fCurRetransmitTimeout; + SInt32 fUnadjustedRTO; + + // + // Tracking our window sizes + SInt64 fLastCongestionAdjust; + SInt32 fCongestionWindow; // implentation of VJ congestion avoidance + SInt32 fSlowStartThreshold; // point at which we stop adding to the window for each ack, and add to the window for each window full of acks + SInt32 fSlowStartByteCount; // counts window a full of acks when past ss thresh + SInt32 fClientWindow; // max window size based on client UDP buffer + UInt32 fBytesInList; // how many unacked bytes on this stream + UInt32 fAckTimeout; + + Bool16 fUseSlowStart; + Bool16 fIsRetransmitting; // are we in the re-transmit 'state' ( started resending, but have yet to send 'new' data + + // + // Stats + SInt32 fMaxCongestionWindowSize; + SInt32 fMinCongestionWindowSize; + SInt32 fMaxRTO; + SInt32 fMinRTO; + SInt64 fTotalCongestionWindowSize; + SInt64 fTotalRTO; + SInt32 fNumStatsSamples; + + enum + { + kMinRetransmitIntervalMSecs = 600, + kMaxRetransmitIntervalMSecs = 24000 + }; +}; + +#endif // __RTP_BANDWIDTH_TRACKER_H__ diff --git a/Server.tproj/RTPOverbufferWindow.cpp b/Server.tproj/RTPOverbufferWindow.cpp new file mode 100644 index 0000000..a59bddd --- /dev/null +++ b/Server.tproj/RTPOverbufferWindow.cpp @@ -0,0 +1,179 @@ +/* + * + * @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: RTPOverbufferWindow.cpp + + Contains: Implementation of the class + + Written By: Denis Serenyi + +*/ + +#include "RTPOverbufferWindow.h" +#include "OSMemory.h" +#include "MyAssert.h" + +RTPOverbufferWindow::RTPOverbufferWindow(UInt32 inSendInterval, UInt32 inInitialWindowSize, UInt32 inMaxSendAheadTimeInSec, + Float32 inOverbufferRate) +: fWindowSize(inInitialWindowSize), + fBytesSentSinceLastReport(0), + fSendInterval(inSendInterval), + fBytesDuringLastSecond(0), + fLastSecondStart(-1), + fBytesDuringPreviousSecond(0), + fPreviousSecondStart(-1), + fBytesDuringBucket(0), + fBucketBegin(0), + fBucketTimeAhead(0), + fPreviousBucketTimeAhead(0), + fMaxSendAheadTime(inMaxSendAheadTimeInSec * 1000), + fWriteBurstBeginning(false), + fOverbufferingEnabled(true), + fOverbufferRate(inOverbufferRate), + fSendAheadDurationInMsec(1000), + fOverbufferWindowBegin(-1) +{ + if (fSendInterval == 0) + { + fOverbufferingEnabled = false; + fSendInterval = 200; + } + + if(fOverbufferRate < 1.0) + fOverbufferRate = 1.0; + +} + +SInt64 RTPOverbufferWindow::CheckTransmitTime(const SInt64& inTransmitTime, const SInt64& inCurrentTime, SInt32 inPacketSize) +{ + // if this is the beginning of a bucket interval, roll over figures from last time. + // accumulate statistics over the period of a second + if (inCurrentTime - fBucketBegin > fSendInterval) + { + fPreviousBucketBegin = fBucketBegin; + fBucketBegin = inCurrentTime; + if (fPreviousBucketBegin == 0) + fPreviousBucketBegin = fBucketBegin - fSendInterval; + fBytesDuringBucket = 0; + if (inCurrentTime - fLastSecondStart > 1000) + { + fBytesDuringPreviousSecond = fBytesDuringLastSecond; + fBytesDuringLastSecond = 0; + fPreviousSecondStart = fLastSecondStart; + fLastSecondStart = inCurrentTime; + } + + fPreviousBucketTimeAhead = fBucketTimeAhead; + } + + if (fOverbufferWindowBegin == -1) + fOverbufferWindowBegin = inCurrentTime; + + if ((inTransmitTime <= inCurrentTime + fSendInterval) || + (fOverbufferingEnabled && (inTransmitTime <= inCurrentTime + fSendInterval + fSendAheadDurationInMsec))) + { + // + // If this happens, this packet needs to be sent regardless of overbuffering + return -1; + } + + if (!fOverbufferingEnabled || (fWindowSize == 0)) + return inTransmitTime; + + // if the client is running low on memory, wait a while for it to be freed up + // there's nothing magic bout these numbers, we're just trying to be conservative + if ((fWindowSize != -1) && (inPacketSize * 5 > fWindowSize - fBytesSentSinceLastReport)) + { + return inCurrentTime + (fSendInterval * 5); // client reports don't come that often + } + + // if we're far enough ahead, then wait until it's time to send more packets + if (inTransmitTime - inCurrentTime > fMaxSendAheadTime) + return inTransmitTime - fMaxSendAheadTime + fSendInterval; + + // during the first second just send packets normally +// if (fPreviousSecondStart == -1) +// return inCurrentTime + fSendInterval; + + // now figure if we want to send this packet during this bucket. We have two limitations. + // First we scale up bitrate slowly, so we should only try and send a little more than we + // sent recently (averaged over a second or two). However, we always try and send at + // least the current bitrate and never more than double. +// SInt32 currentBitRate = fBytesDuringBucket * 1000 / (inCurrentTime - fPreviousBucketBegin); +// SInt32 averageBitRate = (fBytesDuringPreviousSecond + fBytesDuringLastSecond) * 1000 / (inCurrentTime - fPreviousSecondStart); +// SInt32 averageBitRate = fBytesDuringPreviousSecond * 1000 / (fLastSecondStart - fPreviousSecondStart); + fBucketTimeAhead = inTransmitTime - inCurrentTime; +// printf("Current br = %d, average br = %d (cta = %qd, pta = %qd)\n", currentBitRate, averageBitRate, currentTimeAhead, fPreviousBucketTimeAhead); + + // always try and stay as far ahead as we were before + if (fBucketTimeAhead < fPreviousBucketTimeAhead) + return -1; + + // but don't send at more that double the bitrate (for any given time we should only get further + // ahead by that amount of time) + //printf("cta - pta = %qd, ct - pbb = %qd\n", fBucketTimeAhead - fPreviousBucketTimeAhead, SInt64((inCurrentTime - fPreviousBucketBegin) * (fOverbufferRate - 1.0))); + if (fBucketTimeAhead - fPreviousBucketTimeAhead > ((inCurrentTime - fPreviousBucketBegin) * (fOverbufferRate - 1.0))) + { + fBucketTimeAhead = fPreviousBucketTimeAhead + SInt64((inCurrentTime - fPreviousBucketBegin) * (fOverbufferRate - 1.0)); + return inCurrentTime + fSendInterval; // this will get us to the next bucket + } + + // don't send more than 10% over the average bitrate for the previous second +// if (currentBitRate > averageBitRate * 11 / 10) +// return inCurrentTime + fSendInterval; // this will get us to the next bucket + + return -1; // send this packet +} + +void RTPOverbufferWindow::ResetOverBufferWindow() +{ + fBytesDuringLastSecond = 0; + fLastSecondStart = -1; + fBytesDuringPreviousSecond = 0; + fPreviousSecondStart = -1; + fBytesDuringBucket = 0; + fBucketBegin = 0; + fBucketTimeAhead = 0; + fPreviousBucketTimeAhead = 0; + fOverbufferWindowBegin = -1; +} + +void RTPOverbufferWindow::AddPacketToWindow(SInt32 inPacketSize) +{ + fBytesDuringBucket += inPacketSize; + fBytesDuringLastSecond += inPacketSize; + fBytesSentSinceLastReport += inPacketSize; +} + +void RTPOverbufferWindow::EmptyOutWindow(const SInt64& inCurrentTime) +{ + // no longer needed +} + +void RTPOverbufferWindow::SetWindowSize(UInt32 inWindowSizeInBytes) +{ + fWindowSize = inWindowSizeInBytes; + fBytesSentSinceLastReport = 0; +} diff --git a/Server.tproj/RTPOverbufferWindow.h b/Server.tproj/RTPOverbufferWindow.h new file mode 100644 index 0000000..bb98437 --- /dev/null +++ b/Server.tproj/RTPOverbufferWindow.h @@ -0,0 +1,130 @@ +/* + * + * @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: RTPOverbufferWindow.h + + Contains: Class that tracks packets that are part of the "overbuffer". That is, + packets that are being sent ahead of time. This class can be used + to make sure the server isn't overflowing the client's overbuffer size. + + Written By: Denis Serenyi + +*/ + +#ifndef __RTP_OVERBUFFER_WINDOW_H__ +#define __RTP_OVERBUFFER_WINDOW_H__ + +#include "OSHeaders.h" + +class RTPOverbufferWindow +{ + public: + + RTPOverbufferWindow(UInt32 inSendInterval, UInt32 inInitialWindowSize, UInt32 inMaxSendAheadTimeInSec, + Float32 inOverbufferRate); + ~RTPOverbufferWindow() { } + + void ResetOverBufferWindow(); + + // + // ACCESSORS + + UInt32 GetSendInterval() { return fSendInterval; } + + // This may be negative! + SInt32 AvailableSpaceInWindow() { return fWindowSize - fBytesSentSinceLastReport; } + + + // + // The window size may be changed at any time + void SetWindowSize(UInt32 inWindowSizeInBytes); + + // + // Without changing the window size, you can enable / disable all overbuffering + // using these calls. Defaults to enabled + void TurnOffOverbuffering() { fOverbufferingEnabled = false; } + void TurnOnOverbuffering() { fOverbufferingEnabled = true; } + Bool16* OverbufferingEnabledPtr() { return &fOverbufferingEnabled; } + + // + // If the overbuffer window is full, this returns a time in the future when + // enough space will open up for this packet. Otherwise, returns -1. + // + // The overbuffer window is full if the byte count is filled up, or if the + // bitrate is above the max play rate. + SInt64 CheckTransmitTime(const SInt64& inTransmitTime, const SInt64& inCurrentTime, SInt32 inPacketSize); + + // + // Remembers that this packet has been sent + void AddPacketToWindow(SInt32 inPacketSize); + + // + // As time passes, transmit times that were in the future become transmit + // times that are in the past or present. Call this function to empty + // those old packets out of the window, freeing up space in the window. + void EmptyOutWindow(const SInt64& inCurrentTime); + + // + // MarkBeginningOfWriteBurst + // Call this on the first write of a write burst for a client. This + // allows the overbuffer window to track whether the bitrate of the movie + // is above the play rate. + void MarkBeginningOfWriteBurst() { fWriteBurstBeginning = true; } + + private: + + SInt32 fWindowSize; + SInt32 fBytesSentSinceLastReport; + SInt32 fSendInterval; + + SInt32 fBytesDuringLastSecond; + SInt64 fLastSecondStart; + + SInt32 fBytesDuringPreviousSecond; + SInt64 fPreviousSecondStart; + + SInt32 fBytesDuringBucket; + SInt64 fBucketBegin; + SInt64 fPreviousBucketBegin; + + SInt64 fBucketTimeAhead; + SInt64 fPreviousBucketTimeAhead; + + UInt32 fMaxSendAheadTime; + + Bool16 fWriteBurstBeginning; + Bool16 fOverbufferingEnabled; + + Float32 fOverbufferRate; + UInt32 fSendAheadDurationInMsec; + + SInt64 fOverbufferWindowBegin; + +}; + + +#endif // __RTP_OVERBUFFER_TRACKER_H__ + + diff --git a/Server.tproj/RTPPacketResender.cpp b/Server.tproj/RTPPacketResender.cpp new file mode 100644 index 0000000..4f01c7a --- /dev/null +++ b/Server.tproj/RTPPacketResender.cpp @@ -0,0 +1,557 @@ +/* + * + * @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: RTPPacketResender.cpp + + Contains: RTPPacketResender class to buffer and track re-transmits of RTP packets. + + +*/ + +#include + +#include "RTPPacketResender.h" +#include "RTPStream.h" +#include "atomic.h" +#include "OSMutex.h" + +#if RTP_PACKET_RESENDER_DEBUGGING +#include "QTSSRollingLog.h" +#include + +class MyAckListLog : public QTSSRollingLog +{ + public: + + MyAckListLog(char * logFName) : QTSSRollingLog() {this->SetTaskName("MyAckListLog"); ::strcpy( fLogFName, logFName ); } + virtual ~MyAckListLog() {} + + virtual char* GetLogName() + { + char *logFileNameStr = NEW char[80]; + + ::strcpy( logFileNameStr, fLogFName ); + return logFileNameStr; + } + + virtual char* GetLogDir() + { + char *logDirStr = NEW char[80]; + + ::strcpy( logDirStr, DEFAULTPATHS_LOG_DIR); + return logDirStr; + } + + virtual UInt32 GetRollIntervalInDays() { return 0; } + virtual UInt32 GetMaxLogBytes() { return 0; } + + char fLogFName[128]; + +}; +#endif + +static const UInt32 kPacketArrayIncreaseInterval = 32;// must be multiple of 2 +static const UInt32 kInitialPacketArraySize = 64;// must be multiple of kPacketArrayIncreaseInterval (Turns out this is as big as we typically need) +//static const UInt32 kMaxPacketArraySize = 512;// must be multiple of kPacketArrayIncreaseInterval it would have to be a 3 mbit or more + +static const UInt32 kMaxDataBufferSize = 1600; +OSBufferPool RTPPacketResender::sBufferPool(kMaxDataBufferSize); +unsigned int RTPPacketResender::sNumWastedBytes = 0; + +RTPPacketResender::RTPPacketResender() +: fBandwidthTracker(NULL), + fSocket(NULL), + fDestAddr(0), + fDestPort(0), + fMaxPacketsInList(0), + fPacketsInList(0), + fNumResends(0), + fNumExpired(0), + fNumAcksForMissingPackets(0), + fNumSent(0), + fPacketArray(NULL), + fPacketArraySize(kInitialPacketArraySize), + fPacketArrayMask(0), + fHighestSeqNum(0), + fLastUsed(0), + fPacketQMutex() +{ + fPacketArray = (RTPResenderEntry*) NEW char[sizeof(RTPResenderEntry) * fPacketArraySize]; + ::memset(fPacketArray,0,sizeof(RTPResenderEntry) * fPacketArraySize); + +} + +RTPPacketResender::~RTPPacketResender() +{ + for (UInt32 x = 0; x < fPacketArraySize; x++) + { + if (fPacketArray[x].fPacketSize > 0) + atomic_sub(&sNumWastedBytes, kMaxDataBufferSize - fPacketArray[x].fPacketSize); + if (fPacketArray[x].fPacketData != NULL) + { + if (fPacketArray[x].fIsSpecialBuffer) + delete [] (char*)fPacketArray[x].fPacketData; + else + sBufferPool.Put(fPacketArray[x].fPacketData); + } + } + + delete [] fPacketArray; + + +} + +#if RTP_PACKET_RESENDER_DEBUGGING +void RTPPacketResender::logprintf( const char * format, ... ) +{ + /* + WARNING - the logger is not multiple task thread safe. + its OK when we run just one thread for all of the + sending tasks though. + + each logger for a given session will open up access + to the same log file. with one thread we're serialized + on writing to the file, so it works. + */ + + va_list argptr; + char buff[1024]; + + + va_start( argptr, format ); + + vsprintf( buff, format, argptr ); + + va_end(argptr); + + if ( fLogger ) + { + fLogger->WriteToLog(buff, false); + qtss_printf( buff ); + } +} + + +void RTPPacketResender::SetDebugInfo(UInt32 trackID, UInt16 remoteRTCPPort, UInt32 curPacketDelay) +{ + fTrackID = trackID; + fRemoteRTCPPort = remoteRTCPPort; + fCurrentPacketDelay = curPacketDelay; +} + +void RTPPacketResender::SetLog( StrPtrLen *logname ) +{ + /* + WARNING - see logprintf() + */ + + char logFName[128]; + + memcpy( logFName, logname->Ptr, logname->Len ); + logFName[logname->Len] = 0; + + if ( fLogger ) + delete fLogger; + + fLogger = new MyAckListLog( logFName ); + + fLogger->EnableLog(); +} + +void RTPPacketResender::LogClose(SInt64 inTimeSpentInFlowControl) +{ + this->logprintf("Flow control duration msec: %"_64BITARG_"d. Max outstanding packets: %d\n", inTimeSpentInFlowControl, this->GetMaxPacketsInList()); + +} + + +UInt32 RTPPacketResender::SpillGuts(UInt32 inBytesSentThisInterval) +{ + if (fInfoDisplayTimer.DurationInMilliseconds() > 1000 ) + { + //fDisplayCount++; + + // spill our guts on the state of KRR + char *isFlowed = "open"; + + if ( fBandwidthTracker->IsFlowControlled() ) + isFlowed = "flowed"; + + SInt64 kiloBitperSecond = (( (SInt64)inBytesSentThisInterval * (SInt64)1000 * (SInt64)8 ) / fInfoDisplayTimer.DurationInMilliseconds() ) / (SInt64)1024; + + //fStreamCumDuration += fInfoDisplayTimer.DurationInMilliseconds(); + fInfoDisplayTimer.Reset(); + + //this->logprintf( "\n[%li] info for track %li, cur bytes %li, cur kbit/s %li\n", /*(SInt32)fStreamCumDuration,*/ fTrackID, (SInt32)inBytesSentThisInterval, (SInt32)kiloBitperSecond); + this->logprintf( "\nx info for track %li, cur bytes %li, cur kbit/s %li\n", /*(SInt32)fStreamCumDuration,*/ fTrackID, (SInt32)inBytesSentThisInterval, (SInt32)kiloBitperSecond); + this->logprintf( "stream is %s, bytes pending ack %li, cwnd %li, ssth %li, wind %li \n", isFlowed, fBandwidthTracker->BytesInList(), fBandwidthTracker->CongestionWindow(), fBandwidthTracker->SlowStartThreshold(), fBandwidthTracker->ClientWindowSize() ); + this->logprintf( "stats- resends: %li, expired: %li, dupe acks: %li, sent: %li\n", fNumResends, fNumExpired, fNumAcksForMissingPackets, fNumSent ); + this->logprintf( "delays- cur: %li, srtt: %li , dev: %li, rto: %li, bw: %li\n\n", fCurrentPacketDelay, fBandwidthTracker->RunningAverageMSecs(), fBandwidthTracker->RunningMeanDevationMSecs(), fBandwidthTracker->CurRetransmitTimeout(), fBandwidthTracker->GetCurrentBandwidthInBps()); + + + inBytesSentThisInterval = 0; + } + return inBytesSentThisInterval; +} + +#endif + + +void RTPPacketResender::SetDestination(UDPSocket* inOutputSocket, UInt32 inDestAddr, UInt16 inDestPort) +{ + fSocket = inOutputSocket; + fDestAddr = inDestAddr; + fDestPort = inDestPort; +} + +RTPResenderEntry* RTPPacketResender::GetEmptyEntry(UInt16 inSeqNum, UInt32 inPacketSize) +{ + + RTPResenderEntry* theEntry = NULL; + + for (UInt32 packetIndex = 0 ;packetIndex < fPacketsInList; packetIndex++) // see if packet is already in the array + { if (inSeqNum == fPacketArray[packetIndex].fSeqNum) + { return NULL; + } + } + + if (fPacketsInList == fPacketArraySize) // allocate a new array + { + fPacketArraySize += kPacketArrayIncreaseInterval; + RTPResenderEntry* tempArray = (RTPResenderEntry*) NEW char[sizeof(RTPResenderEntry) * fPacketArraySize]; + ::memset(tempArray,0,sizeof(RTPResenderEntry) * fPacketArraySize); + ::memcpy(tempArray,fPacketArray,sizeof(RTPResenderEntry) * fPacketsInList); + delete [] fPacketArray; + fPacketArray = tempArray; + //qtss_printf("NewArray size=%"_S32BITARG_" packetsInList=%"_S32BITARG_"\n",fPacketArraySize, fPacketsInList); + } + + if (fPacketsInList < fPacketArraySize) // have an open spot + { theEntry = &fPacketArray[fPacketsInList]; + fPacketsInList++; + + if (fPacketsInList < fPacketArraySize) + fLastUsed = fPacketsInList; + else + fLastUsed = fPacketArraySize; + } + else + { + // nothing open so re-use + if (fLastUsed < fPacketArraySize - 1) + fLastUsed ++; + else + fLastUsed = 0; + + //qtss_printf("array is full = %"_U32BITARG_" reusing index=%"_U32BITARG_"\n",fPacketsInList,fLastUsed); + theEntry = &fPacketArray[fLastUsed]; + RemovePacket(fLastUsed, false); // delete packet in place don't fill we will use the spot + } + + // + // Check to see if this packet is too big for the buffer. If it is, then + // we need to specially allocate a special buffer + if (inPacketSize > kMaxDataBufferSize) + { + //sBufferPool.Put(theEntry->fPacketData); + theEntry->fIsSpecialBuffer = true; + theEntry->fPacketData = NEW char[inPacketSize]; + } + else// It is not special, it's from the buffer pool + { theEntry->fIsSpecialBuffer = false; + theEntry->fPacketData = sBufferPool.Get(); + } + + + + return theEntry; +} + + +void RTPPacketResender::ClearOutstandingPackets() +{ + //OSMutexLocker packetQLocker(&fPacketQMutex); + //for (UInt16 packetIndex = 0; packetIndex < fPacketArraySize; packetIndex++) //Testing purposes + for (UInt16 packetIndex = 0; packetIndex < fPacketsInList; packetIndex++) + { + this->RemovePacket(packetIndex,false);// don't move packets delete in place + Assert(fPacketArray[packetIndex].fPacketSize==0); + } + if (fBandwidthTracker != NULL) + fBandwidthTracker->EmptyWindow(fBandwidthTracker->BytesInList()); //clean it out + fPacketsInList = 0; // deleting in place doesn't decrement + + Assert(fPacketsInList == 0); +} + +void RTPPacketResender::AddPacket( void * inRTPPacket, UInt32 packetSize, SInt32 ageLimit ) +{ + //OSMutexLocker packetQLocker(&fPacketQMutex); + // the caller needs to adjust the overall age limit by reducing it + // by the current packet lateness. + + // we compute a re-transmit timeout based on the Karns RTT esmitate + + UInt16* theSeqNumP = (UInt16*)inRTPPacket; + UInt16 theSeqNum = ntohs(theSeqNumP[1]); + + if ( ageLimit > 0 ) + { + RTPResenderEntry* theEntry = this->GetEmptyEntry(theSeqNum, packetSize); + + // + // This may happen if this sequence number has already been added. + // That may happen if we have repeat packets in the stream. + if (theEntry == NULL || theEntry->fPacketSize > 0) + return; + + // + // Reset all the information in the RTPResenderEntry + ::memcpy(theEntry->fPacketData, inRTPPacket, packetSize); + theEntry->fPacketSize = packetSize; + theEntry->fAddedTime = OS::Milliseconds(); + theEntry->fOrigRetransTimeout = fBandwidthTracker->CurRetransmitTimeout(); + theEntry->fExpireTime = theEntry->fAddedTime + ageLimit; + theEntry->fNumResends = 0; + theEntry->fSeqNum = theSeqNum; + + // + // Track the number of wasted bytes we have + atomic_add(&sNumWastedBytes, kMaxDataBufferSize - packetSize); + + //PLDoubleLinkedListNode * listNode = NEW PLDoubleLinkedListNode( new RTPResenderEntry(inRTPPacket, packetSize, ageLimit, fRTTEstimator.CurRetransmitTimeout() ) ); + //fAckList.AddNodeToTail(listNode); + fBandwidthTracker->FillWindow(packetSize); + } + else + { +#if RTP_PACKET_RESENDER_DEBUGGING + this->logprintf( "packet too old to add: seq# %li, age limit %li, cur late %li, track id %li\n", (SInt32)ntohs( *((UInt16*)(((char*)inRTPPacket)+2)) ), (SInt32)ageLimit, fCurrentPacketDelay, fTrackID ); +#endif + fNumExpired++; + } + fNumSent++; +} + +void RTPPacketResender::AckPacket( UInt16 inSeqNum, SInt64& inCurTimeInMsec ) +{ + //OSMutexLocker packetQLocker(&fPacketQMutex); + + SInt32 foundIndex = -1; + for (UInt32 packetIndex = 0 ;packetIndex < fPacketsInList; packetIndex++) + { if (inSeqNum == fPacketArray[packetIndex].fSeqNum) + { foundIndex = packetIndex; + break; + } + } + + RTPResenderEntry* theEntry = NULL; + if (foundIndex != -1) + theEntry = &fPacketArray[foundIndex]; + + + if (theEntry == NULL || theEntry->fPacketSize == 0 ) + { /* we got an ack for a packet that has already expired or + for a packet whose re-transmit crossed with it's original ack + + */ +#if RTP_PACKET_RESENDER_DEBUGGING + this->logprintf( "acked packet not found: %li, track id %li, OS::MSecs %li\n" + , (SInt32)inSeqNum, fTrackID, (SInt32)OS::Milliseconds() + ); +#endif + fNumAcksForMissingPackets++; + //qtss_printf("Ack for missing packet: %d\n", inSeqNum); + + // hmm.. we -should not have- closed down the window in this case + // so reopen it a bit as we normally would. + // ?? who know's what it really was, just use kMaximumSegmentSize + fBandwidthTracker->EmptyWindow( RTPBandwidthTracker::kMaximumSegmentSize, false ); + + // when we don't add an estimate from re-transmitted segments we're actually *underestimating* + // both the variation and srtt since we're throwing away ALL estimates above the current RTO! + // therefore it's difficult for us to rapidly adapt to increases in RTT, as well as RTT that + // are higher than our original RTO estimate. + + // for duplicate acks, use 1.5x the cur RTO as the RTT sample + //fRTTEstimator.AddToEstimate( fRTTEstimator.CurRetransmitTimeout() * 3 / 2 ); + /// this results in some very very big RTO's since the dupes come in batches of maybe 10 or more! + +// qtss_printf("Got ack for expired packet %d\n", inSeqNum); + } + else + { + +#if RTP_PACKET_RESENDER_DEBUGGING + Assert(inSeqNum == theEntry->fSeqNum); + this->logprintf( "Ack for packet: %li, track id %li, OS::MSecs %qd\n" + , (SInt32)inSeqNum, fTrackID, OS::Milliseconds() + ); +#endif + fBandwidthTracker->EmptyWindow(theEntry->fPacketSize); + if ( theEntry->fNumResends == 0 ) + { + // add RTT sample... + // only use rtt from packets acked after their initial send, do not use + // estimates gatherered from re-trasnmitted packets. + //fRTTEstimator.AddToEstimate( theEntry->fPacketRTTDuration.DurationInMilliseconds() ); + fBandwidthTracker->AddToRTTEstimate( (SInt32) ( inCurTimeInMsec - theEntry->fAddedTime ) ); + +// qtss_printf("Got ack for packet %d RTT = %qd\n", inSeqNum, inCurTimeInMsec - theEntry->fAddedTime); + } + else + { + #if RTP_PACKET_RESENDER_DEBUGGING + this->logprintf( "re-tx'd packet acked. ack num : %li, pack seq #: %li, num resends %li, track id %li, size %li, OS::MSecs %qd\n" \ + , (SInt32)inSeqNum, (SInt32)ntohs( *((UInt16*)(((char*)theEntry->fPacketData)+2)) ), (SInt32)theEntry->fNumResends + , (SInt32)fTrackID, theEntry->fPacketSize, OS::Milliseconds() ); + #endif + } + this->RemovePacket(foundIndex); + } +} + +void RTPPacketResender::RemovePacket(UInt32 packetIndex, Bool16 reuseIndex) +{ + //OSMutexLocker packetQLocker(&fPacketQMutex); + + Assert(packetIndex < fPacketArraySize); + if (packetIndex >= fPacketArraySize) + return; + + if (fPacketsInList == 0) + return; + + RTPResenderEntry* theEntry = &fPacketArray[packetIndex]; + if (theEntry->fPacketSize == 0) + return; + + // + // Track the number of wasted bytes we have + atomic_sub(&sNumWastedBytes, kMaxDataBufferSize - theEntry->fPacketSize); + Assert(theEntry->fPacketSize > 0); + + // + // Update our list information + Assert(fPacketsInList > 0); + + if (theEntry->fIsSpecialBuffer) + { delete [] (char*)theEntry->fPacketData; + } + else if (theEntry->fPacketData != NULL) + sBufferPool.Put(theEntry->fPacketData); + + + if (reuseIndex) // we are re-using the space so keep array contiguous + { + fPacketArray[packetIndex] = fPacketArray[fPacketsInList -1]; + ::memset(&fPacketArray[fPacketsInList -1],0,sizeof(RTPResenderEntry)); + fPacketsInList--; + + } + else // the array is full + { + fBandwidthTracker->EmptyWindow( theEntry->fPacketSize, false ); // keep window available + ::memset(theEntry,0,sizeof(RTPResenderEntry)); + } + +} + +void RTPPacketResender::ResendDueEntries() +{ + if (fPacketsInList <= 0) + return; + + //OSMutexLocker packetQLocker(&fPacketQMutex); + // + SInt32 numResends = 0; + RTPResenderEntry* theEntry = NULL; + SInt64 curTime = OS::Milliseconds(); + for (SInt32 packetIndex = fPacketsInList -1; packetIndex >= 0; packetIndex--) // walk backwards because remove packet moves array members forward + { + theEntry = &fPacketArray[packetIndex]; + + if (theEntry->fPacketSize == 0) + continue; + + if ((curTime - theEntry->fAddedTime) > fBandwidthTracker->CurRetransmitTimeout()) + { + // Change: Only expire packets after they were due to be resent. This gives the client + // a chance to ack them and improves congestion avoidance and RTT calculation + if (curTime > theEntry->fExpireTime) + { + #if RTP_PACKET_RESENDER_DEBUGGING + unsigned char version; + version = *((char*)theEntry->fPacketData); + version &= 0x84; // grab most sig 2 bits + version = version >> 6; // shift by 6 bits + this->logprintf( "expired: seq number %li, track id %li (port: %li), vers # %li, pack seq # %li, size: %li, OS::Msecs: %qd\n", \ + (SInt32)ntohs( *((UInt16*)(((char*)theEntry->fPacketData)+2)) ), fTrackID, (SInt32) ntohs(fDestPort), \ + (SInt32)version, (SInt32)ntohs( *((UInt16*)(((char*)theEntry->fPacketData)+2))), theEntry->fPacketSize, OS::Milliseconds() ); + #endif + // + // This packet is expired + fNumExpired++; + //qtss_printf("Packet expired: %d\n", ((UInt16*)thePacket)[1]); + fBandwidthTracker->EmptyWindow(theEntry->fPacketSize); + this->RemovePacket(packetIndex); +// qtss_printf("Expired packet %d\n", theEntry->fSeqNum); + continue; + } + + // Resend this packet + fSocket->SendTo(fDestAddr, fDestPort, theEntry->fPacketData, theEntry->fPacketSize); + //qtss_printf("Packet resent: %d\n", ((UInt16*)theEntry->fPacketData)[1]); + + theEntry->fNumResends++; + #if RTP_PACKET_RESENDER_DEBUGGING + this->logprintf( "re-sent: %li RTO %li, track id %li (port %li), size: %li, OS::Ms %qd\n", (SInt32)ntohs( *((UInt16*)(((char*)theEntry->fPacketData)+2)) ), curTime - theEntry->fAddedTime, \ + fTrackID, (SInt32) ntohs(fDestPort) \ + , theEntry->fPacketSize, OS::Milliseconds()); + #endif + + fNumResends++; + + numResends ++; + //qtss_printf("resend loop numResends=%"_S32BITARG_" packet theEntry->fNumResends=%"_S32BITARG_" stream fNumResends=\n",numResends,theEntry->fNumResends++, fNumResends); + + // ok -- lets try this.. add 1.5x of the INITIAL duration since the last send to the rto estimator + // since we won't get an ack on this packet + // this should keep us from exponentially increasing due o a one time increase + // in the actuall rtt, only AddToEstimate on the first resend ( assume that it's a dupe ) + // if it's not a dupe, but rather an actual loss, the subseqnuent actuals wil bring down the average quickly + + if ( theEntry->fNumResends == 1 ) + fBandwidthTracker->AddToRTTEstimate( (SInt32) ((theEntry->fOrigRetransTimeout * 3) / 2 )); + +// qtss_printf("Retransmitted packet %d\n", theEntry->fSeqNum); + theEntry->fAddedTime = curTime; + fBandwidthTracker->AdjustWindowForRetransmit(); + continue; + } + + } +} +void RTPPacketResender::RemovePacket(RTPResenderEntry* inEntry){ Assert(0); } diff --git a/Server.tproj/RTPPacketResender.h b/Server.tproj/RTPPacketResender.h new file mode 100644 index 0000000..35bcef2 --- /dev/null +++ b/Server.tproj/RTPPacketResender.h @@ -0,0 +1,169 @@ +/* + * + * @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: RTPPacketResender.h + + Contains: RTPPacketResender class to buffer and track re-transmits of RTP packets. + + the ctor copies the packet data, sets a timer for the packet's age limit and + another timer for it's possible re-transmission. + A duration timer is started to measure the RTT based on the client's ack. + +*/ + +#ifndef __RTP_PACKET_RESENDER_H__ +#define __RTP_PACKET_RESENDER_H__ + +#include "PLDoubleLinkedList.h" + +#include "RTPBandwidthTracker.h" +#include "DssStopwatch.h" +#include "UDPSocket.h" +#include "OSMemory.h" +#include "OSBufferPool.h" +#include "OSMutex.h" + +#define RTP_PACKET_RESENDER_DEBUGGING 0 + +class MyAckListLog; + +class RTPResenderEntry +{ + public: + + void* fPacketData; + UInt32 fPacketSize; + Bool16 fIsSpecialBuffer; + SInt64 fExpireTime; + SInt64 fAddedTime; + SInt64 fOrigRetransTimeout; + UInt32 fNumResends; + UInt16 fSeqNum; +#if RTP_PACKET_RESENDER_DEBUGGING + UInt32 fPacketArraySizeWhenAdded; +#endif +}; + + +class RTPPacketResender +{ + public: + + RTPPacketResender(); + ~RTPPacketResender(); + + // + // These must be called before using the object + void SetDestination(UDPSocket* inOutputSocket, UInt32 inDestAddr, UInt16 inDestPort); + void SetBandwidthTracker(RTPBandwidthTracker* inTracker) { fBandwidthTracker = inTracker; } + + // + // AddPacket adds a new packet to the resend queue. This will not send the packet. + // AddPacket itself is not thread safe. + void AddPacket( void * rtpPacket, UInt32 packetSize, SInt32 ageLimitInMsec ); + + // + // Acks a packet. Also not thread safe. + void AckPacket( UInt16 sequenceNumber, SInt64& inCurTimeInMsec ); + + // + // Resends outstanding packets in the queue. Guess what. Not thread safe. + void ResendDueEntries(); + + // + // Clear outstanding packets - if we no longer care about any of the + // outstanding, unacked packets + void ClearOutstandingPackets(); + + // + // ACCESSORS + Bool16 IsFlowControlled() { return fBandwidthTracker->IsFlowControlled(); } + SInt32 GetMaxPacketsInList() { return fMaxPacketsInList; } + SInt32 GetNumPacketsInList() { return fPacketsInList; } + SInt32 GetNumResends() { return fNumResends; } + + static UInt32 GetNumRetransmitBuffers() { return sBufferPool.GetTotalNumBuffers(); } + static UInt32 GetWastedBufferBytes() { return sNumWastedBytes; } + +#if RTP_PACKET_RESENDER_DEBUGGING + void SetDebugInfo(UInt32 trackID, UInt16 remoteRTCPPort, UInt32 curPacketDelay); + void SetLog( StrPtrLen *logname ); + UInt32 SpillGuts(UInt32 inBytesSentThisInterval); + void LogClose(SInt64 inTimeSpentInFlowControl); + void logprintf( const char * format, ... ); + +#else + void SetLog( StrPtrLen * /*logname*/) {} +#endif + + private: + + // Tracking the capacity of the network + RTPBandwidthTracker* fBandwidthTracker; + + // Who to send to + UDPSocket* fSocket; + UInt32 fDestAddr; + UInt16 fDestPort; + + UInt32 fMaxPacketsInList; + UInt32 fPacketsInList; + UInt32 fNumResends; // how many total retransmitted packets + UInt32 fNumExpired; // how many total packets dropped + UInt32 fNumAcksForMissingPackets; // how many acks received in the case where the packet was not in the list + UInt32 fNumSent; // how many packets sent + +#if RTP_PACKET_RESENDER_DEBUGGING + MyAckListLog *fLogger; + + UInt32 fTrackID; + UInt16 fRemoteRTCPPort; + UInt32 fCurrentPacketDelay; + DssDurationTimer fInfoDisplayTimer; +#endif + + RTPResenderEntry* fPacketArray; + UInt16 fStartSeqNum; + UInt32 fPacketArraySize; + UInt32 fPacketArrayMask; + UInt16 fHighestSeqNum; + UInt32 fLastUsed; + OSMutex fPacketQMutex; + + RTPResenderEntry* GetEntryByIndex(UInt16 inIndex); + RTPResenderEntry* GetEntryBySeqNum(UInt16 inSeqNum); + + RTPResenderEntry* GetEmptyEntry(UInt16 inSeqNum, UInt32 inPacketSize); + void ReallocatePacketArray(); + void RemovePacket(UInt32 packetIndex, Bool16 reuse=true); + void RemovePacket(RTPResenderEntry* inEntry); + + static OSBufferPool sBufferPool; + static unsigned int sNumWastedBytes; + + void UpdateCongestionWindow(SInt32 bytesToOpenBy ); +}; + +#endif //__RTP_PACKET_RESENDER_H__ diff --git a/Server.tproj/RTPSession.cpp b/Server.tproj/RTPSession.cpp new file mode 100644 index 0000000..bc5f181 --- /dev/null +++ b/Server.tproj/RTPSession.cpp @@ -0,0 +1,680 @@ +/* + * + * @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: RTPSession.cpp + + Contains: Implementation of RTPSession class. + + Change History (most recent first): + + + +*/ + +#include "RTPSession.h" + +#include "RTSPProtocol.h" +#include "QTSServerInterface.h" +#include "QTSS.h" +#include "RTSPRequest3GPP.h" + +#include "OS.h" +#include "OSMemory.h" + +#include + +#define RTPSESSION_DEBUGGING 0 + +RTPSession::RTPSession() : + RTPSessionInterface(), + fModule(NULL), + fHasAnRTPStream(false), + fCurrentModuleIndex(0), + fCurrentState(kStart), + fClosingReason(qtssCliSesCloseClientTeardown), + fCurrentModule(0), + fModuleDoingAsyncStuff(false), + fLastBandwidthTrackerStatsUpdate(0) +{ +#if DEBUG + fActivateCalled = false; +#endif + + this->SetTaskName("RTPSession"); + fModuleState.curModule = NULL; + fModuleState.curTask = this; + fModuleState.curRole = 0; +} + +RTPSession::~RTPSession() +{ + // Delete all the streams + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + if (QTSServerInterface::GetServer()->GetPrefs()->GetReliableUDPPrintfsEnabled()) + { + SInt32 theNumLatePacketsDropped = 0; + SInt32 theNumResends = 0; + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + Assert(theStream != NULL); + Assert(theLen == sizeof(RTPStream*)); + if (*theStream != NULL) + { + theNumLatePacketsDropped += (*theStream)->GetStalePacketsDropped(); + theNumResends += (*theStream)->GetResender()->GetNumResends(); + } + } + + char* theURL = NULL; + (void)this->GetValueAsString(qtssCliSesFullURL, 0, &theURL); + Assert(theURL != NULL); + + RTPBandwidthTracker* tracker = this->GetBandwidthTracker(); + + qtss_printf("Client complete. URL: %s.\n",theURL); + qtss_printf("Max congestion window: %"_S32BITARG_". Min congestion window: %"_S32BITARG_". Avg congestion window: %"_S32BITARG_"\n", tracker->GetMaxCongestionWindowSize(), tracker->GetMinCongestionWindowSize(), tracker->GetAvgCongestionWindowSize()); + qtss_printf("Max RTT: %"_S32BITARG_". Min RTT: %"_S32BITARG_". Avg RTT: %"_S32BITARG_"\n", tracker->GetMaxRTO(), tracker->GetMinRTO(), tracker->GetAvgRTO()); + qtss_printf("Num resends: %"_S32BITARG_". Num skipped frames: %"_S32BITARG_". Num late packets dropped: %"_S32BITARG_"\n", theNumResends, this->GetFramesSkipped(), theNumLatePacketsDropped); + + delete [] theURL; + } + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + Assert(theStream != NULL); + Assert(theLen == sizeof(RTPStream*)); + + if (*theStream != NULL) + delete *theStream; + } + + QTSServerInterface* theServer = QTSServerInterface::GetServer(); + + { + OSMutexLocker theLocker(theServer->GetMutex()); + + RTPSession** theSession = NULL; + // + // Remove this session from the qtssSvrClientSessions attribute + UInt32 y = 0; + for ( ; y < theServer->GetNumRTPSessions(); y++) + { + QTSS_Error theErr = theServer->GetValuePtr(qtssSvrClientSessions, y, (void**)&theSession, &theLen, true); + Assert(theErr == QTSS_NoErr); + + if (*theSession == this) + { + theErr = theServer->RemoveValue(qtssSvrClientSessions, y, QTSSDictionary::kDontObeyReadOnly); + break; + } + } + + Assert(y < theServer->GetNumRTPSessions()); + theServer->AlterCurrentRTPSessionCount(-1); + if (!fIsFirstPlay) // The session was started playing (the counter ignores additional pause-play changes while session is active) + theServer->AlterRTPPlayingSessions(-1); + + } + + //we better not be in the RTPSessionMap anymore! +#if DEBUG +/* does not compile??? + Assert(!fRTPMapElem.IsInTable()); + OSRef* theRef = QTSServerInterface::GetServer()->GetRTPSessionMap()->Resolve(&fRTSPSessionID); + Assert(theRef == NULL); +*/ +#endif +} + +QTSS_Error RTPSession::Activate(const StrPtrLen& inSessionID) +{ + //Set the session ID for this session + Assert(inSessionID.Len <= QTSS_MAX_SESSION_ID_LENGTH); + ::memcpy(fRTSPSessionIDBuf, inSessionID.Ptr, inSessionID.Len); + fRTSPSessionIDBuf[inSessionID.Len] = '\0'; + this->SetVal(qtssCliSesRTSPSessionID, &fRTSPSessionIDBuf[0], inSessionID.Len); + + fRTPMapElem.Set(*this->GetValue(qtssCliSesRTSPSessionID), this); + + QTSServerInterface* theServer = QTSServerInterface::GetServer(); + + //Activate puts the session into the RTPSession Map + QTSS_Error err = theServer->GetRTPSessionMap()->Register(&fRTPMapElem); + if (err == EPERM) + return err; + Assert(err == QTSS_NoErr); + + // + // Adding this session into the qtssSvrClientSessions attr and incrementing the number of sessions must be atomic + OSMutexLocker locker(theServer->GetMutex()); + + // + // Put this session into the qtssSvrClientSessions attribute of the server +#if DEBUG + Assert(theServer->GetNumValues(qtssSvrClientSessions) == theServer->GetNumRTPSessions()); +#endif + RTPSession* theSession = this; + err = theServer->SetValue(qtssSvrClientSessions, theServer->GetNumRTPSessions(), &theSession, sizeof(theSession), QTSSDictionary::kDontObeyReadOnly); + Assert(err == QTSS_NoErr); + +#if DEBUG + fActivateCalled = true; +#endif + QTSServerInterface::GetServer()->IncrementTotalRTPSessions(); + return QTSS_NoErr; +} + +RTPStream* RTPSession::FindRTPStreamForChannelNum(UInt8 inChannelNum) +{ + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + Assert(theStream != NULL); + Assert(theLen == sizeof(RTPStream*)); + + if (*theStream != NULL) + if (((*theStream)->GetRTPChannelNum() == inChannelNum) || ((*theStream)->GetRTCPChannelNum() == inChannelNum)) + return *theStream; + } + return NULL; // Couldn't find a matching stream +} + +QTSS_Error RTPSession::AddStream(RTSPRequestInterface* request, RTPStream** outStream, + QTSS_AddStreamFlags inFlags) +{ + Assert(outStream != NULL); + + // Create a new SSRC for this stream. This should just be a random number unique + // to all the streams in the session + UInt32 theSSRC = 0; + while (theSSRC == 0) + { + theSSRC = (SInt32)::rand(); + + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + Assert(theStream != NULL); + Assert(theLen == sizeof(RTPStream*)); + + if (*theStream != NULL) + if ((*theStream)->GetSSRC() == theSSRC) + theSSRC = 0; + } + } + + *outStream = NEW RTPStream(theSSRC, this); + + QTSS_Error theErr = (*outStream)->Setup(request, inFlags); + if (theErr != QTSS_NoErr) + // If we couldn't setup the stream, make sure not to leak memory! + delete *outStream; + else + { + // If the stream init succeeded, then put it into the array of setup streams + theErr = this->SetValue(qtssCliSesStreamObjects, this->GetNumValues(qtssCliSesStreamObjects), + outStream, sizeof(RTPStream*), QTSSDictionary::kDontObeyReadOnly); + Assert(theErr == QTSS_NoErr); + fHasAnRTPStream = true; + } + return theErr; +} + +void RTPSession::SetStreamThinningParams(Float32 inLateTolerance) +{ + // Set the thinning params in all the RTPStreams of the RTPSession + // Go through all the streams, setting their thinning params + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + Assert(theStream != NULL); + Assert(theLen == sizeof(RTPStream*)); + if (*theStream != NULL) + { + (*theStream)->SetLateTolerance(inLateTolerance); + (*theStream)->SetThinningParams(); + } + } +} + + +void RTPSession::Set3GPPRateAdaptionData(RateAdapationStreamDataFields *rateAdaptStreamData) +{ + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + if ((*theStream)->GetSDPStreamID() == rateAdaptStreamData->GetSDPStreamID()) + { + (*theStream)->SetRateAdaptData(rateAdaptStreamData); + break; + } + } +} + +void RTPSession::SetMovieBitRateData() +{ + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + //should eventually set the stream rate too. Use some gross amount right now. + UInt32 movieBitRate = GetMovieAvgBitrate(); + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + (*theStream)->SetBitRateData(movieBitRate); + } +} + + + +void RTPSession::Set3GPPLinkCharData(LinkCharDataFields *linkCharData) +{ + this->Get3GPPSessPtr()->SetLinkCharData(linkCharData); +} + + +QTSS_Error RTPSession::Play(RTSPRequestInterface* request, QTSS_PlayFlags inFlags) +{ + //first setup the play associated session interface variables + Assert(request != NULL); + if (fModule == NULL) + return QTSS_RequestFailed;//Can't play if there are no associated streams + + //what time is this play being issued at? + fLastBitRateUpdateTime = fNextSendPacketsTime = fPlayTime = OS::Milliseconds(); + if (fIsFirstPlay) + fFirstPlayTime = fPlayTime; + fAdjustedPlayTime = fPlayTime - ((SInt64)(request->GetStartTime() * 1000)); + + //for RTCP SRs, we also need to store the play time in NTP + fNTPPlayTime = OS::TimeMilli_To_1900Fixed64Secs(fPlayTime); + + //we are definitely playing now, so schedule the object! + fState = qtssPlayingState; + fIsFirstPlay = false; + fPlayFlags = inFlags; + QTSServerInterface::GetServer()-> AlterRTPPlayingSessions(1); + + UInt32 theWindowSize; + UInt32 bitRate = this->GetMovieAvgBitrate(); + if ((bitRate == 0) || (bitRate > QTSServerInterface::GetServer()->GetPrefs()->GetWindowSizeMaxThreshold() * 1024)) + theWindowSize = 1024 * QTSServerInterface::GetServer()->GetPrefs()->GetLargeWindowSizeInK(); + else if (bitRate > QTSServerInterface::GetServer()->GetPrefs()->GetWindowSizeThreshold() * 1024) + theWindowSize = 1024 * QTSServerInterface::GetServer()->GetPrefs()->GetMediumWindowSizeInK(); + else + theWindowSize = 1024 * QTSServerInterface::GetServer()->GetPrefs()->GetSmallWindowSizeInK(); + +// qtss_printf("bitrate = %d, window size = %d\n", bitRate, theWindowSize); + this->GetBandwidthTracker()->SetWindowSize(theWindowSize); + this->GetOverbufferWindow()->ResetOverBufferWindow(); + + // + // Go through all the streams, setting their thinning params + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + Assert(theStream != NULL); + Assert(theLen == sizeof(RTPStream*)); + if (*theStream != NULL) + { + (*theStream)->SetThinningParams(); + (*theStream)->ResetThinningDelayParams(); + // + // If we are using reliable UDP, then make sure to clear all the packets + // from the previous play spurt out of the resender + (*theStream)->GetResender()->ClearOutstandingPackets(); + } + } + +// qtss_printf("movie bitrate = %d, window size = %d\n", this->GetMovieAvgBitrate(), theWindowSize); + Assert(this->GetBandwidthTracker()->BytesInList() == 0); + + // Set the size of the RTSPSession's send buffer to an appropriate max size + // based on the bitrate of the movie. This has 2 benefits: + // 1) Each socket normally defaults to 32 K. A smaller buffer prevents the + // system from getting buffer starved if lots of clients get flow-controlled + // + // 2) We may need to scale up buffer sizes for high-bandwidth movies in order + // to maximize thruput, and we may need to scale down buffer sizes for low-bandwidth + // movies to prevent us from buffering lots of data that the client can't use + + // If we don't know any better, assume maximum buffer size. + QTSServerPrefs* thePrefs = QTSServerInterface::GetServer()->GetPrefs(); + UInt32 theBufferSize = thePrefs->GetMaxTCPBufferSizeInBytes(); + +#if RTPSESSION_DEBUGGING + qtss_printf("RTPSession GetMovieAvgBitrate %li\n",(SInt32)this->GetMovieAvgBitrate() ); +#endif + + if (this->GetMovieAvgBitrate() > 0) + { + // We have a bit rate... use it. + Float32 realBufferSize = (Float32)this->GetMovieAvgBitrate() * thePrefs->GetTCPSecondsToBuffer(); + theBufferSize = (UInt32)realBufferSize; + theBufferSize >>= 3; // Divide by 8 to convert from bits to bytes + + // Round down to the next lowest power of 2. + theBufferSize = this->PowerOf2Floor(theBufferSize); + + // This is how much data we should buffer based on the scaling factor... if it is + // lower than the min, raise to min + if (theBufferSize < thePrefs->GetMinTCPBufferSizeInBytes()) + theBufferSize = thePrefs->GetMinTCPBufferSizeInBytes(); + + // Same deal for max buffer size + if (theBufferSize > thePrefs->GetMaxTCPBufferSizeInBytes()) + theBufferSize = thePrefs->GetMaxTCPBufferSizeInBytes(); + + this->SetMovieBitRateData(); + } + + Assert(fRTSPSession != NULL); // can this ever happen? + if (fRTSPSession != NULL) + fRTSPSession->GetSocket()->SetSocketBufSize(theBufferSize); + + +#if RTPSESSION_DEBUGGING + qtss_printf("RTPSession %"_S32BITARG_": In Play, about to call Signal\n",(SInt32)this); +#endif + this->Signal(Task::kStartEvent); + + return QTSS_NoErr; +} + +void RTPSession::Pause() +{ + fState = qtssPausedState; + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + { + Assert(theStream != NULL); + Assert(theLen == sizeof(RTPStream*)); + (*theStream)->Pause(); + } +} + +UInt32 RTPSession::PowerOf2Floor(UInt32 inNumToFloor) +{ + UInt32 retVal = 0x10000000; + while (retVal > 0) + { + if (retVal & inNumToFloor) + return retVal; + else + retVal >>= 1; + } + return retVal; +} + +void RTPSession::Teardown() +{ + // To proffer a quick death of the RTSP session, let's disassociate + // ourselves with it right now. + + // Note that this function relies on the session mutex being grabbed, because + // this fRTSPSession pointer could otherwise be being used simultaneously by + // an RTP stream. + if (fRTSPSession != NULL) + fRTSPSession->DecrementObjectHolderCount(); + fRTSPSession = NULL; + fState = qtssPausedState; + this->Signal(Task::kKillEvent); +} + +void RTPSession::SendPlayResponse(RTSPRequestInterface* request, UInt32 inFlags) +{ + QTSS_RTSPHeader theHeader = qtssRTPInfoHeader; + + RTPStream** theStream = NULL; + UInt32 theLen = 0; + UInt32 valueCount = this->GetNumValues(qtssCliSesStreamObjects); + Bool16 lastValue = false; + for (UInt32 x = 0; x < valueCount; x++) + { + this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen); + Assert(theStream != NULL); + Assert(theLen == sizeof(RTPStream*)); + + if (*theStream != NULL) + { if (x == (valueCount -1)) + lastValue = true; + (*theStream)->AppendRTPInfo(theHeader, request, inFlags,lastValue); + theHeader = qtssSameAsLastHeader; + } + } + request->SendHeader(); +} + +void RTPSession::SendDescribeResponse(RTSPRequestInterface* inRequest) +{ + if (inRequest->GetStatus() == qtssRedirectNotModified) + { + (void)inRequest->SendHeader(); + return; + } + + // write date and expires + inRequest->AppendDateAndExpires(); + + //write content type header + static StrPtrLen sContentType("application/sdp"); + inRequest->AppendHeader(qtssContentTypeHeader, &sContentType); + + // write x-Accept-Retransmit header + static StrPtrLen sRetransmitProtocolName("our-retransmit"); + inRequest->AppendHeader(qtssXAcceptRetransmitHeader, &sRetransmitProtocolName); + + // write x-Accept-Dynamic-Rate header + static StrPtrLen dynamicRateEnabledStr("1"); + inRequest->AppendHeader(qtssXAcceptDynamicRateHeader, &dynamicRateEnabledStr); + + //write content base header + + inRequest->AppendContentBaseHeader(inRequest->GetValue(qtssRTSPReqAbsoluteURL)); + + //I believe the only error that can happen is if the client has disconnected. + //if that's the case, just ignore it, hopefully the calling module will detect + //this and return control back to the server ASAP + (void)inRequest->SendHeader(); +} + +void RTPSession::SendAnnounceResponse(RTSPRequestInterface* inRequest) +{ + // + // Currently, no need to do anything special for an announce response + (void)inRequest->SendHeader(); +} + +SInt64 RTPSession::Run() +{ +#if DEBUG + Assert(fActivateCalled); +#endif + EventFlags events = this->GetEvents(); + QTSS_RoleParams theParams; + theParams.clientSessionClosingParams.inClientSession = this; //every single role being invoked now has this + //as the first parameter + +#if RTPSESSION_DEBUGGING + qtss_printf("RTPSession %"_S32BITARG_": In Run. Events %"_S32BITARG_"\n",(SInt32)this, (SInt32)events); +#endif + // Some callbacks look for this struct in the thread object + OSThreadDataSetter theSetter(&fModuleState, NULL); + + //if we have been instructed to go away, then let's delete ourselves + if ((events & Task::kKillEvent) || (events & Task::kTimeoutEvent) || (fModuleDoingAsyncStuff)) + { + if (!fModuleDoingAsyncStuff) + { + if (events & Task::kTimeoutEvent) + fClosingReason = qtssCliSesCloseTimeout; + + //deletion is a bit complicated. For one thing, it must happen from within + //the Run function to ensure that we aren't getting events when we are deleting + //ourselves. We also need to make sure that we aren't getting RTSP requests + //(or, more accurately, that the stream object isn't being used by any other + //threads). We do this by first removing the session from the session map. + +#if RTPSESSION_DEBUGGING + qtss_printf("RTPSession %"_S32BITARG_": about to be killed. Eventmask = %"_S32BITARG_"\n",(SInt32)this, (SInt32)events); +#endif + // We cannot block waiting to UnRegister, because we have to + // give the RTSPSessionTask a chance to release the RTPSession. + OSRefTable* sessionTable = QTSServerInterface::GetServer()->GetRTPSessionMap(); + Assert(sessionTable != NULL); + if (!sessionTable->TryUnRegister(&fRTPMapElem)) + { + this->Signal(Task::kKillEvent);// So that we get back to this place in the code + return kCantGetMutexIdleTime; + } + + // The ClientSessionClosing role is allowed to do async stuff + fModuleState.curTask = this; + fModuleDoingAsyncStuff = true; // So that we know to jump back to the + fCurrentModule = 0; // right place in the code + + // Set the reason parameter + theParams.clientSessionClosingParams.inReason = fClosingReason; + + // If RTCP packets are being generated internally for this stream, + // Send a BYE now. + RTPStream** theStream = NULL; + UInt32 theLen = 0; + + if (this->GetPlayFlags() & qtssPlayFlagsSendRTCP) + { + SInt64 byePacketTime = OS::Milliseconds(); + for (int x = 0; this->GetValuePtr(qtssCliSesStreamObjects, x, (void**)&theStream, &theLen) == QTSS_NoErr; x++) + if (theStream && *theStream != NULL) + (*theStream)->SendRTCPSR(byePacketTime, true); + } + } + + //at this point, we know no one is using this session, so invoke the + //session cleanup role. We don't need to grab the session mutex before + //invoking modules here, because the session is unregistered and + //therefore there's no way another thread could get involved anyway + + UInt32 numModules = QTSServerInterface::GetNumModulesInRole(QTSSModule::kClientSessionClosingRole); + { + for (; fCurrentModule < numModules; fCurrentModule++) + { + fModuleState.eventRequested = false; + fModuleState.idleTime = 0; + QTSSModule* theModule = QTSServerInterface::GetModule(QTSSModule::kClientSessionClosingRole, fCurrentModule); + (void)theModule->CallDispatch(QTSS_ClientSessionClosing_Role, &theParams); + + // If this module has requested an event, return and wait for the event to transpire + if (fModuleState.eventRequested) + return fModuleState.idleTime; // If the module has requested idle time... + } + } + + return -1;//doing this will cause the destructor to get called. + } + + //if the stream is currently paused, just return without doing anything. + //We'll get woken up again when a play is issued + if ((fState == qtssPausedState) || (fModule == NULL)) + return 0; + + //Make sure to grab the session mutex here, to protect the module against + //RTSP requests coming in while it's sending packets + { + OSMutexLocker locker(&fSessionMutex); + + //just make sure we haven't been scheduled before our scheduled play + //time. If so, reschedule ourselves for the proper time. (if client + //sends a play while we are already playing, this may occur) + theParams.rtpSendPacketsParams.inCurrentTime = OS::Milliseconds(); + if (fNextSendPacketsTime > theParams.rtpSendPacketsParams.inCurrentTime) + { + RTPStream** retransStream = NULL; + UInt32 retransStreamLen = 0; + + // + // Send retransmits if we need to + for (int streamIter = 0; this->GetValuePtr(qtssCliSesStreamObjects, streamIter, (void**)&retransStream, &retransStreamLen) == QTSS_NoErr; streamIter++) + if (retransStream && *retransStream) + (*retransStream)->SendRetransmits(); + + theParams.rtpSendPacketsParams.outNextPacketTime = fNextSendPacketsTime - theParams.rtpSendPacketsParams.inCurrentTime; + } + else + { + #if RTPSESSION_DEBUGGING + qtss_printf("RTPSession %"_S32BITARG_": about to call SendPackets\n",(SInt32)this); + #endif + if ((theParams.rtpSendPacketsParams.inCurrentTime - fLastBandwidthTrackerStatsUpdate) > 1000) + this->GetBandwidthTracker()->UpdateStats(); + + theParams.rtpSendPacketsParams.outNextPacketTime = 0; + // Async event registration is definitely allowed from this role. + fModuleState.eventRequested = false; + Assert(fModule != NULL); + (void)fModule->CallDispatch(QTSS_RTPSendPackets_Role, &theParams); + #if RTPSESSION_DEBUGGING + qtss_printf("RTPSession %"_S32BITARG_": back from sendPackets, nextPacketTime = %"_64BITARG_"d\n",(SInt32)this, theParams.rtpSendPacketsParams.outNextPacketTime); + #endif + //make sure not to get deleted accidently! + if (theParams.rtpSendPacketsParams.outNextPacketTime < 0) + theParams.rtpSendPacketsParams.outNextPacketTime = 0; + fNextSendPacketsTime = theParams.rtpSendPacketsParams.inCurrentTime + theParams.rtpSendPacketsParams.outNextPacketTime; + } + + } + + // + // Make sure the duration between calls to Run() isn't greater than the + // max retransmit delay interval. + UInt32 theRetransDelayInMsec = QTSServerInterface::GetServer()->GetPrefs()->GetMaxRetransmitDelayInMsec(); + UInt32 theSendInterval = QTSServerInterface::GetServer()->GetPrefs()->GetSendIntervalInMsec(); + + // + // We want to avoid waking up to do retransmits, and then going back to sleep for like, 1 msec. So, + // only adjust the time to wake up if the next packet time is greater than the max retransmit delay + + // the standard interval between wakeups. + if (theParams.rtpSendPacketsParams.outNextPacketTime > (theRetransDelayInMsec + theSendInterval)) + theParams.rtpSendPacketsParams.outNextPacketTime = theRetransDelayInMsec; + + Assert(theParams.rtpSendPacketsParams.outNextPacketTime >= 0);//we'd better not get deleted accidently! + return theParams.rtpSendPacketsParams.outNextPacketTime; +} + diff --git a/Server.tproj/RTPSession.h b/Server.tproj/RTPSession.h new file mode 100644 index 0000000..6288110 --- /dev/null +++ b/Server.tproj/RTPSession.h @@ -0,0 +1,172 @@ +/* + * + * @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: RTPSession.h + + Contains: RTPSession represents an, well, an RTP session. The server creates + one of these when a new client connects, and it lives for the duration + of an RTP presentation. It contains all the resources associated with + that presentation, such as RTPStream objects. It also provides an + API for manipulating a session (playing it, pausing it, stopping it, etc) + + It is also the active element, ie. it is the object that gets scheduled + to send out & receive RTP & RTCP packets + + Change History (most recent first): + + + + +*/ + + +#ifndef _RTPSESSION_H_ +#define _RTPSESSION_H_ + +#include "RTPSessionInterface.h" +#include "RTSPRequestInterface.h" +#include "RTPStream.h" +#include "QTSSModule.h" + + +class RTPSession : public RTPSessionInterface +{ + public: + + RTPSession(); + virtual ~RTPSession(); + + // + //ACCESS FUNCTIONS + + QTSSModule* GetPacketSendingModule() { return fModule; } + Bool16 HasAnRTPStream() { return fHasAnRTPStream; } + + RTPStream* FindRTPStreamForChannelNum(UInt8 inChannelNum); + + // + // MODIFIERS + + //This puts this session into the session map (called by the server! not the module!) + //If this function fails (it can return QTSS_DupName), it means that there is already + //a session with this session ID in the map. + QTSS_Error Activate(const StrPtrLen& inSessionID); + + // The way this object is implemented currently, only one module can send the + // packets for a session. + void SetPacketSendingModule(QTSSModule* inModule) { fModule = inModule; } + + //Once the session is bound, a module can add streams to it. + //It must pass in a trackID that uniquely identifies this stream. + //This call can only be made during an RTSP Setup request, and the + //RTSPRequestInterface must be provided. + //You may also opt to attach a codec name and type to this stream. + QTSS_Error AddStream( RTSPRequestInterface* request, RTPStream** outStream, + QTSS_AddStreamFlags inFlags); + + //Reset the thinning params for all streams using the late tolerance value + void SetStreamThinningParams(Float32 inLateTolerance); + + //Find the appropriate stream and set the rate adaptation stream data + void Set3GPPRateAdaptionData(RateAdapationStreamDataFields *rateAdaptStreamData); + + // Store the Session Interface's Link Char values + void Set3GPPLinkCharData(LinkCharDataFields *linkCharData); + + //Begins playing all streams. Currently must be associated with an RTSP Play + //request, and the request interface must be provided. + QTSS_Error Play(RTSPRequestInterface* request, QTSS_PlayFlags inFlags); + + //Pauses all streams. + void Pause(); + + // Tears down the session. This will cause QTSS_SessionClosing_Role to run + void Teardown(); + + //Utility functions. Modules aren't required to use these, but can be useful + void SendDescribeResponse(RTSPRequestInterface* request); + void SendAnnounceResponse(RTSPRequestInterface* request); + void SendPlayResponse(RTSPRequestInterface* request, UInt32 inFlags); + void SendPauseResponse(RTSPRequestInterface* request) + { request->SendHeader(); } + void SendTeardownResponse(RTSPRequestInterface* request) + { request->SetKeepAlive(false); request->SendHeader(); } + + SInt32 GetQualityLevel(); + void SetQualityLevel(SInt32 level); + + void SetMovieBitRateData(); + + private: + + //where timeouts, deletion conditions get processed + virtual SInt64 Run(); + + // Utility function used by Play + UInt32 PowerOf2Floor(UInt32 inNumToFloor); + + //overbuffer logging function + void LogOverbufferStats(); + + enum + { + kRTPStreamArraySize = 20, + kCantGetMutexIdleTime = 10 + }; + + QTSSModule* fModule; + Bool16 fHasAnRTPStream; + SInt32 fSessionQualityLevel; + + char fRTPStreamArray[kRTPStreamArraySize]; + + // Module invocation and module state. + // This info keeps track of our current state so that + // the state machine works properly. + enum + { + kStart = 0, + kSendingPackets = 1 + }; + + UInt32 fCurrentModuleIndex; + UInt32 fCurrentState; + + QTSS_ModuleState fModuleState; + QTSS_CliSesClosingReason fClosingReason; + + UInt32 fCurrentModule; + // This is here to give the ability to the ClientSessionClosing role to + // do asynchronous I/O + Bool16 fModuleDoingAsyncStuff; + +#if DEBUG + Bool16 fActivateCalled; +#endif + SInt64 fLastBandwidthTrackerStatsUpdate; + +}; + +#endif //_RTPSESSION_H_ diff --git a/Server.tproj/RTPSession3GPP.cpp b/Server.tproj/RTPSession3GPP.cpp new file mode 100644 index 0000000..1aa58f0 --- /dev/null +++ b/Server.tproj/RTPSession3GPP.cpp @@ -0,0 +1,98 @@ +/* + * + * @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: RTPSession.cpp + + Contains: Implementation of RTPSession class. + + Change History (most recent first): + + + +*/ + +#include "RTPSession3GPP.h" + +#include "RTSPProtocol.h" +#include "QTSServerInterface.h" +#include "QTSS.h" + +#include "OS.h" +#include "OSMemory.h" + +#include + +#define RTPSESSION_DEBUGGING 0 + +QTSSAttrInfoDict::AttrInfo RTPSession3GPP::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtss3GPPCliSesEnabled", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtss3GPPCliSesLinkCharGuaranteedBitRate", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 2 */ { "qtss3GPPCliSesLinkCharMaxBitRate", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 3 */ { "qtss3GPPCliSesLinkCharMaxTransferDelayMilliSec", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 4 */ { "qtss3GPPCliSesLinkCharURL", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe } + +}; + + +void RTPSession3GPP::Initialize() +{ + for (int x = 0; x < qtss3GPPCliSesNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::k3GPPClientSessionDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); +} + + +RTPSession3GPP::RTPSession3GPP(Bool16 enabled) +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::k3GPPClientSessionDictIndex), NULL), + fEnabled (enabled) +{ + this->SetVal(qtss3GPPCliSesEnabled, &fEnabled, sizeof(fEnabled)); + + this->SetVal(qtss3GPPCliSesLinkCharGuaranteedBitRate, &fLinkCharGuarntdKBitsPerSec, sizeof(fLinkCharGuarntdKBitsPerSec)); + this->SetVal(qtss3GPPCliSesLinkCharMaxBitRate, &fLinkCharMaxKBitsPerSec, sizeof(fLinkCharMaxKBitsPerSec)); + this->SetVal(qtss3GPPCliSesLinkCharMaxTransferDelayMilliSec, &fLinkCharMaxTransferDelayMilliSec, sizeof(fLinkCharMaxTransferDelayMilliSec)); + this->SetVal(qtss3GPPCliSesLinkCharURL, fURL, sizeof(fURL)); + fURL[0] =0; +} + + +void RTPSession3GPP::SetLinkCharData(LinkCharDataFields* linkCharDataPtr) +{ + if (NULL == linkCharDataPtr) + return; + + this->SetGKbits(linkCharDataPtr->GetGKbits()); + this->SetMaxKbits(linkCharDataPtr->GetMaxKBits()); + this->SetMaxDelayMilliSecs(linkCharDataPtr->GetMaxDelayMilliSecs()); + this->SetURLCopy(linkCharDataPtr->GetURL()); + +} + + +RTPSession3GPP::~RTPSession3GPP() +{ +} diff --git a/Server.tproj/RTPSession3GPP.h b/Server.tproj/RTPSession3GPP.h new file mode 100644 index 0000000..6612959 --- /dev/null +++ b/Server.tproj/RTPSession3GPP.h @@ -0,0 +1,79 @@ +/* + * + * @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: RTPSession3GPP.h + + Contains: RTPSession3GPP represents the 3gpp specific support for an RTP session. The server creates + one of these when a new client connects. + + Change History (most recent first): + + + + +*/ + + +#ifndef _RTPSESSION3GPP_H_ +#define _RTPSESSION3GPP_H_ + +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "RTSPRequest3GPP.h" + + +class RTPSession3GPP : public QTSSDictionary +{ + public: + + //Initialize + //Call initialize before instantiating this class: see QTSServer.cpp. + static void Initialize(); + + RTPSession3GPP(Bool16 enabled); + virtual ~RTPSession3GPP(); + + void SetLinkCharData(LinkCharDataFields* linkCharDataPtr); + void SetGKbits(UInt32 gKBits) { fLinkCharGuarntdKBitsPerSec = gKBits; } + void SetMaxKbits(UInt32 maxKbits) { fLinkCharMaxKBitsPerSec = maxKbits; } + void SetMaxDelayMilliSecs(UInt32 maxDelayMilliSecs) { fLinkCharMaxTransferDelayMilliSec = maxDelayMilliSecs; } + void SetURLCopy(StrPtrLen* urlPtr) { if (urlPtr) (void) this->SetValue(qtss3GPPCliSesLinkCharURL, 0, urlPtr->Ptr , urlPtr->Len, QTSSDictionary::kDontObeyReadOnly);} + UInt32 GetLinkCharMaxKBits() { return fLinkCharMaxKBitsPerSec; } + + private: + + Bool16 fEnabled; + char fURL[128]; //will be resized as needed + UInt32 fLinkCharGuarntdKBitsPerSec; + UInt32 fLinkCharMaxKBitsPerSec; + UInt32 fLinkCharMaxTransferDelayMilliSec; + + + //Dictionary support + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + +}; + +#endif //_RTPSESSION_H_ diff --git a/Server.tproj/RTPSessionInterface.cpp b/Server.tproj/RTPSessionInterface.cpp new file mode 100644 index 0000000..10c3ba6 --- /dev/null +++ b/Server.tproj/RTPSessionInterface.cpp @@ -0,0 +1,463 @@ +/* + * + * @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: RTPSessionInterface.h + + Contains: Implementation of object defined in .h + + + + + +*/ + +#include "RTPSessionInterface.h" +#include "QTSServerInterface.h" +#include "RTSPRequestInterface.h" +#include "QTSS.h" +#include "OS.h" +#include "RTPStream.h" +#include "md5.h" +#include "md5digest.h" +#include "base64.h" + +unsigned int RTPSessionInterface::sRTPSessionIDCounter = 0; + + +QTSSAttrInfoDict::AttrInfo RTPSessionInterface::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssCliSesStreamObjects", NULL, qtssAttrDataTypeQTSS_Object, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtssCliSesCreateTimeInMsec", NULL, qtssAttrDataTypeTimeVal, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 2 */ { "qtssCliSesFirstPlayTimeInMsec", NULL, qtssAttrDataTypeTimeVal, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 3 */ { "qtssCliSesPlayTimeInMsec", NULL, qtssAttrDataTypeTimeVal, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 4 */ { "qtssCliSesAdjustedPlayTimeInMsec", NULL, qtssAttrDataTypeTimeVal, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 5 */ { "qtssCliSesRTPBytesSent", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 6 */ { "qtssCliSesRTPPacketsSent", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 7 */ { "qtssCliSesState", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 8 */ { "qtssCliSesPresentationURL", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 9 */ { "qtssCliSesFirstUserAgent", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 10 */ { "qtssCliStrMovieDurationInSecs", NULL, qtssAttrDataTypeFloat64, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 11 */ { "qtssCliStrMovieSizeInBytes", NULL, qtssAttrDataTypeUInt64, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 12 */ { "qtssCliSesMovieAverageBitRate", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 13 */ { "qtssCliSesLastRTSPSession", NULL, qtssAttrDataTypeQTSS_Object, qtssAttrModeRead | qtssAttrModePreempSafe } , + /* 14 */ { "qtssCliSesFullURL", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe } , + /* 15 */ { "qtssCliSesHostName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + + /* 16 */ { "qtssCliRTSPSessRemoteAddrStr", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 17 */ { "qtssCliRTSPSessLocalDNS", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 18 */ { "qtssCliRTSPSessLocalAddrStr", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 19 */ { "qtssCliRTSPSesUserName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 20 */ { "qtssCliRTSPSesUserPassword", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 21 */ { "qtssCliRTSPSesURLRealm", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 22 */ { "qtssCliRTSPReqRealStatusCode", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 23 */ { "qtssCliTeardownReason", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 24 */ { "qtssCliSesReqQueryString", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 25 */ { "qtssCliRTSPReqRespMsg", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + + /* 26 */ { "qtssCliSesCurrentBitRate", CurrentBitRate, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 27 */ { "qtssCliSesPacketLossPercent", PacketLossPercent, qtssAttrDataTypeFloat32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 28 */ { "qtssCliSesTimeConnectedinMsec", TimeConnected, qtssAttrDataTypeSInt64, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 29 */ { "qtssCliSesCounterID", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 30 */ { "qtssCliSesRTSPSessionID", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 31 */ { "qtssCliSesFramesSkipped", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 32 */ { "qtssCliSesTimeoutMsec", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 33 */ { "qtssCliSesOverBufferEnabled", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 34 */ { "qtssCliSesRTCPPacketsRecv", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 35 */ { "qtssCliSesRTCPBytesRecv", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 36 */ { "qtssCliSesStartedThinning", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 37 */ { "qtssCliSes3GPPObject", NULL, qtssAttrDataTypeQTSS_Object, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 38 */ { "qtssCliSessLastRTSPBandwidth", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 39 */ { "qtssCliSessIs3GPPSession", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe } + + +}; + +void RTPSessionInterface::Initialize() +{ + for (UInt32 x = 0; x < qtssCliSesNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kClientSessionDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); +} + +RTPSessionInterface::RTPSessionInterface() +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kClientSessionDictIndex), NULL), + Task(), + fLastQualityCheckTime(0), + fLastQualityCheckMediaTime(0), + fStartedThinning(false), + fIsFirstPlay(true), + fAllTracksInterleaved(true), // assume true until proven false! + fFirstPlayTime(0), + fPlayTime(0), + fAdjustedPlayTime(0), + fNTPPlayTime(0), + fNextSendPacketsTime(0), + fSessionQualityLevel(0), + fState(qtssPausedState), + fPlayFlags(0), + fLastBitRateBytes(0), + fLastBitRateUpdateTime(0), + fMovieCurrentBitRate(0), + fRTSPSession(NULL), + fLastRTSPReqRealStatusCode(200), + fTimeoutTask(NULL, QTSServerInterface::GetServer()->GetPrefs()->GetRTPTimeoutInSecs() * 1000), + fNumQualityLevels(0), + fBytesSent(0), + fPacketsSent(0), + fPacketLossPercent(0.0), + fTimeConnected(0), + fTotalRTCPPacketsRecv(0), + fTotalRTCPBytesRecv(0), + fMovieDuration(0), + fMovieSizeInBytes(0), + fMovieAverageBitRate(0), + fTeardownReason(0), + fUniqueID(0), + fTracker(QTSServerInterface::GetServer()->GetPrefs()->IsSlowStartEnabled()), + fOverbufferWindow(QTSServerInterface::GetServer()->GetPrefs()->GetSendIntervalInMsec(),kUInt32_Max, QTSServerInterface::GetServer()->GetPrefs()->GetMaxSendAheadTimeInSecs(), +QTSServerInterface::GetServer()->GetPrefs()->GetOverbufferRate()), + fAuthScheme(QTSServerInterface::GetServer()->GetPrefs()->GetAuthScheme()), + fAuthQop(RTSPSessionInterface::kNoQop), + fAuthNonceCount(0), + fFramesSkipped(0), + fRTPSession3GPP(QTSServerInterface::GetServer()->GetPrefs()->Get3GPPEnabled() ), + fRTPSession3GPPPtr(&fRTPSession3GPP), + fLastRTSPBandwidthHeaderBits(0), + fIs3GPPSession(false) +{ + //don't actually setup the fTimeoutTask until the session has been bound! + //(we don't want to get timeouts before the session gets bound) + + fTimeoutTask.SetTask(this); + fTimeout = QTSServerInterface::GetServer()->GetPrefs()->GetRTPTimeoutInSecs() * 1000; + fUniqueID = (UInt32)atomic_add(&sRTPSessionIDCounter, 1); + + // fQualityUpdate is a counter the starting value is the unique ID so every session starts at a different position + fQualityUpdate = fUniqueID; + + //mark the session create time + fSessionCreateTime = OS::Milliseconds(); + + // Setup all dictionary attribute values + + // Make sure the dictionary knows about our preallocated memory for the RTP stream array + this->SetEmptyVal(qtssCliSesFirstUserAgent, &fUserAgentBuffer[0], kUserAgentBufSize); + this->SetEmptyVal(qtssCliSesStreamObjects, &fStreamBuffer[0], kStreamBufSize); + this->SetEmptyVal(qtssCliSesPresentationURL, &fPresentationURL[0], kPresentationURLSize); + this->SetEmptyVal(qtssCliSesFullURL, &fFullRequestURL[0], kRequestHostNameBufferSize); + this->SetEmptyVal(qtssCliSesHostName, &fRequestHostName[0], kFullRequestURLBufferSize); + + this->SetVal(qtssCliSesCreateTimeInMsec, &fSessionCreateTime, sizeof(fSessionCreateTime)); + this->SetVal(qtssCliSesFirstPlayTimeInMsec, &fFirstPlayTime, sizeof(fFirstPlayTime)); + this->SetVal(qtssCliSesPlayTimeInMsec, &fPlayTime, sizeof(fPlayTime)); + this->SetVal(qtssCliSesAdjustedPlayTimeInMsec, &fAdjustedPlayTime, sizeof(fAdjustedPlayTime)); + this->SetVal(qtssCliSesRTPBytesSent, &fBytesSent, sizeof(fBytesSent)); + this->SetVal(qtssCliSesRTPPacketsSent, &fPacketsSent, sizeof(fPacketsSent)); + this->SetVal(qtssCliSesState, &fState, sizeof(fState)); + this->SetVal(qtssCliSesMovieDurationInSecs, &fMovieDuration, sizeof(fMovieDuration)); + this->SetVal(qtssCliSesMovieSizeInBytes, &fMovieSizeInBytes, sizeof(fMovieSizeInBytes)); + this->SetVal(qtssCliSesLastRTSPSession, &fRTSPSession, sizeof(fRTSPSession)); + this->SetVal(qtssCliSesMovieAverageBitRate, &fMovieAverageBitRate, sizeof(fMovieAverageBitRate)); + this->SetEmptyVal(qtssCliRTSPSessRemoteAddrStr, &fRTSPSessRemoteAddrStr[0], kIPAddrStrBufSize ); + this->SetEmptyVal(qtssCliRTSPSessLocalDNS, &fRTSPSessLocalDNS[0], kLocalDNSBufSize); + this->SetEmptyVal(qtssCliRTSPSessLocalAddrStr, &fRTSPSessLocalAddrStr[0], kIPAddrStrBufSize); + + this->SetEmptyVal(qtssCliRTSPSesUserName, &fUserNameBuf[0],RTSPSessionInterface::kMaxUserNameLen); + this->SetEmptyVal(qtssCliRTSPSesUserPassword, &fUserPasswordBuf[0], RTSPSessionInterface::kMaxUserPasswordLen); + this->SetEmptyVal(qtssCliRTSPSesURLRealm, &fUserRealmBuf[0], RTSPSessionInterface::kMaxUserRealmLen); + + this->SetVal(qtssCliRTSPReqRealStatusCode, &fLastRTSPReqRealStatusCode, sizeof(fLastRTSPReqRealStatusCode)); + + this->SetVal(qtssCliTeardownReason, &fTeardownReason, sizeof(fTeardownReason)); + // this->SetVal(qtssCliSesCurrentBitRate, &fMovieCurrentBitRate, sizeof(fMovieCurrentBitRate)); + this->SetVal(qtssCliSesCounterID, &fUniqueID, sizeof(fUniqueID)); + this->SetEmptyVal(qtssCliSesRTSPSessionID, &fRTSPSessionIDBuf[0], QTSS_MAX_SESSION_ID_LENGTH + 4); + this->SetVal(qtssCliSesFramesSkipped, &fFramesSkipped, sizeof(fFramesSkipped)); + this->SetVal(qtssCliSesRTCPPacketsRecv, &fTotalRTCPPacketsRecv, sizeof(fTotalRTCPPacketsRecv)); + this->SetVal(qtssCliSesRTCPBytesRecv, &fTotalRTCPBytesRecv, sizeof(fTotalRTCPBytesRecv)); + + this->SetVal(qtssCliSesTimeoutMsec, &fTimeout, sizeof(fTimeout)); + + this->SetVal(qtssCliSesOverBufferEnabled, this->GetOverbufferWindow()->OverbufferingEnabledPtr(), sizeof(Bool16)); + this->SetVal(qtssCliSesStartedThinning, &fStartedThinning, sizeof(Bool16)); + this->SetVal(qtssCliSes3GPPObject, &fRTPSession3GPPPtr, sizeof(fRTPSession3GPPPtr)); + + this->SetVal(qtssCliSessLastRTSPBandwidth, &fLastRTSPBandwidthHeaderBits, sizeof(fLastRTSPBandwidthHeaderBits)); + this->SetVal(qtssCliSessIs3GPPSession, &fIs3GPPSession, sizeof(fIs3GPPSession)); + + +} + +void RTPSessionInterface::SetValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap, + UInt32 inValueIndex, void* inNewValue, UInt32 inNewValueLen) +{ + if (inAttrIndex == qtssCliSesTimeoutMsec) + { + Assert(inNewValueLen == sizeof(UInt32)); + UInt32 newTimeOut = *((UInt32 *) inNewValue); + fTimeoutTask.SetTimeout((SInt64) newTimeOut); + } +} + +void RTPSessionInterface::UpdateRTSPSession(RTSPSessionInterface* inNewRTSPSession) +{ + if (inNewRTSPSession != fRTSPSession) + { + // If there was an old session, let it know that we are done + if (fRTSPSession != NULL) + fRTSPSession->DecrementObjectHolderCount(); + + // Increment this count to prevent the RTSP session from being deleted + fRTSPSession = inNewRTSPSession; + fRTSPSession->IncrementObjectHolderCount(); + } +} + +char* RTPSessionInterface::GetSRBuffer(UInt32 inSRLen) +{ + if (fSRBuffer.Len < inSRLen) + { + delete [] fSRBuffer.Ptr; + fSRBuffer.Set(NEW char[2*inSRLen], 2*inSRLen); + } + return fSRBuffer.Ptr; +} + +QTSS_Error RTPSessionInterface::DoSessionSetupResponse(RTSPRequestInterface* inRequest) +{ + // This function appends a session header to the SETUP response, and + // checks to see if it is a 304 Not Modified. If it is, it sends the entire + // response and returns an error + if ( QTSServerInterface::GetServer()->GetPrefs()->GetRTSPTimeoutInSecs() > 0 ) // adv the timeout + inRequest->AppendSessionHeaderWithTimeout( this->GetValue(qtssCliSesRTSPSessionID), QTSServerInterface::GetServer()->GetPrefs()->GetRTSPTimeoutAsString() ); + else + inRequest->AppendSessionHeaderWithTimeout( this->GetValue(qtssCliSesRTSPSessionID), NULL ); // no timeout in resp. + + if (inRequest->GetStatus() == qtssRedirectNotModified) + { + (void)inRequest->SendHeader(); + return QTSS_RequestFailed; + } + return QTSS_NoErr; +} + +void RTPSessionInterface::UpdateBitRateInternal(const SInt64& curTime) +{ + if (fState == qtssPausedState) + { fMovieCurrentBitRate = 0; + fLastBitRateUpdateTime = curTime; + fLastBitRateBytes = fBytesSent; + } + else + { + UInt32 bitsInInterval = (fBytesSent - fLastBitRateBytes) * 8; + SInt64 updateTime = (curTime - fLastBitRateUpdateTime) / 1000; + if (updateTime > 0) // leave Bit Rate the same if updateTime is 0 also don't divide by 0. + fMovieCurrentBitRate = (UInt32) (bitsInInterval / updateTime); + fTracker.UpdateAckTimeout(bitsInInterval, curTime - fLastBitRateUpdateTime); + fLastBitRateBytes = fBytesSent; + fLastBitRateUpdateTime = curTime; + } + //qtss_printf("fMovieCurrentBitRate=%"_U32BITARG_"\n",fMovieCurrentBitRate); + //qtss_printf("Cur bandwidth: %d. Cur ack timeout: %d.\n",fTracker.GetCurrentBandwidthInBps(), fTracker.RecommendedClientAckTimeout()); +} + +void* RTPSessionInterface::TimeConnected(QTSSDictionary* inSession, UInt32* outLen) +{ + RTPSessionInterface* theSession = (RTPSessionInterface*)inSession; + theSession->fTimeConnected = (OS::Milliseconds() - theSession->GetSessionCreateTime()); + + // Return the result + *outLen = sizeof(theSession->fTimeConnected); + return &theSession->fTimeConnected; +} + +void* RTPSessionInterface::CurrentBitRate(QTSSDictionary* inSession, UInt32* outLen) +{ + RTPSessionInterface* theSession = (RTPSessionInterface*)inSession; + theSession->UpdateBitRateInternal(OS::Milliseconds()); + + // Return the result + *outLen = sizeof(theSession->fMovieCurrentBitRate); + return &theSession->fMovieCurrentBitRate; +} + + +void* RTPSessionInterface::PacketLossPercent(QTSSDictionary* inSession, UInt32* outLen) +{ + RTPSessionInterface* theSession = (RTPSessionInterface*)inSession; + RTPStream* theStream = NULL; + UInt32 theLen = sizeof(theStream); + + SInt64 packetsLost = 0; + SInt64 packetsSent = 0; + + for (int x = 0; theSession->GetValue(qtssCliSesStreamObjects, x, (void*)&theStream, &theLen) == QTSS_NoErr; x++) + { + if (theStream != NULL ) + { + UInt32 streamCurPacketsLost = 0; + theLen = sizeof(UInt32); + (void) theStream->GetValue(qtssRTPStrCurPacketsLostInRTCPInterval,0, &streamCurPacketsLost, &theLen); + //qtss_printf("stream = %d streamCurPacketsLost = %"_U32BITARG_" \n",x, streamCurPacketsLost); + + UInt32 streamCurPackets = 0; + theLen = sizeof(UInt32); + (void) theStream->GetValue(qtssRTPStrPacketCountInRTCPInterval,0, &streamCurPackets, &theLen); + //qtss_printf("stream = %d streamCurPackets = %"_U32BITARG_" \n",x, streamCurPackets); + + packetsSent += (SInt64) streamCurPackets; + packetsLost += (SInt64) streamCurPacketsLost; + //qtss_printf("stream calculated loss = %f \n",x, (Float32) streamCurPacketsLost / (Float32) streamCurPackets); + + } + + theStream = NULL; + theLen = sizeof(UInt32); + } + + //Assert(packetsLost <= packetsSent); + if (packetsSent > 0) + { if (packetsLost <= packetsSent) + theSession->fPacketLossPercent =(Float32) (( ((Float32) packetsLost / (Float32) packetsSent) * 100.0) ); + else + theSession->fPacketLossPercent = 100.0; + } + else + theSession->fPacketLossPercent = 0.0; + //qtss_printf("Session loss percent packetsLost = %qd packetsSent= %qd theSession->fPacketLossPercent=%f\n",packetsLost,packetsSent,theSession->fPacketLossPercent); + // Return the result + *outLen = sizeof(theSession->fPacketLossPercent); + return &theSession->fPacketLossPercent; +} + +void RTPSessionInterface::CreateDigestAuthenticationNonce() { + + // Calculate nonce: MD5 of sessionid:timestamp + SInt64 curTime = OS::Milliseconds(); + char* curTimeStr = NEW char[128]; + qtss_sprintf(curTimeStr, "%"_64BITARG_"d", curTime); + + // Delete old nonce before creating a new one + if(fAuthNonce.Ptr != NULL) + delete [] fAuthNonce.Ptr; + + MD5_CTX ctxt; + unsigned char nonceStr[16]; + unsigned char colon[] = ":"; + MD5_Init(&ctxt); + StrPtrLen* sesID = this->GetValue(qtssCliSesRTSPSessionID); + MD5_Update(&ctxt, (unsigned char *)sesID->Ptr, sesID->Len); + MD5_Update(&ctxt, (unsigned char *)colon, 1); + MD5_Update(&ctxt, (unsigned char *)curTimeStr, ::strlen(curTimeStr)); + MD5_Final(nonceStr, &ctxt); + HashToString(nonceStr, &fAuthNonce); + + delete [] curTimeStr; // No longer required once nonce is created + + // Set the nonce count value to zero + // as a new nonce has been created + fAuthNonceCount = 0; + +} + +void RTPSessionInterface::SetChallengeParams(QTSS_AuthScheme scheme, UInt32 qop, Bool16 newNonce, Bool16 createOpaque) +{ + // Set challenge params + // Set authentication scheme + fAuthScheme = scheme; + + if(fAuthScheme == qtssAuthDigest) { + // Set Quality of Protection + // auth-int (Authentication with integrity) not supported yet + fAuthQop = qop; + + if(newNonce || (fAuthNonce.Ptr == NULL)) + this->CreateDigestAuthenticationNonce(); + + if(createOpaque) { + // Generate a random UInt32 and convert it to a string + // The base64 encoded form of the string is made the opaque value + SInt64 theMicroseconds = OS::Microseconds(); + ::srand((unsigned int)theMicroseconds); + UInt32 randomNum = ::rand(); + char* randomNumStr = NEW char[128]; + qtss_sprintf(randomNumStr, "%"_U32BITARG_"", randomNum); + int len = ::strlen(randomNumStr); + fAuthOpaque.Len = Base64encode_len(len); + char *opaqueStr = NEW char[fAuthOpaque.Len]; + (void) Base64encode(opaqueStr, randomNumStr,len); + delete [] randomNumStr; // Don't need this anymore + if(fAuthOpaque.Ptr != NULL) // Delete existing pointer before assigning new + delete [] fAuthOpaque.Ptr; // one + fAuthOpaque.Ptr = opaqueStr; + } + else { + if(fAuthOpaque.Ptr != NULL) + delete [] fAuthOpaque.Ptr; + fAuthOpaque.Len = 0; + } + // Increase the Nonce Count by one + // This number is a count of the next request the server + // expects with this nonce. (Implies that the server + // has already received nonce count - 1 requests that + // sent authorization with this nonce + fAuthNonceCount ++; + } +} + +void RTPSessionInterface::UpdateDigestAuthChallengeParams(Bool16 newNonce, Bool16 createOpaque, UInt32 qop) { + if(newNonce || (fAuthNonce.Ptr == NULL)) + this->CreateDigestAuthenticationNonce(); + + + if(createOpaque) { + // Generate a random UInt32 and convert it to a string + // The base64 encoded form of the string is made the opaque value + SInt64 theMicroseconds = OS::Microseconds(); + ::srand((unsigned int)theMicroseconds); + UInt32 randomNum = ::rand(); + char* randomNumStr = NEW char[128]; + qtss_sprintf(randomNumStr, "%"_U32BITARG_"", randomNum); + int len = ::strlen(randomNumStr); + fAuthOpaque.Len = Base64encode_len(len); + char *opaqueStr = NEW char[fAuthOpaque.Len]; + (void) Base64encode(opaqueStr, randomNumStr,len); + delete [] randomNumStr; // Don't need this anymore + if(fAuthOpaque.Ptr != NULL) // Delete existing pointer before assigning new + delete [] fAuthOpaque.Ptr; // one + fAuthOpaque.Ptr = opaqueStr; + fAuthOpaque.Len = ::strlen(opaqueStr); + } + else { + if(fAuthOpaque.Ptr != NULL) + delete [] fAuthOpaque.Ptr; + fAuthOpaque.Len = 0; + } + fAuthNonceCount ++; + + fAuthQop = qop; +} diff --git a/Server.tproj/RTPSessionInterface.h b/Server.tproj/RTPSessionInterface.h new file mode 100644 index 0000000..eb38a53 --- /dev/null +++ b/Server.tproj/RTPSessionInterface.h @@ -0,0 +1,353 @@ +/* + * + * @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@ + * + */ + /* + Contains: API interface for objects to use to get access to attributes, + data items, whatever, specific to RTP sessions (see RTPSession.h + for more details on what that object is). This object + implements the RTP Session Dictionary. + + + +*/ + + +#ifndef _RTPSESSIONINTERFACE_H_ +#define _RTPSESSIONINTERFACE_H_ + +#include "QTSSDictionary.h" + +#include "RTCPSRPacket.h" +#include "RTSPSessionInterface.h" +#include "TimeoutTask.h" +#include "Task.h" +#include "RTPBandwidthTracker.h" +#include "RTPOverbufferWindow.h" +#include "QTSServerInterface.h" +#include "OSMutex.h" +#include "atomic.h" +#include "RTPSession3GPP.h" + +class RTSPRequestInterface; + +class RTPSessionInterface : public QTSSDictionary, public Task +{ + public: + + // Initializes dictionary resources + static void Initialize(); + + // + // CONSTRUCTOR / DESTRUCTOR + + RTPSessionInterface(); + virtual ~RTPSessionInterface() + { + if (GetQualityLevel() != 0) + QTSServerInterface::GetServer()->IncrementNumThinned(-1); + if (fRTSPSession != NULL) + fRTSPSession->DecrementObjectHolderCount(); + delete [] fSRBuffer.Ptr; + delete [] fAuthNonce.Ptr; + delete [] fAuthOpaque.Ptr; + } + + virtual void SetValueComplete(UInt32 inAttrIndex, QTSSDictionaryMap* inMap, + UInt32 inValueIndex, void* inNewValue, UInt32 inNewValueLen); + + //Timeouts. This allows clients to refresh the timeout on this session + void RefreshTimeout() { fTimeoutTask.RefreshTimeout(); } + void RefreshRTSPTimeout() { if (fRTSPSession != NULL) fRTSPSession->RefreshTimeout(); } + void RefreshTimeouts() { RefreshTimeout(); RefreshRTSPTimeout();} + + // + // ACCESSORS + + Bool16 IsFirstPlay() { return fIsFirstPlay; } + SInt64 GetFirstPlayTime() { return fFirstPlayTime; } + //Time (msec) most recent play was issued + SInt64 GetPlayTime() { return fPlayTime; } + SInt64 GetNTPPlayTime() { return fNTPPlayTime; } + SInt64 GetSessionCreateTime() { return fSessionCreateTime; } + //Time (msec) most recent play, adjusted for start time of the movie + //ex: PlayTime() == 20,000. Client said start 10 sec into the movie, + //so AdjustedPlayTime() == 10,000 + QTSS_PlayFlags GetPlayFlags() { return fPlayFlags; } + OSMutex* GetSessionMutex() { return &fSessionMutex; } + UInt32 GetPacketsSent() { return fPacketsSent; } + UInt32 GetBytesSent() { return fBytesSent; } + OSRef* GetRef() { return &fRTPMapElem; } + RTSPSessionInterface* GetRTSPSession() { return fRTSPSession; } + UInt32 GetMovieAvgBitrate(){ return fMovieAverageBitRate; } + QTSS_CliSesTeardownReason GetTeardownReason() { return fTeardownReason; } + QTSS_RTPSessionState GetSessionState() { return fState; } + void SetUniqueID(UInt32 theID) {fUniqueID = theID;} + UInt32 GetUniqueID() { return fUniqueID; } + RTPBandwidthTracker* GetBandwidthTracker() { return &fTracker; } + RTPOverbufferWindow* GetOverbufferWindow() { return &fOverbufferWindow; } + UInt32 GetFramesSkipped() { return fFramesSkipped; } + + // + // MEMORY FOR RTCP PACKETS + + // + // Class for easily building a standard RTCP SR + RTCPSRPacket* GetSRPacket() { return &fRTCPSRPacket; } + + // + // Memory if you want to build your own + char* GetSRBuffer(UInt32 inSRLen); + + // + // STATISTICS UPDATING + + //The session tracks the total number of bytes sent during the session. + //Streams can update that value by calling this function + void UpdateBytesSent(UInt32 bytesSent) { fBytesSent += bytesSent; } + + //The session tracks the total number of packets sent during the session. + //Streams can update that value by calling this function + void UpdatePacketsSent(UInt32 packetsSent) { fPacketsSent += packetsSent; } + + void UpdateCurrentBitRate(const SInt64& curTime) + { if (curTime > (fLastBitRateUpdateTime + 3000)) this->UpdateBitRateInternal(curTime); } + + void SetAllTracksInterleaved(Bool16 newValue) { fAllTracksInterleaved = newValue; } + // + // RTSP RESPONSES + + // This function appends a session header to the SETUP response, and + // checks to see if it is a 304 Not Modified. If it is, it sends the entire + // response and returns an error + QTSS_Error DoSessionSetupResponse(RTSPRequestInterface* inRequest); + + // + // RTSP SESSION + + // This object has a current RTSP session. This may change over the + // life of the RTSPSession, so update it. It keeps an RTSP session in + // case interleaved data or commands need to be sent back to the client. + void UpdateRTSPSession(RTSPSessionInterface* inNewRTSPSession); + + // let's RTSP Session pass along it's query string + void SetQueryString( StrPtrLen* queryString ); + + // SETTERS and ACCESSORS for auth information + // Authentication information that needs to be kept around + // for the duration of the session + QTSS_AuthScheme GetAuthScheme() { return fAuthScheme; } + StrPtrLen* GetAuthNonce() { return &fAuthNonce; } + UInt32 GetAuthQop() { return fAuthQop; } + UInt32 GetAuthNonceCount() { return fAuthNonceCount; } + StrPtrLen* GetAuthOpaque() { return &fAuthOpaque; } + void SetAuthScheme(QTSS_AuthScheme scheme) { fAuthScheme = scheme; } + // Use this if the auth scheme or the qop has to be changed from the defaults + // of scheme = Digest, and qop = auth + void SetChallengeParams(QTSS_AuthScheme scheme, UInt32 qop, Bool16 newNonce, Bool16 createOpaque); + // Use this otherwise...if newNonce == true, it will create a new nonce + // and reset nonce count. If newNonce == false but nonce was never created earlier + // a nonce will be created. If newNonce == false, and there is an existing nonce, + // the nounce count will be incremented. + void UpdateDigestAuthChallengeParams(Bool16 newNonce, Bool16 createOpaque, UInt32 qop); + + Float32* GetPacketLossPercent() { UInt32 outLen;return (Float32*) this->PacketLossPercent(this, &outLen);} + + SInt32 GetQualityLevel() { return fSessionQualityLevel; } + SInt32* GetQualityLevelPtr() { return &fSessionQualityLevel; } + void SetQualityLevel(SInt32 level) { if (fSessionQualityLevel == 0 && level != 0) + QTSServerInterface::GetServer()->IncrementNumThinned(1); + else if (fSessionQualityLevel != 0 && level == 0) + QTSServerInterface::GetServer()->IncrementNumThinned(-1); + fSessionQualityLevel = level; + } + SInt64 fLastQualityCheckTime; + SInt64 fLastQualityCheckMediaTime; + Bool16 fStartedThinning; + + // Used by RTPStream to increment the RTCP packet and byte counts. + void IncrTotalRTCPPacketsRecv() { fTotalRTCPPacketsRecv++; } + UInt32 GetTotalRTCPPacketsRecv() { return fTotalRTCPPacketsRecv; } + void IncrTotalRTCPBytesRecv(UInt16 cnt) { fTotalRTCPBytesRecv += cnt; } + UInt32 GetTotalRTCPBytesRecv() { return fTotalRTCPBytesRecv; } + + UInt32 GetLastRTSPBandwithBits() { return fLastRTSPBandwidthHeaderBits; } + UInt32 GetCurrentMovieBitRate() { return fMovieCurrentBitRate; } + + UInt32 GetMaxBandwidthBits() { UInt32 maxRTSP = GetLastRTSPBandwithBits(); UInt32 maxLink = fRTPSession3GPP.GetLinkCharMaxKBits()* 1000; return (maxRTSP > maxLink) ? maxRTSP : maxLink; } + + void SetIs3GPPSession(Bool16 is3GPP) { fIs3GPPSession = is3GPP; } + + protected: + + RTPSession3GPP* Get3GPPSessPtr() { return fRTPSession3GPPPtr; } + + // These variables are setup by the derived RTPSession object when + // Play and Pause get called + + //Some stream related information that is shared amongst all streams + Bool16 fIsFirstPlay; + Bool16 fAllTracksInterleaved; + SInt64 fFirstPlayTime;//in milliseconds + SInt64 fPlayTime; + SInt64 fAdjustedPlayTime; + SInt64 fNTPPlayTime; + SInt64 fNextSendPacketsTime; + + SInt32 fSessionQualityLevel; + + //keeps track of whether we are playing or not + QTSS_RTPSessionState fState; + + // If we are playing, this are the play flags that were set on play + QTSS_PlayFlags fPlayFlags; + + //Session mutex. This mutex should be grabbed before invoking the module + //responsible for managing this session. This allows the module to be + //non-preemptive-safe with respect to a session + OSMutex fSessionMutex; + + //Stores the session ID + OSRef fRTPMapElem; + char fRTSPSessionIDBuf[QTSS_MAX_SESSION_ID_LENGTH + 4]; + + UInt32 fLastBitRateBytes; + SInt64 fLastBitRateUpdateTime; + UInt32 fMovieCurrentBitRate; + + // In order to facilitate sending data over the RTSP channel from + // an RTP session, we store a pointer to the RTSP session used in + // the last RTSP request. + RTSPSessionInterface* fRTSPSession; + + + + private: + + // + // Utility function for calculating current bit rate + void UpdateBitRateInternal(const SInt64& curTime); + + static void* PacketLossPercent(QTSSDictionary* inSession, UInt32* outLen); + static void* TimeConnected(QTSSDictionary* inSession, UInt32* outLen); + static void* CurrentBitRate(QTSSDictionary* inSession, UInt32* outLen); + + // Create nonce + void CreateDigestAuthenticationNonce(); + + // One of the RTP session attributes is an iterated list of all streams. + // As an optimization, we preallocate a "large" buffer of stream pointers, + // even though we don't know how many streams we need at first. + enum + { + kStreamBufSize = 4, + kUserAgentBufSize = 256, + kPresentationURLSize = 256, + kFullRequestURLBufferSize = 256, + kRequestHostNameBufferSize = 80, + + kIPAddrStrBufSize = 20, + kLocalDNSBufSize = 80, + + kAuthNonceBufSize = 32, + kAuthOpaqueBufSize = 32, + + }; + + void* fStreamBuffer[kStreamBufSize]; + + + // theses are dictionary items picked up by the RTSPSession + // but we need to store copies of them for logging purposes. + char fUserAgentBuffer[kUserAgentBufSize]; + char fPresentationURL[kPresentationURLSize]; // eg /foo/bar.mov + char fFullRequestURL[kFullRequestURLBufferSize]; // eg rtsp://yourdomain.com/foo/bar.mov + char fRequestHostName[kRequestHostNameBufferSize]; // eg yourdomain.com + + char fRTSPSessRemoteAddrStr[kIPAddrStrBufSize]; + char fRTSPSessLocalDNS[kLocalDNSBufSize]; + char fRTSPSessLocalAddrStr[kIPAddrStrBufSize]; + + char fUserNameBuf[RTSPSessionInterface::kMaxUserNameLen]; + char fUserPasswordBuf[RTSPSessionInterface::kMaxUserPasswordLen]; + char fUserRealmBuf[RTSPSessionInterface::kMaxUserRealmLen]; + UInt32 fLastRTSPReqRealStatusCode; + + //for timing out this session + TimeoutTask fTimeoutTask; + UInt32 fTimeout; + + // Time when this session got created + SInt64 fSessionCreateTime; + + //Packet priority levels. Each stream has a current level, and + //the module that owns this session sets what the number of levels is. + UInt32 fNumQualityLevels; + + //Statistics + UInt32 fBytesSent; + UInt32 fPacketsSent; + Float32 fPacketLossPercent; + SInt64 fTimeConnected; + UInt32 fTotalRTCPPacketsRecv; + UInt32 fTotalRTCPBytesRecv; + // Movie size & movie duration. It may not be so good to associate these + // statistics with the movie, for a session MAY have multiple movies... + // however, the 1 movie assumption is in too many subsystems at this point + Float64 fMovieDuration; + UInt64 fMovieSizeInBytes; + UInt32 fMovieAverageBitRate; + + QTSS_CliSesTeardownReason fTeardownReason; + // So the streams can send sender reports + UInt32 fUniqueID; + + RTCPSRPacket fRTCPSRPacket; + StrPtrLen fSRBuffer; + + RTPBandwidthTracker fTracker; + RTPOverbufferWindow fOverbufferWindow; + + // Built in dictionary attributes + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + static unsigned int sRTPSessionIDCounter; + + // Authentication information that needs to be kept around + // for the duration of the session + QTSS_AuthScheme fAuthScheme; + StrPtrLen fAuthNonce; + UInt32 fAuthQop; + UInt32 fAuthNonceCount; + StrPtrLen fAuthOpaque; + UInt32 fQualityUpdate; + + UInt32 fFramesSkipped; + + RTPSession3GPP fRTPSession3GPP; + RTPSession3GPP* fRTPSession3GPPPtr; + UInt32 fLastRTSPBandwidthHeaderBits; + Bool16 fIs3GPPSession; + +}; + +#endif //_RTPSESSIONINTERFACE_H_ diff --git a/Server.tproj/RTPStream.cpp b/Server.tproj/RTPStream.cpp new file mode 100644 index 0000000..2d8c9f5 --- /dev/null +++ b/Server.tproj/RTPStream.cpp @@ -0,0 +1,1960 @@ +/* + * + * @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: RTPStream.cpp + + Contains: Implementation of RTPStream class. + + + +*/ + + + +#include +#include +#include "SafeStdLib.h" +#include "RTPStream.h" +#include "RTPSessionInterface.h" + +#include "QTSSModuleUtils.h" +#include "QTSServerInterface.h" +#include "OS.h" + +#include "RTCPPacket.h" +#include "RTCPAPPPacket.h" +#include "RTCPAPPQTSSPacket.h" +#include "RTCPAckPacket.h" +#include "RTCPSRPacket.h" +#include "RTCPAPPNADUPacket.h" + +#include "SocketUtils.h" +#include + +#include + +#if DEBUG +#define RTP_TCP_STREAM_DEBUG 1 +#define RTP_3GPP_DEBUG 1 +#define RTP_RTCP_DEBUG 1 +#else +#define RTP_TCP_STREAM_DEBUG 0 +#define RTP_3GPP_DEBUG 0 +#define RTP_RTCP_DEBUG 0 + +#endif + + +#if RTP_RTCP_DEBUG + #define DEBUG_RTCP_PRINTF(s) qtss_printf s +#else + #define DEBUG_RTCP_PRINTF(s) {} +#endif + +#if RTP_3GPP_DEBUG + #define DEBUG_3GPP_PRINTF(s) qtss_printf s +#else + #define DEBUG_3GPP_PRINTF(s) {} +#endif + + +#define RTCP_TESTING 0 + + +QTSSAttrInfoDict::AttrInfo RTPStream::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssRTPStrTrackID", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 1 */ { "qtssRTPStrSSRC", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 2 */ { "qtssRTPStrPayloadName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 3 */ { "qtssRTPStrPayloadType", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 4 */ { "qtssRTPStrFirstSeqNumber", NULL, qtssAttrDataTypeSInt16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 5 */ { "qtssRTPStrFirstTimestamp", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 6 */ { "qtssRTPStrTimescale", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 7 */ { "qtssRTPStrQualityLevel", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 8 */ { "qtssRTPStrNumQualityLevels", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 9 */ { "qtssRTPStrBufferDelayInSecs", NULL, qtssAttrDataTypeFloat32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + + /* 10 */ { "qtssRTPStrFractionLostPackets", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 11 */ { "qtssRTPStrTotalLostPackets", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 12 */ { "qtssRTPStrJitter", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 13 */ { "qtssRTPStrRecvBitRate", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 14 */ { "qtssRTPStrAvgLateMilliseconds", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 15 */ { "qtssRTPStrPercentPacketsLost", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 16 */ { "qtssRTPStrAvgBufDelayInMsec", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 17 */ { "qtssRTPStrGettingBetter", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 18 */ { "qtssRTPStrGettingWorse", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 19 */ { "qtssRTPStrNumEyes", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 20 */ { "qtssRTPStrNumEyesActive", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 21 */ { "qtssRTPStrNumEyesPaused", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 22 */ { "qtssRTPStrTotPacketsRecv", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 23 */ { "qtssRTPStrTotPacketsDropped", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 24 */ { "qtssRTPStrTotPacketsLost", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 25 */ { "qtssRTPStrClientBufFill", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 26 */ { "qtssRTPStrFrameRate", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 27 */ { "qtssRTPStrExpFrameRate", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 28 */ { "qtssRTPStrAudioDryCount", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 29 */ { "qtssRTPStrIsTCP", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 30 */ { "qtssRTPStrStreamRef", NULL, qtssAttrDataTypeQTSS_StreamRef, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 31 */ { "qtssRTPStrTransportType", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 32 */ { "qtssRTPStrStalePacketsDropped", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 33 */ { "qtssRTPStrCurrentAckTimeout", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 34 */ { "qtssRTPStrCurPacketsLostInRTCPInterval", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 35 */ { "qtssRTPStrPacketCountInRTCPInterval", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 36 */ { "qtssRTPStrSvrRTPPort", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 37 */ { "qtssRTPStrClientRTPPort", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 38 */ { "qtssRTPStrNetworkMode", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 39 */ { "qtssRTPStr3gppObject", NULL, qtssAttrDataTypeQTSS_Object, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 40 */ { "qtssRTPStrThinningDisabled", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe } + +}; + +StrPtrLen RTPStream::sChannelNums[] = +{ + StrPtrLen("0"), + StrPtrLen("1"), + StrPtrLen("2"), + StrPtrLen("3"), + StrPtrLen("4"), + StrPtrLen("5"), + StrPtrLen("6"), + StrPtrLen("7"), + StrPtrLen("8"), + StrPtrLen("9") +}; + +char *RTPStream::noType = "no-type"; +char *RTPStream::UDP = "UDP"; +char *RTPStream::RUDP = "RUDP"; +char *RTPStream::TCP = "TCP"; + +QTSS_ModuleState RTPStream::sRTCPProcessModuleState = { NULL, 0, NULL, false }; + +void RTPStream::Initialize() +{ + for (int x = 0; x < qtssRTPStrNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kRTPStreamDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); +} + +RTPStream::RTPStream(UInt32 inSSRC, RTPSessionInterface* inSession) +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kRTPStreamDictIndex), NULL), + fLastQualityChange(0), + fSockets(NULL), + fSession(inSession), + fBytesSentThisInterval(0), + fDisplayCount(0), + fSawFirstPacket(false), + fTracker(NULL), + fRemoteAddr(0), + fRemoteRTPPort(0), + fRemoteRTCPPort(0), + fLocalRTPPort(0), + fMonitorAddr (0), + fMonitorSocket(0), + fPlayerToMonitorAddr(0), + fLastSenderReportTime(0), + fPacketCount(0), + fLastPacketCount(0), + fPacketCountInRTCPInterval(0), + fByteCount(0), + fTrackID(0), + fSsrc(inSSRC), + fSsrcStringPtr(fSsrcString, 0), + fEnableSSRC(false), + fPayloadType(qtssUnknownPayloadType), + fFirstSeqNumber(0), + fFirstTimeStamp(0), + fTimescale(0), + fStreamURLPtr(fStreamURL, 0), + fQualityLevel(QTSServerInterface::GetServer()->GetPrefs()->GetDefaultStreamQuality()), + fNumQualityLevels(0), + fLastRTPTimestamp(0), + fLastNTPTimeStamp(0), + fEstRTT(0), + fFractionLostPackets(0), + fTotalLostPackets(0), + //fPriorTotalLostPackets(0), + fJitter(0), + fReceiverBitRate(0), + fAvgLateMsec(0), + fPercentPacketsLost(0), + fAvgBufDelayMsec(0), + fIsGettingBetter(0), + fIsGettingWorse(0), + fNumEyes(0), + fNumEyesActive(0), + fNumEyesPaused(0), + fTotalPacketsRecv(0), + fTotalPacketsDropped(0), + fTotalPacketsLost(0), + fCurPacketsLostInRTCPInterval(0), + fClientBufferFill(0), + fFrameRate(0), + fExpectedFrameRate(0), + fAudioDryCount(0), + fClientSSRC(0), + fIsTCP(false), + fTransportType(qtssRTPTransportTypeUDP), + fTurnThinningOffDelay_TCP(0), + fIncreaseThinningDelay_TCP(0), + fDropAllPacketsForThisStreamDelay_TCP(0), + fStalePacketsDropped_TCP(0), + fTimeStreamCaughtUp_TCP(0), + fLastQualityLevelIncreaseTime_TCP(0), + + fThinAllTheWayDelay(0), + fAlwaysThinDelay(0), + fStartThinningDelay(0), + fStartThickingDelay(0), + fThickAllTheWayDelay(0), + fQualityCheckInterval(0), + fDropAllPacketsForThisStreamDelay(0), + fStalePacketsDropped(0), + fLastCurrentPacketDelay(0), + fWaitOnLevelAdjustment(true), + fBufferDelay(3.0), + fLateToleranceInSec(0), + fCurrentAckTimeout(0), + fMaxSendAheadTimeMSec(0), + fRTPChannel(0), + fRTCPChannel(0), + fNetworkMode(qtssRTPNetworkModeDefault), + fStreamStartTimeOSms(OS::Milliseconds()), + fLastQualityLevel(0), + fLastRateLevel(0), + fDisableThinning(QTSServerInterface::GetServer()->GetPrefs()->GetDisableThinning()), + fLastQualityUpdate(0), + fDefaultQualityLevel(QTSServerInterface::GetServer()->GetPrefs()->GetDefaultStreamQuality()), + fMaxQualityLevel(fDefaultQualityLevel), + fInitialMaxQualityLevelIsSet(false), + fUDPMonitorEnabled(QTSServerInterface::GetServer()->GetPrefs()->GetUDPMonitorEnabled()), + fMonitorVideoDestPort(QTSServerInterface::GetServer()->GetPrefs()->GetUDPMonitorVideoPort() ), + fMonitorAudioDestPort(QTSServerInterface::GetServer()->GetPrefs()->GetUDPMonitorAudioPort() ) +{ + Bool16 doRateAdaptation = QTSServerInterface::GetServer()->GetPrefs()->Get3GPPEnabled() && QTSServerInterface::GetServer()->GetPrefs()->Get3GPPRateAdaptationEnabled(); + + QTSS_StandardRTSP_Params inParamBlock; + inParamBlock.inClientSession=inSession; + Bool16 disableRateAdaptForPlayer = !QTSSModuleUtils::HavePlayerProfile((void *) QTSServerInterface::GetServer()->GetPrefs(),&inParamBlock,QTSSModuleUtils::kDisable3gppRateAdaptation); + if (doRateAdaptation) + doRateAdaptation = disableRateAdaptForPlayer; + + // Set the whether thinning is enabled. + Bool16 thinningDisabledForUserAgent = QTSSModuleUtils::HavePlayerProfile((void *) QTSServerInterface::GetServer()->GetPrefs(), &inParamBlock,QTSSModuleUtils::kDisableThinning); + if (thinningDisabledForUserAgent) + fDisableThinning = thinningDisabledForUserAgent; + + + fStream3GPP = NEW RTPStream3GPP(*this, doRateAdaptation); + fStreamRef = this; + if (fUDPMonitorEnabled) + { + StrPtrLenDel destIP(QTSServerInterface::GetServer()->GetPrefs()->GetMonitorDestIP()); + StrPtrLenDel srcIP(QTSServerInterface::GetServer()->GetPrefs()->GetMonitorSrcIP()); + + fMonitorAddr = SocketUtils::ConvertStringToAddr(destIP.Ptr); + fPlayerToMonitorAddr = SocketUtils::ConvertStringToAddr(srcIP.Ptr); + + fMonitorSocket = ::socket(AF_INET, SOCK_DGRAM, 0); + #ifdef __Win32__ + u_long one = 1; + (void) ::ioctlsocket(fMonitorSocket, FIONBIO, &one); + #else + int flag = ::fcntl(fMonitorSocket, F_GETFL, 0); + (void) ::fcntl(fMonitorSocket, F_SETFL, flag | O_NONBLOCK); + #endif + } + + +#if DEBUG + fNumPacketsDroppedOnTCPFlowControl = 0; + fFlowControlStartedMsec = 0; + fFlowControlDurationMsec = 0; +#endif + //format the ssrc as a string + qtss_sprintf(fSsrcString, "%"_U32BITARG_"", fSsrc); + fSsrcStringPtr.Len = ::strlen(fSsrcString); + Assert(fSsrcStringPtr.Len < kMaxSsrcSizeInBytes); + + // SETUP DICTIONARY ATTRIBUTES + + this->SetVal(qtssRTPStrTrackID, &fTrackID, sizeof(fTrackID)); + this->SetVal(qtssRTPStrSSRC, &fSsrc, sizeof(fSsrc)); + this->SetEmptyVal(qtssRTPStrPayloadName, &fPayloadNameBuf, kDefaultPayloadBufSize); + this->SetVal(qtssRTPStrPayloadType, &fPayloadType, sizeof(fPayloadType)); + this->SetVal(qtssRTPStrFirstSeqNumber, &fFirstSeqNumber, sizeof(fFirstSeqNumber)); + this->SetVal(qtssRTPStrFirstTimestamp, &fFirstTimeStamp, sizeof(fFirstTimeStamp)); + this->SetVal(qtssRTPStrTimescale, &fTimescale, sizeof(fTimescale)); + this->SetVal(qtssRTPStrQualityLevel, &fQualityLevel, sizeof(fQualityLevel)); + this->SetVal(qtssRTPStrNumQualityLevels, &fNumQualityLevels, sizeof(fNumQualityLevels)); + this->SetVal(qtssRTPStrBufferDelayInSecs, &fBufferDelay, sizeof(fBufferDelay)); + this->SetVal(qtssRTPStrFractionLostPackets, &fFractionLostPackets, sizeof(fFractionLostPackets)); + this->SetVal(qtssRTPStrTotalLostPackets, &fTotalLostPackets, sizeof(fTotalLostPackets)); + this->SetVal(qtssRTPStrJitter, &fJitter, sizeof(fJitter)); + this->SetVal(qtssRTPStrRecvBitRate, &fReceiverBitRate, sizeof(fReceiverBitRate)); + this->SetVal(qtssRTPStrAvgLateMilliseconds, &fAvgLateMsec, sizeof(fAvgLateMsec)); + this->SetVal(qtssRTPStrPercentPacketsLost, &fPercentPacketsLost, sizeof(fPercentPacketsLost)); + this->SetVal(qtssRTPStrAvgBufDelayInMsec, &fAvgBufDelayMsec, sizeof(fAvgBufDelayMsec)); + this->SetVal(qtssRTPStrGettingBetter, &fIsGettingBetter, sizeof(fIsGettingBetter)); + this->SetVal(qtssRTPStrGettingWorse, &fIsGettingWorse, sizeof(fIsGettingWorse)); + this->SetVal(qtssRTPStrNumEyes, &fNumEyes, sizeof(fNumEyes)); + this->SetVal(qtssRTPStrNumEyesActive, &fNumEyesActive, sizeof(fNumEyesActive)); + this->SetVal(qtssRTPStrNumEyesPaused, &fNumEyesPaused, sizeof(fNumEyesPaused)); + this->SetVal(qtssRTPStrTotPacketsRecv, &fTotalPacketsRecv, sizeof(fTotalPacketsRecv)); + this->SetVal(qtssRTPStrTotPacketsDropped, &fTotalPacketsDropped, sizeof(fTotalPacketsDropped)); + this->SetVal(qtssRTPStrTotPacketsLost, &fTotalPacketsLost, sizeof(fTotalPacketsLost)); + this->SetVal(qtssRTPStrClientBufFill, &fClientBufferFill, sizeof(fClientBufferFill)); + this->SetVal(qtssRTPStrFrameRate, &fFrameRate, sizeof(fFrameRate)); + this->SetVal(qtssRTPStrExpFrameRate, &fExpectedFrameRate, sizeof(fExpectedFrameRate)); + this->SetVal(qtssRTPStrAudioDryCount, &fAudioDryCount, sizeof(fAudioDryCount)); + this->SetVal(qtssRTPStrIsTCP, &fIsTCP, sizeof(fIsTCP)); + this->SetVal(qtssRTPStrStreamRef, &fStreamRef, sizeof(fStreamRef)); + this->SetVal(qtssRTPStrTransportType, &fTransportType, sizeof(fTransportType)); + this->SetVal(qtssRTPStrStalePacketsDropped, &fStalePacketsDropped, sizeof(fStalePacketsDropped)); + this->SetVal(qtssRTPStrCurrentAckTimeout, &fCurrentAckTimeout, sizeof(fCurrentAckTimeout)); + this->SetVal(qtssRTPStrCurPacketsLostInRTCPInterval , &fCurPacketsLostInRTCPInterval , sizeof(fPacketCountInRTCPInterval)); + this->SetVal(qtssRTPStrPacketCountInRTCPInterval, &fPacketCountInRTCPInterval, sizeof(fPacketCountInRTCPInterval)); + this->SetVal(qtssRTPStrSvrRTPPort, &fLocalRTPPort, sizeof(fLocalRTPPort)); + this->SetVal(qtssRTPStrClientRTPPort, &fRemoteRTPPort, sizeof(fRemoteRTPPort)); + this->SetVal(qtssRTPStrNetworkMode, &fNetworkMode, sizeof(fNetworkMode)); + + this->SetVal(qtssRTPStr3gppObject, &fStream3GPP, sizeof(fStream3GPP)); + this->SetVal(qtssRTPStrThinningDisabled, &fDisableThinning, sizeof(fDisableThinning)); + + +} + +RTPStream::~RTPStream() +{ + QTSS_Error err = QTSS_NoErr; + if (fSockets != NULL) + { + // If there is an UDP socket pair associated with this stream, make sure to free it up + Assert(fSockets->GetSocketB()->GetDemuxer() != NULL); + fSockets->GetSocketB()->GetDemuxer()-> + UnregisterTask(fRemoteAddr, fRemoteRTCPPort, this); + Assert(err == QTSS_NoErr); + + QTSServerInterface::GetServer()->GetSocketPool()->ReleaseUDPSocketPair(fSockets); + } + +#if RTP_PACKET_RESENDER_DEBUGGING + //fResender.LogClose(fFlowControlDurationMsec); + //qtss_printf("Flow control duration msec: %"_64BITARG_"d. Max outstanding packets: %d\n", fFlowControlDurationMsec, fResender.GetMaxPacketsInList()); +#endif + +#if RTP_TCP_STREAM_DEBUG + if ( fIsTCP ) + qtss_printf( "DEBUG: ~RTPStream %li sends got EAGAIN'd.\n", (SInt32)fNumPacketsDroppedOnTCPFlowControl ); +#endif + delete fStream3GPP; + + if (fMonitorSocket != 0) + { + #ifdef __Win32__ + ::closesocket(fMonitorSocket); + #else + ::close(fMonitorSocket); + #endif + } + +} + +SInt32 RTPStream::GetQualityLevel() +{ + if (fTransportType == qtssRTPTransportTypeUDP) + return MIN(fQualityLevel, (SInt32) fNumQualityLevels - 1); + else + return fSession->GetQualityLevel(); +} + +void RTPStream::SetQualityLevel(SInt32 level) +{ + if (fDisableThinning) + return; + + SInt32 minLevel = MAX(0, (SInt32) fNumQualityLevels - 1); + level = MIN(MAX(level, fMaxQualityLevel), minLevel); + + if (level == minLevel) //Instead of going down to key-frames only, go down to key-frames plus 1 P frame instead. + level++; + + if (level == fQualityLevel) + return; + + if (fTransportType == qtssRTPTransportTypeUDP) + fQualityLevel = level; + else + fSession->SetQualityLevel(level); + + fLastQualityLevel = level; +} + + void RTPStream::SetOverBufferState(RTSPRequestInterface* request) +{ + SInt32 requestedOverBufferState = request->GetDynamicRateState(); + Bool16 enableOverBuffer = false; + + switch (fTransportType) + { + case qtssRTPTransportTypeReliableUDP: + { + enableOverBuffer = true; // default is on + if (requestedOverBufferState == 0) // client specifically set to false + enableOverBuffer = false; + } + break; + + case qtssRTPTransportTypeUDP: + { + enableOverBuffer = false; // always off + } + break; + + + case qtssRTPTransportTypeTCP: + { + + enableOverBuffer = true; // default is on same as 4.0 and earlier. Allows tcp to compensate for falling behind from congestion or slow-start. + if (requestedOverBufferState == 0) // client specifically set to false + enableOverBuffer = false; + } + break; + + } + + //over buffering is enabled for the session by default + //if any stream turns it off then it is off for all streams + //a disable is from either the stream type default or a specific rtsp command to disable + if (!enableOverBuffer) + fSession->GetOverbufferWindow()->TurnOffOverbuffering(); +} + +QTSS_Error RTPStream::Setup(RTSPRequestInterface* request, QTSS_AddStreamFlags inFlags) +{ + //Get the URL for this track + fStreamURLPtr.Len = kMaxStreamURLSizeInBytes; + if (request->GetValue(qtssRTSPReqFileName, 0, fStreamURLPtr.Ptr, &fStreamURLPtr.Len) != QTSS_NoErr) + return QTSSModuleUtils::SendErrorResponse(request, qtssClientBadRequest, qtssMsgFileNameTooLong); + fStreamURL[fStreamURLPtr.Len] = '\0';//just in case someone wants to use string routines + + // + // Store the late-tolerance value that came out of hte x-RTP-Options header, + // so that when it comes time to determine our thinning params (when we PLAY), + // we will know this + fLateToleranceInSec = request->GetLateToleranceInSec(); + if (fLateToleranceInSec == -1.0) + fLateToleranceInSec = 1.5; + + // + // Setup the transport type + fTransportType = request->GetTransportType(); + fNetworkMode = request->GetNetworkMode(); + // + // Only allow reliable UDP if it is enabled + if ((fTransportType == qtssRTPTransportTypeReliableUDP) && (!QTSServerInterface::GetServer()->GetPrefs()->IsReliableUDPEnabled())) + fTransportType = qtssRTPTransportTypeUDP; + + // + // Check to see if we are inside a valid reliable UDP directory + if ((fTransportType == qtssRTPTransportTypeReliableUDP) && (!QTSServerInterface::GetServer()->GetPrefs()->IsPathInsideReliableUDPDir(request->GetValue(qtssRTSPReqFilePath)))) + fTransportType = qtssRTPTransportTypeUDP; + + // + // Check to see if caller is forcing raw UDP transport + if ((fTransportType == qtssRTPTransportTypeReliableUDP) && (inFlags & qtssASFlagsForceUDPTransport)) + fTransportType = qtssRTPTransportTypeUDP; + + // + // decide whether to overbuffer + this->SetOverBufferState(request); + + // Check to see if this RTP stream should be sent over TCP. + if (fTransportType == qtssRTPTransportTypeTCP) + { + fIsTCP = true; + fSession->GetOverbufferWindow()->SetWindowSize(kUInt32_Max); + + // If it is, get 2 channel numbers from the RTSP session. + fRTPChannel = request->GetSession()->GetTwoChannelNumbers(fSession->GetValue(qtssCliSesRTSPSessionID)); + fRTCPChannel = fRTPChannel+1; + + // If we are interleaving, this is all we need to do to setup. + return QTSS_NoErr; + } + + // + // This track is not interleaved, so let the session know that all + // tracks are not interleaved. This affects our scheduling of packets + fSession->SetAllTracksInterleaved(false); + + //Get and store the remote addresses provided by the client. The remote addr is the + //same as the RTSP client's IP address, unless an alternate was specified in the + //transport header. + fRemoteAddr = request->GetSession()->GetSocket()->GetRemoteAddr(); + if (request->GetDestAddr() != INADDR_ANY) + { + // Sending data to other addresses could be used in malicious ways, therefore + // it is up to the module as to whether this sort of request might be allowed + if (!(inFlags & qtssASFlagsAllowDestination)) + return QTSSModuleUtils::SendErrorResponse(request, qtssClientBadRequest, qtssMsgAltDestNotAllowed); + fRemoteAddr = request->GetDestAddr(); + } + fRemoteRTPPort = request->GetClientPortA(); + fRemoteRTCPPort = request->GetClientPortB(); + + if ((fRemoteRTPPort == 0) || (fRemoteRTCPPort == 0)) + return QTSSModuleUtils::SendErrorResponse(request, qtssClientBadRequest, qtssMsgNoClientPortInTransport); + + //make sure that the client is advertising an even-numbered RTP port, + //and that the RTCP port is actually one greater than the RTP port + if ((fRemoteRTPPort & 1) != 0) + return QTSSModuleUtils::SendErrorResponse(request, qtssClientBadRequest, qtssMsgRTPPortMustBeEven); + + // comment out check below. This allows the rtcp port to be non-contiguous with the rtp port. + // if (fRemoteRTCPPort != (fRemoteRTPPort + 1)) + // return QTSSModuleUtils::SendErrorResponse(request, qtssClientBadRequest, qtssMsgRTCPPortMustBeOneBigger); + + // Find the right source address for this stream. If it isn't specified in the + // RTSP request, assume it is the same interface as for the RTSP request. + UInt32 sourceAddr = request->GetSession()->GetSocket()->GetLocalAddr(); + if ((request->GetSourceAddr() != INADDR_ANY) && (SocketUtils::IsLocalIPAddr(request->GetSourceAddr()))) + sourceAddr = request->GetSourceAddr(); + + // if the transport is TCP or RUDP, then we only want one session quality level instead of a per stream one + if (fTransportType != qtssRTPTransportTypeUDP) + { + this->SetQualityLevel(*(fSession->GetQualityLevelPtr())); + } + + + // If the destination address is multicast, we need to setup multicast socket options + // on the sockets. Because these options may be different for each stream, we need + // a dedicated set of sockets + if (SocketUtils::IsMulticastIPAddr(fRemoteAddr)) + { + fSockets = QTSServerInterface::GetServer()->GetSocketPool()->CreateUDPSocketPair(sourceAddr, 0); + + if (fSockets != NULL) + { + //Set options on both sockets. Not really sure why we need to specify an + //outgoing interface, because these sockets are already bound to an interface! + QTSS_Error err = fSockets->GetSocketA()->SetTtl(request->GetTtl()); + if (err == QTSS_NoErr) + err = fSockets->GetSocketB()->SetTtl(request->GetTtl()); + if (err == QTSS_NoErr) + err = fSockets->GetSocketA()->SetMulticastInterface(fSockets->GetSocketA()->GetLocalAddr()); + if (err == QTSS_NoErr) + err = fSockets->GetSocketB()->SetMulticastInterface(fSockets->GetSocketB()->GetLocalAddr()); + if (err != QTSS_NoErr) + return QTSSModuleUtils::SendErrorResponse(request, qtssServerInternal, qtssMsgCantSetupMulticast); + } + } + else + fSockets = QTSServerInterface::GetServer()->GetSocketPool()->GetUDPSocketPair(sourceAddr, 0, fRemoteAddr, + fRemoteRTCPPort); + + if (fSockets == NULL) + return QTSSModuleUtils::SendErrorResponse(request, qtssServerInternal, qtssMsgOutOfPorts); + + else if (fTransportType == qtssRTPTransportTypeReliableUDP) + { + // + // FIXME - we probably want to get rid of this slow start flag in the API + Bool16 useSlowStart = !(inFlags & qtssASFlagsDontUseSlowStart); + if (!QTSServerInterface::GetServer()->GetPrefs()->IsSlowStartEnabled()) + useSlowStart = false; + + fTracker = fSession->GetBandwidthTracker(); + + fResender.SetBandwidthTracker( fTracker ); + fResender.SetDestination( fSockets->GetSocketA(), fRemoteAddr, fRemoteRTPPort ); + +#if RTP_PACKET_RESENDER_DEBUGGING + if (QTSServerInterface::GetServer()->GetPrefs()->IsAckLoggingEnabled()) + { + char url[256]; + char logfile[256]; + qtss_sprintf(logfile, "resend_log_%"_U32BITARG_"", fSession->GetRTSPSession()->GetSessionID()); + StrPtrLen logName(logfile); + fResender.SetLog(&logName); + + StrPtrLen *presoURL = fSession->GetValue(qtssCliSesPresentationURL); + UInt32 clientAddr = request->GetSession()->GetSocket()->GetRemoteAddr(); + memcpy( url, presoURL->Ptr, presoURL->Len ); + url[presoURL->Len] = 0; + qtss_printf( "RTPStream::Setup for %s will use ACKS, ip addr: %li.%li.%li.%li\n", url, (clientAddr & 0xff000000) >> 24 + , (clientAddr & 0x00ff0000) >> 16 + , (clientAddr & 0x0000ff00) >> 8 + , (clientAddr & 0x000000ff) + ); + } +#endif + } + + // + // Record the Server RTP port + fLocalRTPPort = fSockets->GetSocketA()->GetLocalPort(); + + //finally, register with the demuxer to get RTCP packets from the proper address + Assert(fSockets->GetSocketB()->GetDemuxer() != NULL); + QTSS_Error err = fSockets->GetSocketB()->GetDemuxer()->RegisterTask(fRemoteAddr, fRemoteRTCPPort, this); + //errors should only be returned if there is a routing problem, there should be none + Assert(err == QTSS_NoErr); + return QTSS_NoErr; +} + +void RTPStream::SendSetupResponse( RTSPRequestInterface* inRequest ) +{ + if (fSession->DoSessionSetupResponse(inRequest) != QTSS_NoErr) + return; + + inRequest->AppendDateAndExpires(); + this->AppendTransport(inRequest); + + // + // Append the x-RTP-Options header if there was a late-tolerance field + if (inRequest->GetLateToleranceStr()->Len > 0) + inRequest->AppendHeader(qtssXTransportOptionsHeader, inRequest->GetLateToleranceStr()); + + // + // Append the retransmit header if the client sent it + StrPtrLen* theRetrHdr = inRequest->GetHeaderDictionary()->GetValue(qtssXRetransmitHeader); + if ((theRetrHdr->Len > 0) && (fTransportType == qtssRTPTransportTypeReliableUDP)) + inRequest->AppendHeader(qtssXRetransmitHeader, theRetrHdr); + + // Append the dynamic rate header if the client sent it + SInt32 theRequestedRate =inRequest->GetDynamicRateState(); + static StrPtrLen sHeaderOn("1",1); + static StrPtrLen sHeaderOff("0",1); + if (theRequestedRate > 0) // the client sent the header and wants a dynamic rate + { + if(*(fSession->GetOverbufferWindow()->OverbufferingEnabledPtr())) + inRequest->AppendHeader(qtssXDynamicRateHeader, &sHeaderOn); // send 1 if overbuffering is turned on + else + inRequest->AppendHeader(qtssXDynamicRateHeader, &sHeaderOff); // send 0 if overbuffering is turned off + } + else if (theRequestedRate == 0) // the client sent the header but doesn't want a dynamic rate + inRequest->AppendHeader(qtssXDynamicRateHeader, &sHeaderOff); + //else the client didn't send a header so do nothing + + inRequest->SendHeader(); +} + +void RTPStream::AppendTransport(RTSPRequestInterface* request) +{ + + StrPtrLen* ssrcPtr = NULL; + if (fEnableSSRC) + ssrcPtr = &fSsrcStringPtr; + + // We are either going to append the RTP / RTCP port numbers (UDP), + // or the channel numbers (TCP, interleaved) + if (!fIsTCP) + { + // + // With UDP retransmits its important the client starts sending RTCPs + // to the right address right away. The sure-firest way to get the client + // to do this is to put the src address in the transport. So now we do that always. + // + char srcIPAddrBuf[20]; + StrPtrLen theSrcIPAddress(srcIPAddrBuf, 20); + QTSServerInterface::GetServer()->GetPrefs()->GetTransportSrcAddr(&theSrcIPAddress); + if (theSrcIPAddress.Len == 0) + theSrcIPAddress = *fSockets->GetSocketA()->GetLocalAddrStr(); + + + if(request->IsPushRequest()) + { + char rtpPortStr[10]; + char rtcpPortStr[10]; + qtss_sprintf(rtpPortStr, "%u", request->GetSetUpServerPort()); + qtss_sprintf(rtcpPortStr, "%u", request->GetSetUpServerPort()+1); + //qtss_printf(" RTPStream::AppendTransport rtpPort=%u rtcpPort=%u \n",request->GetSetUpServerPort(),request->GetSetUpServerPort()+1); + StrPtrLen rtpSPL(rtpPortStr); + StrPtrLen rtcpSPL(rtcpPortStr); + // Append UDP socket port numbers. + request->AppendTransportHeader(&rtpSPL, &rtcpSPL, NULL, NULL, &theSrcIPAddress,ssrcPtr); + } + else + { + // Append UDP socket port numbers. + UDPSocket* theRTPSocket = fSockets->GetSocketA(); + UDPSocket* theRTCPSocket = fSockets->GetSocketB(); + request->AppendTransportHeader(theRTPSocket->GetLocalPortStr(), theRTCPSocket->GetLocalPortStr(), NULL, NULL, &theSrcIPAddress,ssrcPtr); + } + } + else if (fRTCPChannel < kNumPrebuiltChNums) + // We keep a certain number of channel number strings prebuilt, so most of the time + // we won't have to call qtss_sprintf + request->AppendTransportHeader(NULL, NULL, &sChannelNums[fRTPChannel], &sChannelNums[fRTCPChannel],NULL,ssrcPtr); + else + { + // If these channel numbers fall outside prebuilt range, we will have to call qtss_sprintf. + char rtpChannelBuf[10]; + char rtcpChannelBuf[10]; + qtss_sprintf(rtpChannelBuf, "%d", fRTPChannel); + qtss_sprintf(rtcpChannelBuf, "%d", fRTCPChannel); + + StrPtrLen rtpChannel(rtpChannelBuf); + StrPtrLen rtcpChannel(rtcpChannelBuf); + + request->AppendTransportHeader(NULL, NULL, &rtpChannel, &rtcpChannel,NULL,ssrcPtr); + } +} + +void RTPStream::AppendRTPInfo(QTSS_RTSPHeader inHeader, RTSPRequestInterface* request, UInt32 inFlags, Bool16 lastInfo) +{ + //format strings for the various numbers we need to send back to the client + char rtpTimeBuf[20]; + StrPtrLen rtpTimeBufPtr; + if (inFlags & qtssPlayRespWriteTrackInfo) + { + qtss_sprintf(rtpTimeBuf, "%"_U32BITARG_"", fFirstTimeStamp); + rtpTimeBufPtr.Set(rtpTimeBuf, ::strlen(rtpTimeBuf)); + Assert(rtpTimeBufPtr.Len < 20); + } + + char seqNumberBuf[20]; + StrPtrLen seqNumberBufPtr; + if (inFlags & qtssPlayRespWriteTrackInfo) + { + qtss_sprintf(seqNumberBuf, "%u", fFirstSeqNumber); + seqNumberBufPtr.Set(seqNumberBuf, ::strlen(seqNumberBuf)); + Assert(seqNumberBufPtr.Len < 20); + } + + StrPtrLen *nullSSRCPtr = NULL; // There is no SSRC in RTP-Info header, it goes in the transport header. + request->AppendRTPInfoHeader(inHeader, &fStreamURLPtr, &seqNumberBufPtr, nullSSRCPtr, &rtpTimeBufPtr,lastInfo); + +} + + +//UDP Monitor reflected write +void RTPStream::UDPMonitorWrite(void* thePacketData, UInt32 inLen, Bool16 isRTCP) +{ + if (FALSE == fUDPMonitorEnabled || 0 == fMonitorSocket || NULL == thePacketData) + return; + + if ((0 != fPlayerToMonitorAddr) && (this->fRemoteAddr != fPlayerToMonitorAddr)) + return; + + UInt16 RTCPportOffset = (TRUE == isRTCP)? 1 : 0; + + + struct sockaddr_in sin; + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(fMonitorAddr); + + if (fPayloadType == qtssVideoPayloadType) + sin.sin_port = (in_port_t) htons(fMonitorVideoDestPort+RTCPportOffset); + else if (fPayloadType == qtssAudioPayloadType) + sin.sin_port = (in_port_t) htons(fMonitorAudioDestPort+RTCPportOffset); + + if (sin.sin_port != 0) + { + ssize_t result = ::sendto(fMonitorSocket, thePacketData, inLen, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)); + if (DEBUG) + { if (result < 0) + qtss_printf("RTCP Monitor Socket sendto failed\n"); + else if (0) + qtss_printf("RTCP Monitor Socket sendto port=%hu, packetLen=%"_U32BITARG_"\n", ntohs(sin.sin_port), inLen); + } + } + +} + +/********************************* +/ +/ InterleavedWrite +/ +/ Write the given RTP packet out on the RTSP channel in interleaved format. +/ update quality levels and statistics +/ on success refresh the RTP session timeout to keep it alive +/ +*/ + +//ReliableRTPWrite must be called from a fSession mutex protected caller +QTSS_Error RTPStream::InterleavedWrite(void* inBuffer, UInt32 inLen, UInt32* outLenWritten, unsigned char channel) +{ + + if (fSession->GetRTSPSession() == NULL) // RTSPSession required for interleaved write + { + return EAGAIN; + } + + //char blahblah[2048]; + + QTSS_Error err = fSession->GetRTSPSession()->InterleavedWrite( inBuffer, inLen, outLenWritten, channel); + //QTSS_Error err = fSession->GetRTSPSession()->InterleavedWrite( blahblah, 2044, outLenWritten, channel); +#if DEBUG + //if (outLenWritten != NULL) + //{ + // Assert((*outLenWritten == 0) || (*outLenWritten == 2044)); + //} +#endif + + +#if DEBUG + if ( err == EAGAIN ) + { + fNumPacketsDroppedOnTCPFlowControl++; + } +#endif + + // reset the timeouts when the connection is still alive + // wehn transmitting over HTTP, we're not going to get + // RTCPs that would normally Refresh the session time. + if ( err == QTSS_NoErr ) + fSession->RefreshTimeout(); // RTSP session gets refreshed internally in WriteV + + #if RTP_TCP_STREAM_DEBUG + //qtss_printf( "DEBUG: RTPStream fCurrentPacketDelay %li, fQualityLevel %i\n", (SInt32)fCurrentPacketDelay, (int)fQualityLevel ); + #endif + + return err; +} + +//SendRetransmits must be called from a fSession mutex protected caller +void RTPStream::SendRetransmits() +{ + + if ( fTransportType == qtssRTPTransportTypeReliableUDP ) + fResender.ResendDueEntries(); + + +} + +//ReliableRTPWrite must be called from a fSession mutex protected caller +QTSS_Error RTPStream::ReliableRTPWrite(void* inBuffer, UInt32 inLen, const SInt64& curPacketDelay) +{ + QTSS_Error err = QTSS_NoErr; + + // this must ALSO be called in response to a packet timeout + // event that can be resecheduled as necessary by the fResender + // for -hacking- purposes we'l do it just as we write packets, + // but we won't be able to play low bit-rate movies ( like MIDI ) + // until this is a schedulable task + + // Send retransmits for all streams on this session + RTPStream** retransStream = NULL; + UInt32 retransStreamLen = 0; + + // + // Send retransmits if we need to + for (int streamIter = 0; fSession->GetValuePtr(qtssCliSesStreamObjects, streamIter, (void**)&retransStream, &retransStreamLen) == QTSS_NoErr; streamIter++) + { + //qtss_printf("Resending packets for stream: %d\n",(*retransStream)->fTrackID); + //qtss_printf("RTPStream::ReliableRTPWrite. Calling ResendDueEntries\n"); + if (retransStream != NULL && *retransStream != NULL) + (*retransStream)->fResender.ResendDueEntries(); + } + + if ( !fSawFirstPacket ) + { + fSawFirstPacket = true; + fStreamCumDuration = 0; + fStreamCumDuration = OS::Milliseconds() - fSession->GetPlayTime(); + //fInfoDisplayTimer.ResetToDuration( 1000 - fStreamCumDuration % 1000 ); + } + +#if RTP_PACKET_RESENDER_DEBUGGING + fResender.SetDebugInfo(fTrackID, fRemoteRTCPPort, curPacketDelay); + fBytesSentThisInterval = fResender.SpillGuts(fBytesSentThisInterval); +#endif + + if ( fResender.IsFlowControlled() ) + { +// qtss_printf("Flow controlled\n"); +#if DEBUG + if (fFlowControlStartedMsec == 0) + { + //qtss_printf("Flow control start\n"); + fFlowControlStartedMsec = OS::Milliseconds(); + } +#endif + err = QTSS_WouldBlock; + } + else + { +#if DEBUG + if (fFlowControlStartedMsec != 0) + { + fFlowControlDurationMsec += OS::Milliseconds() - fFlowControlStartedMsec; + fFlowControlStartedMsec = 0; + } +#endif + + // + // Assign a lifetime to the packet using the current delay of the packet and + // the time until this packet becomes stale. + fBytesSentThisInterval += inLen; + fResender.AddPacket( inBuffer, inLen, (SInt32) (fDropAllPacketsForThisStreamDelay - curPacketDelay) ); + + (void)fSockets->GetSocketA()->SendTo(fRemoteAddr, fRemoteRTPPort, inBuffer, inLen); + } + + + return err; +} + +void RTPStream::SetRateAdaptData(RateAdapationStreamDataFields *rateAdaptStreamData) +{ + if (NULL == rateAdaptStreamData) + return; + + fStream3GPP->SetRateAdaptationData(rateAdaptStreamData); + + QTSS_StandardRTSP_Params inParamBlock; + inParamBlock.inClientSession=fSession; + Bool16 setTargetTimeForPlayer = QTSSModuleUtils::HavePlayerProfile((void *) QTSServerInterface::GetServer()->GetPrefs(),&inParamBlock,QTSSModuleUtils::kAdjust3gppTargetTime); + if (setTargetTimeForPlayer) + fStream3GPP->SetBufferTime(QTSServerInterface::GetServer()->GetPrefs()->Get3GPPForcedTargetTime()); + +} + +void RTPStream::SetThinningParams() +{ + SInt32 toleranceAdjust = 1500 - (SInt32(fLateToleranceInSec * 1000)); + + QTSServerPrefs* thePrefs = QTSServerInterface::GetServer()->GetPrefs(); + + if (fPayloadType == qtssVideoPayloadType) + fDropAllPacketsForThisStreamDelay = thePrefs->GetDropAllVideoPacketsTimeInMsec() - toleranceAdjust; + else + fDropAllPacketsForThisStreamDelay = thePrefs->GetDropAllPacketsTimeInMsec() - toleranceAdjust; + + fThinAllTheWayDelay = thePrefs->GetThinAllTheWayTimeInMsec() - toleranceAdjust; + fAlwaysThinDelay = thePrefs->GetAlwaysThinTimeInMsec() - toleranceAdjust; + fStartThinningDelay = thePrefs->GetStartThinningTimeInMsec() - toleranceAdjust; + fStartThickingDelay = thePrefs->GetStartThickingTimeInMsec() - toleranceAdjust; + fThickAllTheWayDelay = thePrefs->GetThickAllTheWayTimeInMsec(); + fQualityCheckInterval = thePrefs->GetQualityCheckIntervalInMsec(); + fSession->fLastQualityCheckTime = 0; + fSession->fLastQualityCheckMediaTime = 0; + fSession->fStartedThinning = false; + + +} + +void RTPStream::SetInitialMaxQualityLevel() +{ + UInt32 movieBitRate = GetSession().GetMovieAvgBitrate(); + UInt32 bandwidth = GetSession().GetMaxBandwidthBits(); + if (bandwidth != 0 && movieBitRate != 0) + { + double ratio = movieBitRate / static_cast(bandwidth); + + //interpolate between ratio and fNumQualityLevels such that 0.90 maps to 0 and 3.0 maps to fNumQualityLevels + SetMaxQualityLevelLimit(static_cast(fNumQualityLevels * (ratio / 2.1 - 0.43))); + SetQualityLevel(GetQualityLevel()); + DEBUG_3GPP_PRINTF(("RTPStream::SetInitialMaxQualityLevel movieBitRate=%"_U32BITARG_", bandwidth=%"_U32BITARG_", ratio=%f, fMaxQualityLevel=%"_S32BITARG_"\n", + movieBitRate, bandwidth, ratio, fMaxQualityLevel)); + } +} + + +Bool16 RTPStream::Supports3GPPQualityLevels() +{ + + if (fStream3GPP->RateAdaptationEnabled() && !fDisableThinning) + { + return true; + } + + return false; + +} + + +Bool16 RTPStream::UpdateQualityLevel(const SInt64& inTransmitTime, const SInt64& inCurrentPacketDelay, + const SInt64& inCurrentTime, UInt32 inPacketSize) +{ + Assert(fNumQualityLevels > 0); + + if (inTransmitTime <= fSession->GetPlayTime()) + return true; + + if (this->Supports3GPPQualityLevels()) + return true; + + if (fTransportType == qtssRTPTransportTypeUDP) + return true; + + if (fSession->fLastQualityCheckTime == 0) + { + // Reset the interval for checking quality levels + fSession->fLastQualityCheckTime = inCurrentTime; + fSession->fLastQualityCheckMediaTime = inTransmitTime; + fLastCurrentPacketDelay = inCurrentPacketDelay; + return true; + } + + if (!fSession->fStartedThinning) + { + // if we're still behind but not falling further behind, then don't thin + if ((inCurrentPacketDelay > fStartThinningDelay) && (inCurrentPacketDelay - fLastCurrentPacketDelay < 250)) + { + if (inCurrentPacketDelay < fLastCurrentPacketDelay) + fLastCurrentPacketDelay = inCurrentPacketDelay; + return true; + } + else + { fSession->fStartedThinning = true; + } + } + + if ((fSession->fLastQualityCheckTime == 0) || (inCurrentPacketDelay > fThinAllTheWayDelay)) + { + // + // Reset the interval for checking quality levels + fSession->fLastQualityCheckTime = inCurrentTime; + fSession->fLastQualityCheckMediaTime = inTransmitTime; + fLastCurrentPacketDelay = inCurrentPacketDelay; + + if (inCurrentPacketDelay > fThinAllTheWayDelay ) + { + // + // If we have fallen behind enough such that we risk trasmitting + // stale packets to the client, AGGRESSIVELY thin the stream + this->SetMinQuality(); +// if (fPayloadType == qtssVideoPayloadType) +// qtss_printf("Q=%d, delay = %qd\n", GetQualityLevel(), inCurrentPacketDelay); + if (inCurrentPacketDelay > fDropAllPacketsForThisStreamDelay) + { + fStalePacketsDropped++; + return false; // We should not send this packet + } + } + } + + if (fNumQualityLevels <= 2) + { + if ((inCurrentPacketDelay < fStartThickingDelay) && (GetQualityLevel() > 0)) + this->SetMaxQuality(); + + return true; // not enough quality levels to do fine tuning + } + + if (((inCurrentTime - fSession->fLastQualityCheckTime) > fQualityCheckInterval) || + ((inTransmitTime - fSession->fLastQualityCheckMediaTime) > fQualityCheckInterval)) + { + if ((inCurrentPacketDelay > fAlwaysThinDelay) && (GetQualityLevel() < (SInt32) fNumQualityLevels)) + SetQualityLevel(GetQualityLevel() + 1); + else if ((inCurrentPacketDelay > fStartThinningDelay) && (inCurrentPacketDelay > fLastCurrentPacketDelay)) + { + if (!fWaitOnLevelAdjustment && (GetQualityLevel() < (SInt32) fNumQualityLevels)) + { + SetQualityLevel(GetQualityLevel() + 1); + fWaitOnLevelAdjustment = true; + } + else + fWaitOnLevelAdjustment = false; + } + + if ((inCurrentPacketDelay < fStartThickingDelay) && (GetQualityLevel() > 0) && (inCurrentPacketDelay < fLastCurrentPacketDelay)) + { + SetQualityLevel(GetQualityLevel() - 1); + fWaitOnLevelAdjustment = true; + } + + if (inCurrentPacketDelay < fThickAllTheWayDelay) + { + this->SetMaxQuality(); + fWaitOnLevelAdjustment = false; + } + +// if (fPayloadType == qtssVideoPayloadType) +// qtss_printf("Q=%d, delay = %qd\n", GetQualityLevel(), inCurrentPacketDelay); + fLastCurrentPacketDelay = inCurrentPacketDelay; + fSession->fLastQualityCheckTime = inCurrentTime; + fSession->fLastQualityCheckMediaTime = inTransmitTime; + } + + return true; // We should send this packet +} + + +QTSS_Error RTPStream::Write(void* inBuffer, UInt32 inLen, UInt32* outLenWritten, UInt32 inFlags) +{ + Assert(fSession != NULL); + if (!fSession->GetSessionMutex()->TryLock()) + return EAGAIN; + + + QTSS_Error err = QTSS_NoErr; + SInt64 theTime = OS::Milliseconds(); + + // + // Data passed into this version of write must be a QTSS_PacketStruct + QTSS_PacketStruct* thePacket = (QTSS_PacketStruct*)inBuffer; + thePacket->suggestedWakeupTime = -1; + SInt64 theCurrentPacketDelay = theTime - thePacket->packetTransmitTime; + + //If we are doing rate-adaptation, set the maximum quality level if the bandwidth header is received + if (!fInitialMaxQualityLevelIsSet && fStream3GPP->RateAdaptationEnabled() && !fDisableThinning) + { + fInitialMaxQualityLevelIsSet = true; + SetInitialMaxQualityLevel(); + } + + // + // Empty the overbuffer window + fSession->GetOverbufferWindow()->EmptyOutWindow(theTime); + + // + // Update the bit rate value + fSession->UpdateCurrentBitRate(theTime); + + // + // Is this the first write in a write burst? + if (inFlags & qtssWriteFlagsWriteBurstBegin) + fSession->GetOverbufferWindow()->MarkBeginningOfWriteBurst(); + + if (inFlags & qtssWriteFlagsIsRTCP) + { + // + // Check to see if this packet is ready to send + if (false == *(fSession->GetOverbufferWindow()->OverbufferingEnabledPtr())) // only force rtcps on time if overbuffering is off + { + thePacket->suggestedWakeupTime = fSession->GetOverbufferWindow()->CheckTransmitTime(thePacket->packetTransmitTime, theTime, inLen); + if (thePacket->suggestedWakeupTime > theTime) + { + Assert(thePacket->suggestedWakeupTime >= fSession->GetOverbufferWindow()->GetSendInterval()); + fSession->GetSessionMutex()->Unlock();// Make sure to unlock the mutex + return QTSS_WouldBlock; + } + } + + if ( fTransportType == qtssRTPTransportTypeTCP )// write out in interleave format on the RTSP TCP channel + { + err = this->InterleavedWrite( thePacket->packetData, inLen, outLenWritten, fRTCPChannel ); + } + else if ( inLen > 0 ) + { + (void)this->fSockets->GetSocketB()->SendTo(fRemoteAddr, fRemoteRTCPPort, thePacket->packetData, inLen); + + this->UDPMonitorWrite(thePacket->packetData, inLen, kIsRTCPPacket); + + } + + + if (err == QTSS_NoErr) + this->PrintPacketPrefEnabled( (char*) thePacket->packetData, inLen, (SInt32) RTPStream::rtcpSR); + } + else if (inFlags & qtssWriteFlagsIsRTP) + { + + if (fStream3GPP->RateAdaptationEnabled()) + thePacket->suggestedWakeupTime = fStream3GPP->GetAdjustedTransmitTime(thePacket->packetTransmitTime, theTime); + else + { // + // Check to see if this packet fits in the overbuffer window + thePacket->suggestedWakeupTime = fSession->GetOverbufferWindow()->CheckTransmitTime(thePacket->packetTransmitTime, theTime, inLen); + } + DEBUG_3GPP_PRINTF(("RTPStream::Write time: %"_S64BITARG_" -> %"_S64BITARG_" (%"_S64BITARG_")\n", + thePacket->packetTransmitTime, thePacket->suggestedWakeupTime, thePacket->suggestedWakeupTime - thePacket->packetTransmitTime)); + + if (thePacket->suggestedWakeupTime > theTime) + { + // Assert(thePacket->suggestedWakeupTime >= fSession->GetOverbufferWindow()->GetSendInterval()); +#if RTP_PACKET_RESENDER_DEBUGGING + fResender.logprintf("Overbuffer window full. Num bytes in overbuffer: %d. Wakeup time: %qd\n",fSession->GetOverbufferWindow()->AvailableSpaceInWindow(), thePacket->packetTransmitTime); +#endif + //qtss_printf("Overbuffer window full. Returning: %qd\n", thePacket->suggestedWakeupTime - theTime); + + fSession->GetSessionMutex()->Unlock();// Make sure to unlock the mutex + return QTSS_WouldBlock; + } + + // + // Check to make sure our quality level is correct. This function + // also tells us whether this packet is just too old to send + if (this->UpdateQualityLevel(thePacket->packetTransmitTime, theCurrentPacketDelay, theTime, inLen)) + { + if ( fTransportType == qtssRTPTransportTypeTCP ) // write out in interleave format on the RTSP TCP channel. + err = this->InterleavedWrite( thePacket->packetData, inLen, outLenWritten, fRTPChannel ); + else if ( fTransportType == qtssRTPTransportTypeReliableUDP ) + err = this->ReliableRTPWrite( thePacket->packetData, inLen, theCurrentPacketDelay ); + else if ( inLen > 0 ) + { + (void)fSockets->GetSocketA()->SendTo(fRemoteAddr, fRemoteRTPPort, thePacket->packetData, inLen); + + this->UDPMonitorWrite(thePacket->packetData, inLen, kIsRTPPacket); + } + + if (err == QTSS_NoErr) + this->PrintPacketPrefEnabled( (char*) thePacket->packetData, inLen, (SInt32) RTPStream::rtp); + + UInt16* theSeqNumP = (UInt16*)thePacket->packetData; + UInt16 theSeqNum = ntohs(theSeqNumP[1]); + + //Add the packet sequence number and the timestamp to the list of mappings if doing 3GPP-rate-adaptation. + fStream3GPP->AddSeqNumTimeMapping(theSeqNum, thePacket->packetTransmitTime); + +#if 0 // testing + { + if (err == 0) + { + static SInt64 time = -1; + static int byteCount = 0; + static SInt64 startTime = -1; + static int totalBytes = 0; + static int numPackets = 0; + static SInt64 firstTime; + + if (theTime - time > 1000) + { + if (time != -1) + { + qtss_printf(" %qd KBit (%d in %qd secs)", byteCount * 8 * 1000 / (theTime - time) / 1024, totalBytes, (theTime - startTime) / 1000); + if (fTracker) + qtss_printf(" Window = %d\n", fTracker->CongestionWindow()); + else + qtss_printf("\n"); + qtss_printf("Packet #%d xmit time = %qd\n", numPackets, (thePacket->packetTransmitTime - firstTime) / 1000); + } + else + { + startTime = theTime; + firstTime = thePacket->packetTransmitTime; + } + + byteCount = 0; + time = theTime; + } + + byteCount += inLen; + totalBytes += inLen; + numPackets++; + + qtss_printf("Packet %d for time %qd sent at %qd (%d bytes)\n", theSeqNum, thePacket->packetTransmitTime - fSession->GetPlayTime(), theTime - fSession->GetPlayTime(), inLen); + } + } +#endif + + } + +#if RTP_PACKET_RESENDER_DEBUGGING + if (err != QTSS_NoErr) + fResender.logprintf("Flow controlled: %qd Overbuffer window: %d. Cur time %qd\n", theCurrentPacketDelay, fSession->GetOverbufferWindow()->AvailableSpaceInWindow(), theTime); + else + fResender.logprintf("Sent packet: %d. Overbuffer window: %d Transmit time %qd. Cur time %qd\n", ntohs(theSeqNum[1]), fSession->GetOverbufferWindow()->AvailableSpaceInWindow(), thePacket->packetTransmitTime, theTime); +#endif + //if (err != QTSS_NoErr) + // qtss_printf("flow controlled\n"); + if ( err == QTSS_NoErr && inLen > 0 ) + { + // Update statistics if we were actually able to send the data (don't + // update if the socket is flow controlled or some such thing) + + fSession->GetOverbufferWindow()->AddPacketToWindow(inLen); + fSession->UpdatePacketsSent(1); + fSession->UpdateBytesSent(inLen); + QTSServerInterface::GetServer()->IncrementTotalRTPBytes(inLen); + QTSServerInterface::GetServer()->IncrementTotalPackets(); + + QTSServerInterface::GetServer()->IncrementTotalLate(theCurrentPacketDelay); + QTSServerInterface::GetServer()->IncrementTotalQuality(this->GetQualityLevel()); + + // Record the RTP timestamp for RTCPs + UInt32* timeStampP = (UInt32*)(thePacket->packetData); + fLastRTPTimestamp = ntohl(timeStampP[1]); + + //stream statistics + fPacketCount++; + fByteCount += inLen; + + // Send an RTCP sender report if it's time. Again, we only want to send an + // RTCP if the RTP packet was sent sucessfully + // If doing rate-adaptation, then send an RTCP SR every seconds so that we get faster RTT feedback. + UInt32 senderReportInterval = fStream3GPP->RateAdaptationEnabled() ? kSenderReportInterval3GPPInSecs : kSenderReportIntervalInSecs; + if ((fSession->GetPlayFlags() & qtssPlayFlagsSendRTCP) && + (theTime > (fLastSenderReportTime + (senderReportInterval * 1000)))) + { + fLastSenderReportTime = theTime; + // CISCO comments + // thePacket->packetTransmissionTime is + // the expected transmission time, which + // is what we should report in RTCP for + // synchronization purposes, not theTime, + // which is the actual transmission time. + this->SendRTCPSR(thePacket->packetTransmitTime); + } + + } + } + else + { fSession->GetSessionMutex()->Unlock();// Make sure to unlock the mutex + return QTSS_BadArgument;//qtssWriteFlagsIsRTCP or qtssWriteFlagsIsRTP wasn't specified + } + + if (outLenWritten != NULL) + *outLenWritten = inLen; + + fSession->GetSessionMutex()->Unlock();// Make sure to unlock the mutex + return err; +} + + + +// SendRTCPSR is called by the session as well as the strem +// SendRTCPSR must be called from a fSession mutex protected caller +void RTPStream::SendRTCPSR(const SInt64& inTime, Bool16 inAppendBye) +{ + // This will roll over, after which payloadByteCount will be all messed up. + // But because it is a 32 bit number, that is bound to happen eventually, + // and we are limited by the RTCP packet format in that respect, so this is + // pretty much ok. + UInt32 payloadByteCount = fByteCount - (12 * fPacketCount); + + RTCPSRPacket* theSR = fSession->GetSRPacket(); + theSR->SetSSRC(fSsrc); + theSR->SetClientSSRC(fClientSSRC); + //fLastNTPTimeStamp = fSession->GetNTPPlayTime() + OS::TimeMilli_To_Fixed64Secs(inTime - fSession->GetPlayTime()); + fLastNTPTimeStamp = OS::TimeMilli_To_1900Fixed64Secs(OS::Milliseconds()); //The time value should be filled in as late as possible. + theSR->SetNTPTimestamp(fLastNTPTimeStamp); + theSR->SetRTPTimestamp(fLastRTPTimestamp); + theSR->SetPacketCount(fPacketCount); + theSR->SetByteCount(payloadByteCount); +#if RTP_PACKET_RESENDER_DEBUGGING + fResender.logprintf("Recommending ack timeout of: %d\n",fSession->GetBandwidthTracker()->RecommendedClientAckTimeout()); +#endif + theSR->SetAckTimeout(fSession->GetBandwidthTracker()->RecommendedClientAckTimeout()); + + UInt32 thePacketLen = theSR->GetSRPacketLen(); + if (inAppendBye) + thePacketLen = theSR->GetSRWithByePacketLen(); + + QTSS_Error err = QTSS_NoErr; + if ( fTransportType == qtssRTPTransportTypeTCP ) // write out in interleave format on the RTSP TCP channel + { + UInt32 wasWritten; + err = this->InterleavedWrite( theSR->GetSRPacket(), thePacketLen, &wasWritten, fRTCPChannel ); + } + else + { + void *ptr = theSR->GetSRPacket(); + err = fSockets->GetSocketB()->SendTo(fRemoteAddr, fRemoteRTCPPort, ptr, thePacketLen); + this->UDPMonitorWrite(ptr, thePacketLen, kIsRTCPPacket); + } + + + if (err == QTSS_NoErr) + this->PrintPacketPrefEnabled((char *) theSR->GetSRPacket(), thePacketLen, (SInt32) RTPStream::rtcpSR); // if we are flow controlled this packet is not sent +} + + +void RTPStream::ProcessIncomingInterleavedData(UInt8 inChannelNum, RTSPSessionInterface* inRTSPSession, StrPtrLen* inPacket) +{ + if (inChannelNum == fRTPChannel) + { + // + // Currently we don't do anything with incoming RTP packets. Eventually, + // we might need to make a role to deal with these + } + else if (inChannelNum == fRTCPChannel) + this->ProcessIncomingRTCPPacket(inPacket); +} + + +Bool16 RTPStream::ProcessNADUPacket(RTCPPacket &rtcpPacket, SInt64 &curTime, StrPtrLen ¤tPtr, UInt32 highestSeqNum) +{ + RTCPNaduPacket naduPacket(false); + UInt8* packetBuffer = rtcpPacket.GetPacketBuffer(); + UInt32 packetLen = (rtcpPacket.GetPacketLength() * 4) + RTCPPacket::kRTCPHeaderSizeInBytes; + + this->PrintPacketPrefEnabled( (char*) packetBuffer, packetLen, RTPStream::rtcpAPP); + + if (!naduPacket.ParseAPPData((UInt8*)currentPtr.Ptr, currentPtr.Len)) + return false;//abort if we discover a malformed app packet + + fStream3GPP->AddNadu((UInt8*)currentPtr.Ptr, currentPtr.Len, highestSeqNum); + + if (RTCP_TESTING) // testing + { fStream3GPP->fNaduList.DumpList(); + } + + return true; +} + +Bool16 RTPStream::ProcessCompressedQTSSPacket(RTCPPacket &rtcpPacket, SInt64 &curTime, StrPtrLen ¤tPtr) +{ + RTCPCompressedQTSSPacket compressedQTSSPacket; + UInt8* packetBuffer = rtcpPacket.GetPacketBuffer(); + UInt32 packetLen = (rtcpPacket.GetPacketLength() * 4) + RTCPPacket::kRTCPHeaderSizeInBytes; + + this->PrintPacketPrefEnabled( (char*) packetBuffer, packetLen, RTPStream::rtcpAPP); + + if (!compressedQTSSPacket.ParseAPPData((UInt8*)currentPtr.Ptr, currentPtr.Len)) + return false;//abort if we discover a malformed app packet + + + fReceiverBitRate = compressedQTSSPacket.GetReceiverBitRate(); + fAvgLateMsec = compressedQTSSPacket.GetAverageLateMilliseconds(); + + fPercentPacketsLost = compressedQTSSPacket.GetPercentPacketsLost(); + fAvgBufDelayMsec = compressedQTSSPacket.GetAverageBufferDelayMilliseconds(); + fIsGettingBetter = (UInt16)compressedQTSSPacket.GetIsGettingBetter(); + fIsGettingWorse = (UInt16)compressedQTSSPacket.GetIsGettingWorse(); + fNumEyes = compressedQTSSPacket.GetNumEyes(); + fNumEyesActive = compressedQTSSPacket.GetNumEyesActive(); + fNumEyesPaused = compressedQTSSPacket.GetNumEyesPaused(); + fTotalPacketsRecv = compressedQTSSPacket.GetTotalPacketReceived(); + fTotalPacketsDropped = compressedQTSSPacket.GetTotalPacketsDropped(); + fTotalPacketsLost = compressedQTSSPacket.GetTotalPacketsLost(); + fClientBufferFill = compressedQTSSPacket.GetClientBufferFill(); + fFrameRate = compressedQTSSPacket.GetFrameRate(); + fExpectedFrameRate = compressedQTSSPacket.GetExpectedFrameRate(); + fAudioDryCount = compressedQTSSPacket.GetAudioDryCount(); + + + // Update our overbuffer window size to match what the client is telling us + if (fTransportType != qtssRTPTransportTypeUDP) + { + // qtss_printf("Setting over buffer to %d\n", compressedQTSSPacket.GetOverbufferWindowSize()); + fSession->GetOverbufferWindow()->SetWindowSize(compressedQTSSPacket.GetOverbufferWindowSize()); + } + +#ifdef DEBUG_RTCP_PACKETS + compressedQTSSPacket.Dump(); +#endif + + return true; + +} + + +Bool16 RTPStream::ProcessAckPacket(RTCPPacket &rtcpPacket, SInt64 &curTime) +{ + RTCPAckPacket theAckPacket; + UInt8* packetBuffer = rtcpPacket.GetPacketBuffer(); + UInt32 packetLen = (rtcpPacket.GetPacketLength() * 4) + RTCPPacket::kRTCPHeaderSizeInBytes; + + if (!theAckPacket.ParseAPPData(packetBuffer, packetLen)) + return false; + + if (NULL != fTracker && false == fTracker->ReadyForAckProcessing()) // this stream must be ready to receive acks. Between RTSP setup and sending of first packet on stream we must protect against a bad ack. + return false;//abort if we receive an ack when we haven't sent anything. + + + this->PrintPacketPrefEnabled( (char*)packetBuffer, packetLen, RTPStream::rtcpACK); + // Only check for ack packets if we are using Reliable UDP + if (fTransportType == qtssRTPTransportTypeReliableUDP) + { + UInt16 theSeqNum = theAckPacket.GetAckSeqNum(); + fResender.AckPacket(theSeqNum, curTime); + //qtss_printf("Got ack: %d\n",theSeqNum); + + for (UInt16 maskCount = 0; maskCount < theAckPacket.GetAckMaskSizeInBits(); maskCount++) + { + if (theAckPacket.IsNthBitEnabled(maskCount)) + { + fResender.AckPacket( theSeqNum + maskCount + 1, curTime); + //qtss_printf("Got ack in mask: %d\n",theSeqNum + maskCount + 1); + } + } + + } + + return true; + + + +} + +Bool16 RTPStream::TestRTCPPackets(StrPtrLen* inPacketPtr, UInt32 itemName) +{ + // Testing? + if (!RTCP_TESTING) + return false; + + + itemName = RTCPNaduPacket::kNaduPacketName; + + + qtss_printf("RTPStream::TestRTCPPackets received packet inPacketPtr.Ptr=%p inPacketPtr.len =%lu\n", inPacketPtr->Ptr, inPacketPtr->Len); + + switch (itemName) + { + + case RTCPAckPacket::kAckPacketName: + case RTCPAckPacket::kAckPacketAlternateName: + { + qtss_printf ("testing RTCPAckPacket"); + RTCPAckPacket::GetTestPacket(inPacketPtr); + } + break; + + case RTCPCompressedQTSSPacket::kCompressedQTSSPacketName: + { + qtss_printf ("testing RTCPCompressedQTSSPacket"); + RTCPCompressedQTSSPacket::GetTestPacket(inPacketPtr); + } + break; + + case RTCPNaduPacket::kNaduPacketName: + { + qtss_printf ("testing RTCPNaduPacket"); + RTCPNaduPacket::GetTestPacket(inPacketPtr); + } + break; + + }; + + qtss_printf(" using packet inPacketPtr.Ptr=%p inPacketPtr.len =%lu\n", inPacketPtr->Ptr, inPacketPtr->Len); + + return true; +} + + + + +void RTPStream::ProcessIncomingRTCPPacket(StrPtrLen* inPacket) +{ + StrPtrLen currentPtr(*inPacket); + SInt64 curTime = OS::Milliseconds(); + Bool16 hasPacketLoss = false; + UInt32 highestSeqNum = 0; + Bool16 hasNADU = false; + + // Modules are guarenteed atomic access to the session. Also, the RTSP Session accessed + // below could go away at any time. So we need to lock the RTP session mutex. + // *BUT*, when this function is called the caller already has the UDP socket pool & + // UDP Demuxer mutexes. Blocking on grabbing this mutex could cause a deadlock. + // So, dump this RTCP packet if we can't get the mutex. + if (!fSession->GetSessionMutex()->TryLock()) + return; + + //no matter what happens (whether or not this is a valid packet) reset the timeouts + fSession->RefreshTimeout(); + if (fSession->GetRTSPSession() != NULL) + fSession->GetRTSPSession()->RefreshTimeout(); + + this->TestRTCPPackets(¤tPtr, 0); + + while ( currentPtr.Len > 0 ) + { + DEBUG_RTCP_PRINTF(("RTPStream::ProcessIncomingRTCPPacket start parse rtcp currentPtr.Len = %"_U32BITARG_"\n", currentPtr.Len)); + + /* + Due to the variable-type nature of RTCP packets, this is a bit unusual... + We initially treat the packet as a generic RTCPPacket in order to determine its' + actual packet type. Once that is figgered out, we treat it as its' actual packet type + */ + RTCPPacket rtcpPacket; + if (!rtcpPacket.ParsePacket((UInt8*)currentPtr.Ptr, currentPtr.Len)) + { fSession->GetSessionMutex()->Unlock(); + DEBUG_RTCP_PRINTF(("malformed rtcp packet\n")); + return;//abort if we discover a malformed RTCP packet + } + // Increment our RTCP Packet and byte counters for the session. + + fSession->IncrTotalRTCPPacketsRecv(); + fSession->IncrTotalRTCPBytesRecv( (SInt16) currentPtr.Len); + + switch (rtcpPacket.GetPacketType()) + { + case RTCPPacket::kReceiverPacketType: + { DEBUG_RTCP_PRINTF(("RTPStream::ProcessIncomingRTCPPacket kReceiverPacketType\n")); + RTCPReceiverPacket receiverPacket; + if (!receiverPacket.ParseReport((UInt8*)currentPtr.Ptr, currentPtr.Len)) + { fSession->GetSessionMutex()->Unlock(); + return;//abort if we discover a malformed receiver report + } + + this->PrintPacketPrefEnabled(currentPtr.Ptr, currentPtr.Len, RTPStream::rtcpRR); + + // + // Set the Client SSRC based on latest RTCP + fClientSSRC = rtcpPacket.GetPacketSSRC(); + + fFractionLostPackets = receiverPacket.GetCumulativeFractionLostPackets(); + fJitter = receiverPacket.GetCumulativeJitter(); + + UInt32 curTotalLostPackets = receiverPacket.GetCumulativeTotalLostPackets(); + + // Workaround for client problem. Sometimes it appears to report a bogus lost packet count. + // Since we can't have lost more packets than we sent, ignore the packet if that seems to be the case. + if (curTotalLostPackets - fTotalLostPackets <= fPacketCount - fLastPacketCount) + { + // if current value is less than the old value, that means that the packets are out of order + // just wait for another packet that arrives in the right order later and for now, do nothing + if (curTotalLostPackets > fTotalLostPackets) + { + //increment the server total by the new delta + QTSServerInterface::GetServer()->IncrementTotalRTPPacketsLost(curTotalLostPackets - fTotalLostPackets); + fCurPacketsLostInRTCPInterval = curTotalLostPackets - fTotalLostPackets; + // qtss_printf("fCurPacketsLostInRTCPInterval = %d\n", fCurPacketsLostInRTCPInterval); + fTotalLostPackets = curTotalLostPackets; + hasPacketLoss = true; + } + else if(curTotalLostPackets == fTotalLostPackets) + { + fCurPacketsLostInRTCPInterval = 0; + // qtss_printf("fCurPacketsLostInRTCPInterval set to 0\n"); + } + + + fPacketCountInRTCPInterval = fPacketCount - fLastPacketCount; + fLastPacketCount = fPacketCount; + } + + //Marks down the highest sequence number received and calculates the RTT from the DLSR and the LSR + if (receiverPacket.GetReportCount() > 0) + { + highestSeqNum = receiverPacket.GetHighestSeqNumReceived(0); + + UInt32 lsr = receiverPacket.GetLastSenderReportTime(0); + UInt32 dlsr = receiverPacket.GetLastSenderReportDelay(0); + + if (lsr != 0) + { + UInt32 diff = static_cast(OS::TimeMilli_To_1900Fixed64Secs(curTime) >> 16) - lsr - dlsr; + UInt32 measuredRTT = static_cast(OS::Fixed64Secs_To_TimeMilli(static_cast(diff) << 16)); + + if (measuredRTT < 60000) //make sure that the RTT is not some ridiculously large value + { + fEstRTT = fEstRTT == 0 ? measuredRTT : MIN(measuredRTT, fEstRTT); + fStream3GPP->SetRTT(fEstRTT, measuredRTT); + } + DEBUG_3GPP_PRINTF(("RTPStream::ProcessIncomingRTCPPacket measuredRTT=%"_U32BITARG_", fEstRTT=%"_U32BITARG_"\n", measuredRTT, fEstRTT)); + } + } + +#ifdef DEBUG_RTCP_PACKETS + receiverPacket.Dump(); +#endif + } + break; + + case RTCPPacket::kAPPPacketType: + { + DEBUG_RTCP_PRINTF(("RTPStream::ProcessIncomingRTCPPacket kAPPPacketType\n")); + Bool16 packetOK = false; + RTCPAPPPacket theAPPPacket; + if (!theAPPPacket.ParseAPPPacket((UInt8*)currentPtr.Ptr, currentPtr.Len)) + { + fSession->GetSessionMutex()->Unlock(); + return;//abort if we discover a malformed receiver report + } + UInt32 itemName = theAPPPacket.GetAppPacketName(); + itemName = theAPPPacket.GetAppPacketName(); + switch (itemName) + { + + case RTCPAckPacket::kAckPacketName: + case RTCPAckPacket::kAckPacketAlternateName: + { + packetOK = this->ProcessAckPacket(rtcpPacket, curTime); + } + break; + + case RTCPCompressedQTSSPacket::kCompressedQTSSPacketName: + { + packetOK = this->ProcessCompressedQTSSPacket(rtcpPacket, curTime, currentPtr); + } + break; + + case RTCPNaduPacket::kNaduPacketName: + { + packetOK = this->ProcessNADUPacket(rtcpPacket, curTime, currentPtr, highestSeqNum); + hasNADU = true; + } + break; + + default: + { + + } + break; + } + + if (!packetOK) + { + fSession->GetSessionMutex()->Unlock(); + return;//abort if we discover a malformed receiver report + } + + } + break; + + case RTCPPacket::kSDESPacketType: + { + DEBUG_RTCP_PRINTF(("RTPStream::ProcessIncomingRTCPPacket kSDESPacketType\n")); +#ifdef DEBUG_RTCP_PACKETS + SourceDescriptionPacket sdesPacket; + if (!sdesPacket.ParsePacket((UInt8*)currentPtr.Ptr, currentPtr.Len)) + { fSession->GetSessionMutex()->Unlock(); + return;//abort if we discover a malformed app packet + } + + sedsPacket.Dump(); +#endif + } + break; + + default: + DEBUG_RTCP_PRINTF(("RTPStream::ProcessIncomingRTCPPacket Unknown Packet Type\n")); + // WarnV(false, "Unknown RTCP Packet Type"); + break; + + } + + + currentPtr.Ptr += (rtcpPacket.GetPacketLength() * 4 ) + 4; + currentPtr.Len -= (rtcpPacket.GetPacketLength() * 4 ) + 4; + + DEBUG_RTCP_PRINTF(("RTPStream::ProcessIncomingRTCPPacket end parse rtcp currentPtr.Len = %"_U32BITARG_"\n", currentPtr.Len)); + } + + Float32 packetLostPercent = ((Float32) fCurPacketsLostInRTCPInterval / (Float32) fPacketCountInRTCPInterval); + if (hasPacketLoss) + { + DEBUG_3GPP_PRINTF(("RTPStream::ProcessIncomingRTCPPacket fCurPacketsLostInRTCPInterval=%"_U32BITARG_" packetLostPercent=%.0f%%\n", + fCurPacketsLostInRTCPInterval,packetLostPercent * 100)); + fStream3GPP->SetPacketLoss(packetLostPercent); + + } + + if( hasNADU && fStream3GPP->RateAdaptationEnabled() ) + fStream3GPP->UpdateTimeAndQuality(curTime); + + // Invoke the RTCP modules, allowing them to process this packet + QTSS_RoleParams theParams; + theParams.rtcpProcessParams.inRTPStream = this; + theParams.rtcpProcessParams.inClientSession = fSession; + theParams.rtcpProcessParams.inRTCPPacketData = inPacket->Ptr; + theParams.rtcpProcessParams.inRTCPPacketDataLen = inPacket->Len; + + // We don't allow async events from this role, so just set an empty module state. + OSThreadDataSetter theSetter(&sRTCPProcessModuleState, NULL); + + // Invoke RTCP processing modules + for (UInt32 x = 0; x < QTSServerInterface::GetNumModulesInRole(QTSSModule::kRTCPProcessRole); x++) + (void)QTSServerInterface::GetModule(QTSSModule::kRTCPProcessRole, x)->CallDispatch(QTSS_RTCPProcess_Role, &theParams); + + fSession->GetSessionMutex()->Unlock(); +} + +char* RTPStream::GetStreamTypeStr() +{ + char *streamType = NULL; + + switch (fTransportType) + { + case qtssRTPTransportTypeUDP: streamType = RTPStream::UDP; + break; + + case qtssRTPTransportTypeReliableUDP: streamType = RTPStream::RUDP; + break; + + case qtssRTPTransportTypeTCP: streamType = RTPStream::TCP; + break; + + default: + streamType = RTPStream::noType; + }; + + return streamType; +} + +void RTPStream::PrintRTP(char* packetBuff, UInt32 inLen) +{ + + UInt16 sequence = ntohs( ((UInt16*)packetBuff)[1]); + UInt32 timestamp = ntohl( ((UInt32*)packetBuff)[1]); + UInt32 ssrc = ntohl( ((UInt32*)packetBuff)[2]); + + + + if (fFirstTimeStamp == 0) + fFirstTimeStamp = timestamp; + + Float32 rtpTimeInSecs = 0.0; + if (fTimescale > 0 && fFirstTimeStamp < timestamp) + rtpTimeInSecs = (Float32) (timestamp - fFirstTimeStamp) / (Float32) fTimescale; + + + StrPtrLen *payloadStr = this->GetValue(qtssRTPStrPayloadName); + if (payloadStr && payloadStr->Len > 0) + payloadStr->PrintStr(); + else + qtss_printf("?"); + + + qtss_printf(" H_ssrc=%"_S32BITARG_" H_seq=%u H_ts=%"_U32BITARG_" seq_count=%"_U32BITARG_" ts_secs=%.3f \n", ssrc, sequence, timestamp, fPacketCount +1, rtpTimeInSecs ); + +} + + +void RTPStream::PrintRTCPSenderReport(char* packetBuff, UInt32 inLen) +{ + + char timebuffer[kTimeStrSize]; + UInt32* theReport = (UInt32*)packetBuff; + + theReport++; + UInt32 ssrc = htonl(*theReport); + + theReport++; + SInt64 ntp = 0; + ::memcpy(&ntp, theReport, sizeof(SInt64)); + ntp = OS::NetworkToHostSInt64(ntp); + time_t theTime = OS::Time1900Fixed64Secs_To_UnixTimeSecs(ntp); + + theReport += 2; + UInt32 timestamp = ntohl(*theReport); + Float32 theTimeInSecs = 0.0; + + if (fFirstTimeStamp == 0) + fFirstTimeStamp = timestamp; + + if (fTimescale > 0 && fFirstTimeStamp < timestamp ) + theTimeInSecs = (Float32) (timestamp - fFirstTimeStamp) / (Float32) fTimescale; + + theReport++; + UInt32 packetcount = ntohl(*theReport); + + theReport++; + UInt32 bytecount = ntohl(*theReport); + + StrPtrLen *payloadStr = this->GetValue(qtssRTPStrPayloadName); + if (payloadStr && payloadStr->Len > 0) + payloadStr->PrintStr(); + else + qtss_printf("?"); + + qtss_printf(" H_ssrc=%"_U32BITARG_" H_bytes=%"_U32BITARG_" H_ts=%"_U32BITARG_" H_pckts=%"_U32BITARG_" ts_secs=%.3f H_ntp=%s\n", + ssrc,bytecount, timestamp, packetcount, theTimeInSecs, ::qtss_ctime( &theTime,timebuffer,sizeof(timebuffer))); + } + +void RTPStream::PrintPacket(char *inBuffer, UInt32 inLen, SInt32 inType) +{ + static char* rr="RR"; + static char* ack="ACK"; + static char* sTypeAudio=" type=audio"; + static char* sTypeVideo=" type=video"; + static char* sUnknownTypeStr = "?"; + char* theType = sUnknownTypeStr; + + if (fPayloadType == qtssVideoPayloadType) + theType = sTypeVideo; + else if (fPayloadType == qtssAudioPayloadType) + theType = sTypeAudio; + + switch (inType) + { + case RTPStream::rtp: + if (QTSServerInterface::GetServer()->GetPrefs()->PrintRTPHeaders()) + { + qtss_printf("\n"); + qtss_printf("fSession->GetUniqueID(), this->GetStreamTypeStr(), this->GetStreamStartTimeSecs(), theType, inLen); + PrintRTP(inBuffer, inLen); + } + break; + + case RTPStream::rtcpSR: + if (QTSServerInterface::GetServer()->GetPrefs()->PrintSRHeaders()) + { + qtss_printf("\n"); + qtss_printf("fSession->GetUniqueID(), this->GetStreamTypeStr(), this->GetStreamStartTimeSecs(), theType, inLen); + PrintRTCPSenderReport(inBuffer, inLen); + } + break; + + case RTPStream::rtcpRR: + if (QTSServerInterface::GetServer()->GetPrefs()->PrintRRHeaders()) + { + RTCPReceiverPacket rtcpRR; + if (rtcpRR.ParseReport( (UInt8*) inBuffer, inLen)) + { + qtss_printf("\n"); + qtss_printf(">recv sess=%"_U32BITARG_": RTCP %s recv_sec=%.3f %s size=%"_U32BITARG_" ",this->fSession->GetUniqueID(), rr, this->GetStreamStartTimeSecs(), theType, inLen); + rtcpRR.Dump(); + } + } + break; + + case RTPStream::rtcpAPP: + if (QTSServerInterface::GetServer()->GetPrefs()->PrintAPPHeaders()) + { + Bool16 debug = true; + + RTCPAPPPacket appPacket; + if (!appPacket.ParseAPPPacket((UInt8*)inBuffer, inLen)) + break; + + UInt32 itemName = appPacket.GetAppPacketName(); + + if (RTCPCompressedQTSSPacket::kCompressedQTSSPacketName == itemName) + { + qtss_printf(">recv sess=%"_U32BITARG_": RTCP APP QTSS recv_sec=%.3f %s size=%"_U32BITARG_" ",this->fSession->GetUniqueID(), this->GetStreamStartTimeSecs(), theType, inLen); + RTCPCompressedQTSSPacket compressedQTSSPacket(debug); + if (compressedQTSSPacket.ParseAPPData((UInt8*)inBuffer, inLen)) + { + compressedQTSSPacket.Dump(); + } + break; + } + + if (RTCPNaduPacket::kNaduPacketName == itemName) + { + qtss_printf(">recv sess=%"_U32BITARG_": RTCP APP NADU recv_sec=%.3f %s size=%"_U32BITARG_" ",this->fSession->GetUniqueID(), this->GetStreamStartTimeSecs(), theType, inLen); + RTCPNaduPacket naduPacket(debug); + if (naduPacket.ParseAPPData((UInt8*)inBuffer, inLen)) + { + naduPacket.Dump(); + + } + + break; + } + + //unknown app packet + qtss_printf(">recv sess=%"_U32BITARG_": RTCP APP %c%c%c%c recv_sec=%.3f %s size=%"_U32BITARG_" ", this->fSession->GetUniqueID(), ((UInt8*) &itemName)[0],(char) ((UInt8*) &itemName)[1],(char) ((UInt8*) &itemName)[2],(char) ((UInt8*) &itemName)[3], this->GetStreamStartTimeSecs(), theType, inLen); + qtss_printf("unknown APP packet: "); + appPacket.Dump(); + + + } + break; + + case RTPStream::rtcpACK: + if (QTSServerInterface::GetServer()->GetPrefs()->PrintACKHeaders()) + { + RTCPAckPacket rtcpAck; + if (rtcpAck.ParseAPPData((UInt8*)inBuffer,inLen)) + { + qtss_printf(">recv sess=%"_U32BITARG_": RTCP %s recv_sec=%.3f %s size=%"_U32BITARG_" ",this->fSession->GetUniqueID(), ack, this->GetStreamStartTimeSecs(), theType, inLen); + rtcpAck.Dump(); + } + } + break; + + } +} diff --git a/Server.tproj/RTPStream.h b/Server.tproj/RTPStream.h new file mode 100644 index 0000000..fdbea7d --- /dev/null +++ b/Server.tproj/RTPStream.h @@ -0,0 +1,406 @@ +/* + * + * @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: RTPStream.h + + Contains: Represents a single client stream (audio, video, etc). + Control API is similar to overall session API. + + Contains all stream-specific data & resources, used by Session when it + wants to send out or receive data for this stream + + This is also the class that implements the RTP stream dictionary + for QTSS API. + + +*/ + +#ifndef __RTPSTREAM_H__ +#define __RTPSTREAM_H__ + +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "QTSS_Private.h" + +#include "UDPDemuxer.h" +#include "UDPSocketPool.h" + +#include "RTSPRequestInterface.h" +#include "RTPSessionInterface.h" + +#include "RTPPacketResender.h" +#include "QTSServerInterface.h" + +#include "RTPStream3GPP.h" + +#include "RTCPPacket.h" + +#include "RTSPRequest3GPP.h" + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif /* MIN */ +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif /* MAX */ + +class RTPStream : public QTSSDictionary, public UDPDemuxerTask +{ + public: + + // Initializes dictionary resources + static void Initialize(); + + // + // CONSTRUCTOR / DESTRUCTOR + + RTPStream(UInt32 inSSRC, RTPSessionInterface* inSession); + virtual ~RTPStream(); + + // + //ACCESS FUNCTIONS + + UInt32 GetSSRC() { return fSsrc; } + UInt8 GetRTPChannelNum() { return fRTPChannel; } + UInt8 GetRTCPChannelNum() { return fRTCPChannel; } + RTPPacketResender* GetResender() { return &fResender; } + QTSS_RTPTransportType GetTransportType() { return fTransportType; } + UInt32 GetStalePacketsDropped() { return fStalePacketsDropped; } + UInt32 GetTotalPacketsRecv() { return fTotalPacketsRecv; } + UInt32 GetSDPStreamID() { return fTrackID; } //streamID is trackID + RTPSessionInterface &GetSession() { return *fSession; } + + // Setup uses the info in the RTSPRequestInterface to associate + // all the necessary resources, ports, sockets, etc, etc, with this + // stream. + QTSS_Error Setup(RTSPRequestInterface* request, QTSS_AddStreamFlags inFlags); + + // Write sends RTP data to the client. Caller must specify + // either qtssWriteFlagsIsRTP or qtssWriteFlagsIsRTCP + virtual QTSS_Error Write(void* inBuffer, UInt32 inLen, + UInt32* outLenWritten, QTSS_WriteFlags inFlags); + + + //UTILITY FUNCTIONS: + //These are not necessary to call and do not manipulate the state of the + //stream. They may, however, be useful services exported by the server + + // Formats a standard setup response. + void SendSetupResponse(RTSPRequestInterface* request); + + //Formats a transport header for this stream. + void AppendTransport(RTSPRequestInterface* request); + + //Formats a RTP-Info header for this stream. + //Isn't useful unless you've already called Play() + void AppendRTPInfo(QTSS_RTSPHeader inHeader, + RTSPRequestInterface* request, UInt32 inFlags, Bool16 lastInfo); + + // + // When we get an incoming Interleaved Packet for this stream, this + // function should be called + void ProcessIncomingInterleavedData(UInt8 inChannelNum, RTSPSessionInterface* inRTSPSession, StrPtrLen* inPacket); + + //When we get a new RTCP packet, we can directly invoke the RTP session and tell it + //to process the packet right now! + void ProcessIncomingRTCPPacket(StrPtrLen* inPacket); + + //Process the incoming ack RTCP packet + Bool16 ProcessAckPacket(RTCPPacket &rtcpPacket, SInt64 &curTime); + + //Process the incoming qtss app RTCP packet + Bool16 ProcessCompressedQTSSPacket(RTCPPacket &rtcpPacket, SInt64 &curTime, StrPtrLen ¤tPtr); + + Bool16 ProcessNADUPacket(RTCPPacket &rtcpPacket, SInt64 &curTime, StrPtrLen ¤tPtr, UInt32 highestSeqNum); + + + // Send a RTCP SR on this stream. Pass in true if this SR should also have a BYE + void SendRTCPSR(const SInt64& inTime, Bool16 inAppendBye = false); + + // + // Retransmits get sent when there is new data to be sent, but this function + // should be called periodically even if there is no new packet data, as + // the pipe should have a steady stream of data in it. + void SendRetransmits(); + + // + // Update the thinning parameters for this stream to match current prefs + void SetThinningParams(); + + // + // Reset the delay parameters that are stored for the thinning calculations + void ResetThinningDelayParams() { fLastCurrentPacketDelay = 0; } + + void SetLateTolerance(Float32 inLateToleranceInSec) { fLateToleranceInSec = inLateToleranceInSec; } + + void EnableSSRC() { fEnableSSRC = true; } + void DisableSSRC() { fEnableSSRC = false; } + + void SetRateAdaptData(RateAdapationStreamDataFields *rateAdaptStreamData); + void SetBitRateData(UInt32 movieBitRate) { fStream3GPP->SetBitRateData(movieBitRate); } + + //Tells the stream that it has been paused; the next Write will restart the stream. + void Pause() { fStream3GPP->Pause(); } + + void SetMinQuality() { SetQualityLevel(fNumQualityLevels); } + void SetMaxQuality() { SetQualityLevel(kMaxQualityLevel); } + SInt32 GetQualityLevel(); + void SetQualityLevel(SInt32 level); + void HalveQualityLevel() + { + UInt32 minLevel = fNumQualityLevels - 1; + SetQualityLevel(minLevel - (minLevel - GetQualityLevel()) / 2); + } + void SetMaxQualityLevelLimit(SInt32 newMaxLimit) //Changes what is the best quality level possible + { + SInt32 minLevel = MAX(0, (SInt32) fNumQualityLevels - 2); //do not drop down to key frames + fMaxQualityLevel = MAX(MIN(minLevel, newMaxLimit), 0); + SetQualityLevel(GetQualityLevel()); + } + + SInt32 GetMaxQualityLevelLimit() { return fMaxQualityLevel; } + + UInt32 GetNumQualityLevels() { return fNumQualityLevels; } + QTSS_RTPPayloadType GetPayLoadType() { return fPayloadType; } + + private: + + enum + { + kMaxSsrcSizeInBytes = 12, + kMaxStreamURLSizeInBytes = 32, + kDefaultPayloadBufSize = 32, + kSenderReportIntervalInSecs = 7, + kSenderReportInterval3GPPInSecs = 1, + kNumPrebuiltChNums = 10, + kMaxQualityLevel = 0, + kIsRTCPPacket = TRUE, + kIsRTPPacket = FALSE + }; + + SInt64 fLastQualityChange; + SInt32 fQualityInterval; + + //either pointers to the statically allocated sockets (maintained by the server) + //or fresh ones (only fresh in extreme special cases) + UDPSocketPair* fSockets; + RTPSessionInterface* fSession; + + // info for kinda reliable UDP + //DssDurationTimer fInfoDisplayTimer; + SInt32 fBytesSentThisInterval; + SInt32 fDisplayCount; + Bool16 fSawFirstPacket; + SInt64 fStreamCumDuration; + // manages UDP retransmits + RTPPacketResender fResender; + RTPBandwidthTracker* fTracker; + + + //who am i sending to? + UInt32 fRemoteAddr; + UInt16 fRemoteRTPPort; + UInt16 fRemoteRTCPPort; + UInt16 fLocalRTPPort; + UInt32 fMonitorAddr; + int fMonitorSocket; + UInt32 fPlayerToMonitorAddr; + + //RTCP stuff + SInt64 fLastSenderReportTime; + UInt32 fPacketCount; + UInt32 fLastPacketCount; + UInt32 fPacketCountInRTCPInterval; + UInt32 fByteCount; + + // DICTIONARY ATTRIBUTES + + //Module assigns a streamID to this object + UInt32 fTrackID; + + //low-level RTP stuff + UInt32 fSsrc; + char fSsrcString[kMaxSsrcSizeInBytes]; + StrPtrLen fSsrcStringPtr; + Bool16 fEnableSSRC; + + //Payload name and codec type. + char fPayloadNameBuf[kDefaultPayloadBufSize]; + QTSS_RTPPayloadType fPayloadType; + + //Media information. + UInt16 fFirstSeqNumber;//used in sending the play response + UInt32 fFirstTimeStamp;//RTP time + UInt32 fTimescale; + + //what is the URL for this stream? + char fStreamURL[kMaxStreamURLSizeInBytes]; + StrPtrLen fStreamURLPtr; + + SInt32 fQualityLevel; + UInt32 fNumQualityLevels; + + UInt32 fLastRTPTimestamp; + SInt64 fLastNTPTimeStamp; + UInt32 fEstRTT; //The estimated RTT calculated from RTCP's DLSR and LSR fields + + // RTCP data + UInt32 fFractionLostPackets; + UInt32 fTotalLostPackets; + UInt32 fJitter; + UInt32 fReceiverBitRate; + UInt16 fAvgLateMsec; + UInt16 fPercentPacketsLost; + UInt16 fAvgBufDelayMsec; + UInt16 fIsGettingBetter; + UInt16 fIsGettingWorse; + UInt32 fNumEyes; + UInt32 fNumEyesActive; + UInt32 fNumEyesPaused; + UInt32 fTotalPacketsRecv; + UInt32 fPriorTotalPacketsRecv; + UInt16 fTotalPacketsDropped; + UInt16 fTotalPacketsLost; + UInt32 fCurPacketsLostInRTCPInterval; + UInt16 fClientBufferFill; + UInt16 fFrameRate; + UInt16 fExpectedFrameRate; + UInt16 fAudioDryCount; + UInt32 fClientSSRC; + + Bool16 fIsTCP; + QTSS_RTPTransportType fTransportType; + + // HTTP params + // Each stream has a set of thinning related tolerances, + // that are dependent on prefs and parameters in the SETUP. + // These params, as well as the current packet delay determine + // whether a packet gets dropped. + SInt32 fTurnThinningOffDelay_TCP; + SInt32 fIncreaseThinningDelay_TCP; + SInt32 fDropAllPacketsForThisStreamDelay_TCP; + UInt32 fStalePacketsDropped_TCP; + SInt64 fTimeStreamCaughtUp_TCP; + SInt64 fLastQualityLevelIncreaseTime_TCP; + // + // Each stream has a set of thinning related tolerances, + // that are dependent on prefs and parameters in the SETUP. + // These params, as well as the current packet delay determine + // whether a packet gets dropped. + SInt32 fThinAllTheWayDelay; + SInt32 fAlwaysThinDelay; + SInt32 fStartThinningDelay; + SInt32 fStartThickingDelay; + SInt32 fThickAllTheWayDelay; + SInt32 fQualityCheckInterval; + SInt32 fDropAllPacketsForThisStreamDelay; + UInt32 fStalePacketsDropped; + SInt64 fLastCurrentPacketDelay; + Bool16 fWaitOnLevelAdjustment; + + Float32 fBufferDelay; // from the sdp + Float32 fLateToleranceInSec; + + // Pointer to the stream ref (this is just a this pointer) + QTSS_StreamRef fStreamRef; + + UInt32 fCurrentAckTimeout; + SInt32 fMaxSendAheadTimeMSec; + +#if DEBUG + UInt32 fNumPacketsDroppedOnTCPFlowControl; + SInt64 fFlowControlStartedMsec; + SInt64 fFlowControlDurationMsec; +#endif + + // If we are interleaving RTP data over the TCP connection, + // these are channel numbers to use for RTP & RTCP + UInt8 fRTPChannel; + UInt8 fRTCPChannel; + + QTSS_RTPNetworkMode fNetworkMode; + + SInt64 fStreamStartTimeOSms; + + RTPStream3GPP* fStream3GPP; + + SInt32 fLastQualityLevel; + SInt32 fLastRateLevel; + + Bool16 fDisableThinning; + SInt64 fLastQualityUpdate; + UInt32 fDefaultQualityLevel; + SInt32 fMaxQualityLevel; + Bool16 fInitialMaxQualityLevelIsSet; + Bool16 fUDPMonitorEnabled; + UInt16 fMonitorVideoDestPort; + UInt16 fMonitorAudioDestPort; + + //----------------------------------------------------------- + // acutally write the data out that way + QTSS_Error InterleavedWrite(void* inBuffer, UInt32 inLen, UInt32* outLenWritten, unsigned char channel ); + + // implements the ReliableRTP protocol + QTSS_Error ReliableRTPWrite(void* inBuffer, UInt32 inLen, const SInt64& curPacketDelay); + + + void SetTCPThinningParams(); + QTSS_Error TCPWrite(void* inBuffer, UInt32 inLen, UInt32* outLenWritten, UInt32 inFlags); + + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + static StrPtrLen sChannelNums[]; + static QTSS_ModuleState sRTCPProcessModuleState; + + static char *noType; + static char *UDP; + static char *RUDP; + static char *TCP; + + Bool16 UpdateQualityLevel(const SInt64& inTransmitTime, const SInt64& inCurrentPacketDelay, + const SInt64& inCurrentTime, UInt32 inPacketSize); + + void DisableThinning() { fDisableThinning = true; } + void Update3GPPQualityLevels(QTSS_PacketStruct* thePacket, SInt64 theTime); + Bool16 Supports3GPPQualityLevels(); + void SetInitialMaxQualityLevel(); + + char *GetStreamTypeStr(); + enum { rtp = 0, rtcpSR = 1, rtcpRR = 2, rtcpACK = 3, rtcpAPP = 4 }; + Float32 GetStreamStartTimeSecs() { return (Float32) ((OS::Milliseconds() - this->fSession->GetSessionCreateTime())/1000.0); } + void PrintPacket(char *inBuffer, UInt32 inLen, SInt32 inType); + void PrintRTP(char* packetBuff, UInt32 inLen); + void PrintRTCPSenderReport(char* packetBuff, UInt32 inLen); +inline void PrintPacketPrefEnabled(char *inBuffer,UInt32 inLen, SInt32 inType) { if (QTSServerInterface::GetServer()->GetPrefs()->PacketHeaderPrintfsEnabled() ) this->PrintPacket(inBuffer,inLen, inType); } + + void SetOverBufferState(RTSPRequestInterface* request); + + Bool16 TestRTCPPackets(StrPtrLen* inPacketPtr, UInt32 itemName); + + void UDPMonitorWrite(void* thePacketData, UInt32 inLen, Bool16 isRTCP); + + +}; + +#endif // __RTPSTREAM_H__ diff --git a/Server.tproj/RTPStream3GPP.cpp b/Server.tproj/RTPStream3GPP.cpp new file mode 100644 index 0000000..a1c4ea7 --- /dev/null +++ b/Server.tproj/RTPStream3GPP.cpp @@ -0,0 +1,549 @@ +/* + * + * @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: RTPStream3gpp.cpp + + Contains: Implementation of RTPStream3gpp class. +*/ + +#include +#include "SafeStdLib.h" +#include "OS.h" +#include "RTPStream.h" +#include "RTPStream3GPP.h" +#include "RTPSessionInterface.h" +#include "RTSPRequest3GPP.h" +#include "RTCPAPPNADUPacket.h" + +#if DEBUG + #define RTP_STREAM_3GPP_DEBUG 1 +#else + #define RTP_STREAM_3GPP_DEBUG 0 +#endif + +#if RTP_STREAM_3GPP_DEBUG + #define DEBUG_PRINTF(s) qtss_printf s +#else + #define DEBUG_PRINTF(s) if (GetDebugPrintfs()) qtss_printf s +#endif + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif /* MIN */ +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif /* MAX */ + +QTSSAttrInfoDict::AttrInfo RTPStream3GPP::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtss3GPPStreamEnabled", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtss3GPPStreamRateAdaptBufferBytes", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 2 */ { "qtss3GPPStreamRateAdaptTimeMilli", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe } + +}; + + +void RTPStream3GPP::Initialize() +{ + for (int x = 0; x < qtss3GPPStreamNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::k3GPPStreamDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); +} + + +RTPStream3GPP::RTPStream3GPP(RTPStream &owner, Bool16 enabled) +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::k3GPPStreamDictIndex), NULL), + fRTPStream(owner), + fEnabled (enabled), + fHasRateAdaptData(false), + fBufferSize(0), + fTargetBufferingDelay(0), + fMovieBitRate(0), + fStartDoingAdaptation(false), + fNumRTCPWithoutPacketLoss(kUInt32_Max / 4), + fNumLargeRTT(0), + fNumSmallRTT(0), + fCurRTT(0), + fLastReportedFreeBufferSpace(0), + fLastReportedBufferingDelay(0), + fLastLocalTime(0), + fPlayTimeOffset(0), + fAdjustSize(kNoChange), + fAdjustTime(kNoChange), + fLastSeqNum(0), + fDebugPrintfs(QTSServerInterface::GetServer()->GetPrefs()->Get3GPPDebugPrintfs()) +{ + this->SetVal(qtss3GPPStreamEnabled, &fEnabled, sizeof(fEnabled)); + this->SetVal(qtss3GPPStreamRateAdaptBufferBytes, &fBufferSize, sizeof(fBufferSize)); + this->SetVal(qtss3GPPStreamRateAdaptTimeMilli, &fTargetBufferingDelay, sizeof(fTargetBufferingDelay)); + fNaduList.Initialize(kNaduListSize); +} + +void RTPStream3GPP::AddNadu(UInt8* inPacketBuffer, UInt32 inPacketLength, UInt32 highestSeqNum) +{ + if (!RateAdaptationEnabled()) + return; + + UInt32 id; + fNaduList.AddReport(inPacketBuffer, inPacketLength, &id); + fLastReportedFreeBufferSpace = fNaduList.LastReportedFreeBuffSizeBytes(); + UInt32 lastReportedPlayoutDelay = fNaduList.LastReportedTimeDelayMilli(); + + //Overwrites the high 16 bits of the highest sequence number with what we think it should be since some clients report the wrong high 16 bits. + if (highestSeqNum != 0) + highestSeqNum = ExtendSeqNum(static_cast(highestSeqNum), fLastSeqNum); + + UInt32 nextSeqNum = ExtendSeqNum(fNaduList.GetLastReportedNSN(), highestSeqNum); //Extend the sequence number based on the highest sequence number + + if (nextSeqNum <= highestSeqNum) + { + SInt64 nextPacketTime = 0; + SInt64 highestPacketTime = 0; + + //Go through the list of sequence numbers to time mapping and find the two sequence numbers, and kick off all the sequence numbers + //less than next sequence number since they are not needed anymore. + for(UInt32 i = 0; i < fSeqNumTimeMapping.size();) + { + if (fSeqNumTimeMapping[i].fSeqNum < nextSeqNum) + fSeqNumTimeMapping.swap_erase(i); + else + { + if (fSeqNumTimeMapping[i].fSeqNum == nextSeqNum) + nextPacketTime = fSeqNumTimeMapping[i].fTime; + if (fSeqNumTimeMapping[i].fSeqNum == highestSeqNum) + highestPacketTime = fSeqNumTimeMapping[i].fTime; + ++i; + // not sure about this but it means if highestSeqNum is the same as nextSeqNum, maybe they are both 0 as in nothing to report. + if (nextPacketTime != 0 && highestPacketTime != 0) + break; + } + } + + if (nextPacketTime == 0 || highestPacketTime == 0) + fLastReportedBufferingDelay = kUInt32_Max; + else + fLastReportedBufferingDelay = static_cast(highestPacketTime - nextPacketTime); + } + else if (nextSeqNum == highestSeqNum + 1) //The client buffer is empty! + { + fLastReportedBufferingDelay = 0; + } + else //error! + fLastReportedBufferingDelay = kUInt32_Max; + + //Add the playout delay to the buffering delay + if (fLastReportedBufferingDelay == kUInt32_Max) + { + if (highestSeqNum != 0) + DEBUG_PRINTF(( + "RTPStream3GPP::AddNadu sequence number error: seq=%hu, nextSeqNum=%"_U32BITARG_", highestSeqNum=%"_U32BITARG_"\n", + fNaduList.GetLastReportedNSN(), nextSeqNum, highestSeqNum)); + } + else if (lastReportedPlayoutDelay != RTCPNaduPacket::kReservedPlayoutDelayValue) //Is the playout delay available? + fLastReportedBufferingDelay += lastReportedPlayoutDelay; + +} + +void RTPStream3GPP::SetRTT(UInt32 minRTT, UInt32 curRTT) +{ + int scaleFactor = 10; + curRTT /= scaleFactor; + DEBUG_PRINTF(( "RTPStream3GPP::SetRTT curRTT ==%"_U32BITARG_" scaling by %d\n", curRTT,scaleFactor)); + + double ratio = curRTT / static_cast(minRTT); + if (ratio < 1.6) + { + fNumSmallRTT++; + fNumLargeRTT = MIN(0, fNumLargeRTT); + } + else if (ratio >= 4.0) + { + fNumLargeRTT++; + fNumSmallRTT = MIN(0, fNumSmallRTT); + } + else + { + fNumLargeRTT = MIN(0, fNumLargeRTT); + fNumSmallRTT = MIN(0, fNumSmallRTT); + } + fCurRTT = curRTT; +} + +//Does nothing if we are not doing 3GPP-rate-adaptation. +void RTPStream3GPP::AddSeqNumTimeMapping(UInt16 theSeqNum, SInt64 timeStamp) +{ + if (!RateAdaptationEnabled()) + return; + + //We need to extend the sequence number to 32 bits + fLastSeqNum = (fLastSeqNum == 0) ? theSeqNum : ExtendSeqNum(theSeqNum, fLastSeqNum); + fSeqNumTimeMapping.push_back(SeqNumTimePair(fLastSeqNum, timeStamp)); +} + +//Extends seqNum to full 32 bits by picking the highest 16 bits such that the sequence number is closest to the reference. +UInt32 RTPStream3GPP::ExtendSeqNum(UInt16 seqNum, UInt32 refSeqNum) +{ + //the highest 16 bits is either 1 less, 1 more, or the same as the highest sequence number. + UInt32 highBits = refSeqNum >> 16; + UInt32 seqNum32 = seqNum | (highBits << 16); + UInt32 diff = MIN(seqNum32 - refSeqNum, refSeqNum - seqNum32); + + UInt32 nextSeqNum = seqNum | ((highBits + 1) << 16); + if (nextSeqNum - refSeqNum < diff) + { + diff = nextSeqNum - refSeqNum; + seqNum32 = nextSeqNum; + } + + nextSeqNum = seqNum | ((highBits - 1) << 16); + if (refSeqNum - nextSeqNum < diff) + seqNum32 = nextSeqNum; + + return seqNum32; +} + +void RTPStream3GPP::UpdateTimeAndQuality(SInt64 curTime) +{ + fAdjustTime = kNoChange; + fAdjustSize = kNoChange; + + if (!RateAdaptationEnabled()) + return; + + + Bool16 limitTargetDelay = true; + UInt32 maxTargetDelayMilli = 5000; + + UInt32 adjustedFreeBuffer = fLastReportedFreeBufferSpace; + UInt32 adjustTargetDelay = fTargetBufferingDelay; + + if (limitTargetDelay) + { + if (adjustTargetDelay > maxTargetDelayMilli) //make this constant a pref + adjustTargetDelay = maxTargetDelayMilli; + } + + if (fBufferSize > RTCPNaduPacket::kMaximumReportableFreeBufferSpace) + fBufferSize = RTCPNaduPacket::kMaximumReportableFreeBufferSpace; + + UInt32 maxUsableBufferSizeBytes = fBufferSize; + UInt32 extraBufferMultipleInSeconds = 2; // use up to 3 times the requested target delay in bytes + UInt32 maxUsableDelaySecs = extraBufferMultipleInSeconds * (adjustTargetDelay/1000); + + UInt32 movieByteRate = fMovieBitRate >> 3; // bits to bytes + UInt32 bufferUsage = fBufferSize - fLastReportedFreeBufferSpace; + UInt32 bufferDelay = fLastReportedBufferingDelay; + + if (bufferUsage > fBufferSize) //there is more reported free buffer than the maximum -- not good for our ratios but a good situation at the client i.e. no buffer overrun here. + { + bufferUsage = fBufferSize / 2; // Have to pick something so use 50%. + } + + DEBUG_PRINTF(("reported buffer size = %"_U32BITARG_" reported Free Buffer=%"_U32BITARG_" current calculated bufferUsage= %"_U32BITARG_"\n", fBufferSize, fLastReportedFreeBufferSpace,bufferUsage)); + DEBUG_PRINTF(("Avg Movie BitRate = %lu original target delay=%lu adjusted TargetDelay =%ld \n",fMovieBitRate,fTargetBufferingDelay, adjustTargetDelay)); + + if (qtssVideoPayloadType == fRTPStream.GetPayLoadType() && fMovieBitRate > 0 && adjustTargetDelay > 0) //limit how much we use + { + + maxUsableBufferSizeBytes = maxUsableDelaySecs * movieByteRate; //buffer time * bit rate for movie is bigger than any single stream buffer + + if (maxUsableBufferSizeBytes > fBufferSize) // reported size is smaller than our buffer target + { maxUsableBufferSizeBytes = fBufferSize; + if (maxUsableBufferSizeBytes < movieByteRate) //hope for the best + maxUsableBufferSizeBytes = movieByteRate; + UInt32 newTargetDelay = (maxUsableBufferSizeBytes / movieByteRate) * 1000; + if (newTargetDelay < adjustTargetDelay) + adjustTargetDelay = newTargetDelay; + } + + if (adjustedFreeBuffer > maxUsableBufferSizeBytes) + adjustedFreeBuffer = maxUsableBufferSizeBytes ; + + UInt32 freeBytes = fBufferSize - bufferUsage; + if (freeBytes > fBufferSize) + bufferUsage = maxUsableBufferSizeBytes / 2; + + DEBUG_PRINTF(("ADJUSTING buffer usage and target delay: maxUsableBufferSizeBytes =%lu adjustedFreeBuffer=%lu bufferUsage=%lu adjusted TargetDelay=%lu\n",maxUsableBufferSizeBytes, adjustedFreeBuffer, bufferUsage, adjustTargetDelay)); + } + + + DEBUG_PRINTF(("Calculated maxUsableBufferSize =%"_U32BITARG_" reported fBufferSize=%"_U32BITARG_" reported buffer delay=%"_U32BITARG_" current calculated bufferUsage= %"_U32BITARG_"\n", maxUsableBufferSizeBytes, fBufferSize,bufferDelay, bufferUsage)); + + + + //bufferDelay should really be the network delay because if buffer delay were really large that would be ok + // it is supposed to be -1 if not supported or a real value. Some clients send 0 incorrectly. + //if buffer delay is small that should mean the buffer is empty and a under-run failure occurred. + if (bufferDelay == 0) + bufferDelay = kUInt32_Max; + + + double bufferUsageRatio = static_cast(bufferUsage) / maxUsableBufferSizeBytes; + double bufferDelayRatio = static_cast(bufferDelay) / adjustTargetDelay; + DEBUG_PRINTF(("bufferUsageRatio =%f bufferDelayRatio=%f\n", bufferUsageRatio, bufferDelayRatio)); + + if(!fStartDoingAdaptation) + { + //This is used to prevent QTSS from thinning in the beginning of the stream, when the buffering delay and usage are expected to be low + //Rate adaptation will start when EITHER of the two low watermarks for thinning have passed, OR the media has been playing for the target buffering delay. + //The ideal situation for the current code is 2x or more buffer size to target time. So target time converted to bytes should be 50% or less the buffer size to avoid overrun + + //this one is agressive and works well with Nokia when all is good and there is extra bandwidth so it makes a good network look good + if (bufferUsageRatio >= 0.7) //start active rate adapt when client is 70% full + fStartDoingAdaptation = true; + else if (curTime - fRTPStream.GetSession().GetFirstPlayTime() >= 15000) // but don't wait longer than 15 seconds + fStartDoingAdaptation = true; + else //neither criteria was met. //speed up while waiting for the buffer to fill. + { fAdjustTime = kAdjustUpUp; + } + + if (fStartDoingAdaptation) + { fNumLargeRTT = 0; + fNumSmallRTT = -3; //Delay the first rate increase + } + } + + if (fStartDoingAdaptation) + { + SInt32 currentQualityLevel = fRTPStream.GetQualityLevel(); + + // new code works good for Nokia N93 on wifi and ok for slow links (needs some more comparison testing against non rate adapt code and against build 520 or earlier) + + if (bufferDelay != kUInt32_Max) //not supported + { + DEBUG_PRINTF(("rate adapt is using delay ratio and buffer size\n")); + //The buffering delay information is available. + + //should I speed up or slow down? A Delay Ratio of 100% is a target not a minimum and not a maximum. + if (bufferDelayRatio < 2.0) //allow up to 200% + fAdjustTime = kAdjustUp; + else + fAdjustTime = kAdjustDown; + + if (bufferUsageRatio >= 0.7) //if you are in danger of buffer-overflowing because the buffer size is too small for the movie, also slow + fAdjustTime = kAdjustDownDown; + else if (bufferUsageRatio < 0.5 && bufferDelayRatio > 2.5) // stop pushing. + fAdjustTime = kAdjustDownDown; + else if (bufferUsageRatio < 0.5 && bufferDelayRatio > 2.0) // stop pushing. + fAdjustTime = kAdjustDown; + else if (bufferUsageRatio < 0.5 && bufferDelayRatio > 0.5) // try to push up hard. + fAdjustTime = kAdjustUpUp; + //should I thin or thicken? + + if (bufferUsageRatio < 0.2 && bufferDelayRatio > 2.5) // avoid underflow since the bandwidth is low. + { fAdjustSize = kAdjustDown; + DEBUG_PRINTF(("fAdjustSize=kAdjustDown 1\n")); + } + else if (bufferUsageRatio <= 0.1 ) //try thickening + { fAdjustSize = kAdjustUp; + DEBUG_PRINTF(("fAdjustSize=kAdjustUp 1\n")); + } + else if (bufferUsageRatio <= 0.3 && bufferDelayRatio < 1.0) //still in danger of underflow + { fAdjustSize = kAdjustDown; + DEBUG_PRINTF(("fAdjustSize=kAdjustDown 2\n")); + } + else if (bufferUsageRatio < 0.7 ) //no longer in danger of underflow; ok to thick + { fAdjustSize = kAdjustUp; + DEBUG_PRINTF(("fAdjustSize=kAdjustUp 2\n")); + } + else + fAdjustSize = kNoChange; + } + else + { + DEBUG_PRINTF(("rate adapt is using only buffer size\n")); + + //The buffering delay is not available; we make thin/slow decisions based on just the buffer usage alone + if (bufferUsageRatio > 0.9) //need to slow and thin to avoid overflow + { + fAdjustSize = kAdjustDown; + fAdjustTime = kAdjustDown; + } + if (bufferUsageRatio > 0.8) //need to slow and thin to avoid overflow + { + fAdjustSize = kNoChange; + fAdjustTime = kAdjustDown; + } + else if (bufferUsageRatio > 0.7) //need to slow and thin to avoid overflow + { + fAdjustSize = kAdjustUp; + fAdjustTime = kAdjustDown; + } + else if (bufferUsageRatio >= 0.5) //OK to start thickening + { + fAdjustSize = kAdjustUp; + fAdjustTime = kAdjustDown; + } + else if (bufferUsageRatio > 0.4) //OK to start thickening + { + fAdjustSize = kAdjustUp; + fAdjustTime = kAdjustUp; + } + else if (bufferUsageRatio > 0.3) //need to speed up to avoid underflow; not enough bandwidth + { + fAdjustSize = kNoChange; + fAdjustTime = kAdjustUpUp; + } + else if (bufferUsageRatio > 0.2) //need to speed up and thin to avoid underflow; not enough bandwidth + { + fAdjustSize = kAdjustDown; + fAdjustTime = kAdjustUpUp; + } + else //below 20% //need to speed up and thin to avoid underflow; not enough bandwidth + { + fAdjustSize = kAdjustDown; + fAdjustTime = kAdjustUp; + } + } + } + + if(fNumRTCPWithoutPacketLoss == 0) //RTCP have reported packet loss --> thinning + { + if (fCurRTT <= 10) + { + DEBUG_PRINTF(("RTPStream3GPP::UpdateTimeAndQuality fast network packet loss slowing down fNumRTCPWithoutPacketLoss=%"_S32BITARG_"\n",fNumRTCPWithoutPacketLoss)); + fAdjustTime = kAdjustDown; //slow down could be random packet loss. + } + else + { + DEBUG_PRINTF(("RTPStream3GPP::UpdateTimeAndQuality slow network packet loss decrease quality fNumRTCPWithoutPacketLoss=%"_S32BITARG_"\n",fNumRTCPWithoutPacketLoss)); + fAdjustSize = kAdjustDown; //most likely out of bandwidth so reduce quality. + fAdjustTime = kAdjustUpUp; //don't let the buffer drain while reducing quality. + } + } + else if (fNumRTCPWithoutPacketLoss <= 2) //If I get packet loss, then do not increase the rate for 2 RTCP cycles + fAdjustSize = MIN(kNoChange, fAdjustSize); + fNumRTCPWithoutPacketLoss++; + + //Set the quality based on the thinning value + if (fAdjustSize == kAdjustUp) // increase bit rate gradually + fRTPStream.SetQualityLevel(fRTPStream.GetQualityLevel() - 1); + else if (fAdjustSize == kAdjustDown) // thin down aggressively + fRTPStream.HalveQualityLevel(); + + SInt32 levelTest = ( (fRTPStream.GetNumQualityLevels() - fRTPStream.GetQualityLevel()) /2 )+ 1; + DEBUG_PRINTF(("RTPStream3GPP::UpdateTimeAndQuality update threshold=%ld\n", levelTest)); + + //Adjust the maximum quality if the router is getting congested(1 consecutive large RTT ratios) + if (fNumLargeRTT >= 4) + { + fNumLargeRTT = 0; //separate consecutive maximum quality lowering by at least 1 RTCP cycles. + fRTPStream.SetMaxQualityLevelLimit(fRTPStream.GetMaxQualityLevelLimit() + 1); + DEBUG_PRINTF(("RTPStream3GPP::UpdateTimeAndQuality fNumLargeRTT(%"_S32BITARG_") >= 4 maximum quality level decreased: %"_S32BITARG_"\n",fNumLargeRTT, fRTPStream.GetMaxQualityLevelLimit())); + } + else if (fNumSmallRTT >= levelTest) //Router is not congested(4 consecutive small RTT ratios); can start thickening. + { + fNumSmallRTT = 0; //separate consecutive maximum quality raising by at least x RTCP cycles. + fRTPStream.SetMaxQualityLevelLimit(fRTPStream.GetMaxQualityLevelLimit() - 1); + DEBUG_PRINTF(("RTPStream3GPP::UpdateTimeAndQuality fNumSmallRTT (%"_S32BITARG_") >= levelTest(%"_S32BITARG_") maximum quality level increased: %"_S32BITARG_"\n",fNumSmallRTT, levelTest, fRTPStream.GetMaxQualityLevelLimit())); + } + + char *payload = "?"; + if (GetDebugPrintfs()) + { + UInt8 payloadType = fRTPStream.GetPayLoadType(); + if (qtssVideoPayloadType == payloadType) + payload="video"; + else if (qtssAudioPayloadType == payloadType) + payload="audio"; + } + + if (bufferDelay != kUInt32_Max) + DEBUG_PRINTF(( + "RTPStream3GPP::UpdateTimeAndQuality type=%s quality=%"_S32BITARG_", qualitylimit=%"_S32BITARG_", fAdjustTime=%i bufferUsage=%"_U32BITARG_"(%.0f%%), " + "bufferDelay=%"_U32BITARG_"(%.0f%%)\n", + payload, + fRTPStream.GetQualityLevel(), fRTPStream.GetMaxQualityLevelLimit(), + fAdjustTime, + bufferUsage, bufferUsageRatio * 100, + bufferDelay, bufferDelayRatio * 100 + )); + else + DEBUG_PRINTF(( + "RTPStream3GPP::UpdateTimeAndQuality type=%s quality=%"_S32BITARG_", qualitylimit=%"_S32BITARG_", fAdjustTime=%i bufferUsage=%"_U32BITARG_"(%.0f%%), " + "bufferDelay=?\n", + payload, + fRTPStream.GetQualityLevel(), fRTPStream.GetMaxQualityLevelLimit(), + fAdjustTime, + bufferUsage, bufferUsageRatio * 100 + )); + + +} + +void RTPStream3GPP::SetRateAdaptationData(RateAdapationStreamDataFields* rateDataPtr) +{ + if (NULL == rateDataPtr || !fEnabled) + return; + + this->SetBufferBytes(rateDataPtr->GetBufferSizeBytes()); + this->SetBufferTime(rateDataPtr->GetTargetTimeMilliSec()); + fHasRateAdaptData = true; + + if (fTargetBufferingDelay < 1000) + fTargetBufferingDelay = 1000; + + if (fBufferSize == 0) + fBufferSize = 1; + + //rateDataPtr->PrintData(); +} + +//Returns the adjusted transmit time to take into account of speed increases/decreases +SInt64 RTPStream3GPP::GetAdjustedTransmitTime(SInt64 packetTransmitTime, SInt64 theTime) +{ + if (!RateAdaptationEnabled()) + return packetTransmitTime; + + if (fLastLocalTime == 0) + { + //can this happen? In case the file module tries to send a packet while the session is paused. + if (fRTPStream.GetSession().GetSessionState() == qtssPausedState) + return packetTransmitTime + fPlayTimeOffset; + + //First packet after starting to play or after a pause. + DEBUG_PRINTF(("RTPStream3GPP::GetAdjustedTransmitTime: initial quality=%"_U32BITARG_"\n", fRTPStream.GetQualityLevel())); + fLastLocalTime = theTime; + fPlayTimeOffset = 0; + return packetTransmitTime; + } + + SInt32 elapsedTime = theTime - fLastLocalTime; + if (elapsedTime >= 50) //Don't adjust the time over very small increments + { + fLastLocalTime = theTime; + if (fAdjustTime == kAdjustUpUp) // go faster subtract time (by 50%) + fPlayTimeOffset -= elapsedTime / 2; + else if (fAdjustTime == kAdjustUp) // go a bit faster by 10% + fPlayTimeOffset -= elapsedTime / 10; + else if (fAdjustTime == kAdjustDown) // go slower add time (by 20%) + fPlayTimeOffset += elapsedTime / 5; + else if (fAdjustTime == kAdjustDownDown) // go slower add time (by 50%) + fPlayTimeOffset += elapsedTime / 2; + } + return packetTransmitTime + fPlayTimeOffset; +} + diff --git a/Server.tproj/RTPStream3GPP.h b/Server.tproj/RTPStream3GPP.h new file mode 100644 index 0000000..74a0149 --- /dev/null +++ b/Server.tproj/RTPStream3GPP.h @@ -0,0 +1,135 @@ +/* + * + * @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: RTPStream3gpp.h + + Contains: Handles the rate-adaptation algorithm +*/ + +#ifndef __RTPSTREAM3GPP_H__ +#define __RTPSTREAM3GPP_H__ + +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "QTSS_Private.h" +#include "RTCPAPPNADUPacket.h" +#include "RTSPRequest3GPP.h" +#include "SVector.h" + +class RTPStream; + +class RTPStream3GPP : public QTSSDictionary +{ + public: + + // Initializes dictionary resources + static void Initialize(); + + // + // CONSTRUCTOR / DESTRUCTOR + + RTPStream3GPP(RTPStream &owner, Bool16 enabled); + virtual ~RTPStream3GPP() {} + + // + //ACCESS FUNCTIONS + void AddNadu(UInt8* inPacketBuffer, UInt32 inPacketLength, UInt32 highestSeqNum); + void AddSeqNumTimeMapping(UInt16 theSeqNum, SInt64 timeStamp); //Add a sequence number, timestamp pair; needed for figuring out the buffering delay + Bool16 Enabled() { return fEnabled; } + Bool16 HasRateAdaptation() { return fHasRateAdaptData; } + Bool16 RateAdaptationEnabled() { return Enabled() && HasRateAdaptation(); } + + void SetBufferBytes(UInt32 inBytes) { fBufferSize = inBytes; } + void SetBufferTime(UInt32 inMilliSecs) { fTargetBufferingDelay = inMilliSecs; } + void SetRateAdaptationData(RateAdapationStreamDataFields* rateDataPtr); + void SetBitRateData(UInt32 movieBitRate) {fMovieBitRate = movieBitRate; } + + //Will modify the containing RTPStream's QualityLevel + void UpdateTimeAndQuality(SInt64 curTime); //Call this function after every RTCP + + SInt64 GetAdjustedTransmitTime(SInt64 packetTransmitTime, SInt64 theTime); + + //This needs to be called so that the next call to GetAdjustedTransmitTime will return the right value. + void Pause() { fLastLocalTime = 0; fSeqNumTimeMapping.clear(); } + + //Called when there is a packet loss to slow down quality adjustment increase upon the next RTCP + void SetPacketLoss(Float32 percentage) { if (percentage > .10) fNumRTCPWithoutPacketLoss = 0; } + void SetRTT(UInt32 minRTT, UInt32 curRTT); + + Bool16 GetDebugPrintfs() { return fDebugPrintfs; } + //public members + NaduList fNaduList; + + private: + static UInt32 ExtendSeqNum(UInt16 seqNum, UInt32 refSeqNum); + + RTPStream& fRTPStream; //The RTPStream object that contains this RTPStream3GPP object + Bool16 fEnabled; + Bool16 fHasRateAdaptData; + UInt32 fBufferSize; //Buffer size as declared in 3GPP-Adaptation + UInt32 fTargetBufferingDelay; //Target buffering delay as declared in 3GPP-Adaptation + UInt32 fMovieBitRate; //in bits per second; + Bool16 fStartDoingAdaptation; + + SInt32 fNumRTCPWithoutPacketLoss; //Number of consecutive RTCP's without any reported packet loss + SInt32 fNumLargeRTT; //Number of consecutive RTCP's with a large RTT + SInt32 fNumSmallRTT; //Number of consecutive RTCP's with a small RTT + SInt32 fCurRTT; + //units are in bytes and milliseconds + UInt32 fLastReportedFreeBufferSpace; + UInt32 fLastReportedBufferingDelay; //set to kUInt32_Max if this value is not available. + + //Used to do play rate increase/decrease + SInt64 fLastLocalTime; //set to 0 whenver there is a pause + SInt64 fPlayTimeOffset; + + enum { kNaduListSize = 3 }; + + + enum AdjustMent { + kAdjustDownDown = -2, + kAdjustDown = -1, + kNoChange = 0, + kAdjustUp = 1, + kAdjustUpUp = 2 + }; + AdjustMent fAdjustSize; + AdjustMent fAdjustTime; + + //Dictionary support + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + + //Used to map sequence number to timestamp; needed to figure out the client's buffering delay + struct SeqNumTimePair + { + SeqNumTimePair(UInt32 seqNum = 0, SInt64 time = 0) : fSeqNum(seqNum), fTime(time) {} + UInt32 fSeqNum; + SInt64 fTime; + }; + SVector fSeqNumTimeMapping; + UInt32 fLastSeqNum; + Bool16 fDebugPrintfs; +}; +#endif // __RTPSTREAM3GPP_H__ diff --git a/Server.tproj/RTSPProtocol.cpp b/Server.tproj/RTSPProtocol.cpp new file mode 100644 index 0000000..bc2e4c6 --- /dev/null +++ b/Server.tproj/RTSPProtocol.cpp @@ -0,0 +1,372 @@ +/* + * + * @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: RTSPProtocol.cpp + + Contains: Implementation of class defined in RTSPProtocol.h + +*/ + +#include +#include "RTSPProtocol.h" + +StrPtrLen RTSPProtocol::sRetrProtName("our-retransmit"); + +StrPtrLen RTSPProtocol::sMethods[] = +{ + StrPtrLen("DESCRIBE"), + StrPtrLen("SETUP"), + StrPtrLen("TEARDOWN"), + StrPtrLen("PLAY"), + StrPtrLen("PAUSE"), + StrPtrLen("OPTIONS"), + StrPtrLen("ANNOUNCE"), + StrPtrLen("GET_PARAMETER"), + StrPtrLen("SET_PARAMETER"), + StrPtrLen("REDIRECT"), + StrPtrLen("RECORD") +}; + +QTSS_RTSPMethod +RTSPProtocol::GetMethod(const StrPtrLen &inMethodStr) +{ + //chances are this is one of our selected "VIP" methods. so check for this. + QTSS_RTSPMethod theMethod = qtssIllegalMethod; + + switch(*inMethodStr.Ptr) + { + case 'S': case 's': theMethod = qtssSetupMethod; break; + case 'D': case 'd': theMethod = qtssDescribeMethod; break; + case 'T': case 't': theMethod = qtssTeardownMethod; break; + case 'O': case 'o': theMethod = qtssOptionsMethod; break; + case 'A': case 'a': theMethod = qtssAnnounceMethod; break; + } + + if ((theMethod != qtssIllegalMethod) && + (inMethodStr.EqualIgnoreCase(sMethods[theMethod].Ptr, sMethods[theMethod].Len))) + return theMethod; + + for (SInt32 x = qtssNumVIPMethods; x < qtssIllegalMethod; x++) + if (inMethodStr.EqualIgnoreCase(sMethods[x].Ptr, sMethods[x].Len)) + return x; + return qtssIllegalMethod; +} + + +StrPtrLen RTSPProtocol::sHeaders[] = +{ + StrPtrLen("Accept"), + StrPtrLen("Cseq"), + StrPtrLen("User-Agent"), + StrPtrLen("Transport"), + StrPtrLen("Session"), + StrPtrLen("Range"), + + StrPtrLen("Accept-Encoding"), + StrPtrLen("Accept-Language"), + StrPtrLen("Authorization"), + StrPtrLen("Bandwidth"), + StrPtrLen("Blocksize"), + StrPtrLen("Cache-Control"), + StrPtrLen("Conference"), + StrPtrLen("Connection"), + StrPtrLen("Content-Base"), + StrPtrLen("Content-Encoding"), + StrPtrLen("Content-Language"), + StrPtrLen("Content-length"), + StrPtrLen("Content-Location"), + StrPtrLen("Content-Type"), + StrPtrLen("Date"), + StrPtrLen("Expires"), + StrPtrLen("From"), + StrPtrLen("Host"), + StrPtrLen("If-Match"), + StrPtrLen("If-Modified-Since"), + StrPtrLen("Last-Modified"), + StrPtrLen("Location"), + StrPtrLen("Proxy-Authenticate"), + StrPtrLen("Proxy-Require"), + StrPtrLen("Referer"), + StrPtrLen("Retry-After"), + StrPtrLen("Require"), + StrPtrLen("RTP-Info"), + StrPtrLen("Scale"), + StrPtrLen("Speed"), + StrPtrLen("Timestamp"), + StrPtrLen("Vary"), + StrPtrLen("Via"), + StrPtrLen("Allow"), + StrPtrLen("Public"), + StrPtrLen("Server"), + StrPtrLen("Unsupported"), + StrPtrLen("WWW-Authenticate"), + StrPtrLen(","), + StrPtrLen("x-Retransmit"), + StrPtrLen("x-Accept-Retransmit"), + StrPtrLen("x-RTP-Meta-Info"), + StrPtrLen("x-Transport-Options"), + StrPtrLen("x-Packet-Range"), + StrPtrLen("x-Prebuffer"), + StrPtrLen("x-Dynamic-Rate"), + StrPtrLen("x-Accept-Dynamic-Rate"), + // DJM PROTOTYPE + StrPtrLen("x-Random-Data-Size"), + + //3gpp release 6 headers + StrPtrLen("3GPP-Link-Char"), + StrPtrLen("3GPP-Adaptation"), + StrPtrLen("3GPP-QoE-Feedback"), + StrPtrLen("3GPP-QoE-Metrics"), + + //Annex G + StrPtrLen("x-predecbufsize"), + StrPtrLen("x-initpredecbufperiod"), + StrPtrLen("x-initpostdecbufperiod"), + StrPtrLen("3gpp-videopostdecbufsize") + + + +}; + +QTSS_RTSPHeader RTSPProtocol::GetRequestHeader(const StrPtrLen &inHeaderStr) +{ + if (inHeaderStr.Len == 0) + return qtssIllegalHeader; + + QTSS_RTSPHeader theHeader = qtssIllegalHeader; + + //chances are this is one of our selected "VIP" headers. so check for this. + switch(*inHeaderStr.Ptr) + { + case 'C': case 'c': theHeader = qtssCSeqHeader; break; + case 'S': case 's': theHeader = qtssSessionHeader; break; + case 'U': case 'u': theHeader = qtssUserAgentHeader; break; + case 'A': case 'a': theHeader = qtssAcceptHeader; break; + case 'T': case 't': theHeader = qtssTransportHeader; break; + case 'R': case 'r': theHeader = qtssRangeHeader; break; + case 'X': case 'x': theHeader = qtssExtensionHeaders; break; + } + + // + // Check to see whether this is one of our extension headers. These + // are very likely to appear in requests. + if (theHeader == qtssExtensionHeaders) + { + for (SInt32 y = qtssExtensionHeaders; y < qtssNumHeaders; y++) + { + if (inHeaderStr.EqualIgnoreCase(sHeaders[y].Ptr, sHeaders[y].Len)) + return y; + } + } + + // + // It's not one of our extension headers, check to see if this is one of + // our normal VIP headers + if ((theHeader != qtssIllegalHeader) && + (inHeaderStr.EqualIgnoreCase(sHeaders[theHeader].Ptr, sHeaders[theHeader].Len))) + return theHeader; + + // + //If this isn't one of our VIP headers, go through the remaining request headers, trying + //to find the right one. + for (SInt32 x = qtssNumVIPHeaders; x < qtssNumHeaders; x++) + { + if (inHeaderStr.EqualIgnoreCase(sHeaders[x].Ptr, sHeaders[x].Len)) + return x; + } + return qtssIllegalHeader; +} + + + +StrPtrLen RTSPProtocol::sStatusCodeStrings[] = +{ + StrPtrLen("Continue"), //kContinue + StrPtrLen("OK"), //kSuccessOK + StrPtrLen("Created"), //kSuccessCreated + StrPtrLen("Accepted"), //kSuccessAccepted + StrPtrLen("No Content"), //kSuccessNoContent + StrPtrLen("Partial Content"), //kSuccessPartialContent + StrPtrLen("Low on Storage Space"), //kSuccessLowOnStorage + StrPtrLen("Multiple Choices"), //kMultipleChoices + StrPtrLen("Moved Permanently"), //kRedirectPermMoved + StrPtrLen("Found"), //kRedirectTempMoved + StrPtrLen("See Other"), //kRedirectSeeOther + StrPtrLen("Not Modified"), //kRedirectNotModified + StrPtrLen("Use Proxy"), //kUseProxy + StrPtrLen("Bad Request"), //kClientBadRequest + StrPtrLen("Unauthorized"), //kClientUnAuthorized + StrPtrLen("Payment Required"), //kPaymentRequired + StrPtrLen("Forbidden"), //kClientForbidden + StrPtrLen("Not Found"), //kClientNotFound + StrPtrLen("Method Not Allowed"), //kClientMethodNotAllowed + StrPtrLen("Not Acceptable"), //kNotAcceptable + StrPtrLen("Proxy Authentication Required"), //kProxyAuthenticationRequired + StrPtrLen("Request Time-out"), //kRequestTimeout + StrPtrLen("Conflict"), //kClientConflict + StrPtrLen("Gone"), //kGone + StrPtrLen("Length Required"), //kLengthRequired + StrPtrLen("Precondition Failed"), //kPreconditionFailed + StrPtrLen("Request Entity Too Large"), //kRequestEntityTooLarge + StrPtrLen("Request-URI Too Large"), //kRequestURITooLarge + StrPtrLen("Unsupported Media Type"), //kUnsupportedMediaType + StrPtrLen("Parameter Not Understood"), //kClientParameterNotUnderstood + StrPtrLen("Conference Not Found"), //kClientConferenceNotFound + StrPtrLen("Not Enough Bandwidth"), //kClientNotEnoughBandwidth + StrPtrLen("Session Not Found"), //kClientSessionNotFound + StrPtrLen("Method Not Valid in this State"), //kClientMethodNotValidInState + StrPtrLen("Header Field Not Valid For Resource"), //kClientHeaderFieldNotValid + StrPtrLen("Invalid Range"), //kClientInvalidRange + StrPtrLen("Parameter Is Read-Only"), //kClientReadOnlyParameter + StrPtrLen("Aggregate Option Not Allowed"), //kClientAggregateOptionNotAllowed + StrPtrLen("Only Aggregate Option Allowed"), //kClientAggregateOptionAllowed + StrPtrLen("Unsupported Transport"), //kClientUnsupportedTransport + StrPtrLen("Destination Unreachable"), //kClientDestinationUnreachable + StrPtrLen("Internal Server Error"), //kServerInternal + StrPtrLen("Not Implemented"), //kServerNotImplemented + StrPtrLen("Bad Gateway"), //kServerBadGateway + StrPtrLen("Service Unavailable"), //kServerUnavailable + StrPtrLen("Gateway Timeout"), //kServerGatewayTimeout + StrPtrLen("RTSP Version not supported"), //kRTSPVersionNotSupported + StrPtrLen("Option Not Supported") //kServerOptionNotSupported +}; + +SInt32 RTSPProtocol::sStatusCodes[] = +{ + 100, //kContinue + 200, //kSuccessOK + 201, //kSuccessCreated + 202, //kSuccessAccepted + 204, //kSuccessNoContent + 206, //kSuccessPartialContent + 250, //kSuccessLowOnStorage + 300, //kMultipleChoices + 301, //kRedirectPermMoved + 302, //kRedirectTempMoved + 303, //kRedirectSeeOther + 304, //kRedirectNotModified + 305, //kUseProxy + 400, //kClientBadRequest + 401, //kClientUnAuthorized + 402, //kPaymentRequired + 403, //kClientForbidden + 404, //kClientNotFound + 405, //kClientMethodNotAllowed + 406, //kNotAcceptable + 407, //kProxyAuthenticationRequired + 408, //kRequestTimeout + 409, //kClientConflict + 410, //kGone + 411, //kLengthRequired + 412, //kPreconditionFailed + 413, //kRequestEntityTooLarge + 414, //kRequestURITooLarge + 415, //kUnsupportedMediaType + 451, //kClientParameterNotUnderstood + 452, //kClientConferenceNotFound + 453, //kClientNotEnoughBandwidth + 454, //kClientSessionNotFound + 455, //kClientMethodNotValidInState + 456, //kClientHeaderFieldNotValid + 457, //kClientInvalidRange + 458, //kClientReadOnlyParameter + 459, //kClientAggregateOptionNotAllowed + 460, //kClientAggregateOptionAllowed + 461, //kClientUnsupportedTransport + 462, //kClientDestinationUnreachable + 500, //kServerInternal + 501, //kServerNotImplemented + 502, //kServerBadGateway + 503, //kServerUnavailable + 504, //kServerGatewayTimeout + 505, //kRTSPVersionNotSupported + 551 //kServerOptionNotSupported +}; + +StrPtrLen RTSPProtocol::sStatusCodeAsStrings[] = +{ + StrPtrLen("100"), //kContinue + StrPtrLen("200"), //kSuccessOK + StrPtrLen("201"), //kSuccessCreated + StrPtrLen("202"), //kSuccessAccepted + StrPtrLen("204"), //kSuccessNoContent + StrPtrLen("206"), //kSuccessPartialContent + StrPtrLen("250"), //kSuccessLowOnStorage + StrPtrLen("300"), //kMultipleChoices + StrPtrLen("301"), //kRedirectPermMoved + StrPtrLen("302"), //kRedirectTempMoved + StrPtrLen("303"), //kRedirectSeeOther + StrPtrLen("304"), //kRedirectNotModified + StrPtrLen("305"), //kUseProxy + StrPtrLen("400"), //kClientBadRequest + StrPtrLen("401"), //kClientUnAuthorized + StrPtrLen("402"), //kPaymentRequired + StrPtrLen("403"), //kClientForbidden + StrPtrLen("404"), //kClientNotFound + StrPtrLen("405"), //kClientMethodNotAllowed + StrPtrLen("406"), //kNotAcceptable + StrPtrLen("407"), //kProxyAuthenticationRequired + StrPtrLen("408"), //kRequestTimeout + StrPtrLen("409"), //kClientConflict + StrPtrLen("410"), //kGone + StrPtrLen("411"), //kLengthRequired + StrPtrLen("412"), //kPreconditionFailed + StrPtrLen("413"), //kRequestEntityTooLarge + StrPtrLen("414"), //kRequestURITooLarge + StrPtrLen("415"), //kUnsupportedMediaType + StrPtrLen("451"), //kClientParameterNotUnderstood + StrPtrLen("452"), //kClientConferenceNotFound + StrPtrLen("453"), //kClientNotEnoughBandwidth + StrPtrLen("454"), //kClientSessionNotFound + StrPtrLen("455"), //kClientMethodNotValidInState + StrPtrLen("456"), //kClientHeaderFieldNotValid + StrPtrLen("457"), //kClientInvalidRange + StrPtrLen("458"), //kClientReadOnlyParameter + StrPtrLen("459"), //kClientAggregateOptionNotAllowed + StrPtrLen("460"), //kClientAggregateOptionAllowed + StrPtrLen("461"), //kClientUnsupportedTransport + StrPtrLen("462"), //kClientDestinationUnreachable + StrPtrLen("500"), //kServerInternal + StrPtrLen("501"), //kServerNotImplemented + StrPtrLen("502"), //kServerBadGateway + StrPtrLen("503"), //kServerUnavailable + StrPtrLen("504"), //kServerGatewayTimeout + StrPtrLen("505"), //kRTSPVersionNotSupported + StrPtrLen("551") //kServerOptionNotSupported +}; + +StrPtrLen RTSPProtocol::sVersionString[] = +{ + StrPtrLen("RTSP/1.0") +}; + +RTSPProtocol::RTSPVersion +RTSPProtocol::GetVersion(StrPtrLen &versionStr) +{ + if (versionStr.Len != 8) + return kIllegalVersion; + else + return k10Version; +} diff --git a/Server.tproj/RTSPProtocol.h b/Server.tproj/RTSPProtocol.h new file mode 100644 index 0000000..e103cd6 --- /dev/null +++ b/Server.tproj/RTSPProtocol.h @@ -0,0 +1,110 @@ +/* + * + * @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: RTSPProtocol.h + + Contains: A grouping of static utilities that abstract keyword strings + in the RTSP protocol. This should be maintained as new versions + of the RTSP protoocl appear & as the server evolves to take + advantage of new RTSP features. + + + +*/ + + +#ifndef __RTSPPROTOCOL_H__ +#define __RTSPPROTOCOL_H__ + +#include "QTSSRTSPProtocol.h" +#include "StrPtrLen.h" + +class RTSPProtocol +{ + public: + + //METHODS + + // Method enumerated type definition in QTSS_RTSPProtocol.h + + //The lookup function. Very simple + static UInt32 GetMethod(const StrPtrLen &inMethodStr); + + static StrPtrLen& GetMethodString(QTSS_RTSPMethod inMethod) + { return sMethods[inMethod]; } + + //HEADERS + + // Header enumerated type definitions in QTSS_RTSPProtocol.h + + //The lookup function. Very simple + static UInt32 GetRequestHeader(const StrPtrLen& inHeaderStr); + + //The lookup function. Very simple. + static StrPtrLen& GetHeaderString(UInt32 inHeader) + { return sHeaders[inHeader]; } + + + //STATUS CODES + + //returns name of this error + static StrPtrLen& GetStatusCodeString(QTSS_RTSPStatusCode inStat) + { return sStatusCodeStrings[inStat]; } + //returns error number for this error + static SInt32 GetStatusCode(QTSS_RTSPStatusCode inStat) + { return sStatusCodes[inStat]; } + //returns error number as a string + static StrPtrLen& GetStatusCodeAsString(QTSS_RTSPStatusCode inStat) + { return sStatusCodeAsStrings[inStat]; } + + // VERSIONS + enum RTSPVersion + { + k10Version = 0, + kIllegalVersion = 1 + }; + + // NAMES OF THINGS + static StrPtrLen& GetRetransmitProtocolName() { return sRetrProtName; } + + //accepts strings that look like "RTSP/1.0" etc... + static RTSPVersion GetVersion(StrPtrLen &versionStr); + static StrPtrLen& GetVersionString(RTSPVersion version) + { return sVersionString[version]; } + + private: + + //for other lookups + static StrPtrLen sMethods[]; + static StrPtrLen sHeaders[]; + static StrPtrLen sStatusCodeStrings[]; + static StrPtrLen sStatusCodeAsStrings[]; + static SInt32 sStatusCodes[]; + static StrPtrLen sVersionString[]; + + static StrPtrLen sRetrProtName; + +}; +#endif // __RTSPPROTOCOL_H__ diff --git a/Server.tproj/RTSPRequest.cpp b/Server.tproj/RTSPRequest.cpp new file mode 100644 index 0000000..1f382d7 --- /dev/null +++ b/Server.tproj/RTSPRequest.cpp @@ -0,0 +1,1115 @@ +/* + * + * @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: RTSPRequest.cpp + + Contains: Implementation of RTSPRequest class. + + + +*/ + + +#include "RTSPRequest.h" +#include "RTSPProtocol.h" +#include "QTSServerInterface.h" + +#include "RTSPSession.h" +#include "RTSPSessionInterface.h" +#include "StringParser.h" +#include "StringTranslator.h" +#include "OS.h" +#include "OSMemory.h" +#include "QTSS.h" +#include "QTSSModuleUtils.h" +#include "base64.h" +#include "OSArrayObjectDeleter.h" +#include "DateTranslator.h" +#include "SocketUtils.h" + +UInt8 +RTSPRequest::sURLStopConditions[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //0-9 //'\t' is a stop condition + 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, //10-19 //'\r' & '\n' are stop conditions + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //20-29 + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, //30-39 //' ' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //40-49 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //50-59 + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, //60-69 //'?' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //70-79 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //80-89 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //90-99 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //100-109 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //110-119 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //120-129 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //130-139 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //140-149 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //150-159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //160-169 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //170-179 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //180-189 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //190-199 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //200-209 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //210-219 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //220-229 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //230-239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //240-249 + 0, 0, 0, 0, 0, 0 //250-255 +}; + +static StrPtrLen sDefaultRealm("Streaming Server", 16); +static StrPtrLen sAuthBasicStr("Basic", 5); +static StrPtrLen sAuthDigestStr("Digest", 6); +static StrPtrLen sUsernameStr("username", 8); +static StrPtrLen sRealmStr("realm", 5); +static StrPtrLen sNonceStr("nonce", 5); +static StrPtrLen sUriStr("uri", 3); +static StrPtrLen sQopStr("qop", 3); +static StrPtrLen sQopAuthStr("auth", 4); +static StrPtrLen sQopAuthIntStr("auth-int", 8); +static StrPtrLen sNonceCountStr("nc", 2); +static StrPtrLen sResponseStr("response", 8); +static StrPtrLen sOpaqueStr("opaque", 6); +static StrPtrLen sEqualQuote("=\"", 2); +static StrPtrLen sQuoteCommaSpace("\", ", 3); +static StrPtrLen sStaleTrue("stale=\"true\", ", 14); + +//Parses the request +QTSS_Error RTSPRequest::Parse() +{ + StringParser parser(this->GetValue(qtssRTSPReqFullRequest)); + Assert(this->GetValue(qtssRTSPReqFullRequest)->Ptr != NULL); + + //parse status line. + QTSS_Error error = ParseFirstLine(parser); + + //handle any errors that come up + if (error != QTSS_NoErr) + return error; + + error = this->ParseHeaders(parser); + if (error != QTSS_NoErr) + return error; + + //Response headers should set themselves up to reflect what's in the request headers + fResponseKeepAlive = fRequestKeepAlive; + + //Make sure that there was some path that was extracted from this request. If not, there is no way + //we can process the request, so generate an error + if (this->GetValue(qtssRTSPReqFilePath)->Len == 0) + return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgNoURLInRequest,this->GetValue(qtssRTSPReqFullRequest)); + + return QTSS_NoErr; +} + +//returns: StatusLineTooLong, SyntaxError, BadMethod +QTSS_Error RTSPRequest::ParseFirstLine(StringParser &parser) +{ + //first get the method + StrPtrLen theParsedData; + parser.ConsumeWord(&theParsedData); + this->SetVal(qtssRTSPReqMethodStr, theParsedData.Ptr, theParsedData.Len); + + + //THIS WORKS UNDER THE ASSUMPTION THAT: + //valid HTTP/1.1 headers are: GET, HEAD, POST, PUT, OPTIONS, DELETE, TRACE + fMethod = RTSPProtocol::GetMethod(theParsedData); + if (fMethod == qtssIllegalMethod) + return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgBadRTSPMethod, &theParsedData); + + //no longer assume this is a space... instead, just consume whitespace + parser.ConsumeWhitespace(); + + //now parse the uri + QTSS_Error err = ParseURI(parser); + if (err != QTSS_NoErr) + return err; + + //no longer assume this is a space... instead, just consume whitespace + parser.ConsumeWhitespace(); + + //if there is a version, consume the version string + StrPtrLen versionStr; + parser.ConsumeUntil(&versionStr, StringParser::sEOLMask); + + //check the version + if (versionStr.Len > 0) + fVersion = RTSPProtocol::GetVersion(versionStr); + + //go past the end of line + if (!parser.ExpectEOL()) + return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgNoRTSPVersion,&theParsedData); + return QTSS_NoErr; +} + +//returns: SyntaxError if there was an error in the uri. Or InternalServerError +QTSS_Error RTSPRequest::ParseURI(StringParser &parser) +{ + //read in the complete URL, set it to be the qtssAbsoluteURLParam + StrPtrLen theAbsURL; + + // RTSPRequestInterface::sPathURLStopConditions stop on ? as well as sURLStopConditions + parser.ConsumeUntil(&theAbsURL, sURLStopConditions ); + + // set qtssRTSPReqAbsoluteURL to the URL throught the path component; will be : :/// + this->SetVal(qtssRTSPReqAbsoluteURL, &theAbsURL); + + StringParser urlParser(&theAbsURL); + + //we always should have a slash before the uri. + //If not, that indicates this is a full URI. Also, this could be a '*' OPTIONS request + if ((*theAbsURL.Ptr != '/') && (*theAbsURL.Ptr != '*')) + { + //if it is a full URL, store the host name off in a separate parameter + StrPtrLen theRTSPString; + urlParser.ConsumeLength(&theRTSPString, 7); //consume "rtsp://" + //assign the host field here to the proper QTSS param + StrPtrLen theHost; + urlParser.ConsumeUntil(&theHost, '/'); + fHeaderDictionary.SetVal(qtssHostHeader, &theHost); + } + + // don't allow non-aggregate operations indicated by a url/media track=id +// might need this for rate adapt if (qtssSetupMethod != fMethod && qtssOptionsMethod != fMethod && qtssSetParameterMethod != fMethod) // any method not a setup, options, or setparameter is not allowed to have a "/trackID=" in the url. + if (qtssSetupMethod != fMethod) // any method not a setup is not allowed to have a "/trackID=" in the url. + { + StrPtrLenDel tempCStr(theAbsURL.GetAsCString()); + StrPtrLen nonaggregate(tempCStr.FindString("/trackID=")); + if (nonaggregate.Len > 0) // check for non-aggregate method and return error + return QTSSModuleUtils::SendErrorResponse(this, qtssClientAggregateOptionAllowed, qtssMsgBadRTSPMethod, &theAbsURL); + } + + // don't allow non-aggregate operations like a setup on a playing session + if (qtssSetupMethod == fMethod) // if it is a setup but we are playing don't allow it + { + RTSPSession* theSession = (RTSPSession*)this->GetSession(); + if (theSession != NULL && theSession->IsPlaying()) + return QTSSModuleUtils::SendErrorResponse(this, qtssClientAggregateOptionAllowed, qtssMsgBadRTSPMethod, &theAbsURL); + } + + // + // In case there is no URI at all... we have to fake it. + static char* sSlashURI = "/"; + + //whatever is in this position in the URL must be the URI. Store that + //in the qtssURLParam. Confused? + UInt32 uriLen = urlParser.GetDataReceivedLen() - urlParser.GetDataParsedLen(); + if (uriLen > 0) + this->SetVal(qtssRTSPReqURI, urlParser.GetCurrentPosition(), urlParser.GetDataReceivedLen() - urlParser.GetDataParsedLen()); + else + // + // This might happen if there is nothing after the host at all, not even + // a '/'. This is legal (RFC 2326, Sec 3.2). If so, just pretend that there + // is a '/' + this->SetVal(qtssRTSPReqURI, sSlashURI, 1); + + // parse the query string from the url if present. + // init qtssRTSPReqQueryString dictionary to an empty string + StrPtrLen queryString; + this->SetVal(qtssRTSPReqQueryString, queryString.Ptr, queryString.Len); + + if ( parser.GetDataRemaining() > 0 ) + { + if ( parser.PeekFast() == '?' ) + { + // we've got some CGI param + parser.ConsumeLength(&queryString, 1); // toss '?' + + // consume the rest of the line.. + parser.ConsumeUntilWhitespace(&queryString); + + this->SetVal(qtssRTSPReqQueryString, queryString.Ptr, queryString.Len); + } + } + + + // + // If the is a '*', return right now because '*' is not a path + // so the below functions don't make any sense. + if ((*theAbsURL.Ptr == '*') && (theAbsURL.Len == 1)) + { + this->SetValue(qtssRTSPReqFilePath, 0, theAbsURL.Ptr, theAbsURL.Len, QTSSDictionary::kDontObeyReadOnly); + + return QTSS_NoErr; + } + + //path strings are statically allocated. Therefore, if they are longer than + //this length we won't be able to handle the request. + StrPtrLen* theURLParam = this->GetValue(qtssRTSPReqURI); + if (theURLParam->Len > RTSPRequestInterface::kMaxFilePathSizeInBytes) + return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgURLTooLong, theURLParam); + + //decode the URL, put the result in the separate buffer for the file path, + //set the file path StrPtrLen to the proper value + SInt32 theBytesWritten = StringTranslator::DecodeURL(theURLParam->Ptr, theURLParam->Len, + fFilePath, RTSPRequestInterface::kMaxFilePathSizeInBytes); + //if negative, an error occurred, reported as an QTSS_Error + //we also need to leave room for a terminator. + if ((theBytesWritten < 0) || (theBytesWritten == RTSPRequestInterface::kMaxFilePathSizeInBytes)) + { + return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgURLInBadFormat, theURLParam); + } + + // Convert from a / delimited path to a local file system path + StringTranslator::DecodePath(fFilePath, theBytesWritten); + + //setup the proper QTSS param + fFilePath[theBytesWritten] = '\0'; + //this->SetVal(qtssRTSPReqFilePath, fFilePath, theBytesWritten); + this->SetValue(qtssRTSPReqFilePath, 0, fFilePath, theBytesWritten, QTSSDictionary::kDontObeyReadOnly); + + + + return QTSS_NoErr; +} + + +//throws eHTTPNoMoreData and eHTTPOutOfBuffer +QTSS_Error RTSPRequest::ParseHeaders(StringParser& parser) +{ + StrPtrLen theKeyWord; + Bool16 isStreamOK; + + //Repeat until we get a \r\n\r\n, which signals the end of the headers + + while ((parser.PeekFast() != '\r') && (parser.PeekFast() != '\n')) + { + //First get the header identifier + + isStreamOK = parser.GetThru(&theKeyWord, ':'); + if (!isStreamOK) + return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgNoColonAfterHeader, this->GetValue(qtssRTSPReqFullRequest)); + + theKeyWord.TrimWhitespace(); + + //Look up the proper header enumeration based on the header string. + //Use the enumeration to look up the dictionary ID of this header, + //and set that dictionary attribute to be whatever is in the body of the header + + UInt32 theHeader = RTSPProtocol::GetRequestHeader(theKeyWord); + StrPtrLen theHeaderVal; + parser.ConsumeUntil(&theHeaderVal, StringParser::sEOLMask); + + StrPtrLen theEOL; + if ((parser.PeekFast() == '\r') || (parser.PeekFast() == '\n')) + { + isStreamOK = true; + parser.ConsumeEOL(&theEOL); + } + else + isStreamOK = false; + + while((parser.PeekFast() == ' ') || (parser.PeekFast() == '\t')) + { + theHeaderVal.Len += theEOL.Len; + StrPtrLen temp; + parser.ConsumeUntil(&temp, StringParser::sEOLMask); + theHeaderVal.Len += temp.Len; + + if ((parser.PeekFast() == '\r') || (parser.PeekFast() == '\n')) + { + isStreamOK = true; + parser.ConsumeEOL(&theEOL); + } + else + isStreamOK = false; + } + + // If this is an unknown header, ignore it. Otherwise, set the proper + // dictionary attribute + if (theHeader != qtssIllegalHeader) + { + Assert(theHeader < qtssNumHeaders); + theHeaderVal.TrimWhitespace(); + fHeaderDictionary.SetVal(theHeader, &theHeaderVal); + } + if (!isStreamOK) + return QTSSModuleUtils::SendErrorResponse(this, qtssClientBadRequest, qtssMsgNoEOLAfterHeader); + + //some headers require some special processing. If this code begins + //to get out of control, we made need to come up with a function pointer table + switch (theHeader) + { + case qtssSessionHeader: ParseSessionHeader(); break; + case qtssTransportHeader: ParseTransportHeader(); break; + case qtssRangeHeader: ParseRangeHeader(); break; + case qtssIfModifiedSinceHeader: ParseIfModSinceHeader();break; + case qtssXRetransmitHeader: ParseRetransmitHeader();break; + case qtssContentLengthHeader: ParseContentLengthHeader();break; + case qtssSpeedHeader: ParseSpeedHeader(); break; + case qtssXTransportOptionsHeader: ParseTransportOptionsHeader();break; + case qtssXPreBufferHeader: ParsePrebufferHeader();break; + case qtssXDynamicRateHeader: ParseDynamicRateHeader(); break; + case qtssXRandomDataSizeHeader: ParseRandomDataSizeHeader(); break; + case qtss3GPPAdaptationHeader: fRequest3GPP.ParseAdpationHeader(&fHeaderDictionary); break; + case qtss3GPPLinkCharHeader: fRequest3GPP.ParseLinkCharHeader(&fHeaderDictionary); break; + case qtssBandwidthHeader: ParseBandwidthHeader(); break; + default: break; + } + } + + // Tell the session what the request body length is for this request + // so that it can prevent people from reading past the end of the request. + StrPtrLen* theContentLengthBody = fHeaderDictionary.GetValue(qtssContentLengthHeader); + if (theContentLengthBody->Len > 0) + { + StringParser theHeaderParser(fHeaderDictionary.GetValue(qtssContentLengthHeader)); + theHeaderParser.ConsumeWhitespace(); + this->GetSession()->SetRequestBodyLength(theHeaderParser.ConsumeInteger(NULL)); + } + + isStreamOK = parser.ExpectEOL(); + Assert(isStreamOK); + return QTSS_NoErr; +} + +void RTSPRequest::ParseSessionHeader() +{ + StringParser theSessionParser(fHeaderDictionary.GetValue(qtssSessionHeader)); + StrPtrLen theSessionID; + (void)theSessionParser.GetThru(&theSessionID, ';'); + fHeaderDictionary.SetVal(qtssSessionHeader, &theSessionID); +} + +Bool16 RTSPRequest::ParseNetworkModeSubHeader(StrPtrLen* inSubHeader) +{ + static StrPtrLen sUnicast("unicast"); + static StrPtrLen sMulticast("multiicast"); + Bool16 result = false; // true means header was found + + if (!result && inSubHeader->EqualIgnoreCase(sUnicast)) + { + fNetworkMode = qtssRTPNetworkModeUnicast; + result = true; + } + + if (!result && inSubHeader->EqualIgnoreCase(sMulticast)) + { + fNetworkMode = qtssRTPNetworkModeMulticast; + result = true; + } + + return result; +} + +void RTSPRequest::ParseTransportHeader() +{ + static char* sRTPAVPTransportStr = "RTP/AVP"; + + StringParser theTransParser(fHeaderDictionary.GetValue(qtssTransportHeader)); + + //transport header from client: Transport: RTP/AVP;unicast;client_port=5000-5001\r\n + // Transport: RTP/AVP;multicast;ttl=15;destination=229.41.244.93;client_port=5000-5002\r\n + // Transport: RTP/AVP/TCP;unicast\r\n + + // + // A client may send multiple transports to the server, comma separated. + // In this case, the server should just pick one and use that. + + while (theTransParser.GetDataRemaining() > 0) + { + (void)theTransParser.ConsumeWhitespace(); + (void)theTransParser.ConsumeUntil(&fFirstTransport, ','); + + if (fFirstTransport.NumEqualIgnoreCase(sRTPAVPTransportStr, ::strlen(sRTPAVPTransportStr))) + break; + + if (theTransParser.PeekFast() == ',') + theTransParser.Expect(','); + } + + StringParser theFirstTransportParser(&fFirstTransport); + + StrPtrLen theTransportSubHeader; + (void)theFirstTransportParser.GetThru(&theTransportSubHeader, ';'); + + while (theTransportSubHeader.Len > 0) + { + + // Extract the relevent information from the relevent subheader. + // So far we care about 3 sub-headers + + if (!this->ParseNetworkModeSubHeader(&theTransportSubHeader)) + { + theTransportSubHeader.TrimWhitespace(); + + switch (*theTransportSubHeader.Ptr) + { + case 'r': // rtp/avp/??? Is this tcp or udp? + case 'R': // RTP/AVP/??? Is this TCP or UDP? + { + if ( theTransportSubHeader.EqualIgnoreCase("RTP/AVP/TCP") ) + fTransportType = qtssRTPTransportTypeTCP; + break; + } + case 'c': //client_port sub-header + case 'C': //client_port sub-header + { + this->ParseClientPortSubHeader(&theTransportSubHeader); + break; + } + case 'd': //destination sub-header + case 'D': //destination sub-header + { + static StrPtrLen sDestinationSubHeader("destination"); + + //Parse the header, extract the destination address + this->ParseAddrSubHeader(&theTransportSubHeader, &sDestinationSubHeader, &fDestinationAddr); + break; + } + case 's': //source sub-header + case 'S': //source sub-header + { + //Same as above code + static StrPtrLen sSourceSubHeader("source"); + this->ParseAddrSubHeader(&theTransportSubHeader, &sSourceSubHeader, &fSourceAddr); + break; + } + case 't': //time-to-live sub-header + case 'T': //time-to-live sub-header + { + this->ParseTimeToLiveSubHeader(&theTransportSubHeader); + break; + } + case 'm': //mode sub-header + case 'M': //mode sub-header + { + this->ParseModeSubHeader(&theTransportSubHeader); + break; + } + } + } + + // Move onto the next parameter + (void)theFirstTransportParser.GetThru(&theTransportSubHeader, ';'); + } +} + +void RTSPRequest::ParseRangeHeader() +{ + StringParser theRangeParser(fHeaderDictionary.GetValue(qtssRangeHeader)); + + // Setup the start and stop time dictionary attributes + this->SetVal(qtssRTSPReqStartTime, &fStartTime, sizeof(fStartTime)); + this->SetVal(qtssRTSPReqStopTime, &fStopTime, sizeof(fStopTime)); + + theRangeParser.GetThru(NULL, '=');//consume "npt=" + theRangeParser.ConsumeWhitespace(); + fStartTime = (Float64)theRangeParser.ConsumeNPT(); + //see if there is a stop time as well. + if (theRangeParser.GetDataRemaining() > 1) + { + theRangeParser.GetThru(NULL, '-'); + theRangeParser.ConsumeWhitespace(); + fStopTime = (Float64)theRangeParser.ConsumeNPT(); + } +} + +void RTSPRequest::ParseRetransmitHeader() +{ + StringParser theRetransmitParser(fHeaderDictionary.GetValue(qtssXRetransmitHeader)); + StrPtrLen theProtName; + Bool16 foundRetransmitProt = false; + + do + { + theRetransmitParser.ConsumeWhitespace(); + theRetransmitParser.ConsumeWord(&theProtName); + theProtName.TrimTrailingWhitespace(); + foundRetransmitProt = theProtName.EqualIgnoreCase(RTSPProtocol::GetRetransmitProtocolName()); + } + while ( (!foundRetransmitProt) && + (theRetransmitParser.GetThru(NULL, ',')) ); + + if (!foundRetransmitProt) + return; + + // + // We are using Reliable RTP as the transport for this stream, + // but if there was a previous transport header that indicated TCP, + // do not set the transport to be reliable UDP + if (fTransportType == qtssRTPTransportTypeUDP) + fTransportType = qtssRTPTransportTypeReliableUDP; + + StrPtrLen theProtArg; + while (theRetransmitParser.GetThru(&theProtArg, '=')) + { + // + // Parse out params + static const StrPtrLen kWindow("window"); + + theProtArg.TrimWhitespace(); + if (theProtArg.EqualIgnoreCase(kWindow)) + { + theRetransmitParser.ConsumeWhitespace(); + fWindowSize = theRetransmitParser.ConsumeInteger(NULL); + + // Save out the window size argument as a string so we + // can easily put it into the response + // (we never muck with this header) + fWindowSizeStr.Ptr = theProtArg.Ptr; + fWindowSizeStr.Len = theRetransmitParser.GetCurrentPosition() - theProtArg.Ptr; + } + + theRetransmitParser.GetThru(NULL, ';'); //Skip past ';' + } +} + +void RTSPRequest::ParseContentLengthHeader() +{ + StringParser theContentLenParser(fHeaderDictionary.GetValue(qtssContentLengthHeader)); + theContentLenParser.ConsumeWhitespace(); + fContentLength = theContentLenParser.ConsumeInteger(NULL); +} + +void RTSPRequest::ParsePrebufferHeader() +{ + StringParser thePrebufferParser(fHeaderDictionary.GetValue(qtssXPreBufferHeader)); + + StrPtrLen thePrebufferArg; + while (thePrebufferParser.GetThru(&thePrebufferArg, '=')) + { + thePrebufferArg.TrimWhitespace(); + + static const StrPtrLen kMaxTimeSubHeader("maxtime"); + if (thePrebufferArg.EqualIgnoreCase(kMaxTimeSubHeader)) + { + thePrebufferParser.ConsumeWhitespace(); + fPrebufferAmt = thePrebufferParser.ConsumeFloat(); + } + + thePrebufferParser.GetThru(NULL, ';'); //Skip past ';' + + } +} + +void RTSPRequest::ParseDynamicRateHeader() +{ + StringParser theParser(fHeaderDictionary.GetValue(qtssXDynamicRateHeader)); + theParser.ConsumeWhitespace(); + SInt32 value = theParser.ConsumeInteger(NULL); + + // fEnableDynamicRate: < 0 undefined, 0 disable, > 0 enable + if (value > 0) + fEnableDynamicRateState = 1; + else + fEnableDynamicRateState = 0; +} + +void RTSPRequest::ParseIfModSinceHeader() +{ + fIfModSinceDate = DateTranslator::ParseDate(fHeaderDictionary.GetValue(qtssIfModifiedSinceHeader)); + + // Only set the param if this is a legal date + if (fIfModSinceDate != 0) + this->SetVal(qtssRTSPReqIfModSinceDate, &fIfModSinceDate, sizeof(fIfModSinceDate)); +} + +void RTSPRequest::ParseSpeedHeader() +{ + StringParser theSpeedParser(fHeaderDictionary.GetValue(qtssSpeedHeader)); + theSpeedParser.ConsumeWhitespace(); + fSpeed = theSpeedParser.ConsumeFloat(); +} + +void RTSPRequest::ParseTransportOptionsHeader() +{ + StringParser theRTPOptionsParser(fHeaderDictionary.GetValue(qtssXTransportOptionsHeader)); + StrPtrLen theRTPOptionsSubHeader; + + do + { + static StrPtrLen sLateTolerance("late-tolerance"); + + if (theRTPOptionsSubHeader.NumEqualIgnoreCase(sLateTolerance.Ptr, sLateTolerance.Len)) + { + StringParser theLateTolParser(&theRTPOptionsSubHeader); + theLateTolParser.GetThru(NULL,'='); + theLateTolParser.ConsumeWhitespace(); + fLateTolerance = theLateTolParser.ConsumeFloat(); + fLateToleranceStr = theRTPOptionsSubHeader; + } + + (void)theRTPOptionsParser.GetThru(&theRTPOptionsSubHeader, ';'); + + } while(theRTPOptionsSubHeader.Len > 0); +} + + +void RTSPRequest::ParseAddrSubHeader(StrPtrLen* inSubHeader, StrPtrLen* inHeaderName, UInt32* outAddr) +{ + if (!inSubHeader || !inHeaderName || !outAddr) + return; + + StringParser theSubHeaderParser(inSubHeader); + + // Skip over to the value + StrPtrLen theFirstBit; + theSubHeaderParser.GetThru(&theFirstBit, '='); + theFirstBit.TrimWhitespace(); + + // First make sure this is the proper subheader + if (!theFirstBit.EqualIgnoreCase(*inHeaderName)) + return; + + //Find the IP address + theSubHeaderParser.ConsumeUntilDigit(); + + //Set the addr string param. + StrPtrLen theAddr(theSubHeaderParser.GetCurrentPosition(), theSubHeaderParser.GetDataRemaining()); + + //Convert the string to a UInt32 IP address + char theTerminator = theAddr.Ptr[theAddr.Len]; + theAddr.Ptr[theAddr.Len] = '\0'; + + *outAddr = SocketUtils::ConvertStringToAddr(theAddr.Ptr); + + theAddr.Ptr[theAddr.Len] = theTerminator; + +} + +void RTSPRequest::ParseModeSubHeader(StrPtrLen* inModeSubHeader) +{ + static StrPtrLen sModeSubHeader("mode"); + static StrPtrLen sReceiveMode("receive"); + static StrPtrLen sRecordMode("record"); + StringParser theSubHeaderParser(inModeSubHeader); + + // Skip over to the first port + StrPtrLen theFirstBit; + theSubHeaderParser.GetThru(&theFirstBit, '='); + theFirstBit.TrimWhitespace(); + + // Make sure this is the client port subheader + if (theFirstBit.EqualIgnoreCase(sModeSubHeader)) do + { + theSubHeaderParser.ConsumeWhitespace(); + + StrPtrLen theMode; + theSubHeaderParser.ConsumeWord(&theMode); + + if ( theMode.EqualIgnoreCase(sReceiveMode) || theMode.EqualIgnoreCase(sRecordMode) ) + { fTransportMode = qtssRTPTransportModeRecord; + break; + } + + } while (false); + +} + +void RTSPRequest::ParseClientPortSubHeader(StrPtrLen* inClientPortSubHeader) +{ + static StrPtrLen sClientPortSubHeader("client_port"); + static StrPtrLen sErrorMessage("Received invalid client_port field: "); + StringParser theSubHeaderParser(inClientPortSubHeader); + + // Skip over to the first port + StrPtrLen theFirstBit; + theSubHeaderParser.GetThru(&theFirstBit, '='); + theFirstBit.TrimWhitespace(); + + // Make sure this is the client port subheader + if (!theFirstBit.EqualIgnoreCase(sClientPortSubHeader)) + return; + + // Store the two client ports as integers + theSubHeaderParser.ConsumeWhitespace(); + fClientPortA = (UInt16)theSubHeaderParser.ConsumeInteger(NULL); + theSubHeaderParser.GetThru(NULL,'-'); + theSubHeaderParser.ConsumeWhitespace(); + fClientPortB = (UInt16)theSubHeaderParser.ConsumeInteger(NULL); + if (fClientPortB != fClientPortA + 1) // an error in the port values + { + // The following to setup and log the error as a message level 2. + StrPtrLen *userAgentPtr = fHeaderDictionary.GetValue(qtssUserAgentHeader); + ResizeableStringFormatter errorPortMessage; + errorPortMessage.Put(sErrorMessage); + if (userAgentPtr != NULL) + errorPortMessage.Put(*userAgentPtr); + errorPortMessage.PutSpace(); + errorPortMessage.Put(*inClientPortSubHeader); + errorPortMessage.PutTerminator(); + QTSSModuleUtils::LogError(qtssMessageVerbosity,qtssMsgNoMessage, 0, errorPortMessage.GetBufPtr(), NULL); + + + //fix the rtcp port and hope it works. + fClientPortB = fClientPortA + 1; + } +} + +void RTSPRequest::ParseTimeToLiveSubHeader(StrPtrLen* inTimeToLiveSubHeader) +{ + static StrPtrLen sTimeToLiveSubHeader("ttl"); + + StringParser theSubHeaderParser(inTimeToLiveSubHeader); + + // Skip over to the first part + StrPtrLen theFirstBit; + theSubHeaderParser.GetThru(&theFirstBit, '='); + theFirstBit.TrimWhitespace(); + // Make sure this is the ttl subheader + if (!theFirstBit.EqualIgnoreCase(sTimeToLiveSubHeader)) + return; + + // Parse out the time to live... + theSubHeaderParser.ConsumeWhitespace(); + fTtl = (UInt16)theSubHeaderParser.ConsumeInteger(NULL); +} + +// DJM PROTOTYPE +void RTSPRequest::ParseRandomDataSizeHeader() +{ + StringParser theContentLenParser(fHeaderDictionary.GetValue(qtssXRandomDataSizeHeader)); + theContentLenParser.ConsumeWhitespace(); + fRandomDataSize = theContentLenParser.ConsumeInteger(NULL); + + if (fRandomDataSize > RTSPSessionInterface::kMaxRandomDataSize) { + fRandomDataSize = RTSPSessionInterface::kMaxRandomDataSize; + } +} + +void RTSPRequest::ParseBandwidthHeader() +{ + StringParser theContentLenParser(fHeaderDictionary.GetValue(qtssBandwidthHeader)); + theContentLenParser.ConsumeWhitespace(); + fBandwidthBits = theContentLenParser.ConsumeInteger(NULL); + +} + + + +QTSS_Error RTSPRequest::ParseBasicHeader(StringParser *inParsedAuthLinePtr) +{ + QTSS_Error theErr = QTSS_NoErr; + fAuthScheme = qtssAuthBasic; + + StrPtrLen authWord; + + inParsedAuthLinePtr->ConsumeWhitespace(); + inParsedAuthLinePtr->ConsumeUntilWhitespace(&authWord); + if (0 == authWord.Len ) + return theErr; + + char* encodedStr = authWord.GetAsCString(); + OSCharArrayDeleter encodedStrDeleter(encodedStr); + + char *decodedAuthWord = NEW char[Base64decode_len(encodedStr) + 1]; + OSCharArrayDeleter decodedAuthWordDeleter(decodedAuthWord); + + (void) Base64decode(decodedAuthWord, encodedStr); + + StrPtrLen nameAndPassword; + nameAndPassword.Set(decodedAuthWord, ::strlen(decodedAuthWord)); + + StrPtrLen name(""); + StrPtrLen password(""); + StringParser parsedNameAndPassword(&nameAndPassword); + + parsedNameAndPassword.ConsumeUntil(&name,':'); + parsedNameAndPassword.ConsumeLength(NULL, 1); + parsedNameAndPassword.GetThruEOL(&password); + + + // Set the qtssRTSPReqUserName and qtssRTSPReqUserPassword attributes in the Request object + (void) this->SetValue(qtssRTSPReqUserName, 0, name.Ptr , name.Len, QTSSDictionary::kDontObeyReadOnly); + (void) this->SetValue(qtssRTSPReqUserPassword, 0, password.Ptr , password.Len, QTSSDictionary::kDontObeyReadOnly); + + // Also set the qtssUserName attribute in the qtssRTSPReqUserProfile object attribute of the Request Object + (void) fUserProfile.SetValue(qtssUserName, 0, name.Ptr, name.Len, QTSSDictionary::kDontObeyReadOnly); + + return theErr; +} + +QTSS_Error RTSPRequest::ParseDigestHeader(StringParser *inParsedAuthLinePtr) +{ + QTSS_Error theErr = QTSS_NoErr; + fAuthScheme = qtssAuthDigest; + + inParsedAuthLinePtr->ConsumeWhitespace(); + StrPtrLen *authLine = inParsedAuthLinePtr->GetStream(); + if (NULL != authLine) + { + StringParser digestAuthLine(authLine); + digestAuthLine.GetThru(NULL, '='); + digestAuthLine.ConsumeWhitespace(); + + fAuthDigestResponse.Set(authLine->Ptr, authLine->Len); + } + + while(inParsedAuthLinePtr->GetDataRemaining() != 0) + { + StrPtrLen fieldNameAndValue(""); + inParsedAuthLinePtr->GetThru(&fieldNameAndValue, ','); + StringParser parsedNameAndValue(&fieldNameAndValue); + StrPtrLen fieldName(""); + StrPtrLen fieldValue(""); + + //Parse name="value" pair fields in the auth line + parsedNameAndValue.ConsumeUntil(&fieldName, '='); + parsedNameAndValue.ConsumeLength(NULL, 1); + parsedNameAndValue.GetThruEOL(&fieldValue); + StringParser::UnQuote(&fieldValue); + + // fieldValue.Ptr below is a pointer to a part of the qtssAuthorizationHeader + // as GetValue returns a pointer + // Since the header attribute remains for the entire time the request is alive + // we don't need to make copies of the values of each field into the request + // object, and can just keep pointers to the values + // Thus, no need to delete memory for the following fields when the request is deleted: + // fAuthRealm, fAuthNonce, fAuthUri, fAuthNonceCount, fAuthResponse, fAuthOpaque + if(fieldName.Equal(sUsernameStr)) { + // Set the qtssRTSPReqUserName attribute in the Request object + (void) this->SetValue(qtssRTSPReqUserName, 0, fieldValue.Ptr , fieldValue.Len, QTSSDictionary::kDontObeyReadOnly); + // Also set the qtssUserName attribute in the qtssRTSPReqUserProfile object attribute of the Request Object + (void) fUserProfile.SetValue(qtssUserName, 0, fieldValue.Ptr, fieldValue.Len, QTSSDictionary::kDontObeyReadOnly); + } + else if(fieldName.Equal(sRealmStr)) { + fAuthRealm.Set(fieldValue.Ptr, fieldValue.Len); + } + else if(fieldName.Equal(sNonceStr)) { + fAuthNonce.Set(fieldValue.Ptr, fieldValue.Len); + } + else if(fieldName.Equal(sUriStr)) { + fAuthUri.Set(fieldValue.Ptr, fieldValue.Len); + } + else if(fieldName.Equal(sQopStr)) { + if(fieldValue.Equal(sQopAuthStr)) + fAuthQop = RTSPSessionInterface::kAuthQop; + else if(fieldValue.Equal(sQopAuthIntStr)) + fAuthQop = RTSPSessionInterface::kAuthIntQop; + } + else if(fieldName.Equal(sNonceCountStr)) { + fAuthNonceCount.Set(fieldValue.Ptr, fieldValue.Len); + } + else if(fieldName.Equal(sResponseStr)) { + fAuthResponse.Set(fieldValue.Ptr, fieldValue.Len); + } + else if(fieldName.Equal(sOpaqueStr)) { + fAuthOpaque.Set(fieldValue.Ptr, fieldValue.Len); + } + + inParsedAuthLinePtr->ConsumeWhitespace(); + } + + return theErr; +} + +QTSS_Error RTSPRequest::ParseAuthHeader(void) +{ + QTSS_Error theErr = QTSS_NoErr; + QTSSDictionary *theRTSPHeaders = this->GetHeaderDictionary(); + StrPtrLen *authLine = theRTSPHeaders->GetValue(qtssAuthorizationHeader); + if ( (authLine == NULL) || (0 == authLine->Len)) + return theErr; + + StrPtrLen authWord(""); + StringParser parsedAuthLine(authLine); + parsedAuthLine.ConsumeUntilWhitespace(&authWord); + + if (authWord.EqualIgnoreCase(sAuthBasicStr.Ptr, sAuthBasicStr.Len)) + return ParseBasicHeader(&parsedAuthLine); + + if (authWord.EqualIgnoreCase(sAuthDigestStr.Ptr, sAuthDigestStr.Len)) + return ParseDigestHeader(&parsedAuthLine); + + return theErr; +} + +void RTSPRequest::SetupAuthLocalPath(void) +{ + QTSS_AttributeID theID = qtssRTSPReqFilePath; + + // + // Get the truncated path on a setup, because setups have the trackID appended + if (qtssSetupMethod == fMethod) + theID = qtssRTSPReqFilePathTrunc; + + UInt32 theLen = 0; + char* theFullPath = QTSSModuleUtils::GetFullPath(this, theID, &theLen, NULL); + this->SetValue(qtssRTSPReqLocalPath, 0, theFullPath, theLen, QTSSDictionary::kDontObeyReadOnly); + delete [] theFullPath; +} + +QTSS_Error RTSPRequest::SendDigestChallenge(UInt32 qop, StrPtrLen *nonce, StrPtrLen* opaque) +{ + QTSS_Error theErr = QTSS_NoErr; + + char challengeBuf[kAuthChallengeHeaderBufSize]; + ResizeableStringFormatter challengeFormatter(challengeBuf, kAuthChallengeHeaderBufSize); + + StrPtrLen realm; + char *prefRealmPtr = NULL; + StrPtrLen *realmPtr = this->GetValue(qtssRTSPReqURLRealm); // Get auth realm set by the module + if(realmPtr->Len > 0) { + realm = *realmPtr; + } + else { // If module hasn't set the realm + QTSServerInterface* theServer = QTSServerInterface::GetServer(); // get the realm from prefs + prefRealmPtr = theServer->GetPrefs()->GetAuthorizationRealm(); // allocates memory + Assert(prefRealmPtr != NULL); + if (prefRealmPtr != NULL){ + realm.Set(prefRealmPtr, strlen(prefRealmPtr)); + } + else { + realm = sDefaultRealm; + } + } + + // Creating the Challenge header + challengeFormatter.Put(sAuthDigestStr); // [Digest] + challengeFormatter.PutSpace(); // [Digest ] + challengeFormatter.Put(sRealmStr); // [Digest realm] + challengeFormatter.Put(sEqualQuote); // [Digest realm="] + challengeFormatter.Put(realm); // [Digest realm="somerealm] + challengeFormatter.Put(sQuoteCommaSpace); // [Digest realm="somerealm", ] + if(this->GetStale()) { + challengeFormatter.Put(sStaleTrue); // [Digest realm="somerealm", stale="true", ] + } + challengeFormatter.Put(sNonceStr); // [Digest realm="somerealm", nonce] + challengeFormatter.Put(sEqualQuote); // [Digest realm="somerealm", nonce="] + challengeFormatter.Put(*nonce); // [Digest realm="somerealm", nonce="19723343a9fd75e019723343a9fd75e0] + challengeFormatter.PutChar('"'); // [Digest realm="somerealm", nonce="19723343a9fd75e019723343a9fd75e0"] + challengeFormatter.PutTerminator(); // [Digest realm="somerealm", nonce="19723343a9fd75e019723343a9fd75e0"\0] + + StrPtrLen challengePtr(challengeFormatter.GetBufPtr(), challengeFormatter.GetBytesWritten() - 1); + + this->SetValue(qtssRTSPReqDigestChallenge, 0, challengePtr.Ptr, challengePtr.Len, QTSSDictionary::kDontObeyReadOnly); + RTSPSessionInterface* thisRTSPSession = this->GetSession(); + if (thisRTSPSession) + { (void)thisRTSPSession->SetValue(qtssRTSPSesLastDigestChallenge, 0, challengePtr.Ptr, challengePtr.Len,QTSSDictionary::kDontObeyReadOnly); + } + + fStatus = qtssClientUnAuthorized; + this->SetResponseKeepAlive(true); + this->AppendHeader(qtssWWWAuthenticateHeader, &challengePtr); + this->SendHeader(); + + // deleting the memory that was allocated in GetPrefs call above + if (prefRealmPtr != NULL) + { + delete[] prefRealmPtr; + } + + return theErr; +} + +QTSS_Error RTSPRequest::SendBasicChallenge(void) +{ + QTSS_Error theErr = QTSS_NoErr; + char *prefRealmPtr = NULL; + + do + { + char realmBuff[kRealmBuffSize] = "Basic realm=\""; + StrPtrLen challenge(realmBuff); + StrPtrLen whichRealm; + + // Get the module's realm + StrPtrLen moduleRealm; + theErr = this->GetValuePtr(qtssRTSPReqURLRealm, 0, (void **) &moduleRealm.Ptr, &moduleRealm.Len); + if ( (QTSS_NoErr == theErr) && (moduleRealm.Len > 0) ) + { + whichRealm = moduleRealm; + } + else + { + theErr = QTSS_NoErr; + // Get the default realm from the config file or use the static default if config realm is not found + QTSServerInterface* theServer = QTSServerInterface::GetServer(); + prefRealmPtr = theServer->GetPrefs()->GetAuthorizationRealm(); // allocates memory + Assert(prefRealmPtr != NULL); + if (prefRealmPtr != NULL) + { whichRealm.Set(prefRealmPtr, strlen(prefRealmPtr)); + } + else + { + whichRealm = sDefaultRealm; + } + } + + int realmLen = whichRealm.Len + challenge.Len + 2; // add 2 based on double quote char + end of string 0x00 + if (realmLen > kRealmBuffSize) // The realm is too big so use the default realm + { Assert(0); + whichRealm = sDefaultRealm; + } + memcpy(&challenge.Ptr[challenge.Len],whichRealm.Ptr,whichRealm.Len); + int newLen = challenge.Len + whichRealm.Len; + + challenge.Ptr[newLen] = '"'; // add the terminating "" this was accounted for with the size check above + challenge.Ptr[newLen + 1] = 0;// add the 0 terminator this was accounted for with the size check above + challenge.Len = newLen +1; // set the real size of the string excluding the 0. + + #if (0) + { // test code + char test[256]; + + memcpy(test,sDefaultRealm.Ptr,sDefaultRealm.Len); + test[sDefaultRealm.Len] = 0; + qtss_printf("the static realm =%s \n",test); + + OSCharArrayDeleter prefDeleter(QTSServerInterface::GetServer()->GetPrefs()->GetAuthorizationRealm()); + memcpy(test,prefDeleter.GetObject(),strlen(prefDeleter.GetObject())); + test[strlen(prefDeleter.GetObject())] = 0; + qtss_printf("the Pref realm =%s \n",test); + + memcpy(test,moduleRealm.Ptr,moduleRealm.Len); + test[moduleRealm.Len] = 0; + qtss_printf("the moduleRealm =%s \n",test); + + memcpy(test,whichRealm.Ptr,whichRealm.Len); + test[whichRealm.Len] = 0; + qtss_printf("the challenge realm =%s \n",test); + + memcpy(test,challenge.Ptr,challenge.Len); + test[challenge.Len] = 0; + qtss_printf("the challenge string =%s len = %"_S32BITARG_"\n",test, challenge.Len); + } + #endif + + fStatus = qtssClientUnAuthorized; + this->SetResponseKeepAlive(true); + this->AppendHeader(qtssWWWAuthenticateHeader, &challenge); + this->SendHeader(); + + + } while (false); + + if (prefRealmPtr != NULL) + { + delete[] prefRealmPtr; + } + + return theErr; +} + +QTSS_Error RTSPRequest::SendForbiddenResponse(void) +{ + fStatus = qtssClientForbidden; + this->SetResponseKeepAlive(false); + this->SendHeader(); + + return QTSS_NoErr; +} diff --git a/Server.tproj/RTSPRequest.h b/Server.tproj/RTSPRequest.h new file mode 100644 index 0000000..4143b39 --- /dev/null +++ b/Server.tproj/RTSPRequest.h @@ -0,0 +1,128 @@ +/* + * + * @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: RTSPRequest.h + + Contains: This class encapsulates a single RTSP request. It stores the meta data + associated with a request, and provides an interface (through its base + class) for modules to access request information + + It divides the request into a series of states. + + State 1: At first, when the object is constructed or right after + its Reset function is called, it is in an uninitialized state. + State 2: Parse() parses an RTSP header. Once this function returns, + most of the request-related query functions have been setup. + (unless an error occurs) + State 3: GetHandler() uses the request information to create the + proper request Handler object for this request. After that, + it is the Handler object's responsibilty to process the + request and send a response to the client. + + + +*/ + +#ifndef __RTSPREQUEST_H__ +#define __RTSPREQUEST_H__ + +#include "RTSPRequestInterface.h" +#include "RTSPRequestStream.h" +#include "RTSPResponseStream.h" +#include "RTSPSessionInterface.h" +#include "StringParser.h" +#include "QTSSRTSPProtocol.h" + +//HTTPRequest class definition +class RTSPRequest : public RTSPRequestInterface +{ +public: + + //CONSTRUCTOR / DESTRUCTOR + //these do very little. Just initialize / delete some member data. + // + //Arguments: session: the session this request is on (massive cyclical dependency) + RTSPRequest(RTSPSessionInterface* session) + : RTSPRequestInterface(session) {} + virtual ~RTSPRequest() {} + + //Parses the request. Returns an error handler if there was an error encountered + //in parsing. + QTSS_Error Parse(); + + QTSS_Error ParseAuthHeader(void); + // called by ParseAuthHeader + QTSS_Error ParseBasicHeader(StringParser *inParsedAuthLinePtr); + + // called by ParseAuthHeader + QTSS_Error ParseDigestHeader(StringParser *inParsedAuthLinePtr); + + void SetupAuthLocalPath(void); + QTSS_Error SendBasicChallenge(void); + QTSS_Error SendDigestChallenge(UInt32 qop, StrPtrLen *nonce, StrPtrLen* opaque); + QTSS_Error SendForbiddenResponse(void); +private: + + //PARSING + enum { kRealmBuffSize = 512, kAuthNameAndPasswordBuffSize = 128, kAuthChallengeHeaderBufSize = 512}; + + //Parsing the URI line (first line of request + QTSS_Error ParseFirstLine(StringParser &parser); + + //Utility functions called by above + QTSS_Error ParseURI(StringParser &parser); + + //Parsing the rest of the headers + //This assumes that the parser is at the beginning of the headers. It will parse + //the headers, fill out the data & HTTPParameters object. + // + //Returns: A handler object signifying that a fatal syntax error has occurred + QTSS_Error ParseHeaders(StringParser& parser); + + + //Functions to parse the contents of particuarly complicated headers (as a convienence + //for modules) + void ParseRangeHeader(); + void ParseTransportHeader(); + void ParseIfModSinceHeader(); + void ParseAddrSubHeader(StrPtrLen* inSubHeader, StrPtrLen* inHeaderName, UInt32* outAddr); + void ParseRetransmitHeader(); + void ParseContentLengthHeader(); + void ParseSpeedHeader(); + void ParsePrebufferHeader(); + void ParseTransportOptionsHeader(); + void ParseSessionHeader(); + void ParseClientPortSubHeader(StrPtrLen* inClientPortSubHeader); + void ParseTimeToLiveSubHeader(StrPtrLen* inTimeToLiveSubHeader); + void ParseModeSubHeader(StrPtrLen* inModeSubHeader); + Bool16 ParseNetworkModeSubHeader(StrPtrLen* inSubHeader); + void ParseDynamicRateHeader(); + void ParseRandomDataSizeHeader(); + void ParseBandwidthHeader(); + + static UInt8 sURLStopConditions[]; +}; +#endif // __RTSPREQUEST_H__ + diff --git a/Server.tproj/RTSPRequest3GPP.cpp b/Server.tproj/RTSPRequest3GPP.cpp new file mode 100644 index 0000000..49efea9 --- /dev/null +++ b/Server.tproj/RTSPRequest3GPP.cpp @@ -0,0 +1,327 @@ +/* + * + * @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: RTSPRequest3GPP.cpp + + Contains: Implementation of RTSPRequest3GPP class. + + + +*/ + + +#include "RTSPRequest3GPP.h" +#include "RTSPProtocol.h" +#include "QTSServerInterface.h" + +#include "RTSPSession.h" +#include "RTSPSessionInterface.h" +#include "StringParser.h" +#include "StringTranslator.h" +#include "OS.h" +#include "OSMemory.h" +#include "QTSS.h" +#include "QTSSModuleUtils.h" +#include "base64.h" +#include "OSArrayObjectDeleter.h" +#include "DateTranslator.h" +#include "SocketUtils.h" + + +void RateAdapationStreamDataFields::SetData(StrPtrLen *streamDataStr) +{ + + static StrPtrLen sSize("size"); + static StrPtrLen sTargetTime("target-time"); + static StrPtrLen sURL("url"); + + StringParser theStreamDataParser(streamDataStr); + + StrPtrLen url; + theStreamDataParser.GetThru(&url, '=');//consume "url=" + url.TrimWhitespace(); // fix if it is " url =" instead of "url=" + + if (false == url.NumEqualIgnoreCase(sURL.Ptr, sURL.Len)) + return; //failed to find url= + + + theStreamDataParser.GetThru(&url, ';');//consume "url=/asdlfjasdf/id=12;" + + { //get the stream id url component + + StringParser theURLParser(&url); + while (theURLParser.GetThru(&url, '/')); //trim to the last path segment + } + + { // get the id value + StringParser theIDParser(&url); + if (false == theIDParser.GetThru(&url, '='))//consume "id=12;" + return; //failed to find '=' + + theIDParser.ConsumeWhitespace(); + fTrackID = theIDParser.ConsumeInteger(); + } + + + StrPtrLen theDataStr; + while ( theStreamDataParser.GetThru(&theDataStr,'=') ) //get the field name + { + theDataStr.TrimWhitespace(); + if (theDataStr.NumEqualIgnoreCase(sSize.Ptr, sSize.Len)) // size= + { + theStreamDataParser.ConsumeWhitespace(); + fBufferSizeBytes = theStreamDataParser.ConsumeInteger(); + } + + if (theDataStr.NumEqualIgnoreCase(sTargetTime.Ptr, sTargetTime.Len)) // target-time = + { + theStreamDataParser.ConsumeWhitespace(); + fTargetTimeMilli = theStreamDataParser.ConsumeInteger(); + } + theStreamDataParser.GetThru(&theDataStr,';'); //skip to next field + } + + +}; + + + +void RateAdapationStreamDataFields::CopyData(RateAdapationStreamDataFields* source) +{ + if (NULL == source) + return; + + fTrackID = source->GetSDPStreamID(); + fBufferSizeBytes = source->GetBufferSizeBytes(); + fTargetTimeMilli = source->GetTargetTimeMilliSec(); + }; + + + +//----------- +void LinkCharDataFields::ParseData( StrPtrLen &theDataStr, StringParser &theLinkCharDataParser) +{ + static StrPtrLen sGBW("GBW"); + static StrPtrLen sMBW("MBW"); + static StrPtrLen sMTD("MTD"); + + if (theDataStr.NumEqualIgnoreCase(sGBW.Ptr, sGBW.Len)) // GBW= + { + theLinkCharDataParser.ConsumeWhitespace(); + fGuaranteedKBitsPerSec = theLinkCharDataParser.ConsumeInteger(); + return; + } + + if (theDataStr.NumEqualIgnoreCase(sMBW.Ptr, sMBW.Len)) //MBW= + { + theLinkCharDataParser.ConsumeWhitespace(); + fMaximumKBitsPerSec = theLinkCharDataParser.ConsumeInteger(); + return; + } + + if (theDataStr.NumEqualIgnoreCase(sMTD.Ptr, sMTD.Len)) //MBW= + { + theLinkCharDataParser.ConsumeWhitespace(); + fMaximumTransferDelayMilliSec = theLinkCharDataParser.ConsumeInteger(); + return; + } + + +} + +void LinkCharDataFields::SetData(StrPtrLen *streamDataStr) +{ + + static StrPtrLen sURL("url"); + + StringParser theLinkCharDataParser(streamDataStr); + + StrPtrLen url; + theLinkCharDataParser.GetThru(&url, '=');//consume "url=" + url.TrimWhitespace(); // fix if it is " url =" instead of "url=" + + if (false == url.NumEqualIgnoreCase(sURL.Ptr, sURL.Len)) + return; //failed to find url= + + theLinkCharDataParser.ConsumeUntil(&url, ';');//consume "url=/asdlfjasdf;" + if (!theLinkCharDataParser.Expect(';')) + return; // no parameters + + url.TrimWhitespace(); + fURL.Set(url.GetAsCString()); + + StrPtrLen theDataStr; + while ( theLinkCharDataParser.GetThru(&theDataStr,'=') ) //get the field name + { + theDataStr.TrimWhitespace(); + this->ParseData(theDataStr,theLinkCharDataParser); + + theLinkCharDataParser.GetThru(&theDataStr,';'); //skip to next field + } + + +}; + + + +void LinkCharDataFields::CopyData(LinkCharDataFields* source) +{ + if (NULL == source) + return; + + fURL.Set(source->GetURL()->GetAsCString()); + fGuaranteedKBitsPerSec = source->GetGKbits(); + fMaximumKBitsPerSec = source->GetMaxKBits(); + fMaximumTransferDelayMilliSec = source->GetMaxDelayMilliSecs(); +}; + + + +//-------- +QTSSAttrInfoDict::AttrInfo RTSPRequest3GPP::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtss3GPPRequestEnabled", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModeWrite | qtssAttrModePreempSafe }, + /* 1 */ { "qtss3GPPRequestRateAdaptationStreamData", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe } + + +}; + +void RTSPRequest3GPP::Initialize() +{ + for (UInt32 x = 0; x < qtss3GPPRequestNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::k3GPPRequestDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); + +} + + +RTSPRequest3GPP::RTSPRequest3GPP(Bool16 enabled) +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::k3GPPRequestDictIndex)), + fEnabled (enabled),fIs3GPP(false), fHasRateAdaptation(false), fHasLinkChar(false) +{ + this->SetVal(qtss3GPPRequestEnabled, &fEnabled, sizeof(fEnabled)); + +} + +QTSS_Error RTSPRequest3GPP::ParseAdpationHeader(QTSSDictionary* headerDictionaryPtr) +{ + + if (NULL == headerDictionaryPtr) + { Assert(0); + return -1; + } + + if (!fEnabled) + return QTSS_NoErr; + + StringParser theRateAdaptHeaderParser(headerDictionaryPtr->GetValue(qtss3GPPAdaptationHeader)); + + if (theRateAdaptHeaderParser.GetDataRemaining() == 0) + return -1; + + fIs3GPP = true; + fHasRateAdaptation = true; + + StrPtrLen theStreamData; + UInt32 numValueIndex = 0; + while (theRateAdaptHeaderParser.GetDataRemaining() != 0) + { + theRateAdaptHeaderParser.GetThru(&theStreamData, ','); + theStreamData.TrimWhitespace(); + (void) this->SetValuePtr(qtss3GPPRequestRateAdaptationStreamData,theStreamData.Ptr, theStreamData.Len, QTSSDictionary::kDontObeyReadOnly); + numValueIndex++; + } + + //this->ParseAdpationHeaderTest(); + + return QTSS_NoErr; +} + +void RTSPRequest3GPP::ParseAdpationHeaderTest() +{ + StrPtrLen dataStr; + UInt32 numValues = this->GetNumValues(qtss3GPPRequestRateAdaptationStreamData); + qtss_printf("RTSPRequest3GPP::ParseAdpationHeaderTest numValues =%lu\n", numValues); + + for (;numValues > 0; numValues --) + { + + if(0 != this->GetValuePtr(qtss3GPPRequestRateAdaptationStreamData, numValues - 1, (void**) &dataStr.Ptr, &dataStr.Len, true)) + qtss_printf("RTSPRequest3GPP::ParseAdpationHeaderTest err GetValuePtr(qtss3GPPRequestRateAdaptationStreamData\n"); + + dataStr.PrintStr("RTSPRequest3GPP::ParseAdpationHeaderTest qtss3GPPRequestRateAdaptationStreamData=[","]\n"); + RateAdapationStreamDataFields fieldsParser; + fieldsParser.SetData(&dataStr); + fieldsParser.PrintData(NULL); + } +} + + + +QTSS_Error RTSPRequest3GPP::ParseLinkCharHeader(QTSSDictionary* headerDictionaryPtr) +{ + + if (NULL == headerDictionaryPtr) + { Assert(0); + return -1; + } + + StrPtrLen* theLinkCharStr = headerDictionaryPtr->GetValue(qtss3GPPLinkCharHeader); + if (theLinkCharStr == NULL) + return -1; + + if (theLinkCharStr->Len == 0) + return -1; + + fIs3GPP = true; + fHasLinkChar = true; + + //this->ParseLinkCharHeaderTest(headerDictionaryPtr); + + return QTSS_NoErr; +} + + +void RTSPRequest3GPP::ParseLinkCharHeaderTest(QTSSDictionary* headerDictionaryPtr) +{ + StrPtrLen dataStr; + UInt32 numValues = headerDictionaryPtr->GetNumValues(qtss3GPPLinkCharHeader); + qtss_printf("RTSPRequest3GPP::ParseLinkCharHeaderTest qtss3GPPLinkCharHeader numValues =%lu\n", numValues); //should be 1 + + for (;numValues > 0; numValues --) + { + + if(0 != headerDictionaryPtr->GetValuePtr(qtss3GPPLinkCharHeader, numValues - 1, (void**) &dataStr.Ptr, &dataStr.Len, true)) + qtss_printf("RTSPRequest3GPP::ParseLinkCharHeaderTest err GetValuePtr(qtss3GPPLinkCharHeader\n"); + + dataStr.PrintStr("RTSPRequest3GPP::ParseLinkCharHeaderTest qtss3GPPLinkCharHeader=[","]\n"); + LinkCharDataFields fieldsParser; + fieldsParser.SetData(&dataStr); + fieldsParser.PrintData(NULL); + } +} + + diff --git a/Server.tproj/RTSPRequest3GPP.h b/Server.tproj/RTSPRequest3GPP.h new file mode 100644 index 0000000..7ed6ea7 --- /dev/null +++ b/Server.tproj/RTSPRequest3GPP.h @@ -0,0 +1,250 @@ +/* + * + * @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: RTSPRequest3GPP.h + + Contains: This parses 3gpp headers from a request + + + +*/ + +#ifndef __RTSPREQUEST3GPP_H__ +#define __RTSPREQUEST3GPP_H__ + +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "StringParser.h" + +/* + +5.3.2.2 The 3GPP-Adaptation header +To enable PSS clients to set bit-rate adaptation parameters, a new RTSP request and response header is defined. The +header can be used in the methods SETUP, PLAY, OPTIONS, and SET_PARAMETER. The header defined in ABNF +[53] has the following syntax: +3GPP-adaptation-def = "3GPP-Adaptation" ":" adaptation-spec 0*("," adaptation-spec) +adaptation-spec = url-def *adapt-params +adapt-params = ";" buffer-size-def + / ";" target-time-def +url-def = "url" "=" <"> url <"> +buffer-size-def = "size" "=" 1*9DIGIT ; bytes +target-time-def = "target-time" "=" 1*9DIGIT; ms +url = ( absoluteURI / relativeURI ) +absoluteURI and relativeURI are defined in RFC 2396 [60] and updated in RFC 2732 [61]. The base URI for any +relative URI is the RTSP request URI. +The "3GPP-Adaptation" header shall be sent in responses to requests containing this header. The PSS server shall not +change the values in the response header. The presence of the header in the response indicates to the client that the +server acknowledges the request. +The buffer size signalled in the "3GPP-Adaptation" header shall correspond to reception, de-jittering, and, if used, de- +interleaving buffer(s) that have this given amount of space for complete application data units (ADU), including the +following RTP header and RTP payload header fields: RTP timestamp, and sequence numbers or decoding order +numbers. The specified buffer size shall also include any Annex G pre-decoder buffer space used for this media, as the +two buffers cannot be separated. +The target protection time signalled in the value of the "target-time" parameter is the targeted minimum buffer level or, +in other words, the client desired amount of playback time in milliseconds to guarantee interrupt-free playback and +allow the server to adjust the transmission rate, if needed. + + +Telnet test data +DESCRIBE rtsp://127.0.0.1/sample_50kbit.3gp RTSP/1.0 +CSeq: 1 +Accept: application/sdp +User-Agent: telnet manual blah blah (03.05) Profile/MIDP-1.0 Configuration/CLDC-1.0 + + +SETUP rtsp://127.0.0.1/sample_50kbit.3gp/trackID=3 RTSP/1.0 +CSeq: 2 +Content-Length: 0 +User-Agent: telnet +Transport: RTP/AVP;unicast;client_port=1566-1567 +3GPP-Adaptation: url="rtsp:/127.0.0.1/sample_50kbit.3gp/streamID=0";target-time=7000,url="rtsp:/127.0.0.1/sample_50kbit.3gp/streamID=1";target-time=7000 + +*/ + + +class RateAdapationStreamDataFields +{ + public: + RateAdapationStreamDataFields() : fTrackID (0), fBufferSizeBytes(0), fTargetTimeMilli(0) {} + void SetData(StrPtrLen *streamDataStr); + void CopyData(RateAdapationStreamDataFields* source); + ~RateAdapationStreamDataFields() {} + UInt32 GetSDPStreamID() { return fTrackID; } + UInt32 GetBufferSizeBytes() { return fBufferSizeBytes; } + UInt32 GetTargetTimeMilliSec() { return fTargetTimeMilli; } + void PrintData(StrPtrLen *streamDataStr = NULL) {if (streamDataStr != NULL) this->SetData(streamDataStr); qtss_printf("RateAdapationStreamDataFields::PrintData trackID=%"_U32BITARG_" bufferSize=%"_U32BITARG_" targetTime=%"_U32BITARG_"\n", GetSDPStreamID(), GetBufferSizeBytes(),GetTargetTimeMilliSec() ); } + + + private: + + UInt32 fTrackID; + UInt32 fBufferSizeBytes; + UInt32 fTargetTimeMilli; + +}; + + +#if 0 +/* +5.3.2.1 The 3GPP-Link-Char header +To enable PSS clients to report the link characteristics of the radio interface to the PSS server, the "3GPP-Link-Char" +RTSP header is defined. The header takes one or more arguments. The reported information should be taken from a +QoS reservation (i.e. the QoS profile as defined in [56]). Note that this information is only valid for the wireless link +and does not apply end-to-end. However, the parameters do provide constraints that can be used. + +Three parameters are defined that can be included in the header, and future extensions are possible to define. Any +unknown parameter shall be ignored. The three parameters are: +- "GBW": the link's guaranteed bit-rate in kilobits per second as defined by [56]; +- "MBW": the link's maximum bit-rate in kilobits per second as defined by [56]; +- "MTD": the link's maximum transfer delay, as defined by [56] in milliseconds. +The "3GPP-Link-Char" header syntax is defined below using ABNF [53]: +3gpplinkheader = "3GPP-Link-Char" ":" link-char-spec *("," 0*1SP link-char-spec) CRLF +link-char-spec = char-link-url *(";" 0*1SP link-parameters) +char-link-url = "url" "=" <">url<"> +link-parameters = Guaranteed-BW / Max-BW / Max-Transfer-delay / extension-type +Guaranteed-BW = "GBW" "=" 1*DIGIT ; bps +Max-BW = "MBW" "=" 1*DIGIT ; bps +Max-Transfer-delay = "MTD" "=" 1*DIGIT ; ms +extension-type = token "=" (token / quoted-string) +DIGIT = as defined in RFC 2326 [5] +token = as defined in RFC 2326 [5] +quoted-string = as defined in RFC 2326 [5] +url = as defined in RFC 2326 [5] +The "3GPP-Link-Char" header can be included in a request using any of the following RTSP methods: SETUP, PLAY, +OPTIONS, and SET_PARAMETER. The header shall not be included in any response. The header can contain one or +more characteristics specifications. Each specification contains a URI that can either be an absolute or a relative, any +relative URI use the RTSP request URI as base. The URI points out the media component that the given parameters +apply to. This can either be an individual media stream or a session aggregate. +If a QoS reservation (PDP context) is shared by several media components in a session the 3GPP-Link-Char header +shall not be sent prior to the RTSP PLAY request. In this case the URI to use is the aggregated RTSP URI. If the QoS +reservation is not shared (one PDP context per media) the media stream URI must be used in the 3GPP-Link-Char +specification. If one QoS reservation (PDP context) per media component is used, the specification parameters shall be +sent per media component. +The "3GPP-Link-Char" header should be included in a SETUP or PLAY request by the client, to give the initial values +for the link characteristics. A SET_PARAMETER or OPTIONS request can be used to update the 3GPP-Link-Char +values in a session currently playing. It is strongly recommended that SET_PARAMETER is used, as this has the +correct semantics for the operation and also requires less overhead both in bandwidth and server processing. When +performing updates of the parameters, all of the previous signalled values are undefined and only the given ones in the +update are defined. This means that even if a parameter has not changed, it must be included in the update. + +Example: +3GPP-LinkChar: url="rtsp://server.example.com/media.3gp"; GBW=32; MBW=128; MTD=2000 + +In the above example the header tells the server that its radio link has a QoS setting with a guaranteed bit-rate of 32 +kbps, a maximum bit-rate of 128 kbps, and a maximum transfer delay of 2.0 seconds. These parameters are valid for the +aggregate of all media components, as the URI is an aggregated RTSP URI. + +Test protocol +DESCRIBE rtsp://127.0.0.1/sample_50kbit.3gp RTSP/1.0 +CSeq: 1 +Accept: application/sdp +User-Agent: telnet manual blah blah (03.05) Profile/MIDP-1.0 Configuration/CLDC-1.0 + + +SETUP rtsp://127.0.0.1/sample_50kbit.3gp/trackID=3 RTSP/1.0 +CSeq: 2 +Content-Length: 0 +User-Agent: telnet +Transport: RTP/AVP;unicast;client_port=1566-1567 +3GPP-Adaptation: url="rtsp:/127.0.0.1/sample_50kbit.3gp/streamID=0";target-time=7000,url="rtsp:/127.0.0.1/sample_50kbit.3gp/streamID=1";target-time=7000 +3GPP-Link-Char: url="rtsp://server.example.com/media.3gp"; GBW=32; MBW=128; MTD=2000 + + +*/ +#endif + +class LinkCharDataFields +{ + public: + LinkCharDataFields() : fGuaranteedKBitsPerSec (0), fMaximumKBitsPerSec(0), fMaximumTransferDelayMilliSec(0), fURL() {} + void SetData(StrPtrLen *streamDataStr); + void CopyData(LinkCharDataFields* source); + ~LinkCharDataFields() {} + UInt32 GetGKbits() { return fGuaranteedKBitsPerSec; } + UInt32 GetMaxKBits() { return fMaximumKBitsPerSec; } + UInt32 GetMaxDelayMilliSecs() { return fMaximumTransferDelayMilliSec; } + StrPtrLen* GetURL() { return &fURL; } + void PrintData(StrPtrLen *streamDataStr = NULL) + {if (streamDataStr != NULL) this->SetData(streamDataStr); qtss_printf("LinkCharDataFields::PrintData fURL=%s fGuaranteedKBitsPerSec=%"_U32BITARG_" fMaximumKBitsPerSec=%"_U32BITARG_" fMaximumTransferDelayMilliSec=%"_U32BITARG_"\n",GetURL()->Ptr, GetGKbits(), GetMaxKBits(),GetMaxDelayMilliSecs() ); } + + + private: + void ParseData( StrPtrLen &theDataStr, StringParser &theLinkCharDataParser); + + UInt32 fGuaranteedKBitsPerSec; + UInt32 fMaximumKBitsPerSec; + UInt32 fMaximumTransferDelayMilliSec; + StrPtrLenDel fURL; + +}; + + + + +//3GPPRequest utility class definition +class RTSPRequest3GPP : public QTSSDictionary +{ + public: + //Initialize + static void Initialize(); + + //CONSTRUCTOR / DESTRUCTOR + //these do very little. Just initialize / delete some member data. + // + //Arguments: enable the object + RTSPRequest3GPP(Bool16 enabled); + ~RTSPRequest3GPP() {} + + //Parses the request. Returns an error if there was an error encountered + //in parsing. + QTSS_Error ParseAdpationHeader(QTSSDictionary* headerDictionaryPtr); + + //Parses the request. Returns an error if there was an error encountered + //in parsing. + QTSS_Error ParseLinkCharHeader(QTSSDictionary* headerDictionaryPtr); + + + Bool16 Is3GPP() {return fIs3GPP;} + Bool16 HasRateAdaptation() {return fHasRateAdaptation;} + Bool16 HasLinkChar() {return fHasLinkChar;} + + private: + void ParseAdpationHeaderTest(); + void ParseLinkCharHeaderTest(QTSSDictionary* headerDictionaryPtr); + + + Bool16 fEnabled; + Bool16 fIs3GPP; + Bool16 fHasRateAdaptation; + Bool16 fHasLinkChar; + + + + //Dictionary support + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + +}; +#endif // __RTSPREQUEST3GPP_H__ + diff --git a/Server.tproj/RTSPRequestInterface.cpp b/Server.tproj/RTSPRequestInterface.cpp new file mode 100644 index 0000000..624bd7e --- /dev/null +++ b/Server.tproj/RTSPRequestInterface.cpp @@ -0,0 +1,806 @@ +/* + * + * @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: RTSPRequestInterface.cp + + Contains: Implementation of class defined in RTSPRequestInterface.h + + + + +*/ + + +//INCLUDES: +#ifndef __Win32__ +#include +#include +#endif + +#include "RTSPRequestInterface.h" +#include "RTSPSessionInterface.h" +#include "RTSPRequestStream.h" + +#include "StringParser.h" +#include "OSMemory.h" +#include "OSThread.h" +#include "DateTranslator.h" +#include "QTSSDataConverter.h" +#include "OSArrayObjectDeleter.h" +#include "QTSSPrefs.h" +#include "QTSServerInterface.h" + +char RTSPRequestInterface::sPremadeHeader[kStaticHeaderSizeInBytes]; +StrPtrLen RTSPRequestInterface::sPremadeHeaderPtr(sPremadeHeader, kStaticHeaderSizeInBytes); + +char RTSPRequestInterface::sPremadeNoHeader[kStaticHeaderSizeInBytes]; +StrPtrLen RTSPRequestInterface::sPremadeNoHeaderPtr(sPremadeNoHeader, kStaticHeaderSizeInBytes); + + +StrPtrLen RTSPRequestInterface::sColonSpace(": ", 2); + +QTSSAttrInfoDict::AttrInfo RTSPRequestInterface::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssRTSPReqFullRequest", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtssRTSPReqMethodStr", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 2 */ { "qtssRTSPReqFilePath", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 3 */ { "qtssRTSPReqURI", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 4 */ { "qtssRTSPReqFilePathTrunc", GetTruncatedPath, qtssAttrDataTypeCharArray, qtssAttrModeRead }, + /* 5 */ { "qtssRTSPReqFileName", GetFileName, qtssAttrDataTypeCharArray, qtssAttrModeRead }, + /* 6 */ { "qtssRTSPReqFileDigit", GetFileDigit, qtssAttrDataTypeCharArray, qtssAttrModeRead }, + /* 7 */ { "qtssRTSPReqAbsoluteURL", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 8 */ { "qtssRTSPReqTruncAbsoluteURL", GetAbsTruncatedPath, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeCacheable }, + /* 9 */ { "qtssRTSPReqMethod", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 10 */ { "qtssRTSPReqStatusCode", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 11 */ { "qtssRTSPReqStartTime", NULL, qtssAttrDataTypeFloat64, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 12 */ { "qtssRTSPReqStopTime", NULL, qtssAttrDataTypeFloat64, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 13 */ { "qtssRTSPReqRespKeepAlive", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 14 */ { "qtssRTSPReqRootDir", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModeWrite }, + /* 15 */ { "qtssRTSPReqRealStatusCode", GetRealStatusCode, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 16 */ { "qtssRTSPReqStreamRef", NULL, qtssAttrDataTypeQTSS_StreamRef, qtssAttrModeRead | qtssAttrModePreempSafe }, + + /* 17 */ { "qtssRTSPReqUserName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 18 */ { "qtssRTSPReqUserPassword", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 19 */ { "qtssRTSPReqUserAllowed", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 20 */ { "qtssRTSPReqURLRealm", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 21 */ { "qtssRTSPReqLocalPath", GetLocalPath, qtssAttrDataTypeCharArray, qtssAttrModeRead }, + /* 22 */ { "qtssRTSPReqIfModSinceDate", NULL, qtssAttrDataTypeTimeVal, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 23 */ { "qtssRTSPReqQueryString", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 24 */ { "qtssRTSPReqRespMsg", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 25 */ { "qtssRTSPReqContentLen", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 26 */ { "qtssRTSPReqSpeed", NULL, qtssAttrDataTypeFloat32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 27 */ { "qtssRTSPReqLateTolerance", NULL, qtssAttrDataTypeFloat32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 28 */ { "qtssRTSPReqTransportType", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 29 */ { "qtssRTSPReqTransportMode", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 30 */ { "qtssRTSPReqSetUpServerPort", NULL, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite}, + /* 31 */ { "qtssRTSPReqAction", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 32 */ { "qtssRTSPReqUserProfile", NULL, qtssAttrDataTypeQTSS_Object, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 33 */ { "qtssRTSPReqPrebufferMaxTime", NULL, qtssAttrDataTypeFloat32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 34 */ { "qtssRTSPReqAuthScheme", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 35 */ { "qtssRTSPReqSkipAuthorization", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 36 */ { "qtssRTSPReqNetworkMode", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 37 */ { "qtssRTSPReqDynamicRateValue", NULL, qtssAttrDataTypeSInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 38 */ { "qtssRTSPReq3GPPRequestObject", NULL, qtssAttrDataTypeQTSS_Object,qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 39 */ { "qtssRTSPReqBandwidthBits", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 40 */ { "qtssRTSPReqUserFound", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 41 */ { "qtssRTSPReqAuthHandled", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeWrite }, + /* 42 */ { "qtssRTSPReqDigestChallenge", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 43 */ { "qtssRTSPReqDigestResponse", GetAuthDigestResponse, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe } + + + + + }; + + +void RTSPRequestInterface::Initialize(void) +{ + //make a partially complete header + StringFormatter headerFormatter(sPremadeHeaderPtr.Ptr, kStaticHeaderSizeInBytes); + PutStatusLine(&headerFormatter, qtssSuccessOK, RTSPProtocol::k10Version); + + headerFormatter.Put(QTSServerInterface::GetServerHeader()); + headerFormatter.PutEOL(); + headerFormatter.Put(RTSPProtocol::GetHeaderString(qtssCSeqHeader)); + headerFormatter.Put(sColonSpace); + sPremadeHeaderPtr.Len = headerFormatter.GetCurrentOffset(); + Assert(sPremadeHeaderPtr.Len < kStaticHeaderSizeInBytes); + + + StringFormatter noServerInfoHeaderFormatter(sPremadeNoHeaderPtr.Ptr, kStaticHeaderSizeInBytes); + PutStatusLine(&noServerInfoHeaderFormatter, qtssSuccessOK, RTSPProtocol::k10Version); + noServerInfoHeaderFormatter.Put(RTSPProtocol::GetHeaderString(qtssCSeqHeader)); + noServerInfoHeaderFormatter.Put(sColonSpace); + sPremadeNoHeaderPtr.Len = noServerInfoHeaderFormatter.GetCurrentOffset(); + Assert(sPremadeNoHeaderPtr.Len < kStaticHeaderSizeInBytes); + + //Setup all the dictionary stuff + for (UInt32 x = 0; x < qtssRTSPReqNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kRTSPRequestDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); + + QTSSDictionaryMap* theHeaderMap = QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kRTSPHeaderDictIndex); + for (UInt32 y = 0; y < qtssNumHeaders; y++) + theHeaderMap->SetAttribute(y, RTSPProtocol::GetHeaderString(y).Ptr, NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe); +} + +//CONSTRUCTOR / DESTRUCTOR: very simple stuff +RTSPRequestInterface::RTSPRequestInterface(RTSPSessionInterface *session) +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kRTSPRequestDictIndex)), + fMethod(qtssIllegalMethod), + fStatus(qtssSuccessOK), + fRealStatusCode(0), + fRequestKeepAlive(true), + //fResponseKeepAlive(true), //parameter need not be set + fVersion(RTSPProtocol::k10Version), + fStartTime(-1), + fStopTime(-1), + fClientPortA(0), + fClientPortB(0), + fTtl(0), + fDestinationAddr(0), + fSourceAddr(0), + fTransportType(qtssRTPTransportTypeUDP), + fNetworkMode(qtssRTPNetworkModeDefault), + fContentLength(0), + fIfModSinceDate(0), + fSpeed(0), + fLateTolerance(-1), + fPrebufferAmt(-1), + fWindowSize(0), + fMovieFolderPtr(&fMovieFolderPath[0]), + fHeaderDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kRTSPHeaderDictIndex)), + fAllowed(true), + fHasUser(false), + fAuthHandled(false), + fTransportMode(qtssRTPTransportModePlay), + fSetUpServerPort(0), + fAction(qtssActionFlagsNoFlags), + fAuthScheme(qtssAuthNone), + fAuthQop(RTSPSessionInterface::kNoQop), + fUserProfile(), + fUserProfilePtr(&fUserProfile), + fStale(false), + fSkipAuthorization(false), + fEnableDynamicRateState(-1),// -1 undefined, 0 disabled, 1 enabled + // DJM PROTOTYPE + fRandomDataSize(0), + fRequest3GPP( QTSServerInterface::GetServer()->GetPrefs()->Get3GPPEnabled() ), + fRequest3GPPPtr(&fRequest3GPP), + fBandwidthBits(0), + + // private storage initializes after protected and public storage above + fSession(session), + fOutputStream(session->GetOutputStream()), + fStandardHeadersWritten(false) // private initializes after protected and public storage above + +{ + //Setup QTSS parameters that can be setup now. These are typically the parameters that are actually + //pointers to binary variable values. Because these variables are just member variables of this object, + //we can properly initialize their pointers right off the bat. + + fStreamRef = this; + RTSPRequestStream* input = session->GetInputStream(); + this->SetVal(qtssRTSPReqFullRequest, input->GetRequestBuffer()->Ptr, input->GetRequestBuffer()->Len); + this->SetVal(qtssRTSPReqMethod, &fMethod, sizeof(fMethod)); + this->SetVal(qtssRTSPReqStatusCode, &fStatus, sizeof(fStatus)); + this->SetVal(qtssRTSPReqRespKeepAlive, &fResponseKeepAlive, sizeof(fResponseKeepAlive)); + this->SetVal(qtssRTSPReqStreamRef, &fStreamRef, sizeof(fStreamRef)); + this->SetVal(qtssRTSPReqContentLen, &fContentLength, sizeof(fContentLength)); + this->SetVal(qtssRTSPReqSpeed, &fSpeed, sizeof(fSpeed)); + this->SetVal(qtssRTSPReqLateTolerance, &fLateTolerance, sizeof(fLateTolerance)); + this->SetVal(qtssRTSPReqPrebufferMaxTime, &fPrebufferAmt, sizeof(fPrebufferAmt)); + + // Get the default root directory from QTSSPrefs, and store that in the proper parameter + // Note that the GetMovieFolderPath function may allocate memory, so we check for that + // in this object's destructor and free that memory if necessary. + UInt32 pathLen = kMovieFolderBufSizeInBytes; + fMovieFolderPtr = QTSServerInterface::GetServer()->GetPrefs()->GetMovieFolder(fMovieFolderPtr, &pathLen); + //this->SetVal(qtssRTSPReqRootDir, fMovieFolderPtr, pathLen); + this->SetValue(qtssRTSPReqRootDir, 0, fMovieFolderPtr, pathLen, QTSSDictionary::kDontObeyReadOnly); + + //There are actually other attributes that point to member variables that we COULD setup now, but they are attributes that + //typically aren't set for every request, so we lazy initialize those when we parse the request + + this->SetVal(qtssRTSPReqUserAllowed, &fAllowed, sizeof(fAllowed)); + this->SetVal(qtssRTSPReqUserFound, &fHasUser, sizeof(fHasUser)); + this->SetVal(qtssRTSPReqAuthHandled, &fAuthHandled, sizeof(fAuthHandled)); + + this->SetVal(qtssRTSPReqTransportType, &fTransportType, sizeof(fTransportType)); + this->SetVal(qtssRTSPReqTransportMode, &fTransportMode, sizeof(fTransportMode)); + this->SetVal(qtssRTSPReqSetUpServerPort, &fSetUpServerPort, sizeof(fSetUpServerPort)); + this->SetVal(qtssRTSPReqAction, &fAction, sizeof(fAction)); + this->SetVal(qtssRTSPReqUserProfile, &fUserProfilePtr, sizeof(fUserProfilePtr)); + this->SetVal(qtssRTSPReqAuthScheme, &fAuthScheme, sizeof(fAuthScheme)); + this->SetVal(qtssRTSPReqSkipAuthorization, &fSkipAuthorization, sizeof(fSkipAuthorization)); + + this->SetVal(qtssRTSPReqDynamicRateState, &fEnableDynamicRateState, sizeof(fEnableDynamicRateState)); + + this->SetVal(qtssRTSPReq3GPPRequestObject, &fRequest3GPPPtr, sizeof(fRequest3GPPPtr)); + this->SetVal(qtssRTSPReqBandwidthBits, &fBandwidthBits, sizeof(fBandwidthBits)); + + this->SetVal(qtssRTSPReqDigestResponse, &fAuthDigestResponse, sizeof(fAuthDigestResponse)); + + + } + +void RTSPRequestInterface::AppendHeader(QTSS_RTSPHeader inHeader, StrPtrLen* inValue) +{ + if (!fStandardHeadersWritten) + this->WriteStandardHeaders(); + + fOutputStream->Put(RTSPProtocol::GetHeaderString(inHeader)); + fOutputStream->Put(sColonSpace); + fOutputStream->Put(*inValue); + fOutputStream->PutEOL(); +} + +void RTSPRequestInterface::PutStatusLine(StringFormatter* putStream, QTSS_RTSPStatusCode status, + RTSPProtocol::RTSPVersion version) +{ + putStream->Put(RTSPProtocol::GetVersionString(version)); + putStream->PutSpace(); + putStream->Put(RTSPProtocol::GetStatusCodeAsString(status)); + putStream->PutSpace(); + putStream->Put(RTSPProtocol::GetStatusCodeString(status)); + putStream->PutEOL(); +} + + +void RTSPRequestInterface::AppendContentLength(UInt32 contentLength) +{ + if (!fStandardHeadersWritten) + this->WriteStandardHeaders(); + + char dataSize[10]; + dataSize[sizeof(dataSize) -1] = 0; + qtss_snprintf(dataSize, sizeof(dataSize) -1, "%"_U32BITARG_"", contentLength); + StrPtrLen contentLengthStr(dataSize); + this->AppendHeader(qtssContentLengthHeader, &contentLengthStr); + +} + +void RTSPRequestInterface::AppendDateAndExpires() +{ + if (!fStandardHeadersWritten) + this->WriteStandardHeaders(); + + Assert(OSThread::GetCurrent() != NULL); + DateBuffer* theDateBuffer = OSThread::GetCurrent()->GetDateBuffer(); + theDateBuffer->InexactUpdate(); // Update the date buffer to the current date & time + StrPtrLen theDate(theDateBuffer->GetDateBuffer(), DateBuffer::kDateBufferLen); + + // Append dates, and have this response expire immediately + this->AppendHeader(qtssDateHeader, &theDate); + this->AppendHeader(qtssExpiresHeader, &theDate); +} + + +void RTSPRequestInterface::AppendSessionHeaderWithTimeout( StrPtrLen* inSessionID, StrPtrLen* inTimeout ) +{ + + // Append a session header if there wasn't one already + if ( GetHeaderDictionary()->GetValue(qtssSessionHeader)->Len == 0) + { + if (!fStandardHeadersWritten) + this->WriteStandardHeaders(); + + static StrPtrLen sTimeoutString(";timeout="); + + // Just write out the session header and session ID + if (inSessionID != NULL && inSessionID->Len > 0) + { + fOutputStream->Put( RTSPProtocol::GetHeaderString(qtssSessionHeader ) ); + fOutputStream->Put(sColonSpace); + fOutputStream->Put( *inSessionID ); + + + if ( inTimeout != NULL && inTimeout->Len != 0) + { + fOutputStream->Put( sTimeoutString ); + fOutputStream->Put( *inTimeout ); + } + + + fOutputStream->PutEOL(); + } + } + +} + +void RTSPRequestInterface::PutTransportStripped(StrPtrLen &fullTransportHeader, StrPtrLen &fieldToStrip) +{ + + // skip the fieldToStrip and echo the rest back + UInt32 offset = (UInt32) (fieldToStrip.Ptr - fullTransportHeader.Ptr); + StrPtrLen transportStart(fullTransportHeader.Ptr,offset); + while (transportStart.Len > 0) // back up removing chars up to and including ; + { + transportStart.Len --; + if (transportStart[transportStart.Len] == ';') + break; + } + + StrPtrLen transportRemainder(fieldToStrip.Ptr,fullTransportHeader.Len - offset); + StringParser transportParser(&transportRemainder); + transportParser.ConsumeUntil(&fieldToStrip, ';'); //remainder starts with ; + transportRemainder.Set(transportParser.GetCurrentPosition(),transportParser.GetDataRemaining()); + + fOutputStream->Put(transportStart); + fOutputStream->Put(transportRemainder); + +} + +void RTSPRequestInterface::AppendTransportHeader(StrPtrLen* serverPortA, + StrPtrLen* serverPortB, + StrPtrLen* channelA, + StrPtrLen* channelB, + StrPtrLen* serverIPAddr, + StrPtrLen* ssrc) +{ + static StrPtrLen sServerPortString(";server_port="); + static StrPtrLen sSourceString(";source="); + static StrPtrLen sInterleavedString(";interleaved="); + static StrPtrLen sSSRC(";ssrc="); + static StrPtrLen sInterLeaved("interleaved");//match the interleaved tag + static StrPtrLen sClientPort("client_port"); + static StrPtrLen sClientPortString(";client_port="); + + if (!fStandardHeadersWritten) + this->WriteStandardHeaders(); + + // Just write out the same transport header the client sent to us. + fOutputStream->Put(RTSPProtocol::GetHeaderString(qtssTransportHeader)); + fOutputStream->Put(sColonSpace); + + StrPtrLen outFirstTransport(fFirstTransport.GetAsCString()); + OSCharArrayDeleter outFirstTransportDeleter(outFirstTransport.Ptr); + outFirstTransport.RemoveWhitespace(); + while (outFirstTransport[outFirstTransport.Len - 1] == ';') + outFirstTransport.Len --; + + // see if it contains an interleaved field or client port field + StrPtrLen stripClientPortStr; + StrPtrLen stripInterleavedStr; + (void) outFirstTransport.FindStringIgnoreCase(sClientPort, &stripClientPortStr); + (void) outFirstTransport.FindStringIgnoreCase(sInterLeaved, &stripInterleavedStr); + + // echo back the transport without the interleaved or client ports fields we will add those in ourselves + if (stripClientPortStr.Len != 0) + PutTransportStripped(outFirstTransport, stripClientPortStr); + else if (stripInterleavedStr.Len != 0) + PutTransportStripped(outFirstTransport, stripInterleavedStr); + else + fOutputStream->Put(outFirstTransport); + + + //The source IP addr is optional, only append it if it is provided + if (serverIPAddr != NULL) + { + fOutputStream->Put(sSourceString); + fOutputStream->Put(*serverIPAddr); + } + + // Append the client ports, + if (stripClientPortStr.Len != 0) + { + fOutputStream->Put(sClientPortString); + UInt16 portA = this->GetClientPortA(); + UInt16 portB = this->GetClientPortB(); + StrPtrLenDel clientPortA(QTSSDataConverter::ValueToString( &portA, sizeof(portA), qtssAttrDataTypeUInt16)); + StrPtrLenDel clientPortB(QTSSDataConverter::ValueToString( &portB, sizeof(portB), qtssAttrDataTypeUInt16)); + + fOutputStream->Put(clientPortA); + fOutputStream->PutChar('-'); + fOutputStream->Put(clientPortB); + } + + // Append the server ports, if provided. + if (serverPortA != NULL) + { + fOutputStream->Put(sServerPortString); + fOutputStream->Put(*serverPortA); + fOutputStream->PutChar('-'); + fOutputStream->Put(*serverPortB); + } + + // Append channel #'s, if provided + if (channelA != NULL) + { + fOutputStream->Put(sInterleavedString); + fOutputStream->Put(*channelA); + fOutputStream->PutChar('-'); + fOutputStream->Put(*channelB); + } + + if (ssrc != NULL && ssrc->Ptr != NULL && ssrc->Len != 0 && fNetworkMode == qtssRTPNetworkModeUnicast && fTransportMode == qtssRTPTransportModePlay) + { + char* theCString = ssrc->GetAsCString(); + OSCharArrayDeleter cStrDeleter(theCString); + + UInt32 ssrcVal = 0; + ::sscanf(theCString, "%"_U32BITARG_"", &ssrcVal); + ssrcVal = htonl(ssrcVal); + + StrPtrLen hexSSRC(QTSSDataConverter::ValueToString( &ssrcVal, sizeof(ssrcVal), qtssAttrDataTypeUnknown)); + OSCharArrayDeleter hexStrDeleter(hexSSRC.Ptr); + + fOutputStream->Put(sSSRC); + fOutputStream->Put(hexSSRC); + } + + fOutputStream->PutEOL(); +} + +void RTSPRequestInterface::AppendContentBaseHeader(StrPtrLen* theURL) +{ + if (!fStandardHeadersWritten) + this->WriteStandardHeaders(); + + fOutputStream->Put(RTSPProtocol::GetHeaderString(qtssContentBaseHeader)); + fOutputStream->Put(sColonSpace); + fOutputStream->Put(*theURL); + fOutputStream->PutChar('/'); + fOutputStream->PutEOL(); +} + +void RTSPRequestInterface::AppendRetransmitHeader(UInt32 inAckTimeout) +{ + static const StrPtrLen kAckTimeout("ack-timeout="); + + fOutputStream->Put(RTSPProtocol::GetHeaderString(qtssXRetransmitHeader)); + fOutputStream->Put(sColonSpace); + fOutputStream->Put(RTSPProtocol::GetRetransmitProtocolName()); + fOutputStream->PutChar(';'); + fOutputStream->Put(kAckTimeout); + fOutputStream->Put(inAckTimeout); + + if (fWindowSizeStr.Len > 0) + { + // + // If the client provided a window size, append that as well. + fOutputStream->PutChar(';'); + fOutputStream->Put(fWindowSizeStr); + } + + fOutputStream->PutEOL(); + +} + + +void RTSPRequestInterface::AppendRTPInfoHeader(QTSS_RTSPHeader inHeader, + StrPtrLen* url, StrPtrLen* seqNumber, + StrPtrLen* ssrc, StrPtrLen* rtpTime, Bool16 lastRTPInfo) +{ + static StrPtrLen sURL("url=", 4); + static StrPtrLen sSeq(";seq=", 5); + static StrPtrLen sSsrc(";ssrc=", 6); + static StrPtrLen sRTPTime(";rtptime=", 9); + + if (!fStandardHeadersWritten) + this->WriteStandardHeaders(); + + fOutputStream->Put(RTSPProtocol::GetHeaderString(inHeader)); + if (inHeader != qtssSameAsLastHeader) + fOutputStream->Put(sColonSpace); + + //Only append the various bits of RTP information if they actually have been + //providied + if ((url != NULL) && (url->Len > 0)) + { + fOutputStream->Put(sURL); + +if (true) //3gpp requires this and it follows RTSP RFC. +{ + RTSPRequestInterface* theRequest = (RTSPRequestInterface*)this; + StrPtrLen *path = (StrPtrLen *) theRequest->GetValue(qtssRTSPReqAbsoluteURL); + + if (path != NULL && path->Len > 0) + { fOutputStream->Put(*path); + if(path->Ptr[path->Len-1] != '/') + fOutputStream->PutChar('/'); + } +} + + fOutputStream->Put(*url); + } + if ((seqNumber != NULL) && (seqNumber->Len > 0)) + { + fOutputStream->Put(sSeq); + fOutputStream->Put(*seqNumber); + } + if ((ssrc != NULL) && (ssrc->Len > 0)) + { + fOutputStream->Put(sSsrc); + fOutputStream->Put(*ssrc); + } + if ((rtpTime != NULL) && (rtpTime->Len > 0)) + { + fOutputStream->Put(sRTPTime); + fOutputStream->Put(*rtpTime); + } + + if (lastRTPInfo) + fOutputStream->PutEOL(); +} + + + +void RTSPRequestInterface::WriteStandardHeaders() +{ + static StrPtrLen sCloseString("Close", 5); + + Assert(sPremadeHeader != NULL); + fStandardHeadersWritten = true; //must be done here to prevent recursive calls + + //if this is a "200 OK" response (most HTTP responses), we have some special + //optmizations here + Bool16 sendServerInfo = QTSServerInterface::GetServer()->GetPrefs()->GetRTSPServerInfoEnabled(); + if (fStatus == qtssSuccessOK) + { + + if (sendServerInfo) + { fOutputStream->Put(sPremadeHeaderPtr); + } + else + { + fOutputStream->Put(sPremadeNoHeaderPtr); + } + StrPtrLen* cSeq = fHeaderDictionary.GetValue(qtssCSeqHeader); + Assert(cSeq != NULL); + if (cSeq->Len > 1) + fOutputStream->Put(*cSeq); + else if (cSeq->Len == 1) + fOutputStream->PutChar(*cSeq->Ptr); + fOutputStream->PutEOL(); + } + else + { +#if 0 + // if you want the connection to stay alive when we don't grok + // the specfied parameter than eneable this code. - [sfu] + if (fStatus == qtssClientParameterNotUnderstood) { + fResponseKeepAlive = true; + } +#endif + //other status codes just get built on the fly + PutStatusLine(fOutputStream, fStatus, RTSPProtocol::k10Version); + if (sendServerInfo) + { + fOutputStream->Put(QTSServerInterface::GetServerHeader()); + fOutputStream->PutEOL(); + } + AppendHeader(qtssCSeqHeader, fHeaderDictionary.GetValue(qtssCSeqHeader)); + } + + //append sessionID header + StrPtrLen* incomingID = fHeaderDictionary.GetValue(qtssSessionHeader); + if ((incomingID != NULL) && (incomingID->Len > 0)) + AppendHeader(qtssSessionHeader, incomingID); + + //follows the HTTP/1.1 convention: if server wants to close the connection, it + //tags the response with the Connection: close header + if (!fResponseKeepAlive) + AppendHeader(qtssConnectionHeader, &sCloseString); + + // 3gpp release 6 rate adaptation calls for echoing the rate adapt header back + // some clients use this header in the response to signal whether to send rate adapt + // NADU rtcp reports. + Bool16 doRateAdaptation = QTSServerInterface::GetServer()->GetPrefs()->Get3GPPEnabled() && QTSServerInterface::GetServer()->GetPrefs()->Get3GPPRateAdaptationEnabled(); + if (doRateAdaptation) + { StrPtrLen* rateAdaptHeader = fHeaderDictionary.GetValue(qtss3GPPAdaptationHeader); + if (rateAdaptHeader && rateAdaptHeader->Ptr && rateAdaptHeader->Len > 0) + AppendHeader(qtss3GPPAdaptationHeader, fHeaderDictionary.GetValue(qtss3GPPAdaptationHeader)); + } + +} + +void RTSPRequestInterface::SendHeader() +{ + if (!fStandardHeadersWritten) + this->WriteStandardHeaders(); + fOutputStream->PutEOL(); +} + +QTSS_Error +RTSPRequestInterface::Write(void* inBuffer, UInt32 inLength, UInt32* outLenWritten, UInt32 /*inFlags*/) +{ + //now just write whatever remains into the output buffer + fOutputStream->Put((char*)inBuffer, inLength); + + if (outLenWritten != NULL) + *outLenWritten = inLength; + + return QTSS_NoErr; +} + +QTSS_Error +RTSPRequestInterface::WriteV(iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, UInt32* outLenWritten) +{ + (void)fOutputStream->WriteV(inVec, inNumVectors, inTotalLength, NULL, + RTSPResponseStream::kAlwaysBuffer); + if (outLenWritten != NULL) + *outLenWritten = inTotalLength; + return QTSS_NoErr; +} + +//param retrieval functions described in .h file +void* RTSPRequestInterface::GetAbsTruncatedPath(QTSSDictionary* inRequest, UInt32* /*outLen*/) +{ + // This function gets called only once + + RTSPRequestInterface* theRequest = (RTSPRequestInterface*)inRequest; + theRequest->SetVal(qtssRTSPReqTruncAbsoluteURL, theRequest->GetValue(qtssRTSPReqAbsoluteURL)); + + //Adjust the length to truncate off the last file in the path + + StrPtrLen* theAbsTruncPathParam = theRequest->GetValue(qtssRTSPReqTruncAbsoluteURL); + theAbsTruncPathParam->Len--; + while (theAbsTruncPathParam->Ptr[theAbsTruncPathParam->Len] != kPathDelimiterChar) + theAbsTruncPathParam->Len--; + + return NULL; +} + +void* RTSPRequestInterface::GetTruncatedPath(QTSSDictionary* inRequest, UInt32* /*outLen*/) +{ + // This function always gets called + + RTSPRequestInterface* theRequest = (RTSPRequestInterface*)inRequest; + theRequest->SetVal(qtssRTSPReqFilePathTrunc, theRequest->GetValue(qtssRTSPReqFilePath)); + + //Adjust the length to truncate off the last file in the path + StrPtrLen* theTruncPathParam = theRequest->GetValue(qtssRTSPReqFilePathTrunc); + + if (theTruncPathParam->Len > 0) + { + theTruncPathParam->Len--; + while ( (theTruncPathParam->Len != 0) && (theTruncPathParam->Ptr[theTruncPathParam->Len] != kPathDelimiterChar) ) + theTruncPathParam->Len--; + } + + return NULL; +} + +void* RTSPRequestInterface::GetFileName(QTSSDictionary* inRequest, UInt32* /*outLen*/) +{ + // This function always gets called + + RTSPRequestInterface* theRequest = (RTSPRequestInterface*)inRequest; + theRequest->SetVal(qtssRTSPReqFileName, theRequest->GetValue(qtssRTSPReqFilePath)); + + StrPtrLen* theFileNameParam = theRequest->GetValue(qtssRTSPReqFileName); + + //paranoid check + if (theFileNameParam->Len == 0) + return theFileNameParam; + + //walk back in the file name until we hit a / + SInt32 x = theFileNameParam->Len - 1; + for (; x > 0; x--) + if (theFileNameParam->Ptr[x] == kPathDelimiterChar) + break; + //once we do, make the tempPtr point to the next character after the slash, + //and adjust the length accordingly + if (theFileNameParam->Ptr[x] == kPathDelimiterChar ) + { + theFileNameParam->Ptr = (&theFileNameParam->Ptr[x]) + 1; + theFileNameParam->Len -= (x + 1); + } + + return NULL; +} + + +void* RTSPRequestInterface::GetFileDigit(QTSSDictionary* inRequest, UInt32* /*outLen*/) +{ + // This function always gets called + + RTSPRequestInterface* theRequest = (RTSPRequestInterface*)inRequest; + theRequest->SetVal(qtssRTSPReqFileDigit, theRequest->GetValue(qtssRTSPReqFilePath)); + + StrPtrLen* theFileDigit = theRequest->GetValue(qtssRTSPReqFileDigit); + + UInt32 theFilePathLen = theRequest->GetValue(qtssRTSPReqFilePath)->Len; + theFileDigit->Ptr += theFilePathLen - 1; + theFileDigit->Len = 0; + while ((StringParser::sDigitMask[(unsigned int) *(*theFileDigit).Ptr] != '\0') && + (theFileDigit->Len <= theFilePathLen)) + { + theFileDigit->Ptr--; + theFileDigit->Len++; + } + //termination condition means that we aren't actually on a digit right now. + //Move pointer back onto the digit + theFileDigit->Ptr++; + + return NULL; +} + +void* RTSPRequestInterface::GetRealStatusCode(QTSSDictionary* inRequest, UInt32* outLen) +{ + // Set the fRealStatusCode variable based on the current fStatusCode. + // This function always gets called + RTSPRequestInterface* theReq = (RTSPRequestInterface*)inRequest; + theReq->fRealStatusCode = RTSPProtocol::GetStatusCode(theReq->fStatus); + *outLen = sizeof(UInt32); + return &theReq->fRealStatusCode; +} + +void* RTSPRequestInterface::GetLocalPath(QTSSDictionary* inRequest, UInt32* outLen) +{ + // This function always gets called + RTSPRequestInterface* theRequest = (RTSPRequestInterface*)inRequest; + QTSS_AttributeID theID = qtssRTSPReqFilePath; + + // Get the truncated path on a setup, because setups have the trackID appended + if (theRequest->GetMethod() == qtssSetupMethod) + { + theID = qtssRTSPReqFilePathTrunc; + // invoke the param retrieval function here so that we can use the internal GetValue function later + RTSPRequestInterface::GetTruncatedPath(inRequest, outLen); + } + + StrPtrLen* thePath = theRequest->GetValue(theID); + StrPtrLen filePath(thePath->Ptr, thePath->Len); + StrPtrLen* theRootDir = theRequest->GetValue(qtssRTSPReqRootDir); + if (theRootDir->Len && theRootDir->Ptr[theRootDir->Len -1] == kPathDelimiterChar + && thePath->Len && thePath->Ptr[0] == kPathDelimiterChar) + { + char *thePathEnd = &(filePath.Ptr[filePath.Len]); + while (filePath.Ptr != thePathEnd) + { + if (*filePath.Ptr != kPathDelimiterChar) + break; + + filePath.Ptr ++; + filePath.Len --; + } + } + + UInt32 fullPathLen = filePath.Len + theRootDir->Len; + char* theFullPath = NEW char[fullPathLen+1]; + theFullPath[fullPathLen] = '\0'; + + ::memcpy(theFullPath, theRootDir->Ptr, theRootDir->Len); + ::memcpy(theFullPath + theRootDir->Len, filePath.Ptr, filePath.Len); + + (void)theRequest->SetValue(qtssRTSPReqLocalPath, 0, theFullPath,fullPathLen , QTSSDictionary::kDontObeyReadOnly); + + // delete our copy of the data + delete [] theFullPath; + *outLen = 0; + + return NULL; +} + +void* RTSPRequestInterface::GetAuthDigestResponse(QTSSDictionary* inRequest, UInt32* ) +{ + RTSPRequestInterface* theRequest = (RTSPRequestInterface*)inRequest; + (void)theRequest->SetValue(qtssRTSPReqDigestResponse, 0, theRequest->fAuthDigestResponse.Ptr,theRequest->fAuthDigestResponse.Len , QTSSDictionary::kDontObeyReadOnly); + return NULL; +} + diff --git a/Server.tproj/RTSPRequestInterface.h b/Server.tproj/RTSPRequestInterface.h new file mode 100644 index 0000000..158fc8f --- /dev/null +++ b/Server.tproj/RTSPRequestInterface.h @@ -0,0 +1,339 @@ +/* + * + * @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: RTSPRequestInterface.h + + Contains: Provides a simple API for modules to access request information and + manipulate (and possibly send) the client response. + + Implements the RTSP Request dictionary for QTSS API. + + +*/ + + +#ifndef __RTSPREQUESTINTERFACE_H__ +#define __RTSPREQUESTINTERFACE_H__ + +//INCLUDES: +#include "QTSS.h" +#include "QTSSDictionary.h" + +#include "StrPtrLen.h" +#include "RTSPSessionInterface.h" +#include "RTSPResponseStream.h" +#include "RTSPProtocol.h" +#include "QTSSMessages.h" +#include "QTSSUserProfile.h" +#include "RTSPRequest3GPP.h" + +class RTSPRequestInterface : public QTSSDictionary +{ + public: + + //Initialize + //Call initialize before instantiating this class. For maximum performance, this class builds + //any response header it can at startup time. + static void Initialize(); + + //CONSTRUCTOR: + RTSPRequestInterface(RTSPSessionInterface *session); + virtual ~RTSPRequestInterface() + { if (fMovieFolderPtr != &fMovieFolderPath[0]) delete [] fMovieFolderPtr; } + + //FUNCTIONS FOR SENDING OUTPUT: + + //Adds a new header to this object's list of headers to be sent out. + //Note that this is only needed for "special purpose" headers. The Server, + //CSeq, SessionID, and Connection headers are taken care of automatically + void AppendHeader(QTSS_RTSPHeader inHeader, StrPtrLen* inValue); + + + // The transport header constructed by this function mimics the one sent + // by the client, with the addition of server port & interleaved sub headers + void AppendTransportHeader(StrPtrLen* serverPortA, + StrPtrLen* serverPortB, + StrPtrLen* channelA, + StrPtrLen* channelB, + StrPtrLen* serverIPAddr = NULL, + StrPtrLen* ssrc = NULL); + void AppendContentBaseHeader(StrPtrLen* theURL); + void AppendRTPInfoHeader(QTSS_RTSPHeader inHeader, + StrPtrLen* url, StrPtrLen* seqNumber, + StrPtrLen* ssrc, StrPtrLen* rtpTime, Bool16 lastRTPInfo); + + void AppendContentLength(UInt32 contentLength); + void AppendDateAndExpires(); + void AppendSessionHeaderWithTimeout( StrPtrLen* inSessionID, StrPtrLen* inTimeout ); + void AppendRetransmitHeader(UInt32 inAckTimeout); + + // MODIFIERS + + void SetKeepAlive(Bool16 newVal) { fResponseKeepAlive = newVal; } + + //SendHeader: + //Sends the RTSP headers, in their current state, to the client. + void SendHeader(); + + // QTSS STREAM FUNCTIONS + + // THE FIRST ENTRY OF THE IOVEC MUST BE BLANK!!! + virtual QTSS_Error WriteV(iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, UInt32* outLenWritten); + + //Write + //A "buffered send" that can be used for sending small chunks of data at a time. + virtual QTSS_Error Write(void* inBuffer, UInt32 inLength, UInt32* outLenWritten, UInt32 inFlags); + + // Flushes all currently buffered data to the network. This either returns + // QTSS_NoErr or EWOULDBLOCK. If it returns EWOULDBLOCK, you should wait for + // a EV_WR on the socket, and call flush again. + virtual QTSS_Error Flush() { return fOutputStream->Flush(); } + + // Reads data off the stream. Same behavior as calling RTSPSessionInterface::Read + virtual QTSS_Error Read(void* ioBuffer, UInt32 inLength, UInt32* outLenRead) + { return fSession->Read(ioBuffer, inLength, outLenRead); } + + // Requests an event. Same behavior as calling RTSPSessionInterface::RequestEvent + virtual QTSS_Error RequestEvent(QTSS_EventType inEventMask) + { return fSession->RequestEvent(inEventMask); } + + + //ACCESS FUNCTIONS: + + // These functions are shortcuts that objects internal to the server + // use to get access to RTSP request information. Pretty much all + // of this stuff is also available as QTSS API attributes. + + QTSS_RTSPMethod GetMethod() const { return fMethod; } + QTSS_RTSPStatusCode GetStatus() const { return fStatus; } + Bool16 GetResponseKeepAlive() const { return fResponseKeepAlive; } + void SetResponseKeepAlive(Bool16 keepAlive) { fResponseKeepAlive = keepAlive; } + + //will be -1 unless there was a Range header. May have one or two values + Float64 GetStartTime() { return fStartTime; } + Float64 GetStopTime() { return fStopTime; } + + // + // Value of Speed: header in request + Float32 GetSpeed() { return fSpeed; } + + // + // Value of late-tolerance field of x-RTP-Options header + Float32 GetLateToleranceInSec(){ return fLateTolerance; } + StrPtrLen* GetLateToleranceStr(){ return &fLateToleranceStr; } + + // these get set if there is a transport header + UInt16 GetClientPortA() { return fClientPortA; } + UInt16 GetClientPortB() { return fClientPortB; } + UInt32 GetDestAddr() { return fDestinationAddr; } + UInt32 GetSourceAddr() { return fSourceAddr; } + UInt16 GetTtl() { return fTtl; } + QTSS_RTPTransportType GetTransportType() { return fTransportType; } + QTSS_RTPNetworkMode GetNetworkMode() { return fNetworkMode; } + UInt32 GetWindowSize() { return fWindowSize; } + + + Bool16 HasResponseBeenSent() + { return fOutputStream->GetBytesWritten() > 0; } + + RTSPSessionInterface* GetSession() { return fSession; } + QTSSDictionary* GetHeaderDictionary(){ return &fHeaderDictionary; } + + Bool16 GetAllowed() { return fAllowed; } + void SetAllowed(Bool16 allowed) { fAllowed = allowed;} + + Bool16 GetHasUser() { return fHasUser; } + void SetHasUser(Bool16 hasUser) { fHasUser = hasUser;} + + Bool16 GetAuthHandled() { return fAuthHandled; } + void SetAuthHandled(Bool16 handled) { fAuthHandled = handled;} + + QTSS_ActionFlags GetAction() { return fAction; } + void SetAction(QTSS_ActionFlags action) { fAction = action;} + + Bool16 IsPushRequest() { return (fTransportMode == qtssRTPTransportModeRecord) ? true : false; } + UInt16 GetSetUpServerPort() { return fSetUpServerPort;} + QTSS_RTPTransportMode GetTransportMode() { return fTransportMode; } + + QTSS_AuthScheme GetAuthScheme() { return fAuthScheme; } + void SetAuthScheme(QTSS_AuthScheme scheme) { fAuthScheme = scheme;} + StrPtrLen* GetAuthRealm() { return &fAuthRealm; } + StrPtrLen* GetAuthNonce() { return &fAuthNonce; } + StrPtrLen* GetAuthUri() { return &fAuthUri; } + UInt32 GetAuthQop() { return fAuthQop; } + StrPtrLen* GetAuthNonceCount() { return &fAuthNonceCount; } + StrPtrLen* GetAuthCNonce() { return &fAuthCNonce; } + StrPtrLen* GetAuthResponse() { return &fAuthResponse; } + StrPtrLen* GetAuthOpaque() { return &fAuthOpaque; } + QTSSUserProfile* GetUserProfile() { return fUserProfilePtr; } + RTSPRequest3GPP* GetRequest3GPPInfo() { return fRequest3GPPPtr; } + + + Bool16 GetStale() { return fStale; } + void SetStale(Bool16 stale) { fStale = stale; } + + Bool16 SkipAuthorization() { return fSkipAuthorization; } + + SInt32 GetDynamicRateState() { return fEnableDynamicRateState; } + + // DJM PROTOTYPE + UInt32 GetRandomDataSize() { return fRandomDataSize; } + + UInt32 GetBandwidthHeaderBits() { return fBandwidthBits; } + + StrPtrLen* GetRequestChallenge() { return &fAuthDigestChallenge; } + + + protected: + + //ALL THIS STUFF HERE IS SETUP BY RTSPREQUEST object (derived) + + //REQUEST HEADER DATA + enum + { + kMovieFolderBufSizeInBytes = 256, //Uint32 + kMaxFilePathSizeInBytes = 256 //Uint32 + }; + + QTSS_RTSPMethod fMethod; //Method of this request + QTSS_RTSPStatusCode fStatus; //Current status of this request + UInt32 fRealStatusCode; //Current RTSP status num of this request + Bool16 fRequestKeepAlive; //Does the client want keep-alive? + Bool16 fResponseKeepAlive; //Are we going to keep-alive? + RTSPProtocol::RTSPVersion fVersion; + + Float64 fStartTime; //Range header info: start time + Float64 fStopTime; //Range header info: stop time + + UInt16 fClientPortA; //This is all info that comes out + UInt16 fClientPortB; //of the Transport: header + UInt16 fTtl; + UInt32 fDestinationAddr; + UInt32 fSourceAddr; + QTSS_RTPTransportType fTransportType; + QTSS_RTPNetworkMode fNetworkMode; + + UInt32 fContentLength; + SInt64 fIfModSinceDate; + Float32 fSpeed; + Float32 fLateTolerance; + StrPtrLen fLateToleranceStr; + Float32 fPrebufferAmt; + + StrPtrLen fFirstTransport; + + QTSS_StreamRef fStreamRef; + + // + // For reliable UDP + UInt32 fWindowSize; + StrPtrLen fWindowSizeStr; + + //Because of URL decoding issues, we need to make a copy of the file path. + //Here is a buffer for it. + char fFilePath[kMaxFilePathSizeInBytes]; + char fMovieFolderPath[kMovieFolderBufSizeInBytes]; + char* fMovieFolderPtr; + + QTSSDictionary fHeaderDictionary; + + Bool16 fAllowed; + Bool16 fHasUser; + Bool16 fAuthHandled; + + QTSS_RTPTransportMode fTransportMode; + UInt16 fSetUpServerPort; //send this back as the server_port if is SETUP request + + QTSS_ActionFlags fAction; // The action that will be performed for this request + // Set to a combination of QTSS_ActionFlags + + QTSS_AuthScheme fAuthScheme; + StrPtrLen fAuthRealm; + StrPtrLen fAuthNonce; + StrPtrLen fAuthUri; + UInt32 fAuthQop; + StrPtrLen fAuthNonceCount; + StrPtrLen fAuthCNonce; + StrPtrLen fAuthResponse; + StrPtrLen fAuthOpaque; + QTSSUserProfile fUserProfile; + QTSSUserProfile* fUserProfilePtr; + Bool16 fStale; + + Bool16 fSkipAuthorization; + + SInt32 fEnableDynamicRateState; + + // DJM PROTOTYPE + UInt32 fRandomDataSize; + + RTSPRequest3GPP fRequest3GPP; + RTSPRequest3GPP* fRequest3GPPPtr; + + UInt32 fBandwidthBits; + StrPtrLen fAuthDigestChallenge; + StrPtrLen fAuthDigestResponse; + private: + + RTSPSessionInterface* fSession; + RTSPResponseStream* fOutputStream; + + + enum + { + kStaticHeaderSizeInBytes = 512 //UInt32 + }; + + Bool16 fStandardHeadersWritten; + + void PutTransportStripped(StrPtrLen &outFirstTransport, StrPtrLen &outResultStr); + void WriteStandardHeaders(); + static void PutStatusLine( StringFormatter* putStream, + QTSS_RTSPStatusCode status, + RTSPProtocol::RTSPVersion version); + + //Individual param retrieval functions + static void* GetAbsTruncatedPath(QTSSDictionary* inRequest, UInt32* outLen); + static void* GetTruncatedPath(QTSSDictionary* inRequest, UInt32* outLen); + static void* GetFileName(QTSSDictionary* inRequest, UInt32* outLen); + static void* GetFileDigit(QTSSDictionary* inRequest, UInt32* outLen); + static void* GetRealStatusCode(QTSSDictionary* inRequest, UInt32* outLen); + static void* GetLocalPath(QTSSDictionary* inRequest, UInt32* outLen); + static void* GetAuthDigestResponse(QTSSDictionary* inRequest, UInt32* outLen); + + //optimized preformatted response header strings + static char sPremadeHeader[kStaticHeaderSizeInBytes]; + static StrPtrLen sPremadeHeaderPtr; + + static char sPremadeNoHeader[kStaticHeaderSizeInBytes]; + static StrPtrLen sPremadeNoHeaderPtr; + + static StrPtrLen sColonSpace; + + //Dictionary support + static QTSSAttrInfoDict::AttrInfo sAttributes[]; +}; +#endif // __RTSPREQUESTINTERFACE_H__ + diff --git a/Server.tproj/RTSPRequestStream.cpp b/Server.tproj/RTSPRequestStream.cpp new file mode 100644 index 0000000..458ff5e --- /dev/null +++ b/Server.tproj/RTSPRequestStream.cpp @@ -0,0 +1,375 @@ +/* + * + * @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: RTSPRequestStream.cpp + + Contains: Implementation of RTSPRequestStream class. + + + +*/ + + +#include "RTSPRequestStream.h" +#include "StringParser.h" +#include "OSMemory.h" +#include "base64.h" +#include "OSArrayObjectDeleter.h" +#include "OS.h" + +#include + +#define READ_DEBUGGING 0 + +RTSPRequestStream::RTSPRequestStream(TCPSocket* sock) +: fSocket(sock), + fRetreatBytes(0), + fRetreatBytesRead(0), + fCurOffset(0), + fEncodedBytesRemaining(0), + fRequest(fRequestBuffer, 0), + fRequestPtr(NULL), + fDecode(false), + fPrintRTSP(false) +{} + +void RTSPRequestStream::SnarfRetreat( RTSPRequestStream &fromRequest ) +{ + // Simplest thing to do is to just completely blow away everything in this current + // stream, and replace it with the retreat bytes from the other stream. + fRequestPtr = NULL; + Assert(fRetreatBytes < kRequestBufferSizeInBytes); + fRetreatBytes = fromRequest.fRetreatBytes; + fEncodedBytesRemaining = fCurOffset = fRequest.Len = 0; + ::memcpy(&fRequestBuffer[0], fromRequest.fRequest.Ptr + fromRequest.fRequest.Len, fromRequest.fRetreatBytes); +} + +QTSS_Error RTSPRequestStream::ReadRequest() +{ + while (true) + { + UInt32 newOffset = 0; + + //If this is the case, we already HAVE a request on this session, and we now are done + //with the request and want to move onto the next one. The first thing we should do + //is check whether there is any lingering data in the stream. If there is, the parent + //session believes that is part of a new request + if (fRequestPtr != NULL) + { + fRequestPtr = NULL;//flag that we no longer have a complete request + + // Take all the retreated leftover data and move it to the beginning of the buffer + if ((fRetreatBytes > 0) && (fRequest.Len > 0)) + ::memmove(fRequest.Ptr, fRequest.Ptr + fRequest.Len + fRetreatBytesRead, fRetreatBytes); + + // if we are decoding, we need to also move over the remaining encoded bytes + // to the right position in the fRequestBuffer + if (fEncodedBytesRemaining > 0) + { + //Assert(fEncodedBytesRemaining < 4); + + // The right position is at fRetreatBytes offset in the request buffer. The reason for this is: + // 1) We need to find a place in the request buffer where we know we have enough space to store + // fEncodedBytesRemaining. fRetreatBytes + fEncodedBytesRemaining will always be less than + // kRequestBufferSize because all this data must have been in the same request buffer, together, at one point. + // + // 2) We need to make sure that there is always more data in the RequestBuffer than in the decoded + // request buffer, otherwise we could overrun the decoded request buffer (we bounds check on the encoded + // buffer, not the decoded buffer). Leaving fRetreatBytes as empty space in the request buffer ensures + // that this principle is maintained. + ::memmove(&fRequestBuffer[fRetreatBytes], &fRequestBuffer[fCurOffset - fEncodedBytesRemaining], fEncodedBytesRemaining); + fCurOffset = fRetreatBytes + fEncodedBytesRemaining; + Assert(fCurOffset < kRequestBufferSizeInBytes); + } + else + fCurOffset = fRetreatBytes; + + newOffset = fRequest.Len = fRetreatBytes; + fRetreatBytes = fRetreatBytesRead = 0; + } + + // We don't have any new data, so try and get some + if (newOffset == 0) + { + if (fRetreatBytes > 0) + { + // This will be true if we've just snarfed another input stream, in which case the encoded data + // is copied into our request buffer, and its length is tracked in fRetreatBytes. + // If this is true, just fall through and decode the data. + newOffset = fRetreatBytes; + fRetreatBytes = 0; + Assert(fEncodedBytesRemaining == 0); + } + else + { + // We don't have any new data, get some from the socket... + QTSS_Error sockErr = fSocket->Read(&fRequestBuffer[fCurOffset], + (kRequestBufferSizeInBytes - fCurOffset) - 1, &newOffset); + //assume the client is dead if we get an error back + if (sockErr == EAGAIN) + return QTSS_NoErr; + if (sockErr != QTSS_NoErr) + { + Assert(!fSocket->IsConnected()); + return sockErr; + } + } + + if (fDecode) + { + // If we need to decode this data, do it now. + Assert(fCurOffset >= fEncodedBytesRemaining); + QTSS_Error decodeErr = this->DecodeIncomingData(&fRequestBuffer[fCurOffset - fEncodedBytesRemaining], + newOffset + fEncodedBytesRemaining); + // If the above function returns an error, it is because we've + // encountered some non-base64 data in the stream. We can process + // everything up until that point, but all data after this point will + // be ignored. + if (decodeErr == QTSS_NoErr) + Assert(fEncodedBytesRemaining < 4); + } + else + fRequest.Len += newOffset; + Assert(fRequest.Len < kRequestBufferSizeInBytes); + fCurOffset += newOffset; + } + Assert(newOffset > 0); + + // See if this is an interleaved data packet + if ('$' == *(fRequest.Ptr)) + { + if (fRequest.Len < 4) + continue; + UInt16* dataLenP = (UInt16*)fRequest.Ptr; + UInt32 interleavedPacketLen = ntohs(dataLenP[1]) + 4; + if (interleavedPacketLen > fRequest.Len) + continue; + + //put back any data that is not part of the header + fRetreatBytes += fRequest.Len - interleavedPacketLen; + fRequest.Len = interleavedPacketLen; + + fRequestPtr = &fRequest; + fIsDataPacket = true; + return QTSS_RequestArrived; + } + fIsDataPacket = false; + + if (fPrintRTSP) + { + DateBuffer theDate; + DateTranslator::UpdateDateBuffer(&theDate, 0); // get the current GMT date and time + qtss_printf("\n\n#C->S:\n#time: ms=%"_U32BITARG_" date=%s\n", (UInt32) OS::StartTimeMilli_Int(), theDate.GetDateBuffer()); + + if (fSocket != NULL) + { + UInt16 serverPort = fSocket->GetLocalPort(); + UInt16 clientPort = fSocket->GetRemotePort(); + StrPtrLen* theLocalAddrStr = fSocket->GetLocalAddrStr(); + StrPtrLen* theRemoteAddrStr = fSocket->GetRemoteAddrStr(); + if (theLocalAddrStr != NULL) + { qtss_printf("#server: ip="); theLocalAddrStr->PrintStr(); qtss_printf(" port=%u\n" , serverPort ); + } + else + { qtss_printf("#server: ip=NULL port=%u\n" , serverPort ); + } + + if (theRemoteAddrStr != NULL) + { qtss_printf("#client: ip="); theRemoteAddrStr->PrintStr(); qtss_printf(" port=%u\n" , clientPort ); + } + else + { qtss_printf("#client: ip=NULL port=%u\n" , clientPort ); + } + + } + + StrPtrLen str(fRequest); + str.PrintStrEOL("\n\r\n", "\n");// print the request but stop on \n\r\n and add a \n afterwards. + } + + //use a StringParser object to search for a double EOL, which signifies the end of + //the header. + Bool16 weAreDone = false; + StringParser headerParser(&fRequest); + + UInt16 lcount = 0; + while (headerParser.GetThruEOL(NULL)) + { + lcount++; + if (headerParser.ExpectEOL()) + { + //The legal end-of-header sequences are \r\r, \r\n\r\n, & \n\n. NOT \r\n\r! + //If the packets arrive just a certain way, we could get here with the latter + //combo, and not wait for a final \n. + if ((headerParser.GetDataParsedLen() > 2) && + (memcmp(headerParser.GetCurrentPosition() - 3, "\r\n\r", 3) == 0)) + continue; + weAreDone = true; + break; + } + else if (lcount == 1) { + // if this request is actually a ShoutCast password it will be + // in the form of "xxxxxx\r" where "xxxxx" is the password. + // If we get a 1st request line ending in \r with no blanks we will + // assume that this is the end of the request. + UInt16 flag = 0; + UInt16 i = 0; + for (i=0; i 0) + { + theLengthRead = fRetreatBytes; + if (inBufLen < theLengthRead) + theLengthRead = inBufLen; + + ::memcpy(theIoBuffer, fRequest.Ptr + fRequest.Len + fRetreatBytesRead, theLengthRead); + + // + // We should not update fRequest.Len even though we've read some of the retreat bytes. + // fRequest.Len always refers to the length of the request header. Instead, we + // have a separate variable, fRetreatBytesRead + fRetreatBytes -= theLengthRead; + fRetreatBytesRead += theLengthRead; +#if READ_DEBUGGING + qtss_printf("In RTSPRequestStream::Read: Got %d Retreat Bytes\n",theLengthRead); +#endif + } + + // + // If there is still space available in ioBuffer, continue. Otherwise, we can return now + if (theLengthRead == inBufLen) + { + if (outLengthRead != NULL) + *outLengthRead = theLengthRead; + return QTSS_NoErr; + } + + // + // Read data directly from the socket and place it in our buffer + UInt32 theNewOffset = 0; + QTSS_Error theErr = fSocket->Read(&theIoBuffer[theLengthRead], inBufLen - theLengthRead, &theNewOffset); +#if READ_DEBUGGING + qtss_printf("In RTSPRequestStream::Read: Got %d bytes off Socket\n",theNewOffset); +#endif + if (outLengthRead != NULL) + *outLengthRead = theNewOffset + theLengthRead; + + return theErr; +} + +QTSS_Error RTSPRequestStream::DecodeIncomingData(char* inSrcData, UInt32 inSrcDataLen) +{ + Assert(fRetreatBytes == 0); + + if (fRequest.Ptr == &fRequestBuffer[0]) + { + fRequest.Ptr = NEW char[kRequestBufferSizeInBytes]; + fRequest.Len = 0; + } + + // We always decode up through the last chunk of 4. + fEncodedBytesRemaining = inSrcDataLen & 3; + + // Let our friendly Base64Decode function know this by NULL terminating at that point + UInt32 bytesToDecode = inSrcDataLen - fEncodedBytesRemaining; + char endChar = inSrcData[bytesToDecode]; + inSrcData[bytesToDecode] = '\0'; + + UInt32 encodedBytesConsumed = 0; + + // Loop until the whole load is decoded + while (encodedBytesConsumed < bytesToDecode) + { + Assert((encodedBytesConsumed & 3) == 0); + Assert((bytesToDecode & 3) == 0); + + UInt32 bytesDecoded = Base64decode(fRequest.Ptr + fRequest.Len, inSrcData + encodedBytesConsumed); + + // If bytesDecoded is 0, we will end up being in an endless loop. The + // base64 must be corrupt, so let's just return an error and abort + if (bytesDecoded == 0) + { + //Assert(0); + return QTSS_BadArgument; + } + + fRequest.Len += bytesDecoded; + + // Assuming the stream is valid, the # of encoded bytes we just consumed is + // 4/3rds of the number of decoded bytes returned by the decode function, + // rounded up to the nearest multiple of 4. + encodedBytesConsumed += (bytesDecoded / 3) * 4; + if ((bytesDecoded % 3) > 0) + encodedBytesConsumed += 4; + + } + + // Make sure to replace the sacred endChar + inSrcData[bytesToDecode] = endChar; + + Assert(fRequest.Len < kRequestBufferSizeInBytes); + Assert(encodedBytesConsumed == bytesToDecode); + + return QTSS_NoErr; +} + diff --git a/Server.tproj/RTSPRequestStream.h b/Server.tproj/RTSPRequestStream.h new file mode 100644 index 0000000..2567559 --- /dev/null +++ b/Server.tproj/RTSPRequestStream.h @@ -0,0 +1,120 @@ +/* + * + * @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: RTSPRequestStream.h + + Contains: Provides a stream abstraction for RTSP. Given a socket, this object + can read in data until an entire RTSP request header is available. + (do this by calling ReadRequest). It handles RTSP pipelining (request + headers are produced serially even if multiple headers arrive simultaneously), + & RTSP request data. + +*/ + +#ifndef __RTSPREQUESTSTREAM_H__ +#define __RTSPREQUESTSTREAM_H__ + + +//INCLUDES +#include "StrPtrLen.h" +#include "TCPSocket.h" +#include "QTSS.h" + +class RTSPRequestStream +{ +public: + + //CONSTRUCTOR / DESTRUCTOR + RTSPRequestStream(TCPSocket* sock); + + // We may have to delete this memory if it was allocated due to base64 decoding + ~RTSPRequestStream() { if (fRequest.Ptr != &fRequestBuffer[0]) delete [] fRequest.Ptr; } + + //ReadRequest + //This function will not block. + //Attempts to read data into the stream, stopping when we hit the EOL - EOL that + //ends an RTSP header. + // + //Returns: QTSS_NoErr: Out of data, haven't hit EOL - EOL yet + // QTSS_RequestArrived: full request has arrived + // E2BIG: ran out of buffer space + // QTSS_RequestFailed: if the client has disconnected + // EINVAL: if we are base64 decoding and the stream is corrupt + // QTSS_OutOfState: + QTSS_Error ReadRequest(); + + // Read + // + // This function reads data off of the stream, and places it into the buffer provided + // Returns: QTSS_NoErr, EAGAIN if it will block, or another socket error. + QTSS_Error Read(void* ioBuffer, UInt32 inBufLen, UInt32* outLengthRead); + + // Use a different TCPSocket to read request data + // this will be used by RTSPSessionInterface::SnarfInputSocket + void AttachToSocket(TCPSocket* sock) { fSocket = sock; } + + // Tell the request stream whether or not to decode from base64. + void IsBase64Encoded(Bool16 isDataEncoded) { fDecode = isDataEncoded; } + + //GetRequestBuffer + //This returns a buffer containing the full client request. The length is set to + //the exact length of the request headers. This will return NULL UNLESS this object + //is in the proper state (has been initialized, ReadRequest has been called until it returns + //RequestArrived). + StrPtrLen* GetRequestBuffer() { return fRequestPtr; } + Bool16 IsDataPacket() { return fIsDataPacket; } + void ShowRTSP(Bool16 enable) {fPrintRTSP = enable; } + void SnarfRetreat( RTSPRequestStream &fromRequest ); + +private: + + + //CONSTANTS: + enum + { + kRequestBufferSizeInBytes = 2048 //UInt32 + }; + + // Base64 decodes into fRequest.Ptr, updates fRequest.Len, and returns the amount + // of data left undecoded in inSrcData + QTSS_Error DecodeIncomingData(char* inSrcData, UInt32 inSrcDataLen); + + TCPSocket* fSocket; + UInt32 fRetreatBytes; + UInt32 fRetreatBytesRead; // Used by Read() when it is reading RetreatBytes + + char fRequestBuffer[kRequestBufferSizeInBytes]; + UInt32 fCurOffset; // tracks how much valid data is in the above buffer + UInt32 fEncodedBytesRemaining; // If we are decoding, tracks how many encoded bytes are in the buffer + + StrPtrLen fRequest; + StrPtrLen* fRequestPtr; // pointer to a request header + Bool16 fDecode; // should we base 64 decode? + Bool16 fIsDataPacket; // is this a data packet? Like for a record? + Bool16 fPrintRTSP; // debugging printfs + +}; + +#endif diff --git a/Server.tproj/RTSPResponseStream.cpp b/Server.tproj/RTSPResponseStream.cpp new file mode 100644 index 0000000..9079809 --- /dev/null +++ b/Server.tproj/RTSPResponseStream.cpp @@ -0,0 +1,189 @@ +/* + * + * @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: RTSPResponseStream.cpp + + Contains: Impelementation of object in .h + + +*/ + +#include "RTSPResponseStream.h" +#include "OSMemory.h" +#include "OSArrayObjectDeleter.h" +#include "StringTranslator.h" +#include "OS.h" + +#include + +QTSS_Error RTSPResponseStream::WriteV(iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, + UInt32* outLengthSent, UInt32 inSendType) +{ + QTSS_Error theErr = QTSS_NoErr; + UInt32 theLengthSent = 0; + UInt32 amtInBuffer = this->GetCurrentOffset() - fBytesSentInBuffer; + + if (amtInBuffer > 0) + { + + // There is some data in the output buffer. Make sure to send that + // first, using the empty space in the ioVec. + + inVec[0].iov_base = this->GetBufPtr() + fBytesSentInBuffer; + inVec[0].iov_len = amtInBuffer; + theErr = fSocket->WriteV(inVec, inNumVectors, &theLengthSent); + + if (fPrintRTSP) + { + DateBuffer theDate; + DateTranslator::UpdateDateBuffer(&theDate, 0); // get the current GMT date and time + + qtss_printf("\n#S->C:\n#time: ms=%"_U32BITARG_" date=%s\n", (UInt32) OS::StartTimeMilli_Int(), theDate.GetDateBuffer() ); + for (UInt32 i =0; i < inNumVectors; i++) + { StrPtrLen str((char*)inVec[i].iov_base, (UInt32) inVec[i].iov_len); + str.PrintStrEOL(); + } + } + + if (theLengthSent >= amtInBuffer) + { + // We were able to send all the data in the buffer. Great. Flush it. + this->Reset(); + fBytesSentInBuffer = 0; + + // Make theLengthSent reflect the amount of data sent in the ioVec + theLengthSent -= amtInBuffer; + } + else + { + // Uh oh. Not all the data in the buffer was sent. Update the + // fBytesSentInBuffer count, and set theLengthSent to 0. + + fBytesSentInBuffer += theLengthSent; + Assert(fBytesSentInBuffer < this->GetCurrentOffset()); + theLengthSent = 0; + } + // theLengthSent now represents how much data in the ioVec was sent + } + else if (inNumVectors > 1) + { + theErr = fSocket->WriteV(&inVec[1], inNumVectors - 1, &theLengthSent); + } + // We are supposed to refresh the timeout if there is a successful write. + if (theErr == QTSS_NoErr) + fTimeoutTask->RefreshTimeout(); + + // If there was an error, don't alter anything, just bail + if ((theErr != QTSS_NoErr) && (theErr != EAGAIN)) + return theErr; + + // theLengthSent at this point is the amount of data passed into + // this function that was sent. + if (outLengthSent != NULL) + *outLengthSent = theLengthSent; + + // Update the StringFormatter fBytesWritten variable... this data + // wasn't buffered in the output buffer at any time, so if we + // don't do this, this amount would get lost + fBytesWritten += theLengthSent; + + // All of the data was sent... whew! + if (theLengthSent == inTotalLength) + return QTSS_NoErr; + + // We need to determine now whether to copy the remaining unsent + // iovec data into the buffer. This is determined based on + // the inSendType parameter passed in. + if (inSendType == kDontBuffer) + return theErr; + if ((inSendType == kAllOrNothing) && (theLengthSent == 0)) + return EAGAIN; + + // Some or none of the iovec data was sent. Copy the remainder into the output + // buffer. + + // The caller should consider this data sent. + if (outLengthSent != NULL) + *outLengthSent = inTotalLength; + + UInt32 curVec = 1; + while (theLengthSent >= inVec[curVec].iov_len) + { + // Skip over the vectors that were in fact sent. + Assert(curVec < inNumVectors); + theLengthSent -= inVec[curVec].iov_len; + curVec++; + } + + while (curVec < inNumVectors) + { + // Copy the remaining vectors into the buffer + this->Put( ((char*)inVec[curVec].iov_base) + theLengthSent, + inVec[curVec].iov_len - theLengthSent); + theLengthSent = 0; + curVec++; + } + return QTSS_NoErr; +} + +QTSS_Error RTSPResponseStream::Flush() +{ + UInt32 amtInBuffer = this->GetCurrentOffset() - fBytesSentInBuffer; + if (amtInBuffer > 0) + { + if (fPrintRTSP) + { + DateBuffer theDate; + DateTranslator::UpdateDateBuffer(&theDate, 0); // get the current GMT date and time + + qtss_printf("\n#S->C:\n#time: ms=%"_U32BITARG_" date=%s\n", (UInt32) OS::StartTimeMilli_Int(), theDate.GetDateBuffer() ); + StrPtrLen str(this->GetBufPtr() + fBytesSentInBuffer, amtInBuffer); + str.PrintStrEOL(); + } + + UInt32 theLengthSent = 0; + (void)fSocket->Send(this->GetBufPtr() + fBytesSentInBuffer, amtInBuffer, &theLengthSent); + + // Refresh the timeout if we were able to send any data + if (theLengthSent > 0) + fTimeoutTask->RefreshTimeout(); + + if (theLengthSent == amtInBuffer) + { + // We were able to send all the data in the buffer. Great. Flush it. + this->Reset(); + fBytesSentInBuffer = 0; + } + else + { + // Not all the data was sent, so report an EAGAIN + + fBytesSentInBuffer += theLengthSent; + Assert(fBytesSentInBuffer < this->GetCurrentOffset()); + return EAGAIN; + } + } + return QTSS_NoErr; +} diff --git a/Server.tproj/RTSPResponseStream.h b/Server.tproj/RTSPResponseStream.h new file mode 100644 index 0000000..6f5811d --- /dev/null +++ b/Server.tproj/RTSPResponseStream.h @@ -0,0 +1,111 @@ +/* + * + * @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: RTSPResponseStream.h + + Contains: Object that provides a "buffered WriteV" service. Clients + can call this function to write to a socket, and buffer flow + controlled data in different ways. + + It is derived from StringFormatter, which it uses as an output + stream buffer. The buffer may grow infinitely. +*/ + +#ifndef __RTSP_RESPONSE_STREAM_H__ +#define __RTSP_RESPONSE_STREAM_H__ + +#include "ResizeableStringFormatter.h" +#include "TCPSocket.h" +#include "TimeoutTask.h" +#include "QTSS.h" + +class RTSPResponseStream : public ResizeableStringFormatter +{ + public: + + // This object provides some flow control buffering services. + // It also refreshes the timeout whenever there is a successful write + // on the socket. + RTSPResponseStream(TCPSocket* inSocket, TimeoutTask* inTimeoutTask) + : ResizeableStringFormatter(fOutputBuf, kOutputBufferSizeInBytes), + fSocket(inSocket), fBytesSentInBuffer(0), fTimeoutTask(inTimeoutTask),fPrintRTSP(false) {} + + virtual ~RTSPResponseStream() {} + + // WriteV + // + // This function takes an input ioVec and writes it to the socket. If any + // data has been written to this stream via Put, that data gets written first. + // + // In the event of flow control on the socket, less data than what was + // requested, or no data at all, may be sent. Specify what you want this + // function to do with the unsent data via inSendType. + // + // kAlwaysBuffer: Buffer any unsent data internally. + // kAllOrNothing: If no data could be sent, return EWOULDBLOCK. Otherwise, + // buffer any unsent data. + // kDontBuffer: Never buffer any data. + // + // If some data ends up being buffered, outLengthSent will = inTotalLength, + // and the return value will be QTSS_NoErr + + enum + { + kDontBuffer = 0, + kAllOrNothing = 1, + kAlwaysBuffer = 2 + }; + QTSS_Error WriteV(iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, + UInt32* outLengthSent, UInt32 inSendType); + + // Flushes any buffered data to the socket. If all data could be sent, + // this returns QTSS_NoErr, otherwise, it returns EWOULDBLOCK + QTSS_Error Flush(); + + void ShowRTSP(Bool16 enable) {fPrintRTSP = enable; } + + + private: + + enum + { + kOutputBufferSizeInBytes = 512 //UInt32 + }; + + //The default buffer size is allocated inline as part of the object. Because this size + //is good enough for 99.9% of all requests, we avoid the dynamic memory allocation in most + //cases. But if the response is too big for this buffer, the BufferIsFull function will + //allocate a larger buffer. + char fOutputBuf[kOutputBufferSizeInBytes]; + TCPSocket* fSocket; + UInt32 fBytesSentInBuffer; + TimeoutTask* fTimeoutTask; + Bool16 fPrintRTSP; // debugging printfs + + friend class RTSPRequestInterface; +}; + + +#endif // __RTSP_RESPONSE_STREAM_H__ diff --git a/Server.tproj/RTSPSession.cpp b/Server.tproj/RTSPSession.cpp new file mode 100644 index 0000000..26609e3 --- /dev/null +++ b/Server.tproj/RTSPSession.cpp @@ -0,0 +1,2201 @@ +/* + * + * @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 +#endif + +#include + +#if __solaris__ || __linux__ || __sgi__ || __hpux__ + #include +#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; +} diff --git a/Server.tproj/RTSPSession.h b/Server.tproj/RTSPSession.h new file mode 100644 index 0000000..5309d73 --- /dev/null +++ b/Server.tproj/RTSPSession.h @@ -0,0 +1,179 @@ +/* + * + * @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.h + + Contains: Represents an RTSP session (duh), which I define as a complete TCP connection + lifetime, from connection to FIN or RESET termination. This object is + the active element that gets scheduled and gets work done. It creates requests + and processes them when data arrives. When it is time to close the connection + it takes care of that. + +*/ + +#ifndef __RTSPSESSION_H__ +#define __RTSPSESSION_H__ + +#include "RTSPSessionInterface.h" +#include "RTSPRequestStream.h" +#include "RTSPRequest.h" +#include "RTPSession.h" +#include "TimeoutTask.h" + +class RTSPSession : public RTSPSessionInterface +{ + public: + + RTSPSession(Bool16 doReportHTTPConnectionAddress); + virtual ~RTSPSession(); + + // Call this before using this object + static void Initialize(); + + Bool16 IsPlaying() {if (fRTPSession == NULL) return false; if (fRTPSession->GetSessionState() == qtssPlayingState) return true; return false; } + + + private: + + SInt64 Run(); + + // Gets & creates RTP session for this request. + QTSS_Error FindRTPSession(OSRefTable* inTable); + QTSS_Error CreateNewRTPSession(OSRefTable* inTable); + void SetupClientSessionAttrs(); + + // Does request prep & request cleanup, respectively + void SetupRequest(); + void CleanupRequest(); + + Bool16 ParseOptionsResponse(); + + // Fancy random number generator + UInt32 GenerateNewSessionID(char* ioBuffer); + + // Sends an error response & returns error if not ok. + QTSS_Error IsOkToAddNewRTPSession(); + + // Checks authentication parameters + void CheckAuthentication(); + + // test current connections handled by this object against server pref connection limit + Bool16 OverMaxConnections(UInt32 buffer); + + // Process the 3GPP Request Data for the session + void Process3GPPData(); + + char fLastRTPSessionID[QTSS_MAX_SESSION_ID_LENGTH]; + StrPtrLen fLastRTPSessionIDPtr; + + RTSPRequest* fRequest; + RTPSession* fRTPSession; + + + /* -- begin adds for HTTP ProxyTunnel -- */ + + // This gets grabbed whenever the input side of the session is being used. + // It is to protect POST snarfage while input stuff is in action + OSMutex fReadMutex; + + OSRef* RegisterRTSPSessionIntoHTTPProxyTunnelMap(QTSS_RTSPSessionType inSessionType); + QTSS_Error PreFilterForHTTPProxyTunnel(); // prefilter for HTTP proxies + Bool16 ParseProxyTunnelHTTP(); // use by PreFilterForHTTPProxyTunnel + void HandleIncomingDataPacket(); + + static OSRefTable* sHTTPProxyTunnelMap; // a map of available partners. + + enum + { + kMaxHTTPResponseLen = 512 + }; + static char sHTTPResponseHeaderBuf[kMaxHTTPResponseLen]; + static StrPtrLen sHTTPResponseHeaderPtr; + + static char sHTTPResponseNoServerHeaderBuf[kMaxHTTPResponseLen]; + static StrPtrLen sHTTPResponseNoServerHeaderPtr; + + static char *sHTTPResponseFormatStr; + static char *sHTTPNoServerResponseFormatStr; + char fProxySessionID[QTSS_MAX_SESSION_ID_LENGTH]; // our magic cookie to match proxy connections + StrPtrLen fProxySessionIDPtr; + OSRef fProxyRef; + enum + { + // the kinds of HTTP Methods we're interested in for + // RTSP tunneling + kHTTPMethodInit // initialize to this + , kHTTPMethodUnknown // tested, but unknown + , kHTTPMethodGet // found one of these methods... + , kHTTPMethodPost + }; + + UInt16 fHTTPMethod; + Bool16 fWasHTTPRequest; + Bool16 fFoundValidAccept; + Bool16 fDoReportHTTPConnectionAddress; // true if we need to report our IP adress in reponse to the clients GET request (necessary for servers behind DNS round robin) + /* -- end adds for HTTP ProxyTunnel -- */ + + + // Module invocation and module state. + // This info keeps track of our current state so that + // the state machine works properly. + enum + { + kReadingRequest = 0, + kFilteringRequest = 1, + kRoutingRequest = 2, + kAuthenticatingRequest = 3, + kAuthorizingRequest = 4, + kPreprocessingRequest = 5, + kProcessingRequest = 6, + kSendingResponse = 7, + kPostProcessingRequest = 8, + kCleaningUp = 9, + + // states that RTSP sessions that setup RTSP + // through HTTP tunnels pass through + kWaitingToBindHTTPTunnel = 10, // POST or GET side waiting to be joined with it's matching half + kSocketHasBeenBoundIntoHTTPTunnel = 11, // POST side after attachment by GET side ( its dying ) + kHTTPFilteringRequest = 12, // after kReadingRequest, enter this state + kReadingFirstRequest = 13, // initial state - the only time we look for an HTTP tunnel + kHaveNonTunnelMessage = 14 // we've looked at the message, and its not an HTTP tunnle message + }; + + UInt32 fCurrentModule; + UInt32 fState; + + QTSS_RoleParams fRoleParams;//module param blocks for roles. + QTSS_ModuleState fModuleState; + + QTSS_Error SetupAuthLocalPath(RTSPRequest *theRTSPRequest); + + + void SaveRequestAuthorizationParams(RTSPRequest *theRTSPRequest); + QTSS_Error DumpRequestData(); + +}; +#endif // __RTSPSESSION_H__ + diff --git a/Server.tproj/RTSPSession3GPP.cpp b/Server.tproj/RTSPSession3GPP.cpp new file mode 100644 index 0000000..b924535 --- /dev/null +++ b/Server.tproj/RTSPSession3GPP.cpp @@ -0,0 +1,65 @@ +/* + * + * @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: RTSPSession3GPP.cpp + + Contains: Implementation of RTSPSession3GPP class. + + + +*/ + + +#include "RTSPSession3GPP.h" +#include "RTSPProtocol.h" +#include "QTSServerInterface.h" + + + +QTSSAttrInfoDict::AttrInfo RTSPSession3GPP::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtss3GPPRTSPSesEnabled", NULL, qtssAttrDataTypeBool16, qtssAttrModeRead | qtssAttrModePreempSafe } + +}; + +void RTSPSession3GPP::Initialize() +{ + for (UInt32 x = 0; x < qtss3GPPRTSPSessNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::k3GPPRTSPSessionDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, + sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); + +} + + +RTSPSession3GPP::RTSPSession3GPP(Bool16 enabled) +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::k3GPPRTSPSessionDictIndex)), + fEnabled (enabled) +{ + this->SetVal(qtss3GPPRTSPSesEnabled, &fEnabled, sizeof(fEnabled)); + + +} + diff --git a/Server.tproj/RTSPSession3GPP.h b/Server.tproj/RTSPSession3GPP.h new file mode 100644 index 0000000..d7185b0 --- /dev/null +++ b/Server.tproj/RTSPSession3GPP.h @@ -0,0 +1,66 @@ +/* + * + * @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: RTSPSession3GPP.h + + Contains: This parses 3gpp headers from a request + + + +*/ + +#ifndef __RTSPSESSION3GPP_H__ +#define __RTSPSESSION3GPP_H__ + +#include "QTSS.h" +#include "QTSSDictionary.h" + + +//RTSPSession 3GPP utility class definition +class RTSPSession3GPP : public QTSSDictionary +{ + public: + //Initialize + //Call initialize before instantiating this class: see QTSServer.cpp. + static void Initialize(); + + //CONSTRUCTOR / DESTRUCTOR + //these do very little. Just initialize / delete some member data. + // + //Arguments: enable the object + RTSPSession3GPP(Bool16 enabled); + ~RTSPSession3GPP() {} + + + + private: + Bool16 fEnabled; + + //Dictionary support + static QTSSAttrInfoDict::AttrInfo sAttributes[]; + +}; +#endif // __RTSPSESSION3GPP_H__ + diff --git a/Server.tproj/RTSPSessionInterface.cpp b/Server.tproj/RTSPSessionInterface.cpp new file mode 100644 index 0000000..bf5338e --- /dev/null +++ b/Server.tproj/RTSPSessionInterface.cpp @@ -0,0 +1,512 @@ +/* + * + * @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: RTSPSessionInterface.cpp + + Contains: Implementation of RTSPSessionInterface object. + + + +*/ + +#include "atomic.h" + +#include "RTSPSessionInterface.h" +#include "QTSServerInterface.h" +#include "OSMemory.h" +#include "RTSPProtocol.h" + +#include + + +#if DEBUG + #define RTSP_SESSION_INTERFACE_DEBUGGING 1 +#else + #define RTSP_SESSION_INTERFACE_DEBUGGING 0 +#endif + + + +unsigned int RTSPSessionInterface::sSessionIDCounter = kFirstRTSPSessionID; +Bool16 RTSPSessionInterface::sDoBase64Decoding = true; +UInt32 RTSPSessionInterface::sOptionsRequestBody[kMaxRandomDataSize / sizeof(UInt32)]; + +QTSSAttrInfoDict::AttrInfo RTSPSessionInterface::sAttributes[] = +{ /*fields: fAttrName, fFuncPtr, fAttrDataType, fAttrPermission */ + /* 0 */ { "qtssRTSPSesID", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 1 */ { "qtssRTSPSesLocalAddr", SetupParams, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeCacheable }, + /* 2 */ { "qtssRTSPSesLocalAddrStr", SetupParams, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeCacheable }, + /* 3 */ { "qtssRTSPSesLocalDNS", SetupParams, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeCacheable }, + /* 4 */ { "qtssRTSPSesRemoteAddr", SetupParams, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeCacheable }, + /* 5 */ { "qtssRTSPSesRemoteAddrStr", SetupParams, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeCacheable }, + /* 6 */ { "qtssRTSPSesEventCntxt", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 7 */ { "qtssRTSPSesType", NULL, qtssAttrDataTypeUInt32, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 8 */ { "qtssRTSPSesStreamRef", NULL, qtssAttrDataTypeQTSS_StreamRef, qtssAttrModeRead | qtssAttrModePreempSafe }, + + /* 9 */ { "qtssRTSPSesLastUserName", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 10 */{ "qtssRTSPSesLastUserPassword",NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + /* 11 */{ "qtssRTSPSesLastURLRealm", NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe }, + + /* 12 */{ "qtssRTSPSesLocalPort", SetupParams, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeCacheable }, + /* 13 */{ "qtssRTSPSesRemotePort", SetupParams, qtssAttrDataTypeUInt16, qtssAttrModeRead | qtssAttrModePreempSafe | qtssAttrModeCacheable }, + /* 14 */{ "qtssRTSPSes3GPPObject", NULL, qtssAttrDataTypeQTSS_Object,qtssAttrModeRead | qtssAttrModePreempSafe }, + + /* 15 */{ "qtssRTSPSesLastDigestChallenge",NULL, qtssAttrDataTypeCharArray, qtssAttrModeRead | qtssAttrModePreempSafe } + + +}; + + +void RTSPSessionInterface::Initialize() +{ + for (UInt32 x = 0; x < qtssRTSPSesNumParams; x++) + QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kRTSPSessionDictIndex)-> + SetAttribute(x, sAttributes[x].fAttrName, sAttributes[x].fFuncPtr, sAttributes[x].fAttrDataType, sAttributes[x].fAttrPermission); + + // DJM PROTOTYPE + ::srand((unsigned int) OS::Microseconds()); + for (unsigned int i = 0; i < kMaxRandomDataSize / sizeof(UInt32); i++) + RTSPSessionInterface::sOptionsRequestBody[i] = ::rand(); + ((char *)RTSPSessionInterface::sOptionsRequestBody)[0] = 0; //always set first byte so it doesn't hit any client parser bugs for \r or \n. + +} + + +RTSPSessionInterface::RTSPSessionInterface() +: QTSSDictionary(QTSSDictionaryMap::GetMap(QTSSDictionaryMap::kRTSPSessionDictIndex)), + Task(), + fTimeoutTask(NULL, QTSServerInterface::GetServer()->GetPrefs()->GetRealRTSPTimeoutInSecs() * 1000), + fInputStream(&fSocket), + fOutputStream(&fSocket, &fTimeoutTask), + fSessionMutex(), + fTCPCoalesceBuffer(NULL), + fNumInCoalesceBuffer(0), + fSocket(NULL, Socket::kNonBlockingSocketType), + fOutputSocketP(&fSocket), + fInputSocketP(&fSocket), + fSessionType(qtssRTSPSession), + fLiveSession(true), + fObjectHolders(0), + fCurChannelNum(0), + fChNumToSessIDMap(NULL), + fRequestBodyLen(-1), + fSentOptionsRequest(false), + fOptionsRequestSendTime(-1), + fRoundTripTime(-1), + fRoundTripTimeCalculation(true), + fRTSPSession3GPP(QTSServerInterface::GetServer()->GetPrefs()->Get3GPPEnabled() ), + fRTSPSession3GPPPtr(&fRTSPSession3GPP) +{ + + fTimeoutTask.SetTask(this); + fSocket.SetTask(this); + fStreamRef = this; + + fSessionID = (UInt32)atomic_add(&sSessionIDCounter, 1); + this->SetVal(qtssRTSPSesID, &fSessionID, sizeof(fSessionID)); + this->SetVal(qtssRTSPSesEventCntxt, &fOutputSocketP, sizeof(fOutputSocketP)); + this->SetVal(qtssRTSPSesType, &fSessionType, sizeof(fSessionType)); + this->SetVal(qtssRTSPSesStreamRef, &fStreamRef, sizeof(fStreamRef)); + this->SetVal(qtssRTSPSes3GPPObject, &fRTSPSession3GPPPtr, sizeof(fRTSPSession3GPPPtr)); + + this->SetEmptyVal(qtssRTSPSesLastUserName, &fUserNameBuf[0], kMaxUserNameLen); + this->SetEmptyVal(qtssRTSPSesLastUserPassword, &fUserPasswordBuf[0], kMaxUserPasswordLen); + this->SetEmptyVal(qtssRTSPSesLastURLRealm, &fUserRealmBuf[0], kMaxUserRealmLen); + + + fInputStream.ShowRTSP(QTSServerInterface::GetServer()->GetPrefs()->GetRTSPDebugPrintfs()); + fOutputStream.ShowRTSP(QTSServerInterface::GetServer()->GetPrefs()->GetRTSPDebugPrintfs()); +} + + +RTSPSessionInterface::~RTSPSessionInterface() +{ + // If the input socket is != output socket, the input socket was created dynamically + if (fInputSocketP != fOutputSocketP) + delete fInputSocketP; + + delete [] fTCPCoalesceBuffer; + + for (UInt8 x = 0; x < (fCurChannelNum >> 1); x++) + delete [] fChNumToSessIDMap[x].Ptr; + delete [] fChNumToSessIDMap; +} + +void RTSPSessionInterface::DecrementObjectHolderCount() +{ + +#if __Win32__ +//maybe don't need this special case but for now on Win32 we do it the old way since the killEvent code hasn't been verified on Windows. + this->Signal(Task::kReadEvent);//have the object wakeup in case it can go away. + atomic_sub(&fObjectHolders, 1); +#else + if (0 == atomic_sub(&fObjectHolders, 1)) + this->Signal(Task::kKillEvent); +#endif + +} + +QTSS_Error RTSPSessionInterface::Write(void* inBuffer, UInt32 inLength, + UInt32* outLenWritten, UInt32 inFlags) +{ + UInt32 sendType = RTSPResponseStream::kDontBuffer; + if ((inFlags & qtssWriteFlagsBufferData) != 0) + sendType = RTSPResponseStream::kAlwaysBuffer; + + iovec theVec[2]; + theVec[1].iov_base = (char*)inBuffer; + theVec[1].iov_len = inLength; + return fOutputStream.WriteV(theVec, 2, inLength, outLenWritten, sendType); +} + +QTSS_Error RTSPSessionInterface::WriteV(iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, UInt32* outLenWritten) +{ + return fOutputStream.WriteV(inVec, inNumVectors, inTotalLength, outLenWritten, RTSPResponseStream::kDontBuffer); +} + +QTSS_Error RTSPSessionInterface::Read(void* ioBuffer, UInt32 inLength, UInt32* outLenRead) +{ + // + // Don't let callers of this function accidently creep past the end of the + // request body. If the request body size isn't known, fRequestBodyLen will be -1 + + if (fRequestBodyLen == 0) + return QTSS_NoMoreData; + + if ((fRequestBodyLen > 0) && ((SInt32)inLength > fRequestBodyLen)) + inLength = fRequestBodyLen; + + UInt32 theLenRead = 0; + QTSS_Error theErr = fInputStream.Read(ioBuffer, inLength, &theLenRead); + + if (fRequestBodyLen >= 0) + fRequestBodyLen -= theLenRead; + + if (outLenRead != NULL) + *outLenRead = theLenRead; + + return theErr; +} + +QTSS_Error RTSPSessionInterface::RequestEvent(QTSS_EventType inEventMask) +{ + if (inEventMask & QTSS_ReadableEvent) + fInputSocketP->RequestEvent(EV_RE); + if (inEventMask & QTSS_WriteableEvent) + fOutputSocketP->RequestEvent(EV_WR); + + return QTSS_NoErr; +} + +UInt8 RTSPSessionInterface::GetTwoChannelNumbers(StrPtrLen* inRTSPSessionID) +{ + // + // Allocate a TCP coalesce buffer if still needed + if (fTCPCoalesceBuffer != NULL) + fTCPCoalesceBuffer = new char[kTCPCoalesceBufferSize]; + + // + // Allocate 2 channel numbers + UInt8 theChannelNum = fCurChannelNum; + fCurChannelNum+=2; + + // + // Reallocate the Ch# to Session ID Map + UInt32 numChannelEntries = fCurChannelNum >> 1; + StrPtrLen* newMap = NEW StrPtrLen[numChannelEntries]; + if (fChNumToSessIDMap != NULL) + { + Assert(numChannelEntries > 1); + ::memcpy(newMap, fChNumToSessIDMap, sizeof(StrPtrLen) * (numChannelEntries - 1)); + delete [] fChNumToSessIDMap; + } + fChNumToSessIDMap = newMap; + + // + // Put this sessionID to the proper place in the map + fChNumToSessIDMap[numChannelEntries-1].Set(inRTSPSessionID->GetAsCString(), inRTSPSessionID->Len); + + return theChannelNum; +} + +StrPtrLen* RTSPSessionInterface::GetSessionIDForChannelNum(UInt8 inChannelNum) +{ + if (inChannelNum < fCurChannelNum) + return &fChNumToSessIDMap[inChannelNum >> 1]; + else + return NULL; +} + +/********************************* +/ +/ InterleavedWrite +/ +/ Write the given RTP packet out on the RTSP channel in interleaved format. +/ +*/ + +QTSS_Error RTSPSessionInterface::InterleavedWrite(void* inBuffer, UInt32 inLen, UInt32* outLenWritten, unsigned char channel) +{ + + if ( inLen == 0 && fNumInCoalesceBuffer == 0 ) + { if (outLenWritten != NULL) + *outLenWritten = 0; + return QTSS_NoErr; + } + + // First attempt to grab the RTSPSession mutex. This is to prevent writing data to + // the connection at the same time an RTSPRequest is being processed. We cannot + // wait for this mutex to be freed (there would be a deadlock possibility), so + // just try to grab it, and if we can't, then just report it as an EAGAIN + if ( this->GetSessionMutex()->TryLock() == false ) + { + return EAGAIN; + } + + // DMS - this struct should be packed. + //rt todo -- is this struct more portable (byte alignment could be a problem)? + struct RTPInterleaveHeader + { + unsigned char header; + unsigned char channel; + UInt16 len; + }; + + struct iovec iov[3]; + QTSS_Error err = QTSS_NoErr; + + + + // flush rules + if ( ( inLen > kTCPCoalesceDirectWriteSize || inLen == 0 ) && fNumInCoalesceBuffer > 0 + || ( inLen + fNumInCoalesceBuffer + kInteleaveHeaderSize > kTCPCoalesceBufferSize ) && fNumInCoalesceBuffer > 0 + ) + { + UInt32 buffLenWritten; + + // skip iov[0], WriteV uses it + iov[1].iov_base = fTCPCoalesceBuffer; + iov[1].iov_len = fNumInCoalesceBuffer; + + err = this->GetOutputStream()->WriteV( iov, 2, fNumInCoalesceBuffer, &buffLenWritten, RTSPResponseStream::kAllOrNothing ); + + #if RTSP_SESSION_INTERFACE_DEBUGGING + qtss_printf("InterleavedWrite: flushing %li\n", fNumInCoalesceBuffer ); + #endif + + if ( err == QTSS_NoErr ) + fNumInCoalesceBuffer = 0; + } + + + + if ( err == QTSS_NoErr ) + { + + if ( inLen > kTCPCoalesceDirectWriteSize ) + { + struct RTPInterleaveHeader rih; + + // write direct to stream + rih.header = '$'; + rih.channel = channel; + rih.len = htons( (UInt16)inLen); + + iov[1].iov_base = (char*)&rih; + iov[1].iov_len = sizeof(rih); + + iov[2].iov_base = (char*)inBuffer; + iov[2].iov_len = inLen; + + err = this->GetOutputStream()->WriteV( iov, 3, inLen + sizeof(rih), outLenWritten, RTSPResponseStream::kAllOrNothing ); + + #if RTSP_SESSION_INTERFACE_DEBUGGING + qtss_printf("InterleavedWrite: bypass %li\n", inLen ); + #endif + + } + else + { + // coalesce with other small writes + + fTCPCoalesceBuffer[fNumInCoalesceBuffer] = '$'; + fNumInCoalesceBuffer++;; + + fTCPCoalesceBuffer[fNumInCoalesceBuffer] = channel; + fNumInCoalesceBuffer++; + + //*((short*)&fTCPCoalesceBuffer[fNumInCoalesceBuffer]) = htons(inLen); + // if we ever turn TCPCoalesce back on, this should be optimized + // for processors w/o alignment restrictions as above. + + SInt16 pcketLen = htons( (UInt16) inLen); + ::memcpy( &fTCPCoalesceBuffer[fNumInCoalesceBuffer], &pcketLen, 2 ); + fNumInCoalesceBuffer += 2; + + ::memcpy( &fTCPCoalesceBuffer[fNumInCoalesceBuffer], inBuffer, inLen ); + fNumInCoalesceBuffer += inLen; + + #if RTSP_SESSION_INTERFACE_DEBUGGING + qtss_printf("InterleavedWrite: coalesce %li, total bufff %li\n", inLen, fNumInCoalesceBuffer); + #endif + } + } + + if ( err == QTSS_NoErr ) + { + /* if no error sure to correct outLenWritten, cuz WriteV above includes the interleave header count + + GetOutputStream()->WriteV guarantees all or nothing for writes + if no error, then all was written. + */ + if ( outLenWritten != NULL ) + *outLenWritten = inLen; + } + + this->GetSessionMutex()->Unlock(); + + + return err; + +} + +/* + take the TCP socket away from a RTSP session that's + waiting to be snarfed. + +*/ + +void RTSPSessionInterface::SnarfInputSocket( RTSPSessionInterface* fromRTSPSession ) +{ + Assert( fromRTSPSession != NULL ); + Assert( fromRTSPSession->fOutputSocketP != NULL ); + + // grab the unused, but already read fromsocket data + // this should be the first RTSP request + if (sDoBase64Decoding) + fInputStream.IsBase64Encoded(true); // client sends all data base64 encoded + fInputStream.SnarfRetreat( fromRTSPSession->fInputStream ); + + if (fInputSocketP == fOutputSocketP) + fInputSocketP = NEW TCPSocket( this, Socket::kNonBlockingSocketType ); + else + fInputSocketP->Cleanup(); // if this is a socket replacing an old socket, we need + // to make sure the file descriptor gets closed + fInputSocketP->SnarfSocket( fromRTSPSession->fSocket ); + + // fInputStream, meet your new input socket + fInputStream.AttachToSocket( fInputSocketP ); +} + + +void* RTSPSessionInterface::SetupParams(QTSSDictionary* inSession, UInt32* /*outLen*/) +{ + RTSPSessionInterface* theSession = (RTSPSessionInterface*)inSession; + + theSession->fLocalAddr = theSession->fSocket.GetLocalAddr(); + theSession->fRemoteAddr = theSession->fSocket.GetRemoteAddr(); + + theSession->fLocalPort = theSession->fSocket.GetLocalPort(); + theSession->fRemotePort = theSession->fSocket.GetRemotePort(); + + StrPtrLen* theLocalAddrStr = theSession->fSocket.GetLocalAddrStr(); + StrPtrLen* theLocalDNSStr = theSession->fSocket.GetLocalDNSStr(); + StrPtrLen* theRemoteAddrStr = theSession->fSocket.GetRemoteAddrStr(); + if (theLocalAddrStr == NULL || theLocalDNSStr == NULL || theRemoteAddrStr == NULL) + { //the socket is bad most likely values are all 0. If the socket had an error we shouldn't even be here. + //theLocalDNSStr is set to localAddr if it is unavailable, so it should be present at this point as well. + Assert(0); //for debugging + return NULL; //nothing to set + } + theSession->SetVal(qtssRTSPSesLocalAddr, &theSession->fLocalAddr, sizeof(theSession->fLocalAddr)); + theSession->SetVal(qtssRTSPSesLocalAddrStr, theLocalAddrStr->Ptr, theLocalAddrStr->Len); + theSession->SetVal(qtssRTSPSesLocalDNS, theLocalDNSStr->Ptr, theLocalDNSStr->Len); + theSession->SetVal(qtssRTSPSesRemoteAddr, &theSession->fRemoteAddr, sizeof(theSession->fRemoteAddr)); + theSession->SetVal(qtssRTSPSesRemoteAddrStr, theRemoteAddrStr->Ptr, theRemoteAddrStr->Len); + + theSession->SetVal(qtssRTSPSesLocalPort, &theSession->fLocalPort, sizeof(theSession->fLocalPort)); + theSession->SetVal(qtssRTSPSesRemotePort, &theSession->fRemotePort, sizeof(theSession->fRemotePort)); + return NULL; +} + +void RTSPSessionInterface::SaveOutputStream() +{ + Assert(fOldOutputStreamBuffer.Ptr == NULL); + fOldOutputStreamBuffer.Ptr = NEW char[fOutputStream.GetBytesWritten()]; + fOldOutputStreamBuffer.Len = fOutputStream.GetBytesWritten(); + ::memcpy(fOldOutputStreamBuffer.Ptr, fOutputStream.GetBufPtr(), fOldOutputStreamBuffer.Len); +} + +void RTSPSessionInterface::RevertOutputStream() +{ + Assert(fOldOutputStreamBuffer.Ptr != NULL); + Assert(fOldOutputStreamBuffer.Len != 0); + static StrPtrLen theRTTStr(";rtt=", 5); + + if (fOldOutputStreamBuffer.Ptr != NULL) + { + //fOutputStream.Put(fOldOutputStreamBuffer); + StringParser theStreamParser(&fOldOutputStreamBuffer); + StrPtrLen theHeader; + StrPtrLen theEOL; + StrPtrLen theField; + StrPtrLen theValue; + while(theStreamParser.GetDataRemaining() != 0) + { + theStreamParser.ConsumeUntil(&theHeader, StringParser::sEOLMask); + if (theHeader.Len != 0) + { + fOutputStream.Put(theHeader); + + StringParser theHeaderParser(&theHeader); + theHeaderParser.ConsumeUntil(&theField, ':'); + if (theHeaderParser.PeekFast() == ':') + { + if(theField.Equal(RTSPProtocol::GetHeaderString(qtssXDynamicRateHeader))) + { + fOutputStream.Put(theRTTStr); + fOutputStream.Put(fRoundTripTime); + } + } + } + theStreamParser.ConsumeEOL(&theEOL); + fOutputStream.Put(theEOL); + } + + fOldOutputStreamBuffer.Delete(); + } +} + +void RTSPSessionInterface::SendOptionsRequest() +{ + static StrPtrLen sOptionsRequestHeader("OPTIONS * RTSP/1.0\r\nContent-Type: application/x-random-data\r\nContent-Length: 1400\r\n\r\n"); + + fOutputStream.Put(sOptionsRequestHeader); + fOutputStream.Put((char*)(RTSPSessionInterface::sOptionsRequestBody), 1400); + + fOptionsRequestSendTime = OS::Milliseconds(); + fSentOptionsRequest = true; + fRoundTripTimeCalculation = false; +} diff --git a/Server.tproj/RTSPSessionInterface.h b/Server.tproj/RTSPSessionInterface.h new file mode 100644 index 0000000..c051ef7 --- /dev/null +++ b/Server.tproj/RTSPSessionInterface.h @@ -0,0 +1,223 @@ +/* + * + * @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: RTSPSessionInterface.h + + Contains: Presents an API for session-wide resources for modules to use. + Implements the RTSP Session dictionary for QTSS API. + + +*/ + +#ifndef __RTSPSESSIONINTERFACE_H__ +#define __RTSPSESSIONINTERFACE_H__ + +#include "RTSPRequestStream.h" +#include "RTSPResponseStream.h" +#include "Task.h" +#include "QTSS.h" +#include "QTSSDictionary.h" +#include "atomic.h" +#include "RTSPSession3GPP.h" + +class RTSPSessionInterface : public QTSSDictionary, public Task +{ +public: + + //Initialize must be called right off the bat to initialize dictionary resources + static void Initialize(); + static void SetBase64Decoding(Bool16 newVal) { sDoBase64Decoding = newVal; } + + RTSPSessionInterface(); + virtual ~RTSPSessionInterface(); + + //Is this session alive? If this returns false, clean up and begone as + //fast as possible + Bool16 IsLiveSession() { return fSocket.IsConnected() && fLiveSession; } + + // Allows clients to refresh the timeout + void RefreshTimeout() { fTimeoutTask.RefreshTimeout(); } + + // In order to facilitate sending out of band data on the RTSP connection, + // other objects need to have direct pointer access to this object. But, + // because this object is a task object it can go away at any time. If # of + // object holders is > 0, the RTSPSession will NEVER go away. However, + // the object managing the session should be aware that if IsLiveSession returns + // false it may be wise to relinquish control of the session + void IncrementObjectHolderCount() { (void)atomic_add(&fObjectHolders, 1); } + void DecrementObjectHolderCount(); + + // If RTP data is interleaved into the RTSP connection, we need to associate + // 2 unique channel numbers with each RTP stream, one for RTP and one for RTCP. + // This function allocates 2 channel numbers, returns the lower one. The other one + // is implicitly 1 greater. + // + // Pass in the RTSP Session ID of the Client session to which these channel numbers will + // belong. + UInt8 GetTwoChannelNumbers(StrPtrLen* inRTSPSessionID); + + // + // Given a channel number, returns the RTSP Session ID to which this channel number refers + StrPtrLen* GetSessionIDForChannelNum(UInt8 inChannelNum); + + //Two main things are persistent through the course of a session, not + //associated with any one request. The RequestStream (which can be used for + //getting data from the client), and the socket. OOps, and the ResponseStream + RTSPRequestStream* GetInputStream() { return &fInputStream; } + RTSPResponseStream* GetOutputStream() { return &fOutputStream; } + TCPSocket* GetSocket() { return &fSocket; } + OSMutex* GetSessionMutex() { return &fSessionMutex; } + + UInt32 GetSessionID() { return fSessionID; } + + // Request Body Length + // This object can enforce a length of the request body to prevent callers + // of Read() from overrunning the request body and going into the next request. + // -1 is an unknown request body length. If the body length is unknown, + // this object will do no length enforcement. + void SetRequestBodyLength(SInt32 inLength) { fRequestBodyLen = inLength; } + SInt32 GetRemainingReqBodyLen() { return fRequestBodyLen; } + + // QTSS STREAM FUNCTIONS + + // Allows non-buffered writes to the client. These will flow control. + + // THE FIRST ENTRY OF THE IOVEC MUST BE BLANK!!! + virtual QTSS_Error WriteV(iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength, UInt32* outLenWritten); + virtual QTSS_Error Write(void* inBuffer, UInt32 inLength, UInt32* outLenWritten, UInt32 inFlags); + virtual QTSS_Error Read(void* ioBuffer, UInt32 inLength, UInt32* outLenRead); + virtual QTSS_Error RequestEvent(QTSS_EventType inEventMask); + + // performs RTP over RTSP + QTSS_Error InterleavedWrite(void* inBuffer, UInt32 inLen, UInt32* outLenWritten, unsigned char channel); + + // OPTIONS request + void SaveOutputStream(); + void RevertOutputStream(); + void ResetOutputStream() { fOutputStream.Reset(); fOutputStream.ResetBytesWritten();} + void SendOptionsRequest(); + Bool16 SentOptionsRequest() { return fSentOptionsRequest; } + SInt32 RoundTripTime() { return fRoundTripTime; } + + enum + { + kMaxUserNameLen = 32, + kMaxUserPasswordLen = 32, + kMaxUserRealmLen = 64 + }; + + enum // Quality of protection + { + kNoQop = 0, // No Quality of protection + kAuthQop = 1, // Authentication + kAuthIntQop = 2 // Authentication with Integrity + }; + + // DJM PROTOTYPE + enum + { + kMaxRandomDataSize = 256 * 1024, + }; + +protected: + enum + { + kFirstRTSPSessionID = 1, //UInt32 + }; + + //Each RTSP session has a unique number that identifies it. + + char fUserNameBuf[kMaxUserNameLen]; + char fUserPasswordBuf[kMaxUserPasswordLen]; + char fUserRealmBuf[kMaxUserRealmLen]; + + TimeoutTask fTimeoutTask;//allows the session to be timed out + + RTSPRequestStream fInputStream; + RTSPResponseStream fOutputStream; + + // Any RTP session sending interleaved data on this RTSP session must + // be prevented from writing while an RTSP request is in progress + OSMutex fSessionMutex; + + // for coalescing small interleaved writes into a single TCP frame + enum + { + kTCPCoalesceBufferSize = 1450 //1450 is the max data space in an TCP segment over ent + , kTCPCoalesceDirectWriteSize = 0 // if > this # bytes bypass coalescing and make a direct write + , kInteleaveHeaderSize = 4 // '$ '+ 1 byte ch ID + 2 bytes length + }; + char* fTCPCoalesceBuffer; + SInt32 fNumInCoalesceBuffer; + + + //+rt socket we get from "accept()" + TCPSocket fSocket; + TCPSocket* fOutputSocketP; + TCPSocket* fInputSocketP; // <-- usually same as fSocketP, unless we're HTTP Proxying + + void SnarfInputSocket( RTSPSessionInterface* fromRTSPSession ); + + // What session type are we? + QTSS_RTSPSessionType fSessionType; + Bool16 fLiveSession; + unsigned int fObjectHolders; + UInt8 fCurChannelNum; + StrPtrLen* fChNumToSessIDMap; + + QTSS_StreamRef fStreamRef; + + UInt32 fSessionID; + UInt32 fLocalAddr; + UInt32 fRemoteAddr; + SInt32 fRequestBodyLen; + + UInt16 fLocalPort; + UInt16 fRemotePort; + + // For OPTIONS request + StrPtrLen fOldOutputStreamBuffer; + Bool16 fSentOptionsRequest; + SInt64 fOptionsRequestSendTime; + SInt32 fRoundTripTime; + Bool16 fRoundTripTimeCalculation; + + RTSPSession3GPP fRTSPSession3GPP; + RTSPSession3GPP* fRTSPSession3GPPPtr; + + static unsigned int sSessionIDCounter; + static Bool16 sDoBase64Decoding; + + static UInt32 sOptionsRequestBody[kMaxRandomDataSize / sizeof(UInt32)]; + + //Dictionary support + + // Param retrieval function + static void* SetupParams(QTSSDictionary* inSession, UInt32* outLen); + + static QTSSAttrInfoDict::AttrInfo sAttributes[]; +}; +#endif // __RTSPSESSIONINTERFACE_H__ + diff --git a/Server.tproj/RunServer.cpp b/Server.tproj/RunServer.cpp new file mode 100644 index 0000000..885f5cf --- /dev/null +++ b/Server.tproj/RunServer.cpp @@ -0,0 +1,692 @@ +/* + * + * @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: main.cpp + + Contains: main function to drive streaming server. + + + +*/ + +#include + +#include "RunServer.h" +#include "SafeStdLib.h" +#include "OS.h" +#include "OSMemory.h" +#include "OSThread.h" +#include "Socket.h" +#include "SocketUtils.h" +#include "ev.h" +#include "OSArrayObjectDeleter.h" +#include "Task.h" +#include "IdleTask.h" +#include "TimeoutTask.h" +#include "DateTranslator.h" +#include "QTSSRollingLog.h" + + +#ifndef __Win32__ + #include + #include +#endif +#include "QTSServerInterface.h" +#include "QTSServer.h" + +#include +#include + +QTSServer* sServer = NULL; +int sStatusUpdateInterval = 0; +Bool16 sHasPID = false; +UInt64 sLastStatusPackets = 0; +UInt64 sLastDebugPackets = 0; +SInt64 sLastDebugTotalQuality = 0; +#ifdef __sgi__ +#include +#endif + +QTSS_ServerState StartServer(XMLPrefsParser* inPrefsSource, PrefsSource* inMessagesSource, UInt16 inPortOverride, int statsUpdateInterval, QTSS_ServerState inInitialState, Bool16 inDontFork, UInt32 debugLevel, UInt32 debugOptions) +{ + //Mark when we are done starting up. If auto-restart is enabled, we want to make sure + //to always exit with a status of 0 if we encountered a problem WHILE STARTING UP. This + //will prevent infinite-auto-restart-loop type problems + Bool16 doneStartingUp = false; + QTSS_ServerState theServerState = qtssStartingUpState; + + sStatusUpdateInterval = statsUpdateInterval; + + //Initialize utility classes + OS::Initialize(); + OSThread::Initialize(); + + Socket::Initialize(); + SocketUtils::Initialize(!inDontFork); + +#if !MACOSXEVENTQUEUE + ::select_startevents();//initialize the select() implementation of the event queue +#endif + + //start the server + QTSSDictionaryMap::Initialize(); + QTSServerInterface::Initialize();// this must be called before constructing the server object + sServer = NEW QTSServer(); + sServer->SetDebugLevel(debugLevel); + sServer->SetDebugOptions(debugOptions); + + // re-parse config file + inPrefsSource->Parse(); + + Bool16 createListeners = true; + if (qtssShuttingDownState == inInitialState) + createListeners = false; + + sServer->Initialize(inPrefsSource, inMessagesSource, inPortOverride,createListeners); + + if (inInitialState == qtssShuttingDownState) + { + sServer->InitModules(inInitialState); + return inInitialState; + } + + OSCharArrayDeleter runGroupName(sServer->GetPrefs()->GetRunGroupName()); + OSCharArrayDeleter runUserName(sServer->GetPrefs()->GetRunUserName()); + OSThread::SetPersonality(runUserName.GetObject(), runGroupName.GetObject()); + + if (sServer->GetServerState() != qtssFatalErrorState) + { + UInt32 numShortTaskThreads = 0; + UInt32 numBlockingThreads = 0; + UInt32 numThreads = 0; + UInt32 numProcessors = 0; + + if (OS::ThreadSafe()) + { + numShortTaskThreads = sServer->GetPrefs()->GetNumThreads(); // whatever the prefs say + if (numShortTaskThreads == 0) { + numProcessors = OS::GetNumProcessors(); + // 1 worker thread per processor, up to 2 threads. + // Note: Limiting the number of worker threads to 2 on a MacOS X system with > 2 cores + // results in better performance on those systems, as of MacOS X 10.5. Future + // improvements should make this limit unnecessary. + if (numProcessors > 2) + numShortTaskThreads = 2; + else + numShortTaskThreads = numProcessors; + } + + numBlockingThreads = sServer->GetPrefs()->GetNumBlockingThreads(); // whatever the prefs say + if (numBlockingThreads == 0) + numBlockingThreads = 1; + + } + if (numShortTaskThreads == 0) + numShortTaskThreads = 1; + + if (numBlockingThreads == 0) + numBlockingThreads = 1; + + numThreads = numShortTaskThreads + numBlockingThreads; + //qtss_printf("Add threads shortask=%lu blocking=%lu\n",numShortTaskThreads, numBlockingThreads); + TaskThreadPool::SetNumShortTaskThreads(numShortTaskThreads); + TaskThreadPool::SetNumBlockingTaskThreads(numBlockingThreads); + TaskThreadPool::AddThreads(numThreads); + sServer->InitNumThreads(numThreads); + + #if DEBUG + qtss_printf("Number of task threads: %"_U32BITARG_"\n",numThreads); + #endif + + // Start up the server's global tasks, and start listening + TimeoutTask::Initialize(); // The TimeoutTask mechanism is task based, + // we therefore must do this after adding task threads + // this be done before starting the sockets and server tasks + } + + //Make sure to do this stuff last. Because these are all the threads that + //do work in the server, this ensures that no work can go on while the server + //is in the process of staring up + if (sServer->GetServerState() != qtssFatalErrorState) + { + IdleTask::Initialize(); + Socket::StartThread(); + OSThread::Sleep(1000); + + // + // On Win32, in order to call modwatch the Socket EventQueue thread must be + // created first. Modules call modwatch from their initializer, and we don't + // want to prevent them from doing that, so module initialization is separated + // out from other initialization, and we start the Socket EventQueue thread first. + // The server is still prevented from doing anything as of yet, because there + // aren't any TaskThreads yet. + sServer->InitModules(inInitialState); + sServer->StartTasks(); + sServer->SetupUDPSockets(); // udp sockets are set up after the rtcp task is instantiated + theServerState = sServer->GetServerState(); + } + + if (theServerState != qtssFatalErrorState) + { + CleanPid(true); + WritePid(!inDontFork); + + doneStartingUp = true; + qtss_printf("Streaming Server done starting up\n"); + OSMemory::SetMemoryError(ENOMEM); + } + + + // SWITCH TO RUN USER AND GROUP ID + if (!sServer->SwitchPersonality()) + theServerState = qtssFatalErrorState; + + // + // Tell the caller whether the server started up or not + return theServerState; +} + +void WritePid(Bool16 forked) +{ +#ifndef __Win32__ + // WRITE PID TO FILE + OSCharArrayDeleter thePidFileName(sServer->GetPrefs()->GetPidFilePath()); + FILE *thePidFile = fopen(thePidFileName, "w"); + if(thePidFile) + { + if (!forked) + fprintf(thePidFile,"%d\n",getpid()); // write own pid + else + { + fprintf(thePidFile,"%d\n",getppid()); // write parent pid + fprintf(thePidFile,"%d\n",getpid()); // and our own pid in the next line + } + fclose(thePidFile); + sHasPID = true; + } +#endif +} + +void CleanPid(Bool16 force) +{ +#ifndef __Win32__ + if (sHasPID || force) + { + OSCharArrayDeleter thePidFileName(sServer->GetPrefs()->GetPidFilePath()); + unlink(thePidFileName); + } +#endif +} +void LogStatus(QTSS_ServerState theServerState) +{ + static QTSS_ServerState lastServerState = 0; + static char *sPLISTHeader[] = + { "", +#if __MacOSX__ + "", +#else + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + " ", + " ", + "", + "", + " ", + " ", + " ", + " ", + "]>", +#endif + }; + + static int numHeaderLines = sizeof(sPLISTHeader) / sizeof(char*); + + static char* sPlistStart = ""; + static char* sPlistEnd = ""; + static char* sDictStart = ""; + static char* sDictEnd = ""; + + static char* sKey = " %s\n"; + static char* sValue = " %s\n"; + + static char *sAttributes[] = + { + "qtssSvrServerName", + "qtssSvrServerVersion", + "qtssSvrServerBuild", + "qtssSvrServerPlatform", + "qtssSvrRTSPServerComment", + "qtssSvrServerBuildDate", + "qtssSvrStartupTime", + "qtssSvrCurrentTimeMilliseconds", + "qtssSvrCPULoadPercent", + "qtssSvrState", + "qtssRTPSvrCurConn", + "qtssRTSPCurrentSessionCount", + "qtssRTSPHTTPCurrentSessionCount", + "qtssRTPSvrCurBandwidth", + "qtssRTPSvrCurPackets", + "qtssRTPSvrTotalConn", + "qtssRTPSvrTotalBytes", + "qtssMP3SvrCurConn", + "qtssMP3SvrTotalConn", + "qtssMP3SvrCurBandwidth", + "qtssMP3SvrTotalBytes" + }; + static int numAttributes = sizeof(sAttributes) / sizeof(char*); + + static StrPtrLen statsFileNameStr("server_status"); + + if (false == sServer->GetPrefs()->ServerStatFileEnabled()) + return; + + UInt32 interval = sServer->GetPrefs()->GetStatFileIntervalSec(); + if (interval == 0 || (OS::UnixTime_Secs() % interval) > 0 ) + return; + + // If the total number of RTSP sessions is 0 then we + // might not need to update the "server_status" file. + char* thePrefStr = NULL; + // We start lastRTSPSessionCount off with an impossible value so that + // we force the "server_status" file to be written at least once. + static int lastRTSPSessionCount = -1; + // Get the RTSP session count from the server. + (void)QTSS_GetValueAsString(sServer, qtssRTSPCurrentSessionCount, 0, &thePrefStr); + int currentRTSPSessionCount = ::atoi(thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + if (currentRTSPSessionCount == 0 && currentRTSPSessionCount == lastRTSPSessionCount) + { + // we don't need to update the "server_status" file except the + // first time we are in the idle state. + if (theServerState == qtssIdleState && lastServerState == qtssIdleState) + { + lastRTSPSessionCount = currentRTSPSessionCount; + lastServerState = theServerState; + return; + } + } + else + { + // save the RTSP session count for the next time we execute. + lastRTSPSessionCount = currentRTSPSessionCount; + } + + StrPtrLenDel pathStr(sServer->GetPrefs()->GetErrorLogDir()); + StrPtrLenDel fileNameStr(sServer->GetPrefs()->GetStatsMonitorFileName()); + ResizeableStringFormatter pathBuffer(NULL,0); + pathBuffer.PutFilePath(&pathStr,&fileNameStr); + pathBuffer.PutTerminator(); + + char* filePath = pathBuffer.GetBufPtr(); + FILE* statusFile = ::fopen(filePath, "w"); + char* theAttributeValue = NULL; + int i; + + if (statusFile != NULL) + { + ::chmod(filePath, 0640); + for ( i = 0; i < numHeaderLines; i++) + { + qtss_fprintf(statusFile, "%s\n",sPLISTHeader[i]); + } + + qtss_fprintf(statusFile, "%s\n", sPlistStart); + qtss_fprintf(statusFile, "%s\n", sDictStart); + + // show each element value + for ( i = 0; i < numAttributes; i++) + { + (void)QTSS_GetValueAsString(sServer, QTSSModuleUtils::GetAttrID(sServer,sAttributes[i]), 0, &theAttributeValue); + if (theAttributeValue != NULL) + { + qtss_fprintf(statusFile, sKey, sAttributes[i]); + qtss_fprintf(statusFile, sValue, theAttributeValue); + delete [] theAttributeValue; + theAttributeValue = NULL; + } + } + + qtss_fprintf(statusFile, "%s\n", sDictEnd); + qtss_fprintf(statusFile, "%s\n\n", sPlistEnd); + + ::fclose(statusFile); + } + lastServerState = theServerState; +} + +void print_status(FILE* file, FILE* console, char* format, char* theStr) +{ + if (file) qtss_fprintf(file, format, theStr); + if (console) qtss_fprintf(console, format, theStr); + +} + +void DebugLevel_1(FILE* statusFile, FILE* stdOut, Bool16 printHeader ) +{ + char* thePrefStr = NULL; + static char numStr[12] =""; + static char dateStr[25] =""; + UInt32 theLen = 0; + + if ( printHeader ) + { + + print_status(statusFile,stdOut,"%s", " RTP-Conns RTSP-Conns HTTP-Conns kBits/Sec Pkts/Sec RTP-Playing AvgDelay CurMaxDelay MaxDelay AvgQuality NumThinned Time\n"); + + } + + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrCurConn, 0, &thePrefStr); + print_status(statusFile, stdOut,"%11s", thePrefStr); + + delete [] thePrefStr; thePrefStr = NULL; + + (void)QTSS_GetValueAsString(sServer, qtssRTSPCurrentSessionCount, 0, &thePrefStr); + print_status(statusFile, stdOut,"%11s", thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + + (void)QTSS_GetValueAsString(sServer, qtssRTSPHTTPCurrentSessionCount, 0, &thePrefStr); + print_status(statusFile, stdOut,"%11s", thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + + UInt32 curBandwidth = 0; + theLen = sizeof(curBandwidth); + (void)QTSS_GetValue(sServer, qtssRTPSvrCurBandwidth, 0, &curBandwidth, &theLen); + qtss_snprintf(numStr, 11, "%"_U32BITARG_"", curBandwidth/1024); + print_status(statusFile, stdOut,"%11s", numStr); + + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrCurPackets, 0, &thePrefStr); + print_status(statusFile, stdOut,"%11s", thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + + + UInt32 currentPlaying = sServer->GetNumRTPPlayingSessions(); + qtss_snprintf( numStr, sizeof(numStr) -1, "%"_U32BITARG_"", currentPlaying); + print_status(statusFile, stdOut,"%14s", numStr); + + + //is the server keeping up with the streams? + //what quality are the streams? + SInt64 totalRTPPaackets = sServer->GetTotalRTPPackets(); + SInt64 deltaPackets = totalRTPPaackets - sLastDebugPackets; + sLastDebugPackets = totalRTPPaackets; + + SInt64 totalQuality = sServer->GetTotalQuality(); + SInt64 deltaQuality = totalQuality - sLastDebugTotalQuality; + sLastDebugTotalQuality = totalQuality; + + SInt64 currentMaxLate = sServer->GetCurrentMaxLate(); + SInt64 totalLate = sServer->GetTotalLate(); + + sServer->ClearTotalLate(); + sServer->ClearCurrentMaxLate(); + sServer->ClearTotalQuality(); + + ::qtss_snprintf(numStr, sizeof(numStr) -1, "%s", "0"); + if (deltaPackets > 0) + qtss_snprintf(numStr, sizeof(numStr) -1, "%"_S32BITARG_"", (SInt32) ((SInt64)totalLate / (SInt64) deltaPackets )); + print_status(statusFile, stdOut,"%11s", numStr); + + qtss_snprintf(numStr,sizeof(numStr) -1, "%"_S32BITARG_"", (SInt32) currentMaxLate); + print_status(statusFile, stdOut,"%11s", numStr); + + qtss_snprintf(numStr,sizeof(numStr) -1, "%"_S32BITARG_"", (SInt32) sServer->GetMaxLate() ); + print_status(statusFile, stdOut,"%11s", numStr); + + ::qtss_snprintf(numStr, sizeof(numStr) -1, "%s", "0"); + if (deltaPackets > 0) + qtss_snprintf(numStr, sizeof(numStr) -1, "%"_S32BITARG_"", (SInt32) ((SInt64) deltaQuality / (SInt64) deltaPackets)); + print_status(statusFile, stdOut,"%11s", numStr); + + qtss_snprintf(numStr,sizeof(numStr) -1, "%"_S32BITARG_"", (SInt32) sServer->GetNumThinned() ); + print_status(statusFile, stdOut,"%11s", numStr); + + + + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + (void) QTSSRollingLog::FormatDate(theDateBuffer, false); + + qtss_snprintf(dateStr,sizeof(dateStr) -1, "%s", theDateBuffer ); + print_status(statusFile, stdOut,"%24s\n", dateStr); +} + +FILE* LogDebugEnabled() +{ + + if (DebugLogOn(sServer)) + { + static StrPtrLen statsFileNameStr("server_debug_status"); + + StrPtrLenDel pathStr(sServer->GetPrefs()->GetErrorLogDir()); + ResizeableStringFormatter pathBuffer(NULL,0); + pathBuffer.PutFilePath(&pathStr,&statsFileNameStr); + pathBuffer.PutTerminator(); + + char* filePath = pathBuffer.GetBufPtr(); + return ::fopen(filePath, "a"); + } + + return NULL; +} + + +FILE* DisplayDebugEnabled() +{ + return ( DebugDisplayOn(sServer) ) ? stdout : NULL ; +} + + +void DebugStatus(UInt32 debugLevel, Bool16 printHeader) +{ + + FILE* statusFile = LogDebugEnabled(); + FILE* stdOut = DisplayDebugEnabled(); + + if (debugLevel > 0) + DebugLevel_1(statusFile, stdOut, printHeader); + + if (statusFile) + ::fclose(statusFile); +} + +void FormattedTotalBytesBuffer(char *outBuffer, int outBufferLen, UInt64 totalBytes) +{ + Float32 displayBytes = 0.0; + char sizeStr[] = "B"; + char* format = NULL; + + if (totalBytes > 1073741824 ) //GBytes + { displayBytes = (Float32) ( (Float64) (SInt64) totalBytes / (Float64) (SInt64) 1073741824 ); + sizeStr[0] = 'G'; + format = "%.4f%s "; + } + else if (totalBytes > 1048576 ) //MBytes + { displayBytes = (Float32) (SInt32) totalBytes / (Float32) (SInt32) 1048576; + sizeStr[0] = 'M'; + format = "%.3f%s "; + } + else if (totalBytes > 1024 ) //KBytes + { displayBytes = (Float32) (SInt32) totalBytes / (Float32) (SInt32) 1024; + sizeStr[0] = 'K'; + format = "%.2f%s "; + } + else + { displayBytes = (Float32) (SInt32) totalBytes; //Bytes + sizeStr[0] = 'B'; + format = "%4.0f%s "; + } + + outBuffer[outBufferLen -1] = 0; + qtss_snprintf(outBuffer, outBufferLen -1, format , displayBytes, sizeStr); +} + +void PrintStatus(Bool16 printHeader) +{ + char* thePrefStr = NULL; + UInt32 theLen = 0; + + if ( printHeader ) + { + qtss_printf(" RTP-Conns RTSP-Conns HTTP-Conns kBits/Sec Pkts/Sec TotConn TotBytes TotPktsLost Time\n"); + } + + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrCurConn, 0, &thePrefStr); + qtss_printf( "%11s", thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + + (void)QTSS_GetValueAsString(sServer, qtssRTSPCurrentSessionCount, 0, &thePrefStr); + qtss_printf( "%11s", thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + + (void)QTSS_GetValueAsString(sServer, qtssRTSPHTTPCurrentSessionCount, 0, &thePrefStr); + qtss_printf( "%11s", thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + + UInt32 curBandwidth = 0; + theLen = sizeof(curBandwidth); + (void)QTSS_GetValue(sServer, qtssRTPSvrCurBandwidth, 0, &curBandwidth, &theLen); + qtss_printf("%11"_U32BITARG_, curBandwidth/1024); + + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrCurPackets, 0, &thePrefStr); + qtss_printf( "%11s", thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + + (void)QTSS_GetValueAsString(sServer, qtssRTPSvrTotalConn, 0, &thePrefStr); + qtss_printf( "%11s", thePrefStr); + delete [] thePrefStr; thePrefStr = NULL; + + UInt64 totalBytes = sServer->GetTotalRTPBytes(); + char displayBuff[32] = ""; + FormattedTotalBytesBuffer(displayBuff, sizeof(displayBuff),totalBytes); + qtss_printf( "%17s", displayBuff); + + qtss_printf( "%11"_64BITARG_"u", sServer->GetTotalRTPPacketsLost()); + + char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes]; + (void) QTSSRollingLog::FormatDate(theDateBuffer, false); + qtss_printf( "%25s",theDateBuffer); + + qtss_printf( "\n"); + +} + +Bool16 PrintHeader(UInt32 loopCount) +{ + return ( (loopCount % (sStatusUpdateInterval * 10) ) == 0 ) ? true : false; +} + +Bool16 PrintLine(UInt32 loopCount) +{ + return ( (loopCount % sStatusUpdateInterval) == 0 ) ? true : false; +} + + +void RunServer() +{ + Bool16 restartServer = false; + UInt32 loopCount = 0; + UInt32 debugLevel = 0; + Bool16 printHeader = false; + Bool16 printStatus = false; + + + //just wait until someone stops the server or a fatal error occurs. + QTSS_ServerState theServerState = sServer->GetServerState(); + while ((theServerState != qtssShuttingDownState) && + (theServerState != qtssFatalErrorState)) + { +#ifdef __sgi__ + OSThread::Sleep(999); +#else + OSThread::Sleep(1000); +#endif + + LogStatus(theServerState); + + if (sStatusUpdateInterval) + { + debugLevel = sServer->GetDebugLevel(); + printHeader = PrintHeader(loopCount); + printStatus = PrintLine(loopCount); + + if (printStatus) + { + if (DebugOn(sServer) ) // debug level display or logging is on + DebugStatus(debugLevel, printHeader); + + if (!DebugDisplayOn(sServer)) + PrintStatus(printHeader); // default status output + } + + + loopCount++; + + } + + if ((sServer->SigIntSet()) || (sServer->SigTermSet())) + { + // + // start the shutdown process + theServerState = qtssShuttingDownState; + (void)QTSS_SetValue(QTSServerInterface::GetServer(), qtssSvrState, 0, &theServerState, sizeof(theServerState)); + + if (sServer->SigIntSet()) + restartServer = true; + } + + theServerState = sServer->GetServerState(); + if (theServerState == qtssIdleState) + sServer->KillAllRTPSessions(); + } + + // + // Kill all the sessions and wait for them to die, + // but don't wait more than 5 seconds + sServer->KillAllRTPSessions(); + for (UInt32 shutdownWaitCount = 0; (sServer->GetNumRTPSessions() > 0) && (shutdownWaitCount < 5); shutdownWaitCount++) + OSThread::Sleep(1000); + + //Now, make sure that the server can't do any work + TaskThreadPool::RemoveThreads(); + + //now that the server is definitely stopped, it is safe to initate + //the shutdown process + delete sServer; + + CleanPid(false); + //ok, we're ready to exit. If we're quitting because of some fatal error + //while running the server, make sure to let the parent process know by + //exiting with a nonzero status. Otherwise, exit with a 0 status + if (theServerState == qtssFatalErrorState || restartServer) + ::exit (-2);//-2 signals parent process to restart server +} diff --git a/Server.tproj/RunServer.h b/Server.tproj/RunServer.h new file mode 100644 index 0000000..9333278 --- /dev/null +++ b/Server.tproj/RunServer.h @@ -0,0 +1,79 @@ +/* + * + * @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: RunServer.h + + Contains: Routines to run the Streaming Server + + +*/ + + +#include "OSHeaders.h" +#include "XMLPrefsParser.h" +#include "PrefsSource.h" +#include "QTSS.h" +#include "QTSServer.h" + +enum { + kRunServerDebug_Off = 0, + kRunServerDebugDisplay_On = 1 << 0, + kRunServerDebugLogging_On = 1 << 1 // not implemented + }; + +inline Bool16 DebugOn(QTSServer* server) { return ( server->GetDebugOptions() != kRunServerDebug_Off ) ? true : false ; } +inline Bool16 DebugDisplayOn(QTSServer* server) { return (server->GetDebugOptions() & kRunServerDebugDisplay_On) ? true : false ; } +inline Bool16 DebugLogOn(QTSServer* server) { return (server->GetDebugOptions() & kRunServerDebugLogging_On) ? true : false ; } + +// +// This function starts the Streaming Server. Pass in a source +// for preferences, a source for text messages, and an optional +// port to override the default. +// +// Returns the server state upon completion of startup. If this +// is qtssFatalErrorState, something went horribly wrong and caller +// should just die. +QTSS_ServerState StartServer( XMLPrefsParser* inPrefsSource, + PrefsSource* inMessagesSource, + UInt16 inPortOverride, + int statsUpdateInterval, + QTSS_ServerState inInitialState, + Bool16 inDontFork, + UInt32 debugLevel, + UInt32 debugOptions ); + +// +// Call this after StartServer if it doesn't return qtssFatalError. +// This will not return until the server is going away +void RunServer(); + +// write pid to file +void WritePid(Bool16 forked); + +// clean the pid file +void CleanPid(Bool16 force); + +// make the status file +void LogStatus(QTSS_ServerState theServerState); diff --git a/Server.tproj/main.cpp b/Server.tproj/main.cpp new file mode 100644 index 0000000..3e67c7d --- /dev/null +++ b/Server.tproj/main.cpp @@ -0,0 +1,588 @@ +/* + * + * @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: main.cpp + + Contains: main function to drive streaming server. + + +*/ + +#include +#include +#include "SafeStdLib.h" +#include "../defaultPaths.h" +#ifndef __MacOSX__ +#include "getopt.h" +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef __Win32__ +#include +#endif + +#if defined (__solaris__) || defined (__osf__) || defined (__hpux__) +#include "daemon.h" +#endif + +#if __MacOSX__ || __FreeBSD__ +#include +#include +#endif + +#include "FilePrefsSource.h" +#include "RunServer.h" +#include "QTSServer.h" +#include "QTSSExpirationDate.h" +#include "GenerateXMLPrefs.h" + +static int sSigIntCount = 0; +static int sSigTermCount = 0; +static pid_t sChildPID = 0; + +void usage(); + +void usage() +{ + const char *usage_name = PLATFORM_SERVER_BIN_NAME; +//long ptrsize = sizeof(char *); printf("size of ptr = %ld\n", ptrsize); +//long longsize = sizeof(long); printf("size of long = %ld\n", longsize); + + qtss_printf("%s/%s ( Build/%s; Platform/%s; %s) Built on: %s\n",QTSServerInterface::GetServerName().Ptr, + QTSServerInterface::GetServerVersion().Ptr, + QTSServerInterface::GetServerBuild().Ptr, + QTSServerInterface::GetServerPlatform().Ptr, + QTSServerInterface::GetServerComment().Ptr, + QTSServerInterface::GetServerBuildDate().Ptr); + qtss_printf("usage: %s [ -d | -p port | -v | -c /myconfigpath.xml | -o /myconfigpath.conf | -x | -S numseconds | -I | -h ]\n", usage_name); + qtss_printf("-d: Run in the foreground\n"); + qtss_printf("-D: Display performance data\n"); + qtss_printf("-p XXX: Specify the default RTSP listening port of the server\n"); + qtss_printf("-c /myconfigpath.xml: Specify a config file\n"); + qtss_printf("-o /myconfigpath.conf: Specify a DSS 1.x / 2.x config file to build XML file from\n"); + qtss_printf("-x: Force create new .xml config file and exit.\n"); + qtss_printf("-S n: Display server stats in the console every \"n\" seconds\n"); + qtss_printf("-I: Start the server in the idle state\n"); + qtss_printf("-h: Prints usage\n"); +} + +Bool16 sendtochild(int sig, pid_t myPID); +Bool16 sendtochild(int sig, pid_t myPID) +{ + if (sChildPID != 0 && sChildPID != myPID) // this is the parent + { // Send signal to child + ::kill(sChildPID, sig); + return true; + } + + return false; +} + +void sigcatcher(int sig, int /*sinfo*/, struct sigcontext* /*sctxt*/); +void sigcatcher(int sig, int /*sinfo*/, struct sigcontext* /*sctxt*/) +{ +#if DEBUG + qtss_printf("Signal %d caught\n", sig); +#endif + pid_t myPID = getpid(); + // + // SIGHUP means we should reread our preferences + if (sig == SIGHUP) + { + if (sendtochild(sig,myPID)) + { + return; + } + else + { + // This is the child process. + // Re-read our preferences. + RereadPrefsTask* task = new RereadPrefsTask; + task->Signal(Task::kStartEvent); + + } + } + + //Try to shut down gracefully the first time, shutdown forcefully the next time + if (sig == SIGINT) // kill the child only + { + if (sendtochild(sig,myPID)) + { + return;// ok we're done + } + else + { + // + // Tell the server that there has been a SigInt, the main thread will start + // the shutdown process because of this. The parent and child processes will quit. + if (sSigIntCount == 0) + QTSServerInterface::GetServer()->SetSigInt(); + sSigIntCount++; + } + } + + if (sig == SIGTERM || sig == SIGQUIT) // kill child then quit + { + if (sendtochild(sig,myPID)) + { + return;// ok we're done + } + else + { + // Tell the server that there has been a SigTerm, the main thread will start + // the shutdown process because of this only the child will quit + + + if (sSigTermCount == 0) + QTSServerInterface::GetServer()->SetSigTerm(); + sSigTermCount++; + } + } +} + +extern "C" { +typedef int (*EntryFunction)(int input); +} + +Bool16 RunInForeground(); +Bool16 RunInForeground() +{ + + #if __linux__ || __MacOSX__ + (void) setvbuf(stdout, NULL, _IOLBF, 0); + OSThread::WrapSleep(true); + #endif + + return true; +} + + +Bool16 RestartServer(char* theXMLFilePath) +{ + Bool16 autoRestart = true; + XMLPrefsParser theXMLParser(theXMLFilePath); + theXMLParser.Parse(); + + ContainerRef server = theXMLParser.GetRefForServer(); + ContainerRef pref = theXMLParser.GetPrefRefByName( server, "auto_restart" ); + char* autoStartSetting = NULL; + + if (pref != NULL) + autoStartSetting = theXMLParser.GetPrefValueByRef( pref, 0, NULL, NULL ); + + if ( (autoStartSetting != NULL) && (::strcmp(autoStartSetting, "false") == 0) ) + autoRestart = false; + + return autoRestart; +} + +int main(int argc, char * argv[]) +{ + extern char* optarg; + + + + // on write, don't send signal for SIGPIPE, just set errno to EPIPE + // and return -1 + //signal is a deprecated and potentially dangerous function + //(void) ::signal(SIGPIPE, SIG_IGN); + struct sigaction act; + +#if defined(sun) || defined(i386) || defined (__MacOSX__) || defined(__powerpc__) || defined (__osf__) || defined (__sgi_cc__) || defined (__hpux__) || defined(__linux__) + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = (void(*)(int))&sigcatcher; +#elif defined(__sgi__) + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = (void(*)(...))&sigcatcher; +#else + act.sa_mask = 0; + act.sa_flags = 0; + act.sa_handler = (void(*)(...))&sigcatcher; +#endif + (void)::sigaction(SIGPIPE, &act, NULL); + (void)::sigaction(SIGHUP, &act, NULL); + (void)::sigaction(SIGINT, &act, NULL); + (void)::sigaction(SIGTERM, &act, NULL); + (void)::sigaction(SIGQUIT, &act, NULL); + (void)::sigaction(SIGALRM, &act, NULL); + + +#if __solaris__ || __linux__ || __hpux__ + //grow our pool of file descriptors to the max! + struct rlimit rl; + + // set it to the absolute maximum that the operating system allows - have to be superuser to do this + rl.rlim_cur = RLIM_INFINITY; + rl.rlim_max = RLIM_INFINITY; + + setrlimit (RLIMIT_NOFILE, &rl); +#endif + +#if __MacOSX__ + struct rlimit rl; + getrlimit(RLIMIT_NOFILE, &rl); //get the default values + //printf("current open file limit =%"_U32BITARG_"\n", (UInt32) rl.rlim_cur); //leopard returns 256 + //printf("current open file max =%"_U32BITARG_"\n", (UInt32) rl.rlim_max);//leopard returns infinity (-1) + + rl. rlim_max = (rlim_t) RLIM_INFINITY -1; //use a big number to find out the real max but do not use RLIM_INFINITY that is not allowed. see man page + setrlimit (RLIMIT_NOFILE, &rl); //resets the max value stored by limits to the boot config values. + getrlimit(RLIMIT_NOFILE, &rl); //now get the real max value + //printf("current open file limit =%"_U32BITARG_"\n", (UInt32) rl.rlim_cur); + //printf("current open file max =%"_U32BITARG_"\n", (UInt32) rl.rlim_max); + + rl.rlim_cur = (rlim_t) ( (float) rl.rlim_max * 0.9); //use 90% of the max set in /etc/rc.server and /etc/sysctl.conf.default + setrlimit (RLIMIT_NOFILE, &rl); //finally set the current limit + +#endif + +#if 0 // testing + getrlimit(RLIMIT_NOFILE, &rl); + printf("current open file limit =%"_U32BITARG_"\n", (UInt32) rl.rlim_cur); + printf("current open file max =%"_U32BITARG_"\n", (UInt32) rl.rlim_max); +#endif + +#if __MacOSX__ || __FreeBSD__ + // + // These 2 OSes have problems with large socket buffer sizes. Make sure they allow even + // ridiculously large ones, because we may need them to receive a large volume of ACK packets + // from the client + + // + // We raise the limit imposed by the kernel by calling the sysctl system call. + int mib[CTL_MAXNAME]; + mib[0] = CTL_KERN; + mib[1] = KERN_IPC; + mib[2] = KIPC_MAXSOCKBUF; + mib[3] = 0; + + int maxSocketBufferSizeVal = 2000 * 1024; // Allow up to 2 MB. That is WAY more than we should need + (void) ::sysctl(mib, 3, 0, 0, &maxSocketBufferSizeVal, sizeof(maxSocketBufferSizeVal)); + //int sysctlErr = ::sysctl(mib, 3, 0, 0, &maxSocketBufferSizeVal, sizeof(maxSocketBufferSizeVal)); + //qtss_printf("sysctl maxSocketBufferSizeVal=%d err=%d\n",maxSocketBufferSizeVal, sysctlErr); + #endif + + //First thing to do is to read command-line arguments. + int ch; + int thePort = 0; //port can be set on the command line + int statsUpdateInterval = 0; + QTSS_ServerState theInitialState = qtssRunningState; + + Bool16 dontFork = false; + Bool16 theXMLPrefsExist = true; + UInt32 debugLevel = 0; + UInt32 debugOptions = kRunServerDebug_Off; + static char* sDefaultConfigFilePath = DEFAULTPATHS_ETC_DIR_OLD "streamingserver.conf"; + static char* sDefaultXMLFilePath = DEFAULTPATHS_ETC_DIR "streamingserver.xml"; + + char* theConfigFilePath = sDefaultConfigFilePath; + char* theXMLFilePath = sDefaultXMLFilePath; + while ((ch = getopt(argc,argv, "vdfxp:DZ:c:o:S:Ih")) != EOF) // opt: means requires option arg + { + switch(ch) + { + case 'v': + usage(); + ::exit(0); + case 'd': + dontFork = RunInForeground(); + + break; + case 'D': + dontFork = RunInForeground(); + + debugOptions |= kRunServerDebugDisplay_On; + + if (debugLevel == 0) + debugLevel = 1; + + if (statsUpdateInterval == 0) + statsUpdateInterval = 3; + + break; + case 'Z': + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + debugLevel = (UInt32) ::atoi(optarg); + + break; + case 'f': + theXMLFilePath = DEFAULTPATHS_ETC_DIR "streamingserver.xml"; + break; + case 'p': + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + thePort = ::atoi(optarg); + break; + case 'S': + dontFork = RunInForeground(); + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + statsUpdateInterval = ::atoi(optarg); + break; + case 'c': + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + theXMLFilePath = optarg; + break; + case 'o': + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + theConfigFilePath = optarg; + break; + case 'x': + theXMLPrefsExist = false; // Force us to generate a new XML prefs file + theInitialState = qtssShuttingDownState; + dontFork = true; + break; + case 'I': + theInitialState = qtssIdleState; + break; + case 'h': + usage(); + ::exit(0); + default: + break; + } + } + + + // Check port + if (thePort < 0 || thePort > 65535) + { + qtss_printf("Invalid port value = %d max value = 65535\n",thePort); + exit (-1); + } + + // Check expiration date + QTSSExpirationDate::PrintExpirationDate(); + if (QTSSExpirationDate::IsSoftwareExpired()) + { + qtss_printf("Streaming Server has expired\n"); + ::exit(0); + } + + + XMLPrefsParser theXMLParser(theXMLFilePath); + + // + // Check to see if the XML file exists as a directory. If it does, + // just bail because we do not want to overwrite a directory + if (theXMLParser.DoesFileExistAsDirectory()) + { + qtss_printf("Directory located at location where streaming server prefs file should be.\n"); + exit(-1); + } + + // + // Check to see if we can write to the file + if (!theXMLParser.CanWriteFile()) + { + qtss_printf("Cannot write to the streaming server prefs file.\n"); + exit(-1); + } + + // If we aren't forced to create a new XML prefs file, whether + // we do or not depends solely on whether the XML prefs file exists currently. + if (theXMLPrefsExist) + theXMLPrefsExist = theXMLParser.DoesFileExist(); + + if (!theXMLPrefsExist) + { + + // + // The XML prefs file doesn't exist, so let's create an old-style + // prefs source in order to generate a fresh XML prefs file. + + if (theConfigFilePath != NULL) + { + FilePrefsSource* filePrefsSource = new FilePrefsSource(true); // Allow dups + + if ( filePrefsSource->InitFromConfigFile(theConfigFilePath) ) + { + qtss_printf("Generating a new prefs file at %s\n", theXMLFilePath); + } + + if (GenerateAllXMLPrefs(filePrefsSource, &theXMLParser)) + { + qtss_printf("Fatal Error: Could not create new prefs file at: %s. (%d)\n", theXMLFilePath, OSThread::GetErrno()); + ::exit(-1); + } + } + } + + + // + // Parse the configs from the XML file + int xmlParseErr = theXMLParser.Parse(); + if (xmlParseErr) + { + qtss_printf("Fatal Error: Could not load configuration file at %s. (%d)\n", theXMLFilePath, OSThread::GetErrno()); + ::exit(-1); + } + + //Unless the command line option is set, fork & daemonize the process at this point + if (!dontFork) + { +#ifdef __sgi__ + // for some reason, this method doesn't work right on IRIX 6.4 unless the first arg + // is _DF_NOFORK. if the first arg is 0 (as it should be) the result is a server + // that is essentially paralized and doesn't respond to much at all. So for now, + // leave the first arg as _DF_NOFORK +// if (_daemonize(_DF_NOFORK, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO) != 0) + if (_daemonize(0, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO) != 0) +#else + if (daemon(0,0) != 0) +#endif + { +#if DEBUG + qtss_printf("Failed to daemonize process. Error = %d\n", OSThread::GetErrno()); +#endif + exit(-1); + } + } + + //Construct a Prefs Source object to get server text messages + FilePrefsSource theMessagesSource; + theMessagesSource.InitFromConfigFile("qtssmessages.txt"); + + + int status = 0; + int pid = 0; + pid_t processID = 0; + + if ( !dontFork) // if (fork) + { + //loop until the server exits normally. If the server doesn't exit + //normally, then restart it. + // normal exit means the following + // the child quit + do // fork at least once but stop on the status conditions returned by wait or if autoStart pref is false + { + processID = fork(); + Assert(processID >= 0); + if (processID > 0) // this is the parent and we have a child + { + sChildPID = processID; + status = 0; + while (status == 0) //loop on wait until status is != 0; + { + pid =::wait(&status); + SInt8 exitStatus = (SInt8) WEXITSTATUS(status); + //qtss_printf("Child Process %d wait exited with pid=%d status=%d exit status=%d\n", processID, pid, status, exitStatus); + + if (WIFEXITED(status) && pid > 0 && status != 0) // child exited with status -2 restart or -1 don't restart + { + //qtss_printf("child exited with status=%d\n", exitStatus); + + if ( exitStatus == -1) // child couldn't run don't try again + { + qtss_printf("child exited with -1 fatal error so parent is exiting too.\n"); + exit (EXIT_FAILURE); + } + break; // restart the child + + } + + if (WIFSIGNALED(status)) // child exited on an unhandled signal (maybe a bus error or seg fault) + { + //qtss_printf("child was signalled\n"); + break; // restart the child + } + + + if (pid == -1 && status == 0) // parent woken up by a handled signal + { + //qtss_printf("handled signal continue waiting\n"); + continue; + } + + if (pid > 0 && status == 0) + { + //qtss_printf("child exited cleanly so parent is exiting\n"); + exit(EXIT_SUCCESS); + } + + //qtss_printf("child died for unknown reasons parent is exiting\n"); + exit (EXIT_FAILURE); + } + } + else if (processID == 0) // must be the child + break; + else + exit(EXIT_FAILURE); + + + //eek. If you auto-restart too fast, you might start the new one before the OS has + //cleaned up from the old one, resulting in startup errors when you create the new + //one. Waiting for a second seems to work + sleep(1); + } while (RestartServer(theXMLFilePath)); // fork again based on pref if server dies + if (processID != 0) //the parent is quitting + exit(EXIT_SUCCESS); + + + } + sChildPID = 0; + //we have to do this again for the child process, because sigaction states + //do not span multiple processes. + (void)::sigaction(SIGPIPE, &act, NULL); + (void)::sigaction(SIGHUP, &act, NULL); + (void)::sigaction(SIGINT, &act, NULL); + (void)::sigaction(SIGTERM, &act, NULL); + (void)::sigaction(SIGQUIT, &act, NULL); + +#ifdef __hpux__ + // Set Priority Type to Real Time, timeslice = 100 milliseconds. Change the timeslice upwards as needed. This keeps the server priority above the playlist broadcaster which is a time-share scheduling type. + char commandStr[64]; + qtss_sprintf(commandStr, "/usr/bin/rtprio -t -%d", (int) getpid()); +#if DEBUG + qtss_printf("setting priority to Real Time: %s\n", commandStr); +#endif + (void) ::system(commandStr); +#endif + +#ifdef __solaris__ + // Set Priority Type to Real Time, timeslice = 100 milliseconds. Change the timeslice upwards as needed. This keeps the server priority above the playlist broadcaster which is a time-share scheduling type. + char commandStr[64]; + qtss_sprintf(commandStr, "priocntl -s -c RT -t 10 -i pid %d", (int) getpid()); + (void) ::system(commandStr); +#endif + +#ifdef __MacOSX__ + (void) ::umask(S_IWGRP|S_IWOTH); // make sure files are opened with default of owner -rw-r-r- +#endif + + //This function starts, runs, and shuts down the server + if (::StartServer(&theXMLParser, &theMessagesSource, thePort, statsUpdateInterval, theInitialState, dontFork, debugLevel, debugOptions) != qtssFatalErrorState) + { ::RunServer(); + CleanPid(false); + exit (EXIT_SUCCESS); + } + else + exit(-1); //Cant start server don't try again +} diff --git a/Server.tproj/win32main.cpp b/Server.tproj/win32main.cpp new file mode 100644 index 0000000..dad0c2a --- /dev/null +++ b/Server.tproj/win32main.cpp @@ -0,0 +1,522 @@ +/* + * + * @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: win32main.cpp + + Contains: main function to drive streaming server on win32. + + +*/ + +#include "getopt.h" +#include "FilePrefsSource.h" + +#include "RunServer.h" +#include "QTSServer.h" +#include "QTSSExpirationDate.h" +#include "GenerateXMLPrefs.h" + +// +// Data +static FilePrefsSource sPrefsSource(true); // Allow dups +static XMLPrefsParser* sXMLParser = NULL; +static FilePrefsSource sMessagesSource; +static UInt16 sPort = 0; //port can be set on the command line +static int sStatsUpdateInterval = 0; +static SERVICE_STATUS_HANDLE sServiceStatusHandle = 0; +static QTSS_ServerState sInitialState = qtssRunningState; + +// +// Functions +static void ReportStatus(DWORD inCurrentState, DWORD inExitCode); +static void InstallService(char* inServiceName); +static void RemoveService(char *inServiceName); +static void RunAsService(char* inServiceName); +void WINAPI ServiceControl(DWORD); +void WINAPI ServiceMain(DWORD argc, LPTSTR *argv); + + +int main(int argc, char * argv[]) +{ + extern char* optarg; + + //First thing to do is to read command-line arguments. + int ch; + + char* theConfigFilePath = "c:\\Program Files\\Darwin Streaming Server\\streamingserver.cfg"; + char* theXMLFilePath = "c:\\Program Files\\Darwin Streaming Server\\streamingserver.xml"; + Bool16 notAService = false; + Bool16 theXMLPrefsExist = true; + Bool16 dontFork = false; + +#if _DEBUG + char* compileType = "Compile_Flags/_DEBUG; "; +#else + char* compileType = ""; +#endif + + while ((ch = getopt(argc,argv, "vdxp:o:c:irsS:I")) != EOF) // opt: means requires option + { + switch(ch) + { + case 'v': + + qtss_printf("%s/%s ( Build/%s; Platform/%s; %s%s) Built on: %s\n",QTSServerInterface::GetServerName().Ptr, + QTSServerInterface::GetServerVersion().Ptr, + QTSServerInterface::GetServerBuild().Ptr, + QTSServerInterface::GetServerPlatform().Ptr, + compileType, + QTSServerInterface::GetServerComment().Ptr, + QTSServerInterface::GetServerBuildDate().Ptr); + + qtss_printf("usage: %s [ -d | -p port | -v | -c /myconfigpath.xml | -o /myconfigpath.conf | -x | -S numseconds | -I | -h ]\n", QTSServerInterface::GetServerName().Ptr); + qtss_printf("-d: Don't run as a Win32 Service\n"); + qtss_printf("-p XXX: Specify the default RTSP listening port of the server\n"); + qtss_printf("-c c:\\myconfigpath.xml: Specify a config file path\n"); + qtss_printf("-o c:\\myconfigpath.conf: Specify a DSS 1.x / 2.x config file path\n"); + qtss_printf("-x: Force create new .xml config file from 1.x / 2.x config\n"); + qtss_printf("-i: Install the Darwin Streaming Server service\n"); + qtss_printf("-r: Remove the Darwin Streaming Server service\n"); + qtss_printf("-s: Start the Darwin Streaming Server service\n"); + qtss_printf("-S n: Display server stats in the console every \"n\" seconds\n"); + qtss_printf("-I: Start the server in the idle state\n"); + ::exit(0); + case 'd': + notAService = true; + break; + case 'p': + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + sPort = ::atoi(optarg); + break; + case 'c': + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + theXMLFilePath = optarg; + break; + case 'S': + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + sStatsUpdateInterval = ::atoi(optarg); + break; + case 'o': + Assert(optarg != NULL);// this means we didn't declare getopt options correctly or there is a bug in getopt. + theConfigFilePath = optarg; + break; + case 'x': + theXMLPrefsExist = false; // Force us to generate a new XML prefs file + break; + case 'i': + qtss_printf("Installing the Darwin Streaming Server service...\n"); + ::InstallService("Darwin Streaming Server"); + qtss_printf("Starting the Darwin Streaming Server service...\n"); + ::RunAsService("Darwin Streaming Server"); + ::exit(0); + break; + case 'r': + qtss_printf("Removing the Darwin Streaming Server service...\n"); + ::RemoveService("Darwin Streaming Server"); + ::exit(0); + case 's': + qtss_printf("Starting the Darwin Streaming Server service...\n"); + ::RunAsService("Darwin Streaming Server"); + ::exit(0); + case 'I': + sInitialState = qtssIdleState; + break; + default: + break; + } + } + + // + // Check expiration date + + QTSSExpirationDate::PrintExpirationDate(); + if (QTSSExpirationDate::IsSoftwareExpired()) + { + qtss_printf("Streaming Server has expired\n"); + ::exit(0); + } + + // + // Create an XML prefs parser object using the specified path + sXMLParser = new XMLPrefsParser(theXMLFilePath); + + // + // Check to see if the XML file exists as a directory. If it does, + // just bail because we do not want to overwrite a directory + if (sXMLParser->DoesFileExistAsDirectory()) + { + qtss_printf("Directory located at location where streaming server prefs file should be.\n"); + ::exit(0); + } + + if (!sXMLParser->CanWriteFile()) + { + qtss_printf("Cannot write to the streaming server prefs file.\n"); + ::exit(0); + } + + // If we aren't forced to create a new XML prefs file, whether + // we do or not depends solely on whether the XML prefs file exists currently. + if (theXMLPrefsExist) + theXMLPrefsExist = sXMLParser->DoesFileExist(); + + if (!theXMLPrefsExist) + { + // + //Construct a Prefs Source object to get server preferences + + int prefsErr = sPrefsSource.InitFromConfigFile(theConfigFilePath); + if ( prefsErr ) + qtss_printf("Could not load configuration file at %s.\n Generating a new prefs file at %s\n", theConfigFilePath, theXMLFilePath); + + // + // Generate a brand-new XML prefs file out of the old prefs + int xmlGenerateErr = GenerateAllXMLPrefs(&sPrefsSource, sXMLParser); + if (xmlGenerateErr) + { + qtss_printf("Fatal Error: Could not create new prefs file at: %s. (%d)\n", theConfigFilePath, OSThread::GetErrno()); + ::exit(-1); + } + } + + // + // Parse the configs from the XML file + int xmlParseErr = sXMLParser->Parse(); + if (xmlParseErr) + { + qtss_printf("Fatal Error: Could not load configuration file at %s. (%d)\n", theXMLFilePath, OSThread::GetErrno()); + ::exit(-1); + } + + // + // Construct a messages source object + sMessagesSource.InitFromConfigFile("qtssmessages.txt"); + + // + // Start Win32 DLLs + WORD wsVersion = MAKEWORD(1, 1); + WSADATA wsData; + (void)::WSAStartup(wsVersion, &wsData); + + if (notAService) + { + // If we're running off the command-line, don't do the service initiation crap. + ::StartServer(sXMLParser, &sMessagesSource, sPort, sStatsUpdateInterval, sInitialState, false,0, kRunServerDebug_Off); // No stats update interval for now + ::RunServer(); + ::exit(0); + } + + SERVICE_TABLE_ENTRY dispatchTable[] = + { + { "", ServiceMain }, + { NULL, NULL } + }; + + // + // In case someone runs the server improperly, print out a friendly message. + qtss_printf("Darwin Streaming Server must either be started from the DOS Console\n"); + qtss_printf("using the -d command-line option, or using the Service Control Manager\n\n"); + qtss_printf("Waiting for the Service Control Manager to start Darwin Streaming Server...\n"); + BOOL theErr = ::StartServiceCtrlDispatcher(dispatchTable); + if (!theErr) + { + qtss_printf("Fatal Error: Couldn't start Service\n"); + ::exit(-1); + } + + return (0); +} + + +void __stdcall ServiceMain(DWORD /*argc*/, LPTSTR *argv) +{ + char* theServerName = argv[0]; + + sServiceStatusHandle = ::RegisterServiceCtrlHandler( theServerName, &ServiceControl ); + if (sServiceStatusHandle == 0) + { + qtss_printf("Failure registering service handler"); + return; + } + + // + // Report our status + ::ReportStatus( SERVICE_START_PENDING, NO_ERROR ); + + + // + // Start & Run the server - no stats update interval for now + if (::StartServer(sXMLParser, &sMessagesSource, sPort, sStatsUpdateInterval, sInitialState, false,0, kRunServerDebug_Off) != qtssFatalErrorState) + { + ::ReportStatus( SERVICE_RUNNING, NO_ERROR ); + ::RunServer(); // This function won't return until the server has died + + // + // Ok, server is done... + ::ReportStatus( SERVICE_STOPPED, NO_ERROR ); + } + else + ::ReportStatus( SERVICE_STOPPED, ERROR_BAD_COMMAND ); // I dunno... report some error + +} + +void WINAPI ServiceControl(DWORD inControlCode) +{ + QTSS_ServerState theState; + QTSServerInterface* theServer = QTSServerInterface::GetServer(); + DWORD theStatusReport = SERVICE_START_PENDING; + + if (theServer != NULL) + theState = theServer->GetServerState(); + else + theState = qtssStartingUpState; + + switch(inControlCode) + { + // Stop the service. + // + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + { + if (theState == qtssStartingUpState) + break; + + // + // Signal the server to shut down. + theState = qtssShuttingDownState; + if (theServer != NULL) + theServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState)); + break; + } + case SERVICE_CONTROL_PAUSE: + { + if (theState != qtssRunningState) + break; + + // + // Signal the server to refuse new connections. + theState = qtssRefusingConnectionsState; + if (theServer != NULL) + theServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState)); + break; + } + case SERVICE_CONTROL_CONTINUE: + { + if (theState != qtssRefusingConnectionsState) + break; + + // + // Signal the server to refuse new connections. + theState = qtssRefusingConnectionsState; + if (theServer != NULL) + theServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState)); + break; + } + case SERVICE_CONTROL_INTERROGATE: + break; // Just update our status + + default: + break; + } + + if (theServer != NULL) + { + theState = theServer->GetServerState(); + + // + // Convert a QTSS state to a Win32 Service state + switch (theState) + { + case qtssStartingUpState: theStatusReport = SERVICE_START_PENDING; break; + case qtssRunningState: theStatusReport = SERVICE_RUNNING; break; + case qtssRefusingConnectionsState: theStatusReport = SERVICE_PAUSED; break; + case qtssFatalErrorState: theStatusReport = SERVICE_STOP_PENDING; break; + case qtssShuttingDownState: theStatusReport = SERVICE_STOP_PENDING; break; + default: theStatusReport = SERVICE_RUNNING; break; + } + } + else + theStatusReport = SERVICE_START_PENDING; + + qtss_printf("Reporting status from ServiceControl function\n"); + ::ReportStatus(theStatusReport, NO_ERROR); +} + +void ReportStatus(DWORD inCurrentState, DWORD inExitCode) +{ + static Bool16 sFirstTime = 1; + static UInt32 sCheckpoint = 0; + static SERVICE_STATUS sStatus; + + if(sFirstTime) + { + sFirstTime = false; + + // + // Setup the status structure + sStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + sStatus.dwCurrentState = SERVICE_START_PENDING; + //sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN; + sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + sStatus.dwWin32ExitCode = 0; + sStatus.dwServiceSpecificExitCode = 0; + sStatus.dwCheckPoint = 0; + sStatus.dwWaitHint = 0; + } + + if (sStatus.dwCurrentState == SERVICE_START_PENDING) + sStatus.dwCheckPoint = ++sCheckpoint; + else + sStatus.dwCheckPoint = 0; + + sStatus.dwCurrentState = inCurrentState; + sStatus.dwServiceSpecificExitCode = inExitCode; + BOOL theErr = SetServiceStatus(sServiceStatusHandle, &sStatus); + if (theErr == 0) + { + DWORD theerrvalue = ::GetLastError(); + } +} + +void RunAsService(char* inServiceName) +{ + SC_HANDLE theService; + SC_HANDLE theSCManager; + + theSCManager = ::OpenSCManager( + NULL, // machine (NULL == local) + NULL, // database (NULL == default) + SC_MANAGER_ALL_ACCESS // access required + ); + if (!theSCManager) + return; + + theService = ::OpenService( + theSCManager, // SCManager database + inServiceName, // name of service + SERVICE_ALL_ACCESS ); + + SERVICE_STATUS lpServiceStatus; + + if (theService) + { const SInt32 kNotRunning = 1062; + Bool16 stopped = ::ControlService(theService, SERVICE_CONTROL_STOP, &lpServiceStatus); + if(!stopped && ( (SInt32) ::GetLastError() != kNotRunning) ) + qtss_printf("Stopping Service Error: %d\n", ::GetLastError()); + + Bool16 started = ::StartService(theService, 0, NULL); + if(!started) + qtss_printf("Starting Service Error: %d\n", ::GetLastError()); + + ::CloseServiceHandle(theService); + } + + ::CloseServiceHandle(theSCManager); +} + + +void InstallService(char* inServiceName) +{ + SC_HANDLE theService; + SC_HANDLE theSCManager; + + TCHAR thePath[512]; + TCHAR theQuotedPath[522]; + + BOOL theErr = ::GetModuleFileName( NULL, thePath, 512 ); + if (!theErr) + return; + + qtss_sprintf(theQuotedPath, "\"%s\"", thePath); + + theSCManager = ::OpenSCManager( + NULL, // machine (NULL == local) + NULL, // database (NULL == default) + SC_MANAGER_ALL_ACCESS // access required + ); + if (!theSCManager) + { + qtss_printf("Failed to install Darwin Streaming Server Service\n"); + return; + } + + theService = CreateService( + theSCManager, // SCManager database + inServiceName, // name of service + inServiceName, // name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + theQuotedPath, // service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // dependencies + NULL, // LocalSystem account + NULL); // no password + + if (theService) + { + ::CloseServiceHandle(theService); + qtss_printf("Installed Darwin Streaming Server Service\n"); + } + else + qtss_printf("Failed to install Darwin Streaming Server Service\n"); + + ::CloseServiceHandle(theSCManager); +} + +void RemoveService(char *inServiceName) +{ + SC_HANDLE theSCManager; + SC_HANDLE theService; + + theSCManager = ::OpenSCManager( + NULL, // machine (NULL == local) + NULL, // database (NULL == default) + SC_MANAGER_ALL_ACCESS // access required + ); + if (!theSCManager) + { + qtss_printf("Failed to remove Darwin Streaming Server Service\n"); + return; + } + + theService = ::OpenService(theSCManager, inServiceName, SERVICE_ALL_ACCESS); + if (theService != NULL) + { + Bool16 stopped = ::ControlService(theService, SERVICE_CONTROL_STOP, NULL); + if(!stopped) + qtss_printf("Stopping Service Error: %d\n", ::GetLastError()); + + (void)::DeleteService(theService); + ::CloseServiceHandle(theService); + qtss_printf("Removed Darwin Streaming Server Service\n"); + } + else + qtss_printf("Failed to remove Darwin Streaming Server Service\n"); + + ::CloseServiceHandle(theSCManager); +} diff --git a/StreamingLoadTool/Nokia_N90.conf b/StreamingLoadTool/Nokia_N90.conf new file mode 100644 index 0000000..a6f2cc5 --- /dev/null +++ b/StreamingLoadTool/Nokia_N90.conf @@ -0,0 +1,127 @@ +# This is a StreamingLoadTool config file + +#player user agent name +player QTS + +# Use the "clienttype" directive to specify whether StreamingLoadTool should make +# RTSP / UDP connections or RTSP / HTTP connections or . Say "http" for +# the latter, "udp" for the former. Say "reliableudp" for reliable UDP. +# Say "tcp" for straight interleaved RTSP / RTP +clienttype udp + +# If doing RTSP / HTTP, set droppost to "yes" if you would like StreamingLoadTool +# to drop the POST half of each RTSP / HTTP connection after sending the +# PLAY. "yes" best emulates the "real" client behavior. +droppost yes + +# Set this to the # of concurrent clients you would like StreamingLoadTool to maintain(default = 1) +# When not running forever, this is also the number of clients to load before exiting +concurrentclients 1 + +# Specify a connection port for each connection(default 554) +port 554 + +# Specify a proxy IP address in dotted-decimal form. If 0, StreamingLoadTool +# will not use a proxy to connect +proxyip 0 + +# Client window (size of UDP socket buffers). Default = 32768 +# For reliable UDP, this affects packet loss and the server's retransmission algorithm +clientwindow 32768 + +# StreamingLoadTool should send a TEARDOWN after streaming for this # of seconds +movielength 60 + +# If runforever is set to "no", StreamingLoadTool will quit after finishing the +# list of URLs provided below. If "yes" StreamingLoadTool will loop forever. (Default = no) +runforever no + +# Each instance of StreamingLoadTool must have a unique httpcookie value. This +# can also be specified on the command-line (see usage by doing StreamingLoadTool -v) (Default = 1) +httpcookie 1 + +# Set to "yes" if you would like StreamingLoadTool to generate a connection log +shouldlog yes + +# Append junk data after each DESCRIBE request +appendjunk no + +# Location to place the connection log +logpath streamingloadtool.log + +# Interval in milliseconds between attempts to read media data. For acking +# udp clients, this is also the interval between acks. (Default = 50 milliseconds) +readinterval 10 + +# how late should packets be allowed to be sent? Value is in seconds. +# 0 = no late tolerance will be specified at all +latetolerance 0 + +# The "Packet-Range" header is used as an alternative to the standard "Range" +# header on a Play request for specifying a range of packets. Leave +# this line blank to issue a standard play, specify a packet range header +# here to send that instead of standard Range header. +packetplayheader + +# The overbuffer window size is the number of K bytes the server can send +# ahead of time. This applies to all transports except "udp". +overbufferwindowsize 5192 + +# Set this to be the value of the x-RTP-Meta-Info header sent to the server. +# If it is empty, no x-RTP-Meta-Info header will be sent. Otherwise, specify +# the fields you would like to receive +# rtpmetainfo tt;ft;pn;fd;md;sq + +# Set this to be the speed you want the streams at (1 = normal speed, 2 = 2x normal speed, etc; float) +speed 1 + +# Enable this to have StreamingLoadTool randomly thumb around +randomthumb no + +# Set sendoptions "yes" to send an OPTIONS request before executing each DESCRIBE request +sendoptions no + +# Set requestrandomdata "yes" to send an OPTIONS request with a random data request header before executing each DESCRIBE request +requestrandomdata no + +# How many random bytes the server should send in the body of the OPTIONS response +randomdatasize 0 + +# How often should the client send RTCP messages in milliseconds. (Default = 5000; ACK's in reliableudp are sent ASAP) +rtcpinterval 1000 + +# List of rtsp URLs for StreamingLoadTool to execute +url rtsp://foo.bar.com/sample.mov + +# List of users:passwords for authentication (format: user:password) +user user0:pass0 + +#The advertised bandwidth in bps (default: not sent) +bandwidth 31235 + +#The player buffer space per stream in bytes. Default of 0 is unlimited +buffer 15000 + +#target delay in milliseconds. Default 3000. Use 0 to turn it off +#This is the desired amount of playback time to keep in the buffer. +delay 10000 + +#Start play time delay; expressed as a fraction of the target delay. This is how much data there should be in the buffer before +#the media starts playing. Use 0.0 to start playing immediately, and 1.0 to start playing when the target delay has been met. Default = 5.0 +startdelay 0.5 + +#Turn on 3GPP features and headers? 3GPP should be not be enabled on reliableudp transport; default = no +enable3GPP yes + +# The advertised guarenteed bit rate(GBW), maximum bit rate(MBW), and maximum transfer delay(MTD) for the wireless link +# Units are in kilobits per seconds and milliseconds. +# Default = 0 = no specified value +#GBW 32 +#MBW 128 +#MTD 2000 + +# Enable a forced PlayoutDelay value for 3GPP RTCP packets (The value defined for the playoutDelay preference will be sent) +enableForcePlayoutDelay no + +# PlayoutDelay value to use if enableForcePlayoutDelay is enabled +playoutDelay 65535 diff --git a/StreamingLoadTool/StreamingLoadTool.cpp b/StreamingLoadTool/StreamingLoadTool.cpp new file mode 100644 index 0000000..e8e1ed7 --- /dev/null +++ b/StreamingLoadTool/StreamingLoadTool.cpp @@ -0,0 +1,1200 @@ +/* + * + * @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: StreamingLoadTool.cpp + + Contains: Tool that simulates streaming movie load +*/ + +#include +#include +#include +#include +#include +#include + +#ifndef kVersionString +#include "../revision.h" +#endif +#ifndef __Win32__ +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "ClientSession.h" +#include "FilePrefsSource.h" +#include "OSMemory.h" +#include "OSArrayObjectDeleter.h" +#include "SocketUtils.h" +#include "StringFormatter.h" +#include "Socket.h" +#include "OS.h" +#include "Task.h" +#include "TimeoutTask.h" +#include "SVector.h" +#ifndef __MacOSX__ +#include "getopt.h" +#include "../revision.h" +#endif + +#define STREAMINGLOADTOOL_DEBUG 0 +#define PACKETADDSIZE 28 // IP headers = 20 + UDP headers = 8 + +// +// Static data +static UInt32 sConnectionsThatErrored = 0; +static UInt32 sFailedConnections = 0; +static UInt32 sSuccessfulConnections = 0; +static FILE* sLog = NULL; + +static ClientSession** sClientSessionArray = NULL; +static UInt32 sNumClients = 1; +static Bool16 sNumClientsIsSpecified = false; +static Bool16 sGotSigInt = false; +static Bool16 sQuitNow = false; +static SInt64 sSigIntTime = 0; + +static UInt64 sTotalBytesReceived = 0; +static UInt64 sTotalPacketsReceived = 0; +static UInt64 sTotalPacketsLost = 0; +static UInt64 sTotalOutOfOrder = 0; +static UInt64 sTotalOutOfBound = 0; +static UInt64 sTotalDuplicates = 0; +static UInt64 sTotalNumAcks = 0; +static UInt64 sTotalMalformed = 0; +static UInt64 sTotalLatePackets; +static UInt64 sTotalBufferOverflowedPackets; +static Bool16 sEnable3GPP = false; + + +int main(int argc, char *argv[]); + +// +// Helper functions +char* GetClientTypeDescription(ClientSession::ClientType inClientType); +void DoDNSLookup(SVector &theURLlist, SVector &ioIPAddrs); +void RecordClientInfoBeforeDeath(ClientSession* inSession); +char* GetDeathReasonDescription(UInt32 inDeathReason); +char* GetPayloadDescription(QTSS_RTPPayloadType inPayload); +void CheckForStreamingLoadToolDotMov(SVector &ioIPAddrArray, SVector &theURLlist, UInt16 inPort, SVector &userList, SVector &passwordList, UInt32 verboseLevel); +UInt32 CalcStartTime(Bool16 inRandomThumb, UInt32 inMovieLength); +extern char* optarg; + +#ifndef __Win32__ +void sigcatcher(int sig, int /*sinfo*/, struct sigcontext* /*sctxt*/); + +void sigcatcher(int sig, int /*sinfo*/, struct sigcontext* /*sctxt*/) +{ + //printf("sigcatcher =%d\n", sig); + + if ((sig == SIGINT) || (sig == SIGTERM)) + { // TheTime check and sQuitNow flag tests are needed test with Linux and OSX. + if (sGotSigInt && (OS::Milliseconds() > sSigIntTime + 500) ) + { if (!sQuitNow)// print only once + printf("Force quitting\n"); + sQuitNow = true; + } + else + { + sSigIntTime = OS::Milliseconds(); + sGotSigInt = true; + } + } +} +#endif + +int main(int argc, char *argv[]) +{ +#ifndef __Win32__ + struct sigaction act; + +#if defined(sun) || defined(i386) || defined (__MacOSX__) || defined(__powerpc__) || defined (__osf__) || defined (__sgi_cc__) || defined (__hpux__) || defined(__linux__) + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = (void(*)(int))&sigcatcher; +#elif defined(__sgi__) + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = (void(*)(...))&sigcatcher; +#else + act.sa_mask = 0; + act.sa_flags = 0; + act.sa_handler = (void(*)(...))&sigcatcher; +#endif + (void)::sigaction(SIGPIPE, &act, NULL); + (void)::sigaction(SIGINT, &act, NULL); + (void)::sigaction(SIGTERM, &act, NULL); + +#if __MacOSX__ || __solaris__ + //grow our pool of file descriptors to the max! + struct rlimit rl; + + rl.rlim_cur = Socket::kMaxNumSockets; + rl.rlim_max = Socket::kMaxNumSockets; + + setrlimit (RLIMIT_NOFILE, &rl); +#endif +#else + // + // Start Win32 DLLs + WORD wsVersion = MAKEWORD(1, 1); + WSADATA wsData; + (void)::WSAStartup(wsVersion, &wsData); +#endif + +#ifdef __Win32__ + char* configFilePath = "streamingloadtool.cfg"; +#else + char* configFilePath = "streamingloadtool.conf"; +#endif + Bool16 configFilePathIsSpecified = false; + Bool16 dropPost = false; + ClientSession::ClientType theClientType = ClientSession::kRTSPUDPClientType; + Bool16 theClientTypeIsSpecified = false; + UInt16 thePort = 554; + Bool16 thePortIsSpecified = false; + UInt32 theMovieLength = 60; + Bool16 theMovieLengthIsSpecified = false; + Bool16 runForever = false; + UInt32 theHTTPCookie = 1; + Bool16 shouldLog = false; + char* logPath = "streamingloadtool.log"; + UInt32 proxyIP = 0; + Bool16 appendJunk = false; + UInt32 theReadInterval = 50; + UInt32 sockRcvBuf = 32768; + Float32 lateTolerance = 0; + char* rtpMetaInfo = NULL; + Float32 speed = 1; + UInt32 verboseLevel = 0; + char* packetPlayHeader = NULL; + UInt32 overbufferwindowInK = 0; + Bool16 randomThumb = false; + Bool16 sendOptions = false; + Bool16 requestRandomData = false; + SInt32 randomDataSize = 0; + UInt32 rtcpInterval = 5000; + UInt32 bandwidth = 0; + UInt32 guarenteedBitRate = 0; + UInt32 maxBitRate = 0; + UInt32 maxTransferDelay = 0; + Bool16 enableForcePlayoutDelay = false; + UInt32 playoutDelay = 0; + UInt32 bufferSpace = 100000; + UInt32 delayTime = 10000; + Float32 startDelayFrac = 0.5; + // + // Set up our User Agent string for the RTSP client + char theUserAgentStr[128]; + ::sprintf(theUserAgentStr, "StreamingLoadTool-%s",kVersionString); + RTSPClient::SetUserAgentStr(theUserAgentStr); + + SVectoruserList; + SVectorpasswordList; + SVector theURLlist; + + char* controlID = NULL; + char ch = 0; + // + // Read our command line options + while( (ch = getopt(argc, argv, "vf:c:i:V:u:t:p:n:l:s:")) != -1 ) + { + switch( ch ) + { + case '?': + case 'v': + printf("StreamingLoadTool %s, built on %s, %s.\n", kVersionString, __DATE__ , __TIME__); + printf("Usage: StreamingLoadTool [-u theURL] [-t transport] [-n port] [-l length] [-i urlid] [-f path] [-c #] [-V #]\n"); + printf("-v: Print this message\n"); + printf("-f: Config file to use. Defaults to streamingloadtool.conf if the URL (-u) is not specified.\n"); + printf("-c: HTTP cookie to use. Overrides what is in config file\n"); + printf("-V: debug verbose level\n"); + printf("-i: RTSP stream URL id (i.e. trackID or streamID etc.). Default is -i trackID\n"); + printf("-u: the URL (format: rtsp://etc/movie.mov).\n"); + printf("-t: the transport type: udp, reliableudp, tcp, or http.\n"); + printf("-p: the port.\n"); + printf("-n: the number of clients.\n"); + printf("-l: the movie duration.\n"); + printf("-s: the user and password format: (user:password).\n"); + + exit(0); + break; + case 'f': + configFilePathIsSpecified = true; + configFilePath = optarg; + break; + case 'i': + controlID = optarg; + break; + case 'c': + theHTTPCookie = atoi(optarg); + break; + case 'V': + verboseLevel = atoi(optarg); + break; + case 'u': + theURLlist.push_back(optarg); + break; + case 't': + theClientTypeIsSpecified = true; + if (::strcmp("http", optarg) == 0) + theClientType = ClientSession::kRTSPHTTPClientType; + else if (::strcmp("reliableudp", optarg) == 0) + theClientType = ClientSession::kRTSPReliableUDPClientType; + else if (::strcmp("tcp", optarg) == 0) + theClientType = ClientSession::kRTSPTCPClientType; + else if (::strcmp("udp", optarg) == 0) + theClientType = ClientSession::kRTSPUDPClientType; + else if (::strcmp("3gudp", optarg) == 0) + { theClientType = ClientSession::kRTSPUDPClientType; + sEnable3GPP = true; + } + else + { + printf("Invalid transport type: %s\n", optarg); + exit(0); + } + break; + case 'p': + thePortIsSpecified = true; + thePort = atoi(optarg); + break; + case 'n': + sNumClientsIsSpecified = true; + sNumClients = atoi(optarg); + break; + case 'l': + theMovieLengthIsSpecified = true; + theMovieLength = atoi(optarg); + break; + case 's' : + { + char *str = ::strdup(optarg); + char *user = str; + char *password = NULL; + char *colonChar = ::strchr(str, ':'); + printf("optarg=%s\n",str); + if (colonChar != NULL) + { + *colonChar = '\0'; + password = colonChar + 1; + } + if ((user != NULL) && (password != NULL) ) + { + userList.push_back(user); + passwordList.push_back(password); + } + break; + } + + } + } + + FilePrefsSource theFileParser(true); + // Get settings from the file + Bool16 configFileError = theFileParser.InitFromConfigFile(configFilePath); + if (configFileError && theURLlist.empty()) + { + printf("Couldn't find StreamingLoadTool config file at: %s\n", configFilePath); + ::exit(-1); + } + else if (!configFileError) + { + for (UInt32 x = 0; true; x++) + { + char* theValue = theFileParser.GetValueAtIndex(x); + char* theKey = theFileParser.GetKeyAtIndex(x); + + if (theKey == NULL) + break; + + if (theValue != NULL) + { + if (::strcmp("clienttype", theKey) == 0 && !theClientTypeIsSpecified) + { + if (::strcmp("http", theValue) == 0) + theClientType = ClientSession::kRTSPHTTPClientType; + else if (::strcmp("reliableudp", theValue) == 0) + theClientType = ClientSession::kRTSPReliableUDPClientType; + else if (::strcmp("tcp", theValue) == 0) + theClientType = ClientSession::kRTSPTCPClientType; + else if (::strcmp("udp", theValue) == 0) + theClientType = ClientSession::kRTSPUDPClientType; + else if (::strcmp("3gudp", theValue) == 0) + { theClientType = ClientSession::kRTSPUDPClientType; + sEnable3GPP = true; + } + else + { + printf("Invalid transport type: %s\n", theValue); + exit(0); + } + } + else if (::strcmp("player", theKey) == 0) + { + if (theValue[0] != 0) + { + ::strncpy(theUserAgentStr, theValue, sizeof(theUserAgentStr)); + theUserAgentStr[sizeof(theUserAgentStr) -1] = 0; + RTSPClient::SetUserAgentStr(theUserAgentStr); + } + } + else if (::strcmp("droppost", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + dropPost = true; + } + else if (::strcmp("concurrentclients", theKey) == 0 && !sNumClientsIsSpecified) + { + ::sscanf(theValue, "%"_U32BITARG_"", &sNumClients); + } + else if (::strcmp("port", theKey) == 0 && !thePortIsSpecified) + { + UInt32 tempPort = 0; + ::sscanf(theValue, "%"_U32BITARG_"", &tempPort); + thePort = (UInt16)tempPort; + } + else if (::strcmp("movielength", theKey) == 0 && !theMovieLengthIsSpecified) + { + ::sscanf(theValue, "%"_U32BITARG_"", &theMovieLength); + } + else if (::strcmp("runforever", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + runForever = true; + } + else if (::strcmp("shouldlog", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + shouldLog = true; + } + else if (::strcmp("appendjunk", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + appendJunk = true; + } + else if (::strcmp("logpath", theKey) == 0) + { + logPath = theValue; + } + else if (::strcmp("proxyip", theKey) == 0) + { + proxyIP = SocketUtils::ConvertStringToAddr(theValue); + } + else if (::strcmp("clientwindow", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_"", &sockRcvBuf); + } + else if (::strcmp("httpcookie", theKey) == 0) + { + if (theHTTPCookie == 1) + { + // Ignore if set by command line + ::sscanf(theValue, "%"_U32BITARG_"", &theHTTPCookie); + theHTTPCookie *= 1000000; + } + } + else if (::strcmp("readinterval", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_"", &theReadInterval); + } + else if (::strcmp("latetolerance", theKey) == 0) + { + ::sscanf(theValue, "%f", &lateTolerance); + } + else if (::strcmp("rtpmetainfo", theKey) == 0) + { + if (::strlen(theValue) > 0) + rtpMetaInfo = theValue; + } + else if (::strcmp("speed", theKey) == 0) + { + ::sscanf(theValue, "%f", &speed); + } + else if (::strcmp("packetplayheader", theKey) == 0) + { + if (::strlen(theValue) > 0) + packetPlayHeader = theValue; + } + else if (::strcmp("overbufferwindowsize", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_"", &overbufferwindowInK); + } + else if (::strcmp("randomthumb", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + randomThumb = true; + } + + else if (::strcmp("sendoptions", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + sendOptions = true; + } + else if (::strcmp("requestrandomdata", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + requestRandomData = true; + } + else if (::strcmp("randomdatasize", theKey) == 0) + { + ::sscanf(theValue, "%"_S32BITARG_"", &randomDataSize); + } + else if (::strcmp("rtcpinterval", theKey) == 0) + { + ::sscanf(theValue, "%"_S32BITARG_"", &rtcpInterval); + } + else if (::strcmp("url", theKey) == 0) + { + theURLlist.push_back(theValue); + } + else if (::strcmp("user", theKey) == 0) + { + char *str = ::strdup(theValue); + char *user = str; + char *password = NULL; + char *colonChar = ::strchr(str, ':'); + if (colonChar != NULL) + { + *colonChar = '\0'; + password = colonChar + 1; + } + userList.push_back(user); + passwordList.push_back(password); + } + else if (::strcmp("bandwidth", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_, &bandwidth); + } + else if (::strcmp("enable3GPP", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + sEnable3GPP = true; + } + else if (::strcmp("GBW", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_, &guarenteedBitRate); + } + else if (::strcmp("MBW", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_, &maxBitRate); + } + else if (::strcmp("MTD", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_, &maxTransferDelay); + } + else if (::strcmp("enableForcePlayoutDelay", theKey) == 0) + { + if (::strcmp("yes", theValue) == 0) + enableForcePlayoutDelay = true; + } + else if (::strcmp("playoutDelay", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_, &playoutDelay); + } + else if (::strcmp("buffer", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_"", &bufferSpace); + } + else if (::strcmp("delay", theKey) == 0) + { + ::sscanf(theValue, "%"_U32BITARG_"", &delayTime); + } + else if (::strcmp("startdelay", theKey) == 0) + { + ::sscanf(theValue, "%f", &startDelayFrac); + if(startDelayFrac < 0) + startDelayFrac = -1.0; + } + else + printf("Found bad directive in StreamingLoadTool config file: %s\n", theKey); + } + } + } + + if(theURLlist.empty()) + { + printf("Didn't find any URLs in StreamingLoadTool config file.\n"); + exit(-1); + } + + // + // Figure out what type of clients we are going to run + if ((theClientType == ClientSession::kRTSPHTTPClientType) && dropPost) + theClientType = ClientSession::kRTSPHTTPDropPostClientType; + + // Do IP lookups on all the URLs + SVector theIPAddrArray; + theIPAddrArray.resize(theURLlist.size(), 0); + ::DoDNSLookup(theURLlist, theIPAddrArray); + + + // Final setup before running the show + OS::Initialize(); + OSThread::Initialize(); + OSMemory::SetMemoryError(ENOMEM); + Socket::Initialize(); + SocketUtils::Initialize(); + +#if !MACOSXEVENTQUEUE + ::select_startevents();//initialize the select() implementation of the event queue +#endif + TaskThreadPool::AddThreads(1); + TimeoutTask::Initialize(); + Socket::StartThread(); + + //Check for the existance of a file streamingloadtool.mov before proceeding + ::CheckForStreamingLoadToolDotMov(theIPAddrArray, theURLlist, thePort, userList, passwordList, verboseLevel); + + // If user specified a proxy to connect through, use that IP address, not the IP address in the URL. + if (proxyIP != 0) + { + for (UInt32 ipAddrCounter = 0; ipAddrCounter < theURLlist.size(); ipAddrCounter++) + theIPAddrArray[ipAddrCounter] = proxyIP; + } + + // Open the log if we are logging + if (shouldLog) + sLog = ::fopen(logPath, "w"); + + sClientSessionArray = NEW ClientSession*[sNumClients]; + + UInt32 theCurURLIndex = 0; + UInt32 theCurUserIndex = 0; + for (UInt32 clientCount = 0; clientCount < sNumClients; clientCount++, theCurURLIndex = (theCurURLIndex + 1) % theURLlist.size(), theCurUserIndex++) + { + while(theIPAddrArray[theCurURLIndex] == 0) + theCurURLIndex = (theCurURLIndex + 1) % theURLlist.size(); + if (theCurUserIndex == userList.size()) + theCurUserIndex = 0; + + sClientSessionArray[clientCount] = NEW ClientSession( theIPAddrArray[theCurURLIndex], + thePort, + theURLlist[theCurURLIndex], + theClientType, // Client type + theMovieLength, // Movie length + CalcStartTime(randomThumb, theMovieLength), // Movie start time + rtcpInterval, + 0, // Options interval + theHTTPCookie++, // HTTP cookie + appendJunk, + theReadInterval, // Interval between data reads + sockRcvBuf, // socket recv buffer size + lateTolerance, // late tolerance + rtpMetaInfo, + speed, + verboseLevel, + packetPlayHeader, + overbufferwindowInK, + sendOptions, // send options request before the Describe + requestRandomData, // request random data in the options request + randomDataSize, // size of the random data to request + sEnable3GPP, + guarenteedBitRate, + maxBitRate, + maxTransferDelay, + enableForcePlayoutDelay, + playoutDelay, + bandwidth, + bufferSpace, + delayTime, + static_cast(startDelayFrac * delayTime), + controlID, + userList.empty() ? NULL : userList[theCurUserIndex], + passwordList.empty() ? NULL : passwordList[theCurUserIndex]); + +#if STREAMINGLOADTOOL_DEBUG + printf("Creating a ClientSession for URL: %s\n", theURLlist[theCurURLIndex]); +#endif + } + + // + // Now, all we have to do is loop, destroying and re-creating ClientSession objects when they die. + // Occassionally, lets print out status to show what is currently going on + Bool16 isStillActive = false; + UInt32 loopCount = 0; + do + { + if (sGotSigInt) + break; + + OSThread::Sleep(1000); + + isStillActive = false; + for (UInt32 y = 0; y < sNumClients; y++) + { + if (sClientSessionArray == NULL) + continue; //skip over NULL client sessions + + if ((loopCount & 7) == 0) // Fancy way of mod'ing by 8, so the following will execute every 8 seconds + { + // if the server has not sent me any packets in the last 8 seconds, and the at least 10 seconds has elapsed since the RTSP play, then timeout + if ( (sClientSessionArray[y] != NULL && sClientSessionArray[y]->GetSessionPacketsReceived() == 0 && sClientSessionArray[y]->GetTotalPlayTimeInMsec() > 10000) ) + { + sClientSessionArray[y]->Signal(Task::kTimeoutEvent); // Tell it to destroy itself if the Client is idle for a while + } + } + + if ( (sClientSessionArray[y] != NULL && sClientSessionArray[y]->IsDone()) ) + { + while(theIPAddrArray[theCurURLIndex] == 0) + theCurURLIndex = (theCurURLIndex + 1) % theURLlist.size(); + if (theCurUserIndex == userList.size()) + theCurUserIndex = 0; + + ::RecordClientInfoBeforeDeath(sClientSessionArray[y]); + sClientSessionArray[y]->Signal(Task::kKillEvent); // Tell it to destroy itself + sClientSessionArray[y] = NULL; + if (runForever) + { + isStillActive = true; + // If there is a new URL to fetch, kill this client and restart + sClientSessionArray[y] = NEW ClientSession( theIPAddrArray[theCurURLIndex], // IP addr + thePort, // IP port + theURLlist[theCurURLIndex], + theClientType, // Client type + theMovieLength, // Movie length + CalcStartTime(randomThumb, theMovieLength), // Movie start time + rtcpInterval, + 0, // Options interval + theHTTPCookie++, // HTTP cookie + appendJunk, + theReadInterval, // Interval between data reads + sockRcvBuf, // socket recv buffer size + lateTolerance, // late tolerance + rtpMetaInfo, + speed, + verboseLevel, + packetPlayHeader, + overbufferwindowInK, + sendOptions, // send options request before the Describe + requestRandomData, // request random data in the options request + randomDataSize, // size of the random data to request + sEnable3GPP, + guarenteedBitRate, + maxBitRate, + maxTransferDelay, + enableForcePlayoutDelay, + playoutDelay, + bandwidth, + bufferSpace, + delayTime, + (startDelayFrac < 0) ? static_cast(kUInt32_Max) : static_cast(startDelayFrac * delayTime), + controlID, + userList.empty() ? NULL : userList[theCurUserIndex], + passwordList.empty() ? NULL : passwordList[theCurUserIndex]); + +#if STREAMINGLOADTOOL_DEBUG + printf("Creating a ClientSession for URL: %s\n", theURLlist[theCurURLIndex]); +#endif + theCurURLIndex = (theCurURLIndex + 1) % theURLlist.size(); + theCurUserIndex++; + } + } + else if (sClientSessionArray[y] != NULL) //still running + isStillActive = true; + } + + if ((loopCount & 7) == 0) // Fancy way of mod'ing by 8, so the following will execute every 8 seconds + { + + if ((loopCount & 127) == 0) // Fancy way of dividing by 127, + { + // Occassionally give the user lots of info + printf("StreamingLoadTool test in progress.\n"); + printf("Config file: %s. Client type: %s. Num clients: %"_U32BITARG_".\n", + configFilePath, GetClientTypeDescription(theClientType), sNumClients); + printf("Movie length: %"_U32BITARG_". Run forever: %d. HTTP cookie: %"_U32BITARG_". Port: %d\n", + theMovieLength, runForever, theHTTPCookie, thePort); + if (shouldLog) + printf("Writing StreamingLoadTool log at: %s\n", logPath); + printf("Active Playing Attempts Success Errors Failed Bitrate\n"); + } + + UInt32 addBytes = ClientSession::GetConnectionPacketsReceived() * PACKETADDSIZE; + Float32 bitsReceived = (Float32) (ClientSession::GetConnectionBytesReceived() + addBytes) / 1000; + + bitsReceived += .5; + + printf("%5lu %6lu %8lu %6lu %6lu %6lu %9.0fk\n", + ClientSession:: GetActiveConnections (), + ClientSession:: GetPlayingConnections (), + ClientSession:: GetConnectionAttempts (), + sSuccessfulConnections, + sConnectionsThatErrored, + sFailedConnections, + bitsReceived// depends on 8 second update for bits per second + ); + + } + loopCount++; + + } while (isStillActive == true); + + if (sGotSigInt) + { + // + // If someone hits Ctrl-C, force all the clients to wrap it up + printf("Sending TEARDOWNs.\n"); + + // + // Tell all the clients to wrap it up. + for (UInt32 clientCount = 0; clientCount < sNumClients; clientCount++) + { + if (sClientSessionArray == NULL) + continue; //skip over NULL client sessions + if (sClientSessionArray[clientCount] != NULL) + sClientSessionArray[clientCount]->Signal(ClientSession::kTeardownEvent); + } + // + // Wait for the clients to complete + Bool16 isDone = false; + while (!isDone && !sQuitNow) + { + //wait until all the clients are done doing teardown + OSThread::Sleep(1000); + isDone = true; + for (UInt32 cc2 = 0; cc2 < sNumClients; cc2++) + { + if (sClientSessionArray == NULL) + continue; //skip over NULL client sessions + + if (sClientSessionArray[cc2] == NULL) + continue; + + if (!sClientSessionArray[cc2]->IsDone()) + isDone = false; + } + } + } + + // + // We're done... now go through and delete the last sessions(not really) + for (UInt32 z = 0; z < sNumClients; z++) + { + if (sClientSessionArray == NULL) + continue; //skip over NULL client sessions + + if (sClientSessionArray[z] == NULL) + continue; + + ::RecordClientInfoBeforeDeath(sClientSessionArray[z]); + } + + if (sLog != NULL) + ::fclose(sLog); + + printf("%5lu %6lu %8lu %6lu %6lu %6lu %9.0fk\n", + ClientSession:: GetActiveConnections (), + ClientSession:: GetPlayingConnections (), + ClientSession:: GetConnectionAttempts (), + sSuccessfulConnections, + sConnectionsThatErrored, + sFailedConnections, + 0.0// depends on 8 second update for bits per second + ); + + printf("StreamingLoadTool test complete. Total number of connections: %"_U32BITARG_".\n", ClientSession:: GetConnectionAttempts ()); + printf( + "Total bytes received: %"_U64BITARG_ + ". Total packets received: %"_U64BITARG_ + ". Total out of order packets: %"_U64BITARG_ + ". Total out of bound packets: %"_U64BITARG_ + ". Total ACKs sent: %"_U64BITARG_ + ". Total malformed packets: %"_U64BITARG_, + sTotalBytesReceived, + sTotalPacketsReceived, + sTotalOutOfOrder, + sTotalOutOfBound, + sTotalNumAcks, + sTotalMalformed + ); + if (sEnable3GPP) + { + printf( + ". Total 3g packets lost: %"_U64BITARG_ + ". Total 3g duplicate packets: %"_U64BITARG_ + ". Total 3g late packets: %"_U64BITARG_ + ". Total 3g buffer-overflowed packets: %"_U64BITARG_, + sTotalPacketsLost, + sTotalDuplicates, + sTotalLatePackets, + sTotalBufferOverflowedPackets + ); + + + + } + + printf(".\n"); + +} + +UInt32 CalcStartTime(Bool16 inRandomThumb, UInt32 inMovieLength) +{ + UInt32 theMovieLength = inMovieLength; + if (theMovieLength > 1) + theMovieLength--; + + if (inRandomThumb) + return ::rand() % theMovieLength; + else + return 0; +} + +void CheckForStreamingLoadToolPermission(UInt32* inIPAddrArray, UInt32 inNumURLs, UInt16 inPort) +{ + //Eventually check for the existance of a specially formatted sdp file (assuming the server blindly returns sdps) +} + +//Currently will only authenticate with the FIRST username/password if provided +void CheckForStreamingLoadToolDotMov(SVector &ioIPAddrArray, SVector &theURLlist, UInt16 inPort, SVector &userList, SVector &passwordList, UInt32 verboseLevel) +{ + Assert(ioIPAddrArray.size() == theURLlist.size()); + printf("Checking for 'streamingloadtool.mov' on the target servers\n"); + + OSArrayObjectDeleter holder = NEW UInt32[theURLlist.size() + 1]; + UInt32* uniqueIPAddrs = holder.GetObject(); + ::memset(uniqueIPAddrs, 0, sizeof(UInt32) * (theURLlist.size() + 1)); + + + for (UInt32 count = 0; count < theURLlist.size(); count++) + { + if (ioIPAddrArray[count] == 0) //skip over one's that failed DNS + continue; + + //check for duplicates + /* + Bool16 dup = false; + for (UInt32 x = 0; uniqueIPAddrs[x] != 0; x++) + { + if (uniqueIPAddrs[x] == ioIPAddrArray[count]) + { + dup = true; + break; + } + } + if (dup) + continue; + + // For tracking dups. + uniqueIPAddrs[count] = ioIPAddrArray[count]; + */ + + + // Format the URL: rtsp://xx.xx.xx.xx/streamingloadtool.mov + char theAddrBuf[50]; + StrPtrLen theAddrBufPtr(theAddrBuf, 50); + struct in_addr theAddr; + theAddr.s_addr = htonl(ioIPAddrArray[count]); + + SocketUtils::ConvertAddrToString(theAddr, &theAddrBufPtr); + + char theURLBuf[100]; + StringFormatter theFormatter(theURLBuf, 100); + + theFormatter.Put("rtsp://"); + theFormatter.Put(theAddrBufPtr); + theFormatter.Put("/streamingloadtool.mov"); + theFormatter.PutTerminator(); + + StrPtrLenDel theURL(theFormatter.GetAsCString()); + + // Make an RTSP client. We'll send a DESCRIBE to the server to check for this sucker + TCPClientSocket theSocket = TCPClientSocket(0); //blocking + + // tell the client this is the URL to use + theSocket.Set(ioIPAddrArray[count], inPort); + + RTSPClient theClient = RTSPClient(&theSocket); + theClient.SetVerboseLevel(verboseLevel); + + if(userList.size() > 0) + { + theClient.SetName(userList.back()); + theClient.SetPassword(passwordList.back()); + } + theClient.Set(theURL); + + // + // Send the DESCRIBE! Whoo hoo! + OS_Error theErr = theClient.SendDescribe(); + + while (theErr == EINPROGRESS || theErr == EAGAIN) + theErr = theClient.SendDescribe(); + if (theErr != OS_NoErr) + { + printf("##WARNING: Error connecting to %s.\n\n", theURLlist[count]); + ioIPAddrArray[count] = 0; + continue; + } + + if (theClient.GetStatus() != 200) + { + printf("##WARNING: Cannot access %s\n\n", theURL.Ptr); + ioIPAddrArray[count] = 0; + } + theClient.SendTeardown(); + } + + int addrCount = 0; + for (UInt32 x = 0; x < theURLlist.size(); x++) + { + if ( 0 != ioIPAddrArray[x]) + addrCount++ ; + } + if (addrCount == 0) + { printf("No valid destinations\n"); + exit (-1); + } + printf("Done checking for 'streamingloadtool.mov' on all servers -- %i valid URL's\n", addrCount); +} + + +void DoDNSLookup(SVector &theURLlist, SVector &ioIPAddrs) +{ + Assert(theURLlist.size() == ioIPAddrs.size()); + enum { eDNSNameSize = 128 }; + char theDNSName[eDNSNameSize + 1]; + + for (UInt32 x = 0; x < theURLlist.size(); x++) + { + // First extract the DNS name from this URL as a c-string + StrPtrLen theURL = StrPtrLen(theURLlist[x]); + StringParser theURLParser(&theURL); + StrPtrLen theDNSNamePtr; + + theURLParser.ConsumeLength(NULL, 7); // skip over rtsp:// + theURLParser.ConsumeUntil(&theDNSNamePtr, '/'); // grab the DNS name + StringParser theDNSParser(&theDNSNamePtr); + theDNSParser.ConsumeUntil(&theDNSNamePtr, ':'); // strip off the port number if any + + + if (theDNSNamePtr.Len > eDNSNameSize) + { + theDNSNamePtr.PrintStr("DSN Name Failed Lookup.\n", "\n"); + printf("The DNS name is %"_U32BITARG_" in length and is longer than the allowed %d.\n",theDNSNamePtr.Len, eDNSNameSize); + return; + } + + theDNSName[0] = 0; + ::memcpy(theDNSName, theDNSNamePtr.Ptr, theDNSNamePtr.Len); + theDNSName[theDNSNamePtr.Len] = 0; + + + ioIPAddrs[x] = 0; + + // Now pass that DNS name into gethostbyname. + struct hostent* theHostent = ::gethostbyname(theDNSName); + + if (theHostent != NULL) + ioIPAddrs[x] = ntohl(*(UInt32*)(theHostent->h_addr_list[0])); + else + ioIPAddrs[x] = SocketUtils::ConvertStringToAddr(theDNSName); + + if (ioIPAddrs[x] == 0) + { + printf("Couldn't look up host name: %s.\n", theDNSName); + //exit(-1); + } + } +} + +char* GetClientTypeDescription(ClientSession::ClientType inClientType) +{ + static char* kUDPString = "RTSP/UDP client"; + static char* kTCPString = "RTSP/TCP client"; + static char* kHTTPString = "RTSP/HTTP client"; + static char* kHTTPDropPostString = "RTSP/HTTP drop post client"; + static char* kReliableUDPString = "RTSP/ReliableUDP client"; + + switch (inClientType) + { + case ClientSession::kRTSPUDPClientType: + return kUDPString; + case ClientSession::kRTSPTCPClientType: + return kTCPString; + case ClientSession::kRTSPHTTPClientType: + return kHTTPString; + case ClientSession::kRTSPHTTPDropPostClientType: + return kHTTPDropPostString; + case ClientSession::kRTSPReliableUDPClientType: + return kReliableUDPString; + } + Assert(0); + return NULL; +} + +char* GetDeathReasonDescription(UInt32 inDeathReason) +{ + static char* kDiedNormallyString = "Completed normally"; + static char* kTeardownFailedString = "Failure: Couldn't complete TEARDOWN"; + static char* kRequestFailedString = "Failure: Failed RTSP request"; + static char* kBadSDPString = "Failure: misformatted SDP"; + static char* kSessionTimedoutString = "Failure: Couldn't connect to server(timeout)"; + static char* kConnectionFailedString = "Failure: Server refused connection"; + static char* kDiedWhilePlayingString = "Failure: Disconnected while playing"; + + switch (inDeathReason) + { + case ClientSession::kDiedNormally: + return kDiedNormallyString; + case ClientSession::kTeardownFailed: + return kTeardownFailedString; + case ClientSession::kRequestFailed: + return kRequestFailedString; + case ClientSession::kBadSDP: + return kBadSDPString; + case ClientSession::kSessionTimedout: + return kSessionTimedoutString; + case ClientSession::kConnectionFailed: + return kConnectionFailedString; + case ClientSession::kDiedWhilePlaying: + return kDiedWhilePlayingString; + } + Assert(0); + return NULL; +} + +char* GetPayloadDescription(QTSS_RTPPayloadType inPayload) +{ + static char* kSound = "Sound"; + static char* kVideo = "Video"; + static char* kUnknown = "Unknown"; + + switch (inPayload) + { + case qtssVideoPayloadType: + return kVideo; + case qtssAudioPayloadType: + return kSound; + default: + return kUnknown; + } + return NULL; +} + +void RecordClientInfoBeforeDeath(ClientSession* inSession) +{ + if (inSession->GetReasonForDying() == ClientSession::kRequestFailed) + sConnectionsThatErrored++; + else if (inSession->GetReasonForDying() != ClientSession::kDiedNormally) + sFailedConnections++; + else + sSuccessfulConnections++; + + + + + + { + UInt32 theReason = inSession->GetReasonForDying(); + in_addr theAddr; + theAddr.s_addr = htonl(inSession->GetSocket()->GetHostAddr()); + char* theAddrStr = ::inet_ntoa(theAddr); + + // + // Write a log entry for this client + if (sLog != NULL) + ::fprintf(sLog, "Client complete. IP addr = %s, URL = %s. Connection status: %s. ", + theAddrStr, + inSession->GetClient()->GetURL()->Ptr, + ::GetDeathReasonDescription(theReason)); + + if (theReason == ClientSession::kRequestFailed) + if (sLog != NULL) ::fprintf(sLog, "Failed request status: %"_U32BITARG_"", inSession->GetRequestStatus()); + + if (sLog != NULL) ::fprintf(sLog, "\n"); + + // + // If this was a successful connection, log statistics for this connection + if ((theReason == ClientSession::kDiedNormally) || (theReason == ClientSession::kTeardownFailed) || (theReason == ClientSession::kSessionTimedout)) + { + + UInt32 bytesReceived = 0; + for (UInt32 trackCount = 0; trackCount < inSession->GetSDPInfo()->GetNumStreams(); trackCount++) + { + if (sLog != NULL) ::fprintf(sLog, + "Track type: %s. Total packets received: %"_U32BITARG_ + ". Total out of order packets: %"_U32BITARG_ + ". Total out of bound packets: %"_U32BITARG_ + ". Total ACKs sent: %"_U32BITARG_ + ". Total malformed packets: %"_U32BITARG_, + ::GetPayloadDescription(inSession->GetTrackType(trackCount)), + inSession->GetNumPacketsReceived(trackCount), + inSession->GetNumPacketsOutOfOrder(trackCount), + inSession->GetNumOutOfBoundPackets(trackCount), + inSession->GetNumAcks(trackCount), + inSession->GetNumMalformedPackets(trackCount) + ); + + + if (sEnable3GPP) + { + if (sLog != NULL) ::fprintf(sLog, + ". Total 3g packets lost: %"_U32BITARG_ + ". Total 3g duplicate packets: %"_U32BITARG_ + ". Total 3g late packets: %"_U32BITARG_ + ". Total 3g rate adapt buffer-overflowed packets: %"_U32BITARG_, + inSession->Get3gNumPacketsLost(trackCount), + inSession->Get3gNumDuplicates(trackCount), + inSession->Get3gNumLatePackets(trackCount), + inSession->Get3gNumBufferOverflowedPackets(trackCount) + ); + } + + if (sLog != NULL) ::fprintf(sLog,".\n"); + + + bytesReceived += inSession->GetNumBytesReceived(trackCount); + bytesReceived += inSession->GetNumPacketsReceived(trackCount) * PACKETADDSIZE; + + sTotalBytesReceived += inSession->GetNumBytesReceived(trackCount); + sTotalPacketsReceived += inSession->GetNumPacketsReceived(trackCount); + sTotalOutOfOrder += inSession->GetNumPacketsOutOfOrder(trackCount); + sTotalOutOfBound += inSession->GetNumOutOfBoundPackets(trackCount); + sTotalNumAcks += inSession->GetNumAcks(trackCount); + sTotalMalformed += inSession->GetNumMalformedPackets(trackCount); + sTotalPacketsLost += inSession->Get3gNumPacketsLost(trackCount); + sTotalDuplicates += inSession->Get3gNumDuplicates(trackCount); + sTotalLatePackets += inSession->Get3gNumLatePackets(trackCount); + sTotalBufferOverflowedPackets += inSession->Get3gNumBufferOverflowedPackets(trackCount); + } + UInt32 duration = (UInt32)(inSession->GetTotalPlayTimeInMsec() / 1000); + Float32 bitRate = (((Float32)bytesReceived) / ((Float32)duration) * 8) / 1024; + + + if (sLog != NULL) ::fprintf(sLog, "Play duration in sec: %"_U32BITARG_". Total stream bit rate in Kbits / sec: %f.\n", duration, bitRate); + } + + if (sLog != NULL) ::fprintf(sLog, "\n"); + } +} + diff --git a/StreamingLoadTool/dummynet_fixed_start.script b/StreamingLoadTool/dummynet_fixed_start.script new file mode 100755 index 0000000..c78e097 --- /dev/null +++ b/StreamingLoadTool/dummynet_fixed_start.script @@ -0,0 +1,31 @@ +#!/bin/sh +# +# +echo "SCRIPT $0 [ip address] " +echo "TEST Set a limited bandwidth." +echo "" + +ADDRESS="127.0.0.1" + +if [ ${1} ]; then + ADDRESS=${1} + echo "Address set. Using $ADDRESS" + else + echo "No Address given. Using $ADDRESS" +fi + +ipfw delete pipe 1 +ipfw delete pipe 2 +ipfw delete set 11 +ipfw delete set 12 + +ipfw pipe 1 config bw 80kbits/s delay 0 queue 100 noerror +ipfw pipe 2 config bw 80kbits/s delay 0 queue 100 noerror + +ipfw add 11 set 11 pipe 1 src-ip $ADDRESS proto udp in +ipfw add 12 set 12 pipe 2 src-ip $ADDRESS proto udp out + +ipfw set enable 11 +ipfw set enable 12 +ipfw enable firewall one_pass + diff --git a/StreamingLoadTool/dummynet_stop.script b/StreamingLoadTool/dummynet_stop.script new file mode 100755 index 0000000..80964d6 --- /dev/null +++ b/StreamingLoadTool/dummynet_stop.script @@ -0,0 +1,10 @@ +echo "SCRIPT $0 " +echo "TEST delete pipes, sets, and disable dummynet rule" + +ipfw delete pipe 1 +ipfw delete pipe 2 +ipfw delete set 11 +ipfw delete set 12 + +ipfw disable firewall one_pass + diff --git a/StreamingLoadTool/dummynet_variable.script b/StreamingLoadTool/dummynet_variable.script new file mode 100755 index 0000000..be08c65 --- /dev/null +++ b/StreamingLoadTool/dummynet_variable.script @@ -0,0 +1,73 @@ +#!/bin/sh +# +# +echo "SCRIPT $0 [ip address] [down Kbits] [up Kbits] [test seconds] " +echo "TEST Alternate between no bandwidth limits and limited bandwidth with a delay between" +echo "" + +ADDRESS="127.0.0.1" +UP="32" +DOWN="100" +SLEEP="30" + +if [ ${1} ]; then + ADDRESS=${1} + echo "Address set. Using $ADDRESS" + else + echo "No Address given. Using $ADDRESS" +fi + +if [ ${2} ]; then + DOWN=${2} + echo "Down link limit set. Using "$DOWN"kbits/s" +else + echo "No down link limit given. Using "$DOWN"kbits/s" + +fi + +if [ ${3} ]; then + UP=${3} + echo "Up link limit set. Using "$UP"kbits/s" +else + echo "No up link limit given. Using "$UP"kbits/s" +fi + + +if [ ${4} ]; then + SLEEP=${4} + echo "Test time set. Using $SLEEP seconds" +else + echo "No test time. Using $SLEEP seconds" +fi + +echo "Starting test" + +while [ 1 ] + do + + ipfw delete pipe 1 2 + ipfw delete set 11 12 + ipfw disable firewall + echo "" + echo "Bandwidth open - no limit set" + echo "Sleeping $SLEEP seconds" + sleep 30 + echo "" + + echo "Setting $DOWN kbits down and $UP kbits up" + ipfw pipe 1 config bw "$DOWN"kbits/s delay 0 queue 100 noerror + ipfw pipe 2 config bw "$UP"kbits/s delay 0 queue 100 noerror + + ipfw add 11 set 11 pipe 1 src-ip $ADDRESS proto udp in + ipfw add 12 set 12 pipe 2 src-ip $ADDRESS proto udp out + + ipfw set enable 11 + ipfw set enable 12 + ipfw enable firewall one_pass + + echo "Sleeping $SLEEP seconds" + sleep 30 + + +done + diff --git a/StreamingLoadTool/gdb_script b/StreamingLoadTool/gdb_script new file mode 100644 index 0000000..5701db4 --- /dev/null +++ b/StreamingLoadTool/gdb_script @@ -0,0 +1,6 @@ +dir ../APICommonCode +dir ../CommonUtilitiesLib +dir ../OSMemoryLib +dir ../PrefsSourceLib +dir ../RTSPClientLib +handle SIGPIPE nopass nostop noprint diff --git a/StreamingLoadTool/gdbspam b/StreamingLoadTool/gdbspam new file mode 100644 index 0000000..73d71fb --- /dev/null +++ b/StreamingLoadTool/gdbspam @@ -0,0 +1 @@ +gdb ./StreamingLoadTool -x gdb_script diff --git a/StreamingLoadTool/parseSLTOut.py b/StreamingLoadTool/parseSLTOut.py new file mode 100644 index 0000000..7787a31 --- /dev/null +++ b/StreamingLoadTool/parseSLTOut.py @@ -0,0 +1,203 @@ +#!python + +"""Script for parsing the output of a StreamingLoadTool file. Can be used both as a script and as a python module. +The output is a commma separated file. The StreamingLoadTool has to be run with -V 3 or above. + +Usage: ParseSLTOut.py [-h] [-b interval] [-s all] [filename] + +options: + -h show this help message and exit + -b Take the packet lengths and categorizes them according to their arrival time. + The packet lengths within the same bucket (of size interval seconds) are summed up + and averaged over the interval, which is useful for seeing the bit rate. + -s The streams to parse: one of video, audio, all; default is video + +If filename is missing, then the script will read from standard in. The script writes to standard out.""" + +""" + * + * @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@ + * +""" + +import re, sys + +#the regular expression for matching lines in the SLT output +playRE = re.compile(\ + r"^Receiving track (\d+), trackID=(\d+), (\w+) at time (\d+)") +timeRE = re.compile(\ + r"^Processing incoming packets at time (\d+)") +processedRE = re.compile(\ + r"^Processed packet: track=(\d+), len=(\d+), seq=(\d+), time=(\d+)\((\d+)\); bufferingDelay=(\d+), FBS=(\d+)") + + +def parseSLTInput(inputFile): + """ + Parses a string consisting of outputs from StreamingLoadTool. + The output is a list, where the index is the track index. The value is a map that maps from processing time to + packets. Each packet is a 3-tuple containing the packet length(in bytes), the sequence number, and the timestamp. + """ + videoTrackIndex = None + audioTrackIndex = None + localTimeBase = None + curTime = None + + packetTable = [{}, {}] + + for line in inputFile: + matchObj = playRE.match(line) + if matchObj is not None: + trackIndex, trackID, trackType, startTime = matchObj.groups(); + if trackType == "video": + videoTrackIndex = int(trackIndex) + elif trackType == "audio": + audioTrackIndex = int(trackIndex) + localTimeBase = int(startTime) + continue + + matchObj = timeRE.match(line) + if matchObj is not None: + processingTime, = matchObj.groups() + curTime = int(processingTime) + continue + + matchObj = processedRE.match(line) + if matchObj is not None: + trackIndex, packetLen, seqNum, timeStamp, timeStampInMediaTime, playoutDelay, freeBufferSpace = matchObj.groups() + trackIndex = int(trackIndex); + while trackIndex >= len(packetTable): + packetTable.append({}) + packetList = packetTable[trackIndex].setdefault(curTime, []) + packetList.append( (int(packetLen), int(seqNum), int(timeStamp)) ) + continue + + if localTimeBase is None: + sys.exit("Parse error: cannot find a track") + + # modify the processing times to be 0-based + newPacketTable = [{}] * len(packetTable) + for trackIndex in range(len(packetTable)): + newPackets = {} + for (curTime, packet) in packetTable[trackIndex].items(): + curTime -= localTimeBase + newPackets[curTime] = packet + packetTable[trackIndex] = newPackets + + return (packetTable, videoTrackIndex, audioTrackIndex) + + +def calcBitRate(xList, yList, interval): + """xList is a list of processing time, and yList is a list of packet size. + The function will calculate the bitrate, divided into interval-sized buckets, and returns it as a list + of (processing time, bitrate) pair. + xList is expected to be a list of time in milliseconds, and yList is expected to be a list of packet size in bytes.""" + maxIndex = max(xList) / interval + bitsReceived = [0] * (maxIndex + 1) + + for i in range(len(yList)): + x = xList[i] + y = yList[i] + + bitsReceived[x / interval] += y * 8 + + return [ (i * interval, (bitsReceived[i] * 1000) / interval) for i in range(len(bitsReceived))] + + +if __name__ == "__main__": + # the file is ran as a script + + # first parse the command line + import getopt + from decimal import Decimal + + try: + optlist, args = getopt.getopt(sys.argv[1:], "hb:s:") + except getopt.GetoptError: + sys.exit(__doc__) + + categorize = False + streams = 'video' + for opt, optarg in optlist: + if opt == '-h': + print __doc__ + sys.exit(0) + elif opt == '-b': + categorize = True + try: + interval = int(Decimal(optarg) * 1000) + except: + sys.exit(__doc__) + elif opt == '-s': + streams = optarg + if streams != 'video' and streams != 'audio' and streams != 'all': + sys.exit(__doc__) + + if len(args) == 0: + inputFile = sys.stdin + elif len(args) == 1: + inputFile = open(args[0], 'r') + else: + sys.exit(__doc__) + + (packetTable, videoIndex, audioIndex) = parseSLTInput(inputFile) + + if streams == 'video': + if videoIndex is None: + sys.exit("Parse error: Cannot find a video stream") + packets = packetTable[videoIndex] + elif streams == 'audio': + if audioIndex is None: + sys.exit("Parse error: Cannot find an audio stream") + packets = packetTable[audioIndex] + else: + packets = {} + for perStreamPackets in packetTable: + for (time, packetList) in perStreamPackets.items(): + packets.setdefault(time, []).extend(packetList) + + if len(packets) == 0: + sys.exit("Error: Cannot find a stream") + + + def millisecondsToSec(time): + """Convert time to 123.456 form as a string""" + return str(time / 1000) + '.' + str(time % 1000) + + if categorize: + xList = [] + yList = [] + + for processingTime, packetList in packets.items(): + for (packetLen, seqNum, timeStamp) in packetList: + xList.append(processingTime) + yList.append(packetLen) + data = calcBitRate(xList, yList, interval) + for (time, bitrate) in data: + print "%s, %i" % (millisecondsToSec(time), bitrate) + else: + processingTimes = packets.keys() + processingTimes.sort() + for processingTime in processingTimes: + packetList = packets[processingTime] + for (packetLen, seqNum, timeStamp) in packets[processingTime]: + # output.append( (processingTime, packetLen) ) + print ("%s, %i") % ( millisecondsToSec(processingTime), packetLen ) diff --git a/StreamingLoadTool/streamingloadtool.cfg b/StreamingLoadTool/streamingloadtool.cfg new file mode 100644 index 0000000..0e3778a --- /dev/null +++ b/StreamingLoadTool/streamingloadtool.cfg @@ -0,0 +1,81 @@ +# This is a StreamingLoadTool config file + +# Use the "clienttype" directive to specify whether StreamingLoadTool should make +# RTSP / UDP connections or RTSP / HTTP connections or . Say "http" for +# the latter, "udp" for the former. Say "reliableudp" for reliable UDP. +# Say "tcp" for straight interleaved RTSP / RTP +clienttype reliableudp + +# If doing RTSP / HTTP, set droppost to "yes" if you would like StreamingLoadTool +# to drop the POST half of each RTSP / HTTP connection after sending the +# PLAY. "yes" best emulates the "real" client behavior. +droppost yes + +# Set this to the # of concurrent clients you would like StreamingLoadTool to maintain +concurrentclients 1 + +# Specify a connection port for each connection +port 554 + +# Specify a proxy IP address in dotted-decimal form. If 0, StreamingLoadTool +# will not use a proxy to connect +proxyip 0 + +# Client window (size of UDP socket buffers). +# For reliable UDP, this affects packet loss and the server's +# retransmission algorithm +clientwindow 32768 + +# StreamingLoadTool should send a TEARDOWN after streaming for this # of seconds +movielength 40 + +# If runforever is set to "no", StreamingLoadTool will quit after finishing the +# list of URLs provided below. If "yes" StreamingLoadTool will loop forever. +runforever yes + +# Each instance of StreamingLoadTool must have a unique httpcookie value. This +# can also be specified on the command-line (see usage by doing StreamingLoadTool -v) +httpcookie 1 + +# Set to "yes" if you would like StreamingLoadTool to generate a connection log +shouldlog yes + +# Append junk data after each DESCRIBE request +appendjunk no + +# Location to place the connection log +logpath streamingloadtool.log + +# Interval in milliseconds between attempts to read media data. For acking +# udp clients, this is also the interval between acks. +readinterval 10 + +# how late should packets be allowed to be sent? Value is in seconds. +# 0 = no late tolerance will be specified at all +latetolerance 0 + +# The "Packet-Range" header is used as an alternative to the standard "Range" +# header on a Play request for specifying a range of packets. Leave +# this line blank to issue a standard play, specify a packet range header +# here to send that instead of standard Range header. +packetplayheader + +# The overbuffer window size is the number of K bytes the server can send +# ahead of time. This applies to all transports except "udp". +overbufferwindowsize 5192 + +# Set this to be the value of the x-RTP-Meta-Info header sent to the server. +# If it is empty, no x-RTP-Meta-Info header will be sent. Otherwise, specify +# the fields you would like to receive +# rtpmetainfo tt;ft;pn;fd;md;sq + +# Set this to be the speed you want the streams at (1 = normal speed, 2 = 2x normal speed, etc) +speed 1 + +# Enable this to have StreamingLoadTool randomly thumb around +randomthumb no + +# List of rtsp URLs for StreamingLoadTool to execute +url rtsp://foo.bar.com/sample.mov + + diff --git a/StreamingLoadTool/streamingloadtool.conf b/StreamingLoadTool/streamingloadtool.conf new file mode 100644 index 0000000..31eb28b --- /dev/null +++ b/StreamingLoadTool/streamingloadtool.conf @@ -0,0 +1,129 @@ +# This is a StreamingLoadTool config file + +#player user agent name +player QTS + +# Use the "clienttype" directive to specify whether StreamingLoadTool should make +# RTSP / UDP connections or RTSP / HTTP connections or . Say "http" for +# the latter, "udp" for the former. Say "reliableudp" for reliable UDP. +# Say "tcp" for straight interleaved RTSP / RTP +# Use "3gudp" for 3gpp rate adaptation over UDP +clienttype reliableudp + +# If doing RTSP / HTTP, set droppost to "yes" if you would like StreamingLoadTool +# to drop the POST half of each RTSP / HTTP connection after sending the +# PLAY. "yes" best emulates the "real" client behavior. +droppost yes + +# Set this to the # of concurrent clients you would like StreamingLoadTool to maintain(default = 1) +# When not running forever, this is also the number of clients to load before exiting +concurrentclients 1 + +# Specify a connection port for each connection(default 554) +port 554 + +# Specify a proxy IP address in dotted-decimal form. If 0, StreamingLoadTool +# will not use a proxy to connect +proxyip 0 + +# Client window (size of UDP socket buffers). Default = 32768 +# For reliable UDP, this affects packet loss and the server's retransmission algorithm +clientwindow 32768 + +# StreamingLoadTool should send a TEARDOWN after streaming for this # of seconds +movielength 40 + +# If runforever is set to "no", StreamingLoadTool will quit after finishing the +# list of URLs provided below. If "yes" StreamingLoadTool will loop forever. (Default = no) +runforever yes + +# Each instance of StreamingLoadTool must have a unique httpcookie value. This +# can also be specified on the command-line (see usage by doing StreamingLoadTool -v) (Default = 1) +httpcookie 1 + +# Set to "yes" if you would like StreamingLoadTool to generate a connection log +shouldlog yes + +# Append junk data after each DESCRIBE request +appendjunk no + +# Location to place the connection log +logpath streamingloadtool.log + +# Interval in milliseconds between attempts to read media data. For acking +# udp clients, this is also the interval between acks. (Default = 50 milliseconds) +readinterval 10 + +# how late should packets be allowed to be sent? Value is in seconds. +# 0 = no late tolerance will be specified at all +latetolerance 0 + +# The "Packet-Range" header is used as an alternative to the standard "Range" +# header on a Play request for specifying a range of packets. Leave +# this line blank to issue a standard play, specify a packet range header +# here to send that instead of standard Range header. +packetplayheader + +# The overbuffer window size is the number of K bytes the server can send +# ahead of time. This applies to all transports except "udp". +overbufferwindowsize 5192 + +# Set this to be the value of the x-RTP-Meta-Info header sent to the server. +# If it is empty, no x-RTP-Meta-Info header will be sent. Otherwise, specify +# the fields you would like to receive +# rtpmetainfo tt;ft;pn;fd;md;sq + +# Set this to be the speed you want the streams at (1 = normal speed, 2 = 2x normal speed, etc; float) +speed 1 + +# Enable this to have StreamingLoadTool randomly thumb around +randomthumb no + +# Set sendoptions "yes" to send an OPTIONS request before executing each DESCRIBE request +sendoptions no + +# Set requestrandomdata "yes" to send an OPTIONS request with a random data request header before executing each DESCRIBE request +requestrandomdata no + +# How many random bytes the server should send in the body of the OPTIONS response +randomdatasize 0 + +# How often should the client send RTCP messages in milliseconds. (Default = 5000; ACK's in reliableudp are sent ASAP) +rtcpinterval 1000 + +# List of rtsp URLs for StreamingLoadTool to execute +url rtsp://foo.bar.com/sample.mov + +# List of users:passwords for authentication (format: user:password) +user user0:pass0 + +#The advertised bandwidth in bps (default: not sent) +bandwidth 50000 + +#The player buffer space per stream in bytes. Default of 0 is unlimited +buffer 100000 + +#target delay in milliseconds. Default 3000. Use 0 to turn it off +#This is the desired amount of playback time to keep in the buffer. +delay 10000 + +#Start play time delay; expressed as a fraction of the target delay. This is how much data there should be in the buffer before +#the media starts playing. Use 0.0 to start playing immediately, and 1.0 to start playing when the target delay has been met. Default = 5.0 +#Use -1 for disabled. +startdelay 0.5 + +#Turn on 3GPP features and headers? 3GPP should be not be enabled on reliableudp transport; default = no +enable3GPP no + +# The advertised guarenteed bit rate(GBW), maximum bit rate(MBW), and maximum transfer delay(MTD) for the wireless link +# Units are in kilobits per seconds and milliseconds. +# Default = 0 = no specified value +#GBW 32 +#MBW 128 +#MTD 2000 + +# Enable a forced PlayoutDelay value for 3GPP RTCP packets (The value defined for the playoutDelay preference will be sent) +enableForcePlayoutDelay no + +# PlayoutDelay value to use if enableForcePlayoutDelay is enabled +playoutDelay 65535 diff --git a/StreamingProxy--Linux/StreamingProxy.html b/StreamingProxy--Linux/StreamingProxy.html new file mode 100644 index 0000000..b636597 --- /dev/null +++ b/StreamingProxy--Linux/StreamingProxy.html @@ -0,0 +1,148 @@ + + + + StreamingProxy ReadMe + + +
+

Streaming Proxy ReadMe

+ +

The Darwin Streaming Proxy is an application-specific proxy which +would normally be run in a border zone or perimeter network. It is +used to give client machines within a protected network access to +streaming servers outside that network, in the case when the firewall +blocks RTSP connections or RTP/UDP data flow. The firewall perimeter +network is usually configured to allow:

+ +
    +
  • RTSP connections from within the network, as long as the + destination is the proxy
  • + +
  • RTSP connections to outside the network, as long as the source + is the proxy
  • + +
  • RTP datagrams to and from the proxy to the inner network
  • + +
  • RTP datagrams to and from the proxy to the outside
  • +
+ +

The proxy usually sits within the perimeter network, between an +'inner skin' and 'outer skin', which have different configurations, +of course, to allow the flows above.

+ +

Note that RTSP runs over TCP; the normal connection port is 554 +(but see below). Note that if the URL supplied by the client to the +proxy includes a port number, then the proxy will attempt to connect +to the server using that port number rather than the default 554.

+ +

RTP runs over UDP, and a range of ports may be used. Client-side +ports are usually in a restricted range (starting at 6970), but +servers cannot so easily restrict what ports they use. For safety, do +not restrict port number access to the proxy; use only the IP +address.

+ +

Note that this proxy handles standard RTSP controlling standard +RTP; RTSP can be used to control other media protocols, and is used +by a number of companies to control proprietary media protocols. This +proxy does not attempt to proxy those other protocols. For more +information on these protocols, consult the Internet Engineering Task +Force documentation:

+ +
    +
  • RTSP + <http://info.internet.isi.edu:80/in-notes/rfc/files/rfc2326.txt>
  • + +
  • RTP + <http://info.internet.isi.edu:80/in-notes/rfc/files/rfc1889.txt>
  • +
+ + + +

License

+ +

The Darwin Streaming Server and Streaming Proxy is distributed +under the terms of the Apple Public Source License. For more +information, refer to the license terms at www.publicsource.apple.com. +Note that the Apple Public Source License does not allow you to use +the terms "QuickTime" or "QuickTime Streaming Server" in descriptions +of products developed using Darwin Streaming Server or Streaming +Proxy open source code, nor use any Apple trademarks or logos +associated with QuickTime and QuickTime Streaming Server.

+ + + +

How To Use

+ +

The application must reside on a machine that can see both the +Internet, and your internal network. This would be your Bastion Host, +or another machine in your Perimeter Network.

+ + + +
    +
  1. Install the Streaming Proxy in a reasonable place on your + machine (For example, /usr/local/sbin ).
  2. + +
  3. Edit the streamingproxy.conf file put place + it in the /etc/streaming/ directory.
  4. + +
  5. Launch the StreamingProxy application (ie. + #/usr/local/sbin/StreamingProxy -c /etc/streaming/streamingproxy.conf)
  6. + +
  7. Launch the StreamingProxy application using the -h command line option for a description of the other + command line options.
  8. + +
  9. NOTE: You must run as root to enable the default port 554 for + reception.
  10. + +
+ +

Configuring Client Machines

+ +
    +
  1. Open the QuickTime (Win32) or QuickTime Settings (Mac) control + panel.
  2. + +
  3. Select Streaming Proxy from the popup menu and then + select the RTSP Proxy Server checkbox.
  4. + +
  5. Type in the ip address or domain name of your proxy server in the Name: field, and + the port you configured in the streamingproxy.conf file (or 554 as + a default).
  6. +
+ + +

How It Works

+ +

The Streaming Proxy listens on ports you specify for a RTSP +command sequence. It parses the commands and redirects the command to +the desired server. It also rewrites the RTSP commands to reflect the +new set of port numbers that the proxy must use.

+ +

The Streaming Proxy will only relay data that comes from a server +that the data was requested from.

+ + + +

Credits

+ +

This proxy includes software developed by the University of +California, Berkeley and its contributors; that included software is +the regular expression code developed by Henry Spencer.

+ + +

 

+ +

© 1999, 2000 2001 Apple Computer, Inc. All rights +reserved. Apple, the Apple logo, Mac, Macintosh, PowerBook, Power +Macintosh, and QuickTime are trademarks of Apple Computer, Inc., +registered in the United States and other countries. iBook, iMac, and +Power Mac are trademarks of Apple Computer, Inc. All other product +names are trademarks or registered trademarks of their respective +holders.

+ + +
+ + + diff --git a/StreamingProxy--Linux/streamingproxy.conf b/StreamingProxy--Linux/streamingproxy.conf new file mode 100644 index 0000000..e31b836 --- /dev/null +++ b/StreamingProxy--Linux/streamingproxy.conf @@ -0,0 +1,39 @@ +# +# proxy configuration file +# + +# +# allow IP/bit-range +# allows connections from the IP network masked by bit-range +# NOTE: lack of this line allows connections from any IP address +# It is best to limit incoming connections to your internal network. +# +# allow 10.0.0.0/8 + +# +# users +# Specifies the maximum number of simultaneous users +# +users 150 + +# +# listen
+# tells the proxy which ports to listen to RTSP requests on +# if
is not specified, it listens on all addresses +# +#listen 127.0.0.1 5540 +listen 554 +listen 7070 + +# +# port-range +# specifies the range of UDP ports that the proxy should use +# +port-range 10000-15000 + +# +# rtp-bind-addr
+# REQUIRED: Specify the proxy's ip address to bind to for the RTP sockets. +# This must be a valid host name or network IP address. +# +#rtp-bind-addr 5.6.7.8 diff --git a/StreamingProxy.tproj/StreamingProxy.html b/StreamingProxy.tproj/StreamingProxy.html new file mode 100644 index 0000000..b636597 --- /dev/null +++ b/StreamingProxy.tproj/StreamingProxy.html @@ -0,0 +1,148 @@ + + + + StreamingProxy ReadMe + + +
+

Streaming Proxy ReadMe

+ +

The Darwin Streaming Proxy is an application-specific proxy which +would normally be run in a border zone or perimeter network. It is +used to give client machines within a protected network access to +streaming servers outside that network, in the case when the firewall +blocks RTSP connections or RTP/UDP data flow. The firewall perimeter +network is usually configured to allow:

+ +
    +
  • RTSP connections from within the network, as long as the + destination is the proxy
  • + +
  • RTSP connections to outside the network, as long as the source + is the proxy
  • + +
  • RTP datagrams to and from the proxy to the inner network
  • + +
  • RTP datagrams to and from the proxy to the outside
  • +
+ +

The proxy usually sits within the perimeter network, between an +'inner skin' and 'outer skin', which have different configurations, +of course, to allow the flows above.

+ +

Note that RTSP runs over TCP; the normal connection port is 554 +(but see below). Note that if the URL supplied by the client to the +proxy includes a port number, then the proxy will attempt to connect +to the server using that port number rather than the default 554.

+ +

RTP runs over UDP, and a range of ports may be used. Client-side +ports are usually in a restricted range (starting at 6970), but +servers cannot so easily restrict what ports they use. For safety, do +not restrict port number access to the proxy; use only the IP +address.

+ +

Note that this proxy handles standard RTSP controlling standard +RTP; RTSP can be used to control other media protocols, and is used +by a number of companies to control proprietary media protocols. This +proxy does not attempt to proxy those other protocols. For more +information on these protocols, consult the Internet Engineering Task +Force documentation:

+ +
    +
  • RTSP + <http://info.internet.isi.edu:80/in-notes/rfc/files/rfc2326.txt>
  • + +
  • RTP + <http://info.internet.isi.edu:80/in-notes/rfc/files/rfc1889.txt>
  • +
+ + + +

License

+ +

The Darwin Streaming Server and Streaming Proxy is distributed +under the terms of the Apple Public Source License. For more +information, refer to the license terms at www.publicsource.apple.com. +Note that the Apple Public Source License does not allow you to use +the terms "QuickTime" or "QuickTime Streaming Server" in descriptions +of products developed using Darwin Streaming Server or Streaming +Proxy open source code, nor use any Apple trademarks or logos +associated with QuickTime and QuickTime Streaming Server.

+ + + +

How To Use

+ +

The application must reside on a machine that can see both the +Internet, and your internal network. This would be your Bastion Host, +or another machine in your Perimeter Network.

+ + + +
    +
  1. Install the Streaming Proxy in a reasonable place on your + machine (For example, /usr/local/sbin ).
  2. + +
  3. Edit the streamingproxy.conf file put place + it in the /etc/streaming/ directory.
  4. + +
  5. Launch the StreamingProxy application (ie. + #/usr/local/sbin/StreamingProxy -c /etc/streaming/streamingproxy.conf)
  6. + +
  7. Launch the StreamingProxy application using the -h command line option for a description of the other + command line options.
  8. + +
  9. NOTE: You must run as root to enable the default port 554 for + reception.
  10. + +
+ +

Configuring Client Machines

+ +
    +
  1. Open the QuickTime (Win32) or QuickTime Settings (Mac) control + panel.
  2. + +
  3. Select Streaming Proxy from the popup menu and then + select the RTSP Proxy Server checkbox.
  4. + +
  5. Type in the ip address or domain name of your proxy server in the Name: field, and + the port you configured in the streamingproxy.conf file (or 554 as + a default).
  6. +
+ + +

How It Works

+ +

The Streaming Proxy listens on ports you specify for a RTSP +command sequence. It parses the commands and redirects the command to +the desired server. It also rewrites the RTSP commands to reflect the +new set of port numbers that the proxy must use.

+ +

The Streaming Proxy will only relay data that comes from a server +that the data was requested from.

+ + + +

Credits

+ +

This proxy includes software developed by the University of +California, Berkeley and its contributors; that included software is +the regular expression code developed by Henry Spencer.

+ + +

 

+ +

© 1999, 2000 2001 Apple Computer, Inc. All rights +reserved. Apple, the Apple logo, Mac, Macintosh, PowerBook, Power +Macintosh, and QuickTime are trademarks of Apple Computer, Inc., +registered in the United States and other countries. iBook, iMac, and +Power Mac are trademarks of Apple Computer, Inc. All other product +names are trademarks or registered trademarks of their respective +holders.

+ + +
+ + + diff --git a/StreamingProxy.tproj/streamingproxy.conf b/StreamingProxy.tproj/streamingproxy.conf new file mode 100644 index 0000000..e31b836 --- /dev/null +++ b/StreamingProxy.tproj/streamingproxy.conf @@ -0,0 +1,39 @@ +# +# proxy configuration file +# + +# +# allow IP/bit-range +# allows connections from the IP network masked by bit-range +# NOTE: lack of this line allows connections from any IP address +# It is best to limit incoming connections to your internal network. +# +# allow 10.0.0.0/8 + +# +# users +# Specifies the maximum number of simultaneous users +# +users 150 + +# +# listen
+# tells the proxy which ports to listen to RTSP requests on +# if
is not specified, it listens on all addresses +# +#listen 127.0.0.1 5540 +listen 554 +listen 7070 + +# +# port-range +# specifies the range of UDP ports that the proxy should use +# +port-range 10000-15000 + +# +# rtp-bind-addr
+# REQUIRED: Specify the proxy's ip address to bind to for the RTP sockets. +# This must be a valid host name or network IP address. +# +#rtp-bind-addr 5.6.7.8 diff --git a/WebAdmin/NetSSLeay/SSLeay.c b/WebAdmin/NetSSLeay/SSLeay.c new file mode 100644 index 0000000..93baf8b --- /dev/null +++ b/WebAdmin/NetSSLeay/SSLeay.c @@ -0,0 +1,3847 @@ +/* + * + * @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@ + */ +/* + * This file was generated automatically by xsubpp version 1.9507 from the + * contents of SSLeay.xs. Do not edit this file, edit SSLeay.xs instead. + * + * ANY CHANGES MADE HERE WILL BE LOST! + * + */ + +//#line 1 "SSLeay.xs" +/* SSLeay.xs - Perl module for using Eric Young's implementation of SSL + * + * Copyright (c) 1996-1999 Sampo Kellomaki + * All Rights Reserved. + * + * 19.6.1998, Maintenance release to sync with SSLeay-0.9.0, --Sampo + * 24.6.1998, added write_partial to support ssl_write_all in more + * memory efficient way. --Sampo + * 8.7.1998, Added SSL_(CTX)?_set_options and associated constants. + * 31.3.1999, Tracking OpenSSL-0.9.2b changes, dropping support for + * earlier versions + * 30.7.1999, Tracking OpenSSL-0.9.3a changes, --Sampo + * + * The distribution and use of this module are subject to the conditions + * listed in COPYRIGHT file at the root of Eric Young's SSLeay-0.9.0 + * distribution (i.e. free, but mandatory attribution and NO WARRANTY). + +Removed, perhaps permanently? + +int +SSL_add_session(ctx,ses) + SSL_CTX * ctx + SSL_SESSION * ses + +int +SSL_remove_session(ctx,ses) + SSL_CTX * ctx + SSL_SESSION * ses + +void +SSL_flush_sessions(ctx,tm) + SSL_CTX * ctx + SInt32 tm + + */ + +#ifdef __cplusplus +extern "C" { +#endif +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +#ifdef __cplusplus +} +#endif + +/* OpenSSL-0.9.3a has some strange warning about this in + * openssl/des.h + */ +#undef _ + +#include +#include +#include +#include + +/* Debugging output */ + +#if 0 +#define PR(s) printf(s); +#define PRN(s,n) printf("'%s' (%d)\n",s,n); +#define SEX_DEBUG 1 +#else +#define PR(s) +#define PRN(s,n) +#undef SEX_DEBUG +#endif + + +// Remove warnings +#ifdef dNOOP +#undef dNOOP +#define dNOOP NOOP +#endif +extern void RAND_seed (char * buf, int len); +extern int RAND_load_file(char * file_name, int how_much); +extern void RAND_cleanup(void); +extern int RAND_write_file(char * file_name); +// end remove warnings + +/* xsub automagically generated constant evaluator function */ + +static double +constant(name, arg) +char *name; +int arg; +{ + errno = 0; + switch (*name) { + case 'A': + if (strEQ(name, "AT_MD5_WITH_RSA_ENCRYPTION")) +#ifdef SSL_AT_MD5_WITH_RSA_ENCRYPTION + return SSL_AT_MD5_WITH_RSA_ENCRYPTION; +#else + goto not_there; +#endif + break; + case 'B': + break; + case 'C': + if (strEQ(name, "CB_ACCEPT_EXIT")) +#ifdef SSL_CB_ACCEPT_EXIT + return SSL_CB_ACCEPT_EXIT; +#else + goto not_there; +#endif + if (strEQ(name, "CB_ACCEPT_LOOP")) +#ifdef SSL_CB_ACCEPT_LOOP + return SSL_CB_ACCEPT_LOOP; +#else + goto not_there; +#endif + if (strEQ(name, "CB_CONNECT_EXIT")) +#ifdef SSL_CB_CONNECT_EXIT + return SSL_CB_CONNECT_EXIT; +#else + goto not_there; +#endif + if (strEQ(name, "CB_CONNECT_LOOP")) +#ifdef SSL_CB_CONNECT_LOOP + return SSL_CB_CONNECT_LOOP; +#else + goto not_there; +#endif + if (strEQ(name, "CK_DES_192_EDE3_CBC_WITH_MD5")) +#ifdef SSL_CK_DES_192_EDE3_CBC_WITH_MD5 + return SSL_CK_DES_192_EDE3_CBC_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "CK_DES_192_EDE3_CBC_WITH_SHA")) +#ifdef SSL_CK_DES_192_EDE3_CBC_WITH_SHA + return SSL_CK_DES_192_EDE3_CBC_WITH_SHA; +#else + goto not_there; +#endif + if (strEQ(name, "CK_DES_64_CBC_WITH_MD5")) +#ifdef SSL_CK_DES_64_CBC_WITH_MD5 + return SSL_CK_DES_64_CBC_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "CK_DES_64_CBC_WITH_SHA")) +#ifdef SSL_CK_DES_64_CBC_WITH_SHA + return SSL_CK_DES_64_CBC_WITH_SHA; +#else + goto not_there; +#endif + if (strEQ(name, "CK_DES_64_CFB64_WITH_MD5_1")) +#ifdef SSL_CK_DES_64_CFB64_WITH_MD5_1 + return SSL_CK_DES_64_CFB64_WITH_MD5_1; +#else + goto not_there; +#endif + if (strEQ(name, "CK_IDEA_128_CBC_WITH_MD5")) +#ifdef SSL_CK_IDEA_128_CBC_WITH_MD5 + return SSL_CK_IDEA_128_CBC_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "CK_NULL")) +#ifdef SSL_CK_NULL + return SSL_CK_NULL; +#else + goto not_there; +#endif + if (strEQ(name, "CK_NULL_WITH_MD5")) +#ifdef SSL_CK_NULL_WITH_MD5 + return SSL_CK_NULL_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "CK_RC2_128_CBC_EXPORT40_WITH_MD5")) +#ifdef SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5 + return SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "CK_RC2_128_CBC_WITH_MD5")) +#ifdef SSL_CK_RC2_128_CBC_WITH_MD5 + return SSL_CK_RC2_128_CBC_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "CK_RC4_128_EXPORT40_WITH_MD5")) +#ifdef SSL_CK_RC4_128_EXPORT40_WITH_MD5 + return SSL_CK_RC4_128_EXPORT40_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "CK_RC4_128_WITH_MD5")) +#ifdef SSL_CK_RC4_128_WITH_MD5 + return SSL_CK_RC4_128_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "CLIENT_VERSION")) +#ifdef SSL_CLIENT_VERSION + return SSL_CLIENT_VERSION; +#else + goto not_there; +#endif + if (strEQ(name, "CT_X509_CERTIFICATE")) +#ifdef SSL_CT_X509_CERTIFICATE + return SSL_CT_X509_CERTIFICATE; +#else + goto not_there; +#endif + break; + case 'D': + break; + case 'E': + break; + case 'F': + if (strEQ(name, "FILETYPE_ASN1")) +#ifdef SSL_FILETYPE_ASN1 + return SSL_FILETYPE_ASN1; +#else + goto not_there; +#endif + if (strEQ(name, "FILETYPE_PEM")) +#ifdef SSL_FILETYPE_PEM + return SSL_FILETYPE_PEM; +#else + goto not_there; +#endif + if (strEQ(name, "F_CLIENT_CERTIFICATE")) +#ifdef SSL_F_CLIENT_CERTIFICATE + return SSL_F_CLIENT_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "F_CLIENT_HELLO")) +#ifdef SSL_F_CLIENT_HELLO + return SSL_F_CLIENT_HELLO; +#else + goto not_there; +#endif + if (strEQ(name, "F_CLIENT_MASTER_KEY")) +#ifdef SSL_F_CLIENT_MASTER_KEY + return SSL_F_CLIENT_MASTER_KEY; +#else + goto not_there; +#endif + if (strEQ(name, "F_D2I_SSL_SESSION")) +#ifdef SSL_F_D2I_SSL_SESSION + return SSL_F_D2I_SSL_SESSION; +#else + goto not_there; +#endif + if (strEQ(name, "F_GET_CLIENT_FINISHED")) +#ifdef SSL_F_GET_CLIENT_FINISHED + return SSL_F_GET_CLIENT_FINISHED; +#else + goto not_there; +#endif + if (strEQ(name, "F_GET_CLIENT_HELLO")) +#ifdef SSL_F_GET_CLIENT_HELLO + return SSL_F_GET_CLIENT_HELLO; +#else + goto not_there; +#endif + if (strEQ(name, "F_GET_CLIENT_MASTER_KEY")) +#ifdef SSL_F_GET_CLIENT_MASTER_KEY + return SSL_F_GET_CLIENT_MASTER_KEY; +#else + goto not_there; +#endif + if (strEQ(name, "F_GET_SERVER_FINISHED")) +#ifdef SSL_F_GET_SERVER_FINISHED + return SSL_F_GET_SERVER_FINISHED; +#else + goto not_there; +#endif + if (strEQ(name, "F_GET_SERVER_HELLO")) +#ifdef SSL_F_GET_SERVER_HELLO + return SSL_F_GET_SERVER_HELLO; +#else + goto not_there; +#endif + if (strEQ(name, "F_GET_SERVER_VERIFY")) +#ifdef SSL_F_GET_SERVER_VERIFY + return SSL_F_GET_SERVER_VERIFY; +#else + goto not_there; +#endif + if (strEQ(name, "F_I2D_SSL_SESSION")) +#ifdef SSL_F_I2D_SSL_SESSION + return SSL_F_I2D_SSL_SESSION; +#else + goto not_there; +#endif + if (strEQ(name, "F_READ_N")) +#ifdef SSL_F_READ_N + return SSL_F_READ_N; +#else + goto not_there; +#endif + if (strEQ(name, "F_REQUEST_CERTIFICATE")) +#ifdef SSL_F_REQUEST_CERTIFICATE + return SSL_F_REQUEST_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "F_SERVER_HELLO")) +#ifdef SSL_F_SERVER_HELLO + return SSL_F_SERVER_HELLO; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_ACCEPT")) +#ifdef SSL_F_SSL_ACCEPT + return SSL_F_SSL_ACCEPT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_CERT_NEW")) +#ifdef SSL_F_SSL_CERT_NEW + return SSL_F_SSL_CERT_NEW; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_CONNECT")) +#ifdef SSL_F_SSL_CONNECT + return SSL_F_SSL_CONNECT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_ENC_DES_CBC_INIT")) +#ifdef SSL_F_SSL_ENC_DES_CBC_INIT + return SSL_F_SSL_ENC_DES_CBC_INIT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_ENC_DES_CFB_INIT")) +#ifdef SSL_F_SSL_ENC_DES_CFB_INIT + return SSL_F_SSL_ENC_DES_CFB_INIT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_ENC_DES_EDE3_CBC_INIT")) +#ifdef SSL_F_SSL_ENC_DES_EDE3_CBC_INIT + return SSL_F_SSL_ENC_DES_EDE3_CBC_INIT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_ENC_IDEA_CBC_INIT")) +#ifdef SSL_F_SSL_ENC_IDEA_CBC_INIT + return SSL_F_SSL_ENC_IDEA_CBC_INIT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_ENC_NULL_INIT")) +#ifdef SSL_F_SSL_ENC_NULL_INIT + return SSL_F_SSL_ENC_NULL_INIT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_ENC_RC2_CBC_INIT")) +#ifdef SSL_F_SSL_ENC_RC2_CBC_INIT + return SSL_F_SSL_ENC_RC2_CBC_INIT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_ENC_RC4_INIT")) +#ifdef SSL_F_SSL_ENC_RC4_INIT + return SSL_F_SSL_ENC_RC4_INIT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_GET_NEW_SESSION")) +#ifdef SSL_F_SSL_GET_NEW_SESSION + return SSL_F_SSL_GET_NEW_SESSION; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_MAKE_CIPHER_LIST")) +#ifdef SSL_F_SSL_MAKE_CIPHER_LIST + return SSL_F_SSL_MAKE_CIPHER_LIST; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_NEW")) +#ifdef SSL_F_SSL_NEW + return SSL_F_SSL_NEW; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_READ")) +#ifdef SSL_F_SSL_READ + return SSL_F_SSL_READ; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_RSA_PRIVATE_DECRYPT")) +#ifdef SSL_F_SSL_RSA_PRIVATE_DECRYPT + return SSL_F_SSL_RSA_PRIVATE_DECRYPT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_RSA_PUBLIC_ENCRYPT")) +#ifdef SSL_F_SSL_RSA_PUBLIC_ENCRYPT + return SSL_F_SSL_RSA_PUBLIC_ENCRYPT; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_SESSION_NEW")) +#ifdef SSL_F_SSL_SESSION_NEW + return SSL_F_SSL_SESSION_NEW; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_SESSION_PRINT_FP")) +#ifdef SSL_F_SSL_SESSION_PRINT_FP + return SSL_F_SSL_SESSION_PRINT_FP; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_SET_CERTIFICATE")) +#ifdef SSL_F_SSL_SET_CERTIFICATE + return SSL_F_SSL_SET_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_SET_FD")) +#ifdef SSL_F_SSL_SET_FD + return SSL_F_SSL_SET_FD; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_SET_RFD")) +#ifdef SSL_F_SSL_SET_RFD + return SSL_F_SSL_SET_RFD; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_SET_WFD")) +#ifdef SSL_F_SSL_SET_WFD + return SSL_F_SSL_SET_WFD; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_STARTUP")) +#ifdef SSL_F_SSL_STARTUP + return SSL_F_SSL_STARTUP; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_CERTIFICATE")) +#ifdef SSL_F_SSL_USE_CERTIFICATE + return SSL_F_SSL_USE_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_CERTIFICATE_ASN1")) +#ifdef SSL_F_SSL_USE_CERTIFICATE_ASN1 + return SSL_F_SSL_USE_CERTIFICATE_ASN1; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_CERTIFICATE_FILE")) +#ifdef SSL_F_SSL_USE_CERTIFICATE_FILE + return SSL_F_SSL_USE_CERTIFICATE_FILE; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_PRIVATEKEY")) +#ifdef SSL_F_SSL_USE_PRIVATEKEY + return SSL_F_SSL_USE_PRIVATEKEY; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_PRIVATEKEY_ASN1")) +#ifdef SSL_F_SSL_USE_PRIVATEKEY_ASN1 + return SSL_F_SSL_USE_PRIVATEKEY_ASN1; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_PRIVATEKEY_FILE")) +#ifdef SSL_F_SSL_USE_PRIVATEKEY_FILE + return SSL_F_SSL_USE_PRIVATEKEY_FILE; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_RSAPRIVATEKEY")) +#ifdef SSL_F_SSL_USE_RSAPRIVATEKEY + return SSL_F_SSL_USE_RSAPRIVATEKEY; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_RSAPRIVATEKEY_ASN1")) +#ifdef SSL_F_SSL_USE_RSAPRIVATEKEY_ASN1 + return SSL_F_SSL_USE_RSAPRIVATEKEY_ASN1; +#else + goto not_there; +#endif + if (strEQ(name, "F_SSL_USE_RSAPRIVATEKEY_FILE")) +#ifdef SSL_F_SSL_USE_RSAPRIVATEKEY_FILE + return SSL_F_SSL_USE_RSAPRIVATEKEY_FILE; +#else + goto not_there; +#endif + if (strEQ(name, "F_WRITE_PENDING")) +#ifdef SSL_F_WRITE_PENDING + return SSL_F_WRITE_PENDING; +#else + goto not_there; +#endif + break; + case 'G': + break; + case 'H': + break; + case 'I': + break; + case 'J': + break; + case 'K': + break; + case 'L': + break; + case 'M': + if (strEQ(name, "MAX_MASTER_KEY_LENGTH_IN_BITS")) +#ifdef SSL_MAX_MASTER_KEY_LENGTH_IN_BITS + return SSL_MAX_MASTER_KEY_LENGTH_IN_BITS; +#else + goto not_there; +#endif + if (strEQ(name, "MAX_RECORD_LENGTH_2_BYTE_HEADER")) +#ifdef SSL_MAX_RECORD_LENGTH_2_BYTE_HEADER + return SSL_MAX_RECORD_LENGTH_2_BYTE_HEADER; +#else + goto not_there; +#endif + if (strEQ(name, "MAX_RECORD_LENGTH_3_BYTE_HEADER")) +#ifdef SSL_MAX_RECORD_LENGTH_3_BYTE_HEADER + return SSL_MAX_RECORD_LENGTH_3_BYTE_HEADER; +#else + goto not_there; +#endif + if (strEQ(name, "MAX_SSL_SESSION_ID_LENGTH_IN_BYTES")) +#ifdef SSL_MAX_SSL_SESSION_ID_LENGTH_IN_BYTES + return SSL_MAX_SSL_SESSION_ID_LENGTH_IN_BYTES; +#else + goto not_there; +#endif + if (strEQ(name, "MIN_RSA_MODULUS_LENGTH_IN_BYTES")) +#ifdef SSL_MIN_RSA_MODULUS_LENGTH_IN_BYTES + return SSL_MIN_RSA_MODULUS_LENGTH_IN_BYTES; +#else + goto not_there; +#endif + if (strEQ(name, "MT_CLIENT_CERTIFICATE")) +#ifdef SSL_MT_CLIENT_CERTIFICATE + return SSL_MT_CLIENT_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "MT_CLIENT_FINISHED")) +#ifdef SSL_MT_CLIENT_FINISHED + return SSL_MT_CLIENT_FINISHED; +#else + goto not_there; +#endif + if (strEQ(name, "MT_CLIENT_HELLO")) +#ifdef SSL_MT_CLIENT_HELLO + return SSL_MT_CLIENT_HELLO; +#else + goto not_there; +#endif + if (strEQ(name, "MT_CLIENT_MASTER_KEY")) +#ifdef SSL_MT_CLIENT_MASTER_KEY + return SSL_MT_CLIENT_MASTER_KEY; +#else + goto not_there; +#endif + if (strEQ(name, "MT_ERROR")) +#ifdef SSL_MT_ERROR + return SSL_MT_ERROR; +#else + goto not_there; +#endif + if (strEQ(name, "MT_REQUEST_CERTIFICATE")) +#ifdef SSL_MT_REQUEST_CERTIFICATE + return SSL_MT_REQUEST_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "MT_SERVER_FINISHED")) +#ifdef SSL_MT_SERVER_FINISHED + return SSL_MT_SERVER_FINISHED; +#else + goto not_there; +#endif + if (strEQ(name, "MT_SERVER_HELLO")) +#ifdef SSL_MT_SERVER_HELLO + return SSL_MT_SERVER_HELLO; +#else + goto not_there; +#endif + if (strEQ(name, "MT_SERVER_VERIFY")) +#ifdef SSL_MT_SERVER_VERIFY + return SSL_MT_SERVER_VERIFY; +#else + goto not_there; +#endif + break; + case 'N': + if (strEQ(name, "NOTHING")) +#ifdef SSL_NOTHING + return SSL_NOTHING; +#else + goto not_there; +#endif + break; + case 'O': + if (strEQ(name, "OP_MICROSOFT_SESS_ID_BUG")) +#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG + return SSL_OP_MICROSOFT_SESS_ID_BUG; +#else + goto not_there; +#endif + if (strEQ(name, "OP_NETSCAPE_CHALLENGE_BUG")) +#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG + return SSL_OP_NETSCAPE_CHALLENGE_BUG; +#else + goto not_there; +#endif + if (strEQ(name, "OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG")) +#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG + return SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; +#else + goto not_there; +#endif + if (strEQ(name, "OP_SSLREF2_REUSE_CERT_TYPE_BUG")) +#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG + return SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG; +#else + goto not_there; +#endif + if (strEQ(name, "OP_MICROSOFT_BIG_SSLV3_BUFFER")) +#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER + return SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER; +#else + goto not_there; +#endif + if (strEQ(name, "OP_MSIE_SSLV2_RSA_PADDING")) +#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING + return SSL_OP_MSIE_SSLV2_RSA_PADDING; +#else + goto not_there; +#endif + if (strEQ(name, "OP_SSLEAY_080_CLIENT_DH_BUG")) +#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG + return SSL_OP_SSLEAY_080_CLIENT_DH_BUG; +#else + goto not_there; +#endif + if (strEQ(name, "OP_TLS_D5_BUG")) +#ifdef SSL_OP_TLS_D5_BUG + return SSL_OP_TLS_D5_BUG; +#else + goto not_there; +#endif + if (strEQ(name, "OP_SINGLE_DH_USE")) +#ifdef SSL_OP_SINGLE_DH_USE + return SSL_OP_SINGLE_DH_USE; +#else + goto not_there; +#endif + if (strEQ(name, "OP_EPHEMERAL_RSA")) +#ifdef SSL_OP_EPHEMERAL_RSA + return SSL_OP_EPHEMERAL_RSA; +#else + goto not_there; +#endif + if (strEQ(name, "OP_NETSCAPE_CA_DN_BUG")) +#ifdef SSL_OP_NETSCAPE_CA_DN_BUG + return SSL_OP_NETSCAPE_CA_DN_BUG; +#else + goto not_there; +#endif + if (strEQ(name, "OP_NON_EXPORT_FIRST")) +#ifdef SSL_OP_NON_EXPORT_FIRST + return SSL_OP_NON_EXPORT_FIRST; +#else + goto not_there; +#endif + if (strEQ(name, "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG")) +#ifdef SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG + return SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG; +#else + goto not_there; +#endif + if (strEQ(name, "OP_NO_SSLv2")) +#ifdef SSL_OP_NO_SSLv2 + return SSL_OP_NO_SSLv2; +#else + goto not_there; +#endif + if (strEQ(name, "OP_NO_SSLv3")) +#ifdef SSL_OP_NO_SSLv3 + return SSL_OP_NO_SSLv3; +#else + goto not_there; +#endif + if (strEQ(name, "OP_NO_TLSv1")) +#ifdef SSL_OP_NO_TLSv1 + return SSL_OP_NO_TLSv1; +#else + goto not_there; +#endif + if (strEQ(name, "OP_ALL")) +#ifdef SSL_OP_ALL + return SSL_OP_ALL; +#else + goto not_there; +#endif + + case 'P': + if (strEQ(name, "PE_BAD_CERTIFICATE")) +#ifdef SSL_PE_BAD_CERTIFICATE + return SSL_PE_BAD_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "PE_NO_CERTIFICATE")) +#ifdef SSL_PE_NO_CERTIFICATE + return SSL_PE_NO_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "PE_NO_CIPHER")) +#ifdef SSL_PE_NO_CIPHER + return SSL_PE_NO_CIPHER; +#else + goto not_there; +#endif + if (strEQ(name, "PE_UNSUPPORTED_CERTIFICATE_TYPE")) +#ifdef SSL_PE_UNSUPPORTED_CERTIFICATE_TYPE + return SSL_PE_UNSUPPORTED_CERTIFICATE_TYPE; +#else + goto not_there; +#endif + break; + case 'Q': + break; + case 'R': + if (strEQ(name, "READING")) +#ifdef SSL_READING + return SSL_READING; +#else + goto not_there; +#endif + if (strEQ(name, "RWERR_BAD_MAC_DECODE")) +#ifdef SSL_RWERR_BAD_MAC_DECODE + return SSL_RWERR_BAD_MAC_DECODE; +#else + goto not_there; +#endif + if (strEQ(name, "RWERR_BAD_WRITE_RETRY")) +#ifdef SSL_RWERR_BAD_WRITE_RETRY + return SSL_RWERR_BAD_WRITE_RETRY; +#else + goto not_there; +#endif + if (strEQ(name, "RWERR_INTERNAL_ERROR")) +#ifdef SSL_RWERR_INTERNAL_ERROR + return SSL_RWERR_INTERNAL_ERROR; +#else + goto not_there; +#endif + if (strEQ(name, "R_BAD_AUTHENTICATION_TYPE")) +#ifdef SSL_R_BAD_AUTHENTICATION_TYPE + return SSL_R_BAD_AUTHENTICATION_TYPE; +#else + goto not_there; +#endif + if (strEQ(name, "R_BAD_CHECKSUM")) +#ifdef SSL_R_BAD_CHECKSUM + return SSL_R_BAD_CHECKSUM; +#else + goto not_there; +#endif + if (strEQ(name, "R_BAD_MAC_DECODE")) +#ifdef SSL_R_BAD_MAC_DECODE + return SSL_R_BAD_MAC_DECODE; +#else + goto not_there; +#endif + if (strEQ(name, "R_BAD_RESPONSE_ARGUMENT")) +#ifdef SSL_R_BAD_RESPONSE_ARGUMENT + return SSL_R_BAD_RESPONSE_ARGUMENT; +#else + goto not_there; +#endif + if (strEQ(name, "R_BAD_SSL_FILETYPE")) +#ifdef SSL_R_BAD_SSL_FILETYPE + return SSL_R_BAD_SSL_FILETYPE; +#else + goto not_there; +#endif + if (strEQ(name, "R_BAD_SSL_SESSION_ID_LENGTH")) +#ifdef SSL_R_BAD_SSL_SESSION_ID_LENGTH + return SSL_R_BAD_SSL_SESSION_ID_LENGTH; +#else + goto not_there; +#endif + if (strEQ(name, "R_BAD_STATE")) +#ifdef SSL_R_BAD_STATE + return SSL_R_BAD_STATE; +#else + goto not_there; +#endif + if (strEQ(name, "R_BAD_WRITE_RETRY")) +#ifdef SSL_R_BAD_WRITE_RETRY + return SSL_R_BAD_WRITE_RETRY; +#else + goto not_there; +#endif + if (strEQ(name, "R_CHALLENGE_IS_DIFFERENT")) +#ifdef SSL_R_CHALLENGE_IS_DIFFERENT + return SSL_R_CHALLENGE_IS_DIFFERENT; +#else + goto not_there; +#endif + if (strEQ(name, "R_CIPHER_CODE_TOO_LONG")) +#ifdef SSL_R_CIPHER_CODE_TOO_LONG + return SSL_R_CIPHER_CODE_TOO_LONG; +#else + goto not_there; +#endif + if (strEQ(name, "R_CIPHER_TABLE_SRC_ERROR")) +#ifdef SSL_R_CIPHER_TABLE_SRC_ERROR + return SSL_R_CIPHER_TABLE_SRC_ERROR; +#else + goto not_there; +#endif + if (strEQ(name, "R_CONECTION_ID_IS_DIFFERENT")) +#ifdef SSL_R_CONECTION_ID_IS_DIFFERENT + return SSL_R_CONECTION_ID_IS_DIFFERENT; +#else + goto not_there; +#endif + if (strEQ(name, "R_INVALID_CHALLENGE_LENGTH")) +#ifdef SSL_R_INVALID_CHALLENGE_LENGTH + return SSL_R_INVALID_CHALLENGE_LENGTH; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_CERTIFICATE_SET")) +#ifdef SSL_R_NO_CERTIFICATE_SET + return SSL_R_NO_CERTIFICATE_SET; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_CERTIFICATE_SPECIFIED")) +#ifdef SSL_R_NO_CERTIFICATE_SPECIFIED + return SSL_R_NO_CERTIFICATE_SPECIFIED; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_CIPHER_LIST")) +#ifdef SSL_R_NO_CIPHER_LIST + return SSL_R_NO_CIPHER_LIST; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_CIPHER_MATCH")) +#ifdef SSL_R_NO_CIPHER_MATCH + return SSL_R_NO_CIPHER_MATCH; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_CIPHER_WE_TRUST")) +#ifdef SSL_R_NO_CIPHER_WE_TRUST + return SSL_R_NO_CIPHER_WE_TRUST; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_PRIVATEKEY")) +#ifdef SSL_R_NO_PRIVATEKEY + return SSL_R_NO_PRIVATEKEY; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_PUBLICKEY")) +#ifdef SSL_R_NO_PUBLICKEY + return SSL_R_NO_PUBLICKEY; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_READ_METHOD_SET")) +#ifdef SSL_R_NO_READ_METHOD_SET + return SSL_R_NO_READ_METHOD_SET; +#else + goto not_there; +#endif + if (strEQ(name, "R_NO_WRITE_METHOD_SET")) +#ifdef SSL_R_NO_WRITE_METHOD_SET + return SSL_R_NO_WRITE_METHOD_SET; +#else + goto not_there; +#endif + if (strEQ(name, "R_NULL_SSL_CTX")) +#ifdef SSL_R_NULL_SSL_CTX + return SSL_R_NULL_SSL_CTX; +#else + goto not_there; +#endif + if (strEQ(name, "R_PEER_DID_NOT_RETURN_A_CERTIFICATE")) +#ifdef SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE + return SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "R_PEER_ERROR")) +#ifdef SSL_R_PEER_ERROR + return SSL_R_PEER_ERROR; +#else + goto not_there; +#endif + if (strEQ(name, "R_PEER_ERROR_CERTIFICATE")) +#ifdef SSL_R_PEER_ERROR_CERTIFICATE + return SSL_R_PEER_ERROR_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "R_PEER_ERROR_NO_CIPHER")) +#ifdef SSL_R_PEER_ERROR_NO_CIPHER + return SSL_R_PEER_ERROR_NO_CIPHER; +#else + goto not_there; +#endif + if (strEQ(name, "R_PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE")) +#ifdef SSL_R_PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE + return SSL_R_PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE; +#else + goto not_there; +#endif + if (strEQ(name, "R_PERR_ERROR_NO_CERTIFICATE")) +#ifdef SSL_R_PERR_ERROR_NO_CERTIFICATE + return SSL_R_PERR_ERROR_NO_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "R_PUBLIC_KEY_ENCRYPT_ERROR")) +#ifdef SSL_R_PUBLIC_KEY_ENCRYPT_ERROR + return SSL_R_PUBLIC_KEY_ENCRYPT_ERROR; +#else + goto not_there; +#endif + if (strEQ(name, "R_PUBLIC_KEY_IS_NOT_RSA")) +#ifdef SSL_R_PUBLIC_KEY_IS_NOT_RSA + return SSL_R_PUBLIC_KEY_IS_NOT_RSA; +#else + goto not_there; +#endif + if (strEQ(name, "R_PUBLIC_KEY_NO_RSA")) +#ifdef SSL_R_PUBLIC_KEY_NO_RSA + return SSL_R_PUBLIC_KEY_NO_RSA; +#else + goto not_there; +#endif + if (strEQ(name, "R_READ_WRONG_PACKET_TYPE")) +#ifdef SSL_R_READ_WRONG_PACKET_TYPE + return SSL_R_READ_WRONG_PACKET_TYPE; +#else + goto not_there; +#endif + if (strEQ(name, "R_REVERSE_KEY_ARG_LENGTH_IS_WRONG")) +#ifdef SSL_R_REVERSE_KEY_ARG_LENGTH_IS_WRONG + return SSL_R_REVERSE_KEY_ARG_LENGTH_IS_WRONG; +#else + goto not_there; +#endif + if (strEQ(name, "R_REVERSE_MASTER_KEY_LENGTH_IS_WRONG")) +#ifdef SSL_R_REVERSE_MASTER_KEY_LENGTH_IS_WRONG + return SSL_R_REVERSE_MASTER_KEY_LENGTH_IS_WRONG; +#else + goto not_there; +#endif + if (strEQ(name, "R_REVERSE_SSL_SESSION_ID_LENGTH_IS_WRONG")) +#ifdef SSL_R_REVERSE_SSL_SESSION_ID_LENGTH_IS_WRONG + return SSL_R_REVERSE_SSL_SESSION_ID_LENGTH_IS_WRONG; +#else + goto not_there; +#endif + if (strEQ(name, "R_SHORT_READ")) +#ifdef SSL_R_SHORT_READ + return SSL_R_SHORT_READ; +#else + goto not_there; +#endif + if (strEQ(name, "R_SSL_SESSION_ID_IS_DIFFERENT")) +#ifdef SSL_R_SSL_SESSION_ID_IS_DIFFERENT + return SSL_R_SSL_SESSION_ID_IS_DIFFERENT; +#else + goto not_there; +#endif + if (strEQ(name, "R_UNABLE_TO_EXTRACT_PUBLIC_KEY")) +#ifdef SSL_R_UNABLE_TO_EXTRACT_PUBLIC_KEY + return SSL_R_UNABLE_TO_EXTRACT_PUBLIC_KEY; +#else + goto not_there; +#endif + if (strEQ(name, "R_UNDEFINED_INIT_STATE")) +#ifdef SSL_R_UNDEFINED_INIT_STATE + return SSL_R_UNDEFINED_INIT_STATE; +#else + goto not_there; +#endif + if (strEQ(name, "R_UNKNOWN_REMOTE_ERROR_TYPE")) +#ifdef SSL_R_UNKNOWN_REMOTE_ERROR_TYPE + return SSL_R_UNKNOWN_REMOTE_ERROR_TYPE; +#else + goto not_there; +#endif + if (strEQ(name, "R_UNKNOWN_STATE")) +#ifdef SSL_R_UNKNOWN_STATE + return SSL_R_UNKNOWN_STATE; +#else + goto not_there; +#endif + if (strEQ(name, "R_UNSUPORTED_CIPHER")) +#ifdef SSL_R_UNSUPORTED_CIPHER + return SSL_R_UNSUPORTED_CIPHER; +#else + goto not_there; +#endif + if (strEQ(name, "R_WRONG_PUBLIC_KEY_TYPE")) +#ifdef SSL_R_WRONG_PUBLIC_KEY_TYPE + return SSL_R_WRONG_PUBLIC_KEY_TYPE; +#else + goto not_there; +#endif + if (strEQ(name, "R_X509_LIB")) +#ifdef SSL_R_X509_LIB + return SSL_R_X509_LIB; +#else + goto not_there; +#endif + break; + case 'S': + if (strEQ(name, "SERVER_VERSION")) +#ifdef SSL_SERVER_VERSION + return SSL_SERVER_VERSION; +#else + goto not_there; +#endif + if (strEQ(name, "SESSION_ASN1_VERSION")) +#ifdef SSL_SESSION_ASN1_VERSION + return SSL_SESSION_ASN1_VERSION; +#else + goto not_there; +#endif + if (strEQ(name, "ST_ACCEPT")) +#ifdef SSL_ST_ACCEPT + return SSL_ST_ACCEPT; +#else + goto not_there; +#endif + if (strEQ(name, "ST_BEFORE")) +#ifdef SSL_ST_BEFORE + return SSL_ST_BEFORE; +#else + goto not_there; +#endif + if (strEQ(name, "ST_CLIENT_START_ENCRYPTION")) +#ifdef SSL_ST_CLIENT_START_ENCRYPTION + return SSL_ST_CLIENT_START_ENCRYPTION; +#else + goto not_there; +#endif + if (strEQ(name, "ST_CONNECT")) +#ifdef SSL_ST_CONNECT + return SSL_ST_CONNECT; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_CLIENT_FINISHED_A")) +#ifdef SSL_ST_GET_CLIENT_FINISHED_A + return SSL_ST_GET_CLIENT_FINISHED_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_CLIENT_FINISHED_B")) +#ifdef SSL_ST_GET_CLIENT_FINISHED_B + return SSL_ST_GET_CLIENT_FINISHED_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_CLIENT_HELLO_A")) +#ifdef SSL_ST_GET_CLIENT_HELLO_A + return SSL_ST_GET_CLIENT_HELLO_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_CLIENT_HELLO_B")) +#ifdef SSL_ST_GET_CLIENT_HELLO_B + return SSL_ST_GET_CLIENT_HELLO_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_CLIENT_MASTER_KEY_A")) +#ifdef SSL_ST_GET_CLIENT_MASTER_KEY_A + return SSL_ST_GET_CLIENT_MASTER_KEY_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_CLIENT_MASTER_KEY_B")) +#ifdef SSL_ST_GET_CLIENT_MASTER_KEY_B + return SSL_ST_GET_CLIENT_MASTER_KEY_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_SERVER_FINISHED_A")) +#ifdef SSL_ST_GET_SERVER_FINISHED_A + return SSL_ST_GET_SERVER_FINISHED_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_SERVER_FINISHED_B")) +#ifdef SSL_ST_GET_SERVER_FINISHED_B + return SSL_ST_GET_SERVER_FINISHED_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_SERVER_HELLO_A")) +#ifdef SSL_ST_GET_SERVER_HELLO_A + return SSL_ST_GET_SERVER_HELLO_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_SERVER_HELLO_B")) +#ifdef SSL_ST_GET_SERVER_HELLO_B + return SSL_ST_GET_SERVER_HELLO_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_SERVER_VERIFY_A")) +#ifdef SSL_ST_GET_SERVER_VERIFY_A + return SSL_ST_GET_SERVER_VERIFY_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_GET_SERVER_VERIFY_B")) +#ifdef SSL_ST_GET_SERVER_VERIFY_B + return SSL_ST_GET_SERVER_VERIFY_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_INIT")) +#ifdef SSL_ST_INIT + return SSL_ST_INIT; +#else + goto not_there; +#endif + if (strEQ(name, "ST_OK")) +#ifdef SSL_ST_OK + return SSL_ST_OK; +#else + goto not_there; +#endif + if (strEQ(name, "ST_READ_BODY")) +#ifdef SSL_ST_READ_BODY + return SSL_ST_READ_BODY; +#else + goto not_there; +#endif + if (strEQ(name, "ST_READ_HEADER")) +#ifdef SSL_ST_READ_HEADER + return SSL_ST_READ_HEADER; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_CERTIFICATE_A")) +#ifdef SSL_ST_SEND_CLIENT_CERTIFICATE_A + return SSL_ST_SEND_CLIENT_CERTIFICATE_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_CERTIFICATE_B")) +#ifdef SSL_ST_SEND_CLIENT_CERTIFICATE_B + return SSL_ST_SEND_CLIENT_CERTIFICATE_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_CERTIFICATE_C")) +#ifdef SSL_ST_SEND_CLIENT_CERTIFICATE_C + return SSL_ST_SEND_CLIENT_CERTIFICATE_C; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_CERTIFICATE_D")) +#ifdef SSL_ST_SEND_CLIENT_CERTIFICATE_D + return SSL_ST_SEND_CLIENT_CERTIFICATE_D; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_FINISHED_A")) +#ifdef SSL_ST_SEND_CLIENT_FINISHED_A + return SSL_ST_SEND_CLIENT_FINISHED_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_FINISHED_B")) +#ifdef SSL_ST_SEND_CLIENT_FINISHED_B + return SSL_ST_SEND_CLIENT_FINISHED_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_HELLO_A")) +#ifdef SSL_ST_SEND_CLIENT_HELLO_A + return SSL_ST_SEND_CLIENT_HELLO_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_HELLO_B")) +#ifdef SSL_ST_SEND_CLIENT_HELLO_B + return SSL_ST_SEND_CLIENT_HELLO_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_MASTER_KEY_A")) +#ifdef SSL_ST_SEND_CLIENT_MASTER_KEY_A + return SSL_ST_SEND_CLIENT_MASTER_KEY_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_CLIENT_MASTER_KEY_B")) +#ifdef SSL_ST_SEND_CLIENT_MASTER_KEY_B + return SSL_ST_SEND_CLIENT_MASTER_KEY_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_REQUEST_CERTIFICATE_A")) +#ifdef SSL_ST_SEND_REQUEST_CERTIFICATE_A + return SSL_ST_SEND_REQUEST_CERTIFICATE_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_REQUEST_CERTIFICATE_B")) +#ifdef SSL_ST_SEND_REQUEST_CERTIFICATE_B + return SSL_ST_SEND_REQUEST_CERTIFICATE_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_REQUEST_CERTIFICATE_C")) +#ifdef SSL_ST_SEND_REQUEST_CERTIFICATE_C + return SSL_ST_SEND_REQUEST_CERTIFICATE_C; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_REQUEST_CERTIFICATE_D")) +#ifdef SSL_ST_SEND_REQUEST_CERTIFICATE_D + return SSL_ST_SEND_REQUEST_CERTIFICATE_D; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_SERVER_FINISHED_A")) +#ifdef SSL_ST_SEND_SERVER_FINISHED_A + return SSL_ST_SEND_SERVER_FINISHED_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_SERVER_FINISHED_B")) +#ifdef SSL_ST_SEND_SERVER_FINISHED_B + return SSL_ST_SEND_SERVER_FINISHED_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_SERVER_HELLO_A")) +#ifdef SSL_ST_SEND_SERVER_HELLO_A + return SSL_ST_SEND_SERVER_HELLO_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_SERVER_HELLO_B")) +#ifdef SSL_ST_SEND_SERVER_HELLO_B + return SSL_ST_SEND_SERVER_HELLO_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_SERVER_VERIFY_A")) +#ifdef SSL_ST_SEND_SERVER_VERIFY_A + return SSL_ST_SEND_SERVER_VERIFY_A; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SEND_SERVER_VERIFY_B")) +#ifdef SSL_ST_SEND_SERVER_VERIFY_B + return SSL_ST_SEND_SERVER_VERIFY_B; +#else + goto not_there; +#endif + if (strEQ(name, "ST_SERVER_START_ENCRYPTION")) +#ifdef SSL_ST_SERVER_START_ENCRYPTION + return SSL_ST_SERVER_START_ENCRYPTION; +#else + goto not_there; +#endif + if (strEQ(name, "ST_X509_GET_CLIENT_CERTIFICATE")) +#ifdef SSL_ST_X509_GET_CLIENT_CERTIFICATE + return SSL_ST_X509_GET_CLIENT_CERTIFICATE; +#else + goto not_there; +#endif + if (strEQ(name, "ST_X509_GET_SERVER_CERTIFICATE")) +#ifdef SSL_ST_X509_GET_SERVER_CERTIFICATE + return SSL_ST_X509_GET_SERVER_CERTIFICATE; +#else + goto not_there; +#endif + break; + case 'T': +#if 0 + if (strEQ(name, "TXT_DES_192_EDE3_CBC_WITH_MD5")) +#ifdef SSL_TXT_DES_192_EDE3_CBC_WITH_MD5 + return SSL_TXT_DES_192_EDE3_CBC_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_DES_192_EDE3_CBC_WITH_SHA")) +#ifdef SSL_TXT_DES_192_EDE3_CBC_WITH_SHA + return SSL_TXT_DES_192_EDE3_CBC_WITH_SHA; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_DES_64_CBC_WITH_MD5")) +#ifdef SSL_TXT_DES_64_CBC_WITH_MD5 + return SSL_TXT_DES_64_CBC_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_DES_64_CBC_WITH_SHA")) +#ifdef SSL_TXT_DES_64_CBC_WITH_SHA + return SSL_TXT_DES_64_CBC_WITH_SHA; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_DES_64_CFB64_WITH_MD5_1")) +#ifdef SSL_TXT_DES_64_CFB64_WITH_MD5_1 + return SSL_TXT_DES_64_CFB64_WITH_MD5_1; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_IDEA_128_CBC_WITH_MD5")) +#ifdef SSL_TXT_IDEA_128_CBC_WITH_MD5 + return SSL_TXT_IDEA_128_CBC_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_NULL")) +#ifdef SSL_TXT_NULL + return SSL_TXT_NULL; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_NULL_WITH_MD5")) +#ifdef SSL_TXT_NULL_WITH_MD5 + return SSL_TXT_NULL_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_RC2_128_CBC_EXPORT40_WITH_MD5")) +#ifdef SSL_TXT_RC2_128_CBC_EXPORT40_WITH_MD5 + return SSL_TXT_RC2_128_CBC_EXPORT40_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_RC2_128_CBC_WITH_MD5")) +#ifdef SSL_TXT_RC2_128_CBC_WITH_MD5 + return SSL_TXT_RC2_128_CBC_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_RC4_128_EXPORT40_WITH_MD5")) +#ifdef SSL_TXT_RC4_128_EXPORT40_WITH_MD5 + return SSL_TXT_RC4_128_EXPORT40_WITH_MD5; +#else + goto not_there; +#endif + if (strEQ(name, "TXT_RC4_128_WITH_MD5")) +#ifdef SSL_TXT_RC4_128_WITH_MD5 + return SSL_TXT_RC4_128_WITH_MD5; +#else + goto not_there; +#endif +#endif + break; + case 'U': + break; + case 'V': + if (strEQ(name, "VERIFY_CLIENT_ONCE")) +#ifdef SSL_VERIFY_CLIENT_ONCE + return SSL_VERIFY_CLIENT_ONCE; +#else + goto not_there; +#endif + if (strEQ(name, "VERIFY_FAIL_IF_NO_PEER_CERT")) +#ifdef SSL_VERIFY_FAIL_IF_NO_PEER_CERT + return SSL_VERIFY_FAIL_IF_NO_PEER_CERT; +#else + goto not_there; +#endif + if (strEQ(name, "VERIFY_NONE")) +#ifdef SSL_VERIFY_NONE + return SSL_VERIFY_NONE; +#else + goto not_there; +#endif + if (strEQ(name, "VERIFY_PEER")) +#ifdef SSL_VERIFY_PEER + return SSL_VERIFY_PEER; +#else + goto not_there; +#endif + break; + case 'W': + if (strEQ(name, "WRITING")) +#ifdef SSL_WRITING + return SSL_WRITING; +#else + goto not_there; +#endif + break; + case 'X': + if (strEQ(name, "X509_LOOKUP")) +#ifdef SSL_X509_LOOKUP + return SSL_X509_LOOKUP; +#else + goto not_there; +#endif + break; + case 'Y': + break; + case 'Z': + break; + } + errno = EINVAL; + return 0; + +not_there: + errno = ENOENT; + return 0; +} + +/* ============= callback stuff ============== */ + +static SV * ssleay_verify_callback = (SV*)NULL; + +static int +ssleay_verify_callback_glue (int ok, X509_STORE_CTX* ctx) +{ + dSP ; + int count,res; + + ENTER ; + SAVETMPS; + + PRN("verify callback glue", ok); + + PUSHMARK(sp); + XPUSHs(sv_2mortal(newSViv(ok))); + XPUSHs(sv_2mortal(newSViv((int)ctx))); + PUTBACK ; + + if (ssleay_verify_callback == NULL) + croak ("Net::SSLeay: verify_callback called, but not " + "set to point to any perl function.\n"); + + PR("About to call verify callback.\n"); + count = perl_call_sv(ssleay_verify_callback, G_SCALAR); + PR("Returned from verify callback.\n"); + + SPAGAIN; + + if (count != 1) + croak ( "Net::SSLeay: verify_callback " + "perl function did not return a scalar.\n"); + res = POPi ; + + PUTBACK ; + FREETMPS ; + LEAVE ; + + return POPi; +} + +static SV * ssleay_ctx_verify_callback = (SV*)NULL; + +static int +ssleay_ctx_verify_callback_glue (int ok, X509_STORE_CTX* ctx) +{ + dSP ; + int count,res; + + ENTER ; + SAVETMPS; + + PRN("ctx verify callback glue", ok); + + PUSHMARK(sp); + XPUSHs(sv_2mortal(newSViv(ok))); + XPUSHs(sv_2mortal(newSViv((int)ctx))); + PUTBACK ; + + if (ssleay_ctx_verify_callback == NULL) + croak ("Net::SSLeay: ctx_verify_callback called, but not " + "set to point to any perl function.\n"); + + PR("About to call ctx verify callback.\n"); + count = perl_call_sv(ssleay_ctx_verify_callback, G_SCALAR); + PR("Returned from ctx verify callback.\n"); + + SPAGAIN; + + if (count != 1) + croak ( "Net::SSLeay: ctx_verify_callback " + "perl function did not return a scalar.\n"); + res = POPi ; + + PUTBACK ; + FREETMPS ; + LEAVE ; + + return POPi; +} + +#line 1537 "SSLeay.c" +XS(XS_Net__SSLeay_constant) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::constant(name, arg)"); + { + char * name = (char *)SvPV(ST(0),PL_na); + int arg = (int)SvIV(ST(1)); + double RETVAL; + dXSTARG; + + RETVAL = constant(name, arg); + XSprePUSH; PUSHn((double)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_hello) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::hello()"); + { + int RETVAL; + dXSTARG; +#line 1539 "SSLeay.xs" + PR("\tSSLeay Hello World!\n"); + RETVAL = 1; +#line 1566 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +#define REM1 "============= SSL CONTEXT functions ==============" +XS(XS_Net__SSLeay_CTX_new) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_new()"); + { + SSL_CTX * RETVAL; + dXSTARG; +#line 1549 "SSLeay.xs" + RETVAL = SSL_CTX_new (SSLv23_method()); +#line 1583 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_v2_new) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_v2_new()"); + { + SSL_CTX * RETVAL; + dXSTARG; +#line 1556 "SSLeay.xs" + RETVAL = SSL_CTX_new (SSLv2_method()); +#line 1599 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_v3_new) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_v3_new()"); + { + SSL_CTX * RETVAL; + dXSTARG; +#line 1563 "SSLeay.xs" + RETVAL = SSL_CTX_new (SSLv3_method()); +#line 1615 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_v23_new) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_v23_new()"); + { + SSL_CTX * RETVAL; + dXSTARG; +#line 1570 "SSLeay.xs" + RETVAL = SSL_CTX_new (SSLv23_method()); +#line 1631 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_free) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_free(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + + SSL_CTX_free(ctx); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_CTX_add_session) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_add_session(ctx, ses)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_add_session(ctx, ses); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_remove_session) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_remove_session(ctx, ses)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_remove_session(ctx, ses); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_flush_sessions) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_flush_sessions(ctx, tm)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + SInt32 tm = (SInt32)SvIV(ST(1)); + + SSL_CTX_flush_sessions(ctx, tm); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_CTX_set_default_verify_paths) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_set_default_verify_paths(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_set_default_verify_paths(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_load_verify_locations) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_load_verify_locations(ctx, CAfile, CApath)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + char * CAfile = (char *)SvPV(ST(1),PL_na); + char * CApath = (char *)SvPV(ST(2),PL_na); + int RETVAL; + dXSTARG; +#line 1603 "SSLeay.xs" + RETVAL = SSL_CTX_load_verify_locations (ctx, + CAfile?(*CAfile?CAfile:NULL):NULL, + CApath?(*CApath?CApath:NULL):NULL + ); +#line 1730 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_set_verify) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_set_verify(ctx, mode, callback)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int mode = (int)SvIV(ST(1)); + SV * callback = ST(2); +#line 1616 "SSLeay.xs" + if (ssleay_ctx_verify_callback == (SV*)NULL) { + ssleay_ctx_verify_callback = newSVsv(callback); + } else { + SvSetSV (ssleay_ctx_verify_callback, callback); + } + if (SvTRUE(ssleay_ctx_verify_callback)) { + SSL_CTX_set_verify(ctx,mode,&ssleay_ctx_verify_callback_glue); + } else { + SSL_CTX_set_verify(ctx,mode,NULL); + } +#line 1756 "SSLeay.c" + } + XSRETURN_EMPTY; +} + +#define REM10 "============= SSL functions ==============" +XS(XS_Net__SSLeay_new) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::new(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + SSL * RETVAL; + dXSTARG; + + RETVAL = SSL_new(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_free) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::free(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + + SSL_free(s); + } + XSRETURN_EMPTY; +} + +#if 0 /* this seems to be gone in 0.9.0 */ +#define XSubPPtmpAAAA 1 + +XS(XS_Net__SSLeay_debug) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::debug(file)"); + { + char * file = (char *)SvPV(ST(0),PL_na); + + SSL_debug(file); + } + XSRETURN_EMPTY; +} + +#endif +XS(XS_Net__SSLeay_accept) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::accept(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_accept(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_clear) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::clear(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + + SSL_clear(s); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_connect) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::connect(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_connect(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +#if defined(WIN32) +#define XSubPPtmpAAAB 1 + +XS(XS_Net__SSLeay_set_fd) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_fd(s, fd)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int fd = (int)SvIV(ST(1)); + int RETVAL; + dXSTARG; +#line 1664 "SSLeay.xs" + RETVAL = SSL_set_fd(s,_get_osfhandle(fd)); +#line 1868 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_rfd) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_rfd(s, fd)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int fd = (int)SvIV(ST(1)); + int RETVAL; + dXSTARG; +#line 1673 "SSLeay.xs" + RETVAL = SSL_set_rfd(s,_get_osfhandle(fd)); +#line 1886 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_wfd) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_wfd(s, fd)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int fd = (int)SvIV(ST(1)); + int RETVAL; + dXSTARG; +#line 1682 "SSLeay.xs" + RETVAL = SSL_set_wfd(s,_get_osfhandle(fd)); +#line 1904 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +#else +#define XSubPPtmpAAAC 1 + +XS(XS_Net__SSLeay_set_fd) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_fd(s, fd)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int fd = (int)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_set_fd(s, fd); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_rfd) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_rfd(s, fd)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int fd = (int)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_set_rfd(s, fd); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_wfd) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_wfd(s, fd)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int fd = (int)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_set_wfd(s, fd); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +#endif +XS(XS_Net__SSLeay_get_fd) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_fd(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_get_fd(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_read) +{ + dXSARGS; + if (items < 1 || items > 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::read(s, max=sizeof(buf))"); + { + SSL * s = (SSL *)SvIV(ST(0)); +#line 1713 "SSLeay.xs" + char buf[32768]; +#line 1990 "SSLeay.c" + int max; +#line 1717 "SSLeay.xs" + int got; +#line 1994 "SSLeay.c" + + if (items < 2) + max = sizeof(buf); + else { + max = (int)SvIV(ST(1)); + } +#line 1719 "SSLeay.xs" + ST(0) = sv_newmortal(); /* Undefined to start with */ + if ((got = SSL_read(s, buf, max)) >= 0) + sv_setpvn( ST(0), buf, got); +#line 2005 "SSLeay.c" + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_write) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::write(s, buf)"); + { + SSL * s = (SSL *)SvIV(ST(0)); +#line 1727 "SSLeay.xs" + STRLEN len; +#line 2019 "SSLeay.c" + char * buf = SvPV( ST(1), len); + int RETVAL; + dXSTARG; +#line 1731 "SSLeay.xs" + RETVAL = SSL_write (s, buf, (int)len); +#line 2025 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_write_partial) +{ + dXSARGS; + if (items != 4) + Perl_croak(aTHX_ "Usage: Net::SSLeay::write_partial(s, from, count, buf)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int from = (int)SvIV(ST(1)); + int count = (int)SvIV(ST(2)); +#line 1741 "SSLeay.xs" + STRLEN len; +#line 2042 "SSLeay.c" + char * buf = SvPV( ST(3), len); + int RETVAL; + dXSTARG; +#line 1745 "SSLeay.xs" + /* + if (SvROK( ST(3) )) { + SV* t = SvRV( ST(3) ); + buf = SvPV( t, len); + } else + buf = SvPV( ST(3), len); + */ + PRN("write_partial from",from); + PRN(&buf[from],len); + PRN("write_partial count",count); + len -= from; + if (len < 0) { + croak("from beyound end of buffer"); + RETVAL = -1; + } else + RETVAL = SSL_write (s, &(buf[from]), (count<=len)?count:len); +#line 2063 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_RSAPrivateKey) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_RSAPrivateKey(s, rsa)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + RSA * rsa = (RSA *)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_RSAPrivateKey(s, rsa); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_RSAPrivateKey_ASN1) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_RSAPrivateKey_ASN1(s, d, len)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + unsigned char * d = (unsigned char *)SvPV(ST(1),PL_na); + SInt32 len = (SInt32)SvIV(ST(2)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_RSAPrivateKey_ASN1(s, d, len); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_RSAPrivateKey_file) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_RSAPrivateKey_file(s, file, type)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * file = (char *)SvPV(ST(1),PL_na); + int type = (int)SvIV(ST(2)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_RSAPrivateKey_file(s, file, type); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_use_RSAPrivateKey_file) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_use_RSAPrivateKey_file(ctx, file, type)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + char * file = (char *)SvPV(ST(1),PL_na); + int type = (int)SvIV(ST(2)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_use_RSAPrivateKey_file(ctx, file, type); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_PrivateKey) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_PrivateKey(s, pkey)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + EVP_PKEY * pkey = (EVP_PKEY *)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_PrivateKey(s, pkey); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_PrivateKey_ASN1) +{ + dXSARGS; + if (items != 4) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_PrivateKey_ASN1(pk, s, d, len)"); + { + int pk = (int)SvIV(ST(0)); + SSL * s = (SSL *)SvIV(ST(1)); + unsigned char * d = (unsigned char *)SvPV(ST(2),PL_na); + SInt32 len = (SInt32)SvIV(ST(3)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_PrivateKey_ASN1(pk, s, d, len); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_PrivateKey_file) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_PrivateKey_file(s, file, type)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * file = (char *)SvPV(ST(1),PL_na); + int type = (int)SvIV(ST(2)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_PrivateKey_file(s, file, type); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_use_PrivateKey_file) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_use_PrivateKey_file(ctx, file, type)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + char * file = (char *)SvPV(ST(1),PL_na); + int type = (int)SvIV(ST(2)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_use_PrivateKey_file(ctx, file, type); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_certificate) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_certificate(s, x)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + X509 * x = (X509 *)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_certificate(s, x); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_certificate_ASN1) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_certificate_ASN1(s, d, len)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + unsigned char * d = (unsigned char *)SvPV(ST(1),PL_na); + SInt32 len = (SInt32)SvIV(ST(2)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_certificate_ASN1(s, d, len); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_use_certificate_file) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::use_certificate_file(s, file, type)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * file = (char *)SvPV(ST(1),PL_na); + int type = (int)SvIV(ST(2)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_use_certificate_file(s, file, type); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_use_certificate_file) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_use_certificate_file(ctx, file, type)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + char * file = (char *)SvPV(ST(1),PL_na); + int type = (int)SvIV(ST(2)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_use_certificate_file(ctx, file, type); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_state_string) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::state_string(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * RETVAL; + dXSTARG; + + RETVAL = SSL_state_string(s); + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_rstate_string) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::rstate_string(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * RETVAL; + dXSTARG; + + RETVAL = SSL_rstate_string(s); + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_state_string_long) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::state_string_long(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * RETVAL; + dXSTARG; + + RETVAL = SSL_state_string_long(s); + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_rstate_string_long) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::rstate_string_long(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * RETVAL; + dXSTARG; + + RETVAL = SSL_rstate_string_long(s); + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_time) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_time(ses)"); + { + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(0)); + SInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_get_time(ses); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_time) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_time(ses, t)"); + { + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(0)); + SInt32 t = (SInt32)SvIV(ST(1)); + SInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_set_time(ses, t); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_timeout) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_timeout(ses)"); + { + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(0)); + SInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_get_timeout(ses); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_timeout) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_timeout(ses, t)"); + { + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(0)); + SInt32 t = (SInt32)SvIV(ST(1)); + SInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_set_timeout(ses, t); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_copy_session_id) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::copy_session_id(to, from)"); + { + SSL * to = (SSL *)SvIV(ST(0)); + SSL * from = (SSL *)SvIV(ST(1)); + + SSL_copy_session_id(to, from); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_set_read_ahead) +{ + dXSARGS; + if (items < 1 || items > 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_read_ahead(s, yes=1)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int yes; + + if (items < 2) + yes = 1; + else { + yes = (int)SvIV(ST(1)); + } + + SSL_set_read_ahead(s, yes); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_get_read_ahead) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_read_ahead(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_get_read_ahead(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_pending) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::pending(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_pending(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_set_cipher_list) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_set_cipher_list(s, str)"); + { + SSL_CTX * s = (SSL_CTX *)SvIV(ST(0)); + char * str = (char *)SvPV(ST(1),PL_na); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_set_cipher_list(s, str); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_cipher_list) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_cipher_list(s, n)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int n = (int)SvIV(ST(1)); + char * RETVAL; + dXSTARG; + + RETVAL = SSL_get_cipher_list(s, n); + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_cipher_list) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_cipher_list(s, str)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * str = (char *)SvPV(ST(1),PL_na); + int RETVAL; + dXSTARG; + + RETVAL = SSL_set_cipher_list(s, str); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_cipher) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_cipher(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * RETVAL; + dXSTARG; + + RETVAL = SSL_get_cipher(s); + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_shared_ciphers) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_shared_ciphers(s, buf, len)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + char * buf = (char *)SvPV(ST(1),PL_na); + int len = (int)SvIV(ST(2)); + char * RETVAL; + dXSTARG; + + RETVAL = SSL_get_shared_ciphers(s, buf, len); + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_peer_certificate) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_peer_certificate(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + X509 * RETVAL; + dXSTARG; + + RETVAL = SSL_get_peer_certificate(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_verify) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_verify(s, mode, callback)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int mode = (int)SvIV(ST(1)); + SV * callback = ST(2); +#line 1922 "SSLeay.xs" + if (ssleay_verify_callback == (SV*)NULL) + ssleay_verify_callback = newSVsv(callback); + else + SvSetSV (ssleay_verify_callback, callback); + if (SvTRUE(ssleay_verify_callback)) { + SSL_set_verify(s,mode,&ssleay_verify_callback_glue); + } else { + SSL_set_verify(s,mode,NULL); + } +#line 2599 "SSLeay.c" + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_set_bio) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_bio(s, rbio, wbio)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + BIO * rbio = (BIO *)SvIV(ST(1)); + BIO * wbio = (BIO *)SvIV(ST(2)); + + SSL_set_bio(s, rbio, wbio); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_get_rbio) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_rbio(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + BIO * RETVAL; + dXSTARG; + + RETVAL = SSL_get_rbio(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_wbio) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_wbio(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + BIO * RETVAL; + dXSTARG; + + RETVAL = SSL_get_wbio(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_SESSION_new) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::SESSION_new()"); + { + SSL_SESSION * RETVAL; + dXSTARG; + + RETVAL = SSL_SESSION_new(); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_SESSION_print) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::SESSION_print(fp, ses)"); + { + BIO * fp = (BIO *)SvIV(ST(0)); + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_SESSION_print(fp, ses); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_SESSION_free) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::SESSION_free(ses)"); + { + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(0)); + + SSL_SESSION_free(ses); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_i2d_SSL_SESSION) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::i2d_SSL_SESSION(in, pp)"); + { + SSL_SESSION * in = (SSL_SESSION *)SvIV(ST(0)); + unsigned char * pp = (unsigned char *)SvPV(ST(1),PL_na); + int RETVAL; + dXSTARG; + + RETVAL = i2d_SSL_SESSION(in, &pp); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_session) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_session(to, ses)"); + { + SSL * to = (SSL *)SvIV(ST(0)); + SSL_SESSION * ses = (SSL_SESSION *)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_set_session(to, ses); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_d2i_SSL_SESSION) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::d2i_SSL_SESSION(a, pp, length)"); + { + SSL_SESSION * a = (SSL_SESSION *)SvIV(ST(0)); + unsigned char * pp = (unsigned char *)SvPV(ST(1),PL_na); + SInt32 length = (SInt32)SvIV(ST(2)); + SSL_SESSION * RETVAL; + dXSTARG; + + RETVAL = d2i_SSL_SESSION(&a, &pp, length); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +#define REM30 "SSLeay-0.9.0 defines these as macros. I expand them here for safety's sake" +XS(XS_Net__SSLeay_get_session) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_session(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + SSL_SESSION * RETVAL; + dXSTARG; + + RETVAL = SSL_get_session(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_certificate) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_certificate(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + X509 * RETVAL; + dXSTARG; + + RETVAL = SSL_get_certificate(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_SSL_CTX) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_SSL_CTX(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + SSL_CTX * RETVAL; + dXSTARG; + + RETVAL = SSL_get_SSL_CTX(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_ctrl) +{ + dXSARGS; + if (items != 4) + Perl_croak(aTHX_ "Usage: Net::SSLeay::ctrl(ssl, cmd, larg, parg)"); + { + SSL * ssl = (SSL *)SvIV(ST(0)); + int cmd = (int)SvIV(ST(1)); + SInt32 larg = (SInt32)SvIV(ST(2)); + char * parg = (char *)SvPV(ST(3),PL_na); + SInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_ctrl(ssl, cmd, larg, parg); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_ctrl) +{ + dXSARGS; + if (items != 4) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_ctrl(ctx, cmd, larg, parg)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int cmd = (int)SvIV(ST(1)); + SInt32 larg = (SInt32)SvIV(ST(2)); + char * parg = (char *)SvPV(ST(3),PL_na); + SInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_ctrl(ctx, cmd, larg, parg); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_get_options) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::get_options(ssl)"); + { + SSL * ssl = (SSL *)SvIV(ST(0)); + SInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_get_options(ssl); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_set_options) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::set_options(ssl, op)"); + { + SSL * ssl = (SSL *)SvIV(ST(0)); + UInt32 op = (UInt32)SvUV(ST(1)); + + SSL_set_options(ssl, op); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_CTX_get_options) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_get_options(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + SInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_get_options(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_set_options) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_set_options(ctx, op)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + UInt32 op = (UInt32)SvUV(ST(1)); + + SSL_CTX_set_options(ctx, op); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_CTX_sessions) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sessions(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + LHASH * RETVAL; + dXSTARG; +#line 2025 "SSLeay.xs" + /* NOTE: This should be deprecated. Corresponding macro was removed from ssl.h as of 0.9.2 */ + if (ctx == NULL) croak("NULL SSL context passed as argument."); + RETVAL = ctx -> sessions; +#line 2908 "SSLeay.c" + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_number) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_number(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + UInt32 RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_number(ctx); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_connect) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_connect(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_connect(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_connect_good) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_connect_good(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_connect_good(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_connect_renegotiate) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_connect_renegotiate(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_connect_renegotiate(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_accept) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_accept(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_accept(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_accept_renegotiate) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_accept_renegotiate(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_accept_renegotiate(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_accept_good) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_accept_good(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_accept_good(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_hits) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_hits(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_hits(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_cb_hits) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_cb_hits(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_cb_hits(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_misses) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_misses(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_misses(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_timeouts) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_timeouts(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_timeouts(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_cache_full) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_cache_full(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_cache_full(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_get_cache_size) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_get_cache_size(ctx)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_CTX_sess_get_cache_size(ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_CTX_sess_set_cache_size) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::CTX_sess_set_cache_size(ctx, size)"); + { + SSL_CTX * ctx = (SSL_CTX *)SvIV(ST(0)); + int size = (int)SvIV(ST(1)); + + SSL_CTX_sess_set_cache_size(ctx, size); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_want) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::want(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_want(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_state) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::state(s)"); + { + SSL * s = (SSL *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = SSL_state(s); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_BIO_f_ssl) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::BIO_f_ssl()"); + { + BIO_METHOD * RETVAL; + dXSTARG; + + RETVAL = BIO_f_ssl(); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_ERR_get_error) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::ERR_get_error()"); + { + UInt32 RETVAL; + dXSTARG; + + RETVAL = ERR_get_error(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_ERR_peek_error) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::ERR_peek_error()"); + { + UInt32 RETVAL; + dXSTARG; + + RETVAL = ERR_peek_error(); + XSprePUSH; PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_ERR_put_error) +{ + dXSARGS; + if (items != 5) + Perl_croak(aTHX_ "Usage: Net::SSLeay::ERR_put_error(lib, func, reason, file, line)"); + { + int lib = (int)SvIV(ST(0)); + int func = (int)SvIV(ST(1)); + int reason = (int)SvIV(ST(2)); + char * file = (char *)SvPV(ST(3),PL_na); + int line = (int)SvIV(ST(4)); + + ERR_put_error(lib, func, reason, file, line); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_ERR_clear_error) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::ERR_clear_error()"); + { + + ERR_clear_error(); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_ERR_error_string) +{ + dXSARGS; + if (items < 1 || items > 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::ERR_error_string(error, buf=NULL)"); + { + UInt32 error = (UInt32)SvUV(ST(0)); + char * buf; + char * RETVAL; + dXSTARG; + + if (items < 2) + buf = NULL; + else { + buf = (char *)SvPV(ST(1),PL_na); + } +#line 2121 "SSLeay.xs" + RETVAL = ERR_error_string(error,buf); +#line 3260 "SSLeay.c" + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_load_error_strings) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::load_error_strings()"); + { + + SSL_load_error_strings(); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_ERR_load_crypto_strings) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::ERR_load_crypto_strings()"); + { + + ERR_load_crypto_strings(); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_SSLeay_add_ssl_algorithms) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::SSLeay_add_ssl_algorithms()"); + { + + SSLeay_add_ssl_algorithms(); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_ERR_load_SSL_strings) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::ERR_load_SSL_strings()"); + { + + ERR_load_SSL_strings(); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_RAND_seed) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::RAND_seed(buf)"); + { +#line 2140 "SSLeay.xs" + STRLEN len; +#line 3322 "SSLeay.c" + char * buf = SvPV( ST(1), len); +#line 2144 "SSLeay.xs" + RAND_seed (buf, (int)len); +#line 3326 "SSLeay.c" + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_RAND_cleanup) +{ + dXSARGS; + if (items != 0) + Perl_croak(aTHX_ "Usage: Net::SSLeay::RAND_cleanup()"); + { + + RAND_cleanup(); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_RAND_load_file) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::RAND_load_file(file_name, how_much)"); + { + char * file_name = (char *)SvPV(ST(0),PL_na); + int how_much = (int)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = RAND_load_file(file_name, how_much); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_RAND_write_file) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::RAND_write_file(file_name)"); + { + char * file_name = (char *)SvPV(ST(0),PL_na); + int RETVAL; + dXSTARG; + + RETVAL = RAND_write_file(file_name); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +#define REM40 "Minimal X509 stuff..., this is a bit ugly and should be put in its own modules Net::SSLeay::X509.pm" +XS(XS_Net__SSLeay_X509_get_issuer_name) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_get_issuer_name(cert)"); + { + X509 * cert = (X509 *)SvIV(ST(0)); + X509_NAME * RETVAL; + dXSTARG; + + RETVAL = X509_get_issuer_name(cert); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_get_subject_name) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_get_subject_name(cert)"); + { + X509 * cert = (X509 *)SvIV(ST(0)); + X509_NAME * RETVAL; + dXSTARG; + + RETVAL = X509_get_subject_name(cert); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_NAME_oneline) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_NAME_oneline(name)"); + { + X509_NAME * name = (X509_NAME *)SvIV(ST(0)); +#line 2172 "SSLeay.xs" + char buf[32768]; +#line 3418 "SSLeay.c" +#line 2174 "SSLeay.xs" + ST(0) = sv_newmortal(); /* Undefined to start with */ + if (X509_NAME_oneline(name, buf, sizeof(buf))) + sv_setpvn( ST(0), buf, strlen(buf)); +#line 3423 "SSLeay.c" + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_STORE_CTX_get_current_cert) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_STORE_CTX_get_current_cert(x509_store_ctx)"); + { + X509_STORE_CTX * x509_store_ctx = (X509_STORE_CTX *)SvIV(ST(0)); + X509 * RETVAL; + dXSTARG; + + RETVAL = X509_STORE_CTX_get_current_cert(x509_store_ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_STORE_CTX_get_ex_data) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_STORE_CTX_get_ex_data(x509_store_ctx, idx)"); + { + X509_STORE_CTX * x509_store_ctx = (X509_STORE_CTX *)SvIV(ST(0)); + int idx = (int)SvIV(ST(1)); + void * RETVAL; + dXSTARG; + + RETVAL = X509_STORE_CTX_get_ex_data(x509_store_ctx, idx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_STORE_CTX_get_error) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_STORE_CTX_get_error(x509_store_ctx)"); + { + X509_STORE_CTX * x509_store_ctx = (X509_STORE_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = X509_STORE_CTX_get_error(x509_store_ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_STORE_CTX_get_error_depth) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_STORE_CTX_get_error_depth(x509_store_ctx)"); + { + X509_STORE_CTX * x509_store_ctx = (X509_STORE_CTX *)SvIV(ST(0)); + int RETVAL; + dXSTARG; + + RETVAL = X509_STORE_CTX_get_error_depth(x509_store_ctx); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_STORE_CTX_set_ex_data) +{ + dXSARGS; + if (items != 3) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_STORE_CTX_set_ex_data(x509_store_ctx, idx, data)"); + { + X509_STORE_CTX * x509_store_ctx = (X509_STORE_CTX *)SvIV(ST(0)); + int idx = (int)SvIV(ST(1)); + void * data = INT2PTR(void *,SvIV(ST(2))); + int RETVAL; + dXSTARG; + + RETVAL = X509_STORE_CTX_set_ex_data(x509_store_ctx, idx, data); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_STORE_CTX_set_error) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_STORE_CTX_set_error(x509_store_ctx, s)"); + { + X509_STORE_CTX * x509_store_ctx = (X509_STORE_CTX *)SvIV(ST(0)); + int s = (int)SvIV(ST(1)); + + X509_STORE_CTX_set_error(x509_store_ctx, s); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_X509_STORE_CTX_set_cert) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_STORE_CTX_set_cert(x509_store_ctx, x)"); + { + X509_STORE_CTX * x509_store_ctx = (X509_STORE_CTX *)SvIV(ST(0)); + X509 * x = (X509 *)SvIV(ST(1)); + + X509_STORE_CTX_set_cert(x509_store_ctx, x); + } + XSRETURN_EMPTY; +} + +XS(XS_Net__SSLeay_X509_get_notBefore) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_get_notBefore(cert)"); + { + X509 * cert = (X509 *)SvIV(ST(0)); + ASN1_UTCTIME * RETVAL; + dXSTARG; + + RETVAL = X509_get_notBefore(cert); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_X509_get_notAfter) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::X509_get_notAfter(cert)"); + { + X509 * cert = (X509 *)SvIV(ST(0)); + ASN1_UTCTIME * RETVAL; + dXSTARG; + + RETVAL = X509_get_notAfter(cert); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_P_ASN1_UTCTIME_put2string) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::P_ASN1_UTCTIME_put2string(tm)"); + { + ASN1_UTCTIME * tm = (ASN1_UTCTIME *)SvIV(ST(0)); +#line 2225 "SSLeay.xs" + BIO *bp; + int i; + char buffer[256]; +#line 3582 "SSLeay.c" +#line 2229 "SSLeay.xs" + bp = BIO_new(BIO_s_mem()); + ASN1_UTCTIME_print(bp,tm); + i = BIO_read(bp,buffer,255); + buffer[i] = '\0'; + ST(0) = sv_newmortal(); /* Undefined to start with */ + if ( i > 0 ) + sv_setpvn( ST(0), buffer, i ); + BIO_free(bp); +#line 3592 "SSLeay.c" + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_EVP_PKEY_copy_parameters) +{ + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Net::SSLeay::EVP_PKEY_copy_parameters(to, from)"); + { + EVP_PKEY * to = (EVP_PKEY *)SvIV(ST(0)); + EVP_PKEY * from = (EVP_PKEY *)SvIV(ST(1)); + int RETVAL; + dXSTARG; + + RETVAL = EVP_PKEY_copy_parameters(to, from); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_PEM_get_string_X509) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::PEM_get_string_X509(x509)"); + { + X509 * x509 = (X509 *)SvIV(ST(0)); +#line 2247 "SSLeay.xs" + BIO *bp; + int i; + char buffer[8196]; +#line 3625 "SSLeay.c" +#line 2251 "SSLeay.xs" + bp = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(bp,x509); + i = BIO_read(bp,buffer,8195); + buffer[i] = '\0'; + ST(0) = sv_newmortal(); /* Undefined to start with */ + if ( i > 0 ) + sv_setpvn( ST(0), buffer, i ); + BIO_free(bp); +#line 3635 "SSLeay.c" + } + XSRETURN(1); +} + +XS(XS_Net__SSLeay_MD5) +{ + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Net::SSLeay::MD5(data)"); + { +#line 2263 "SSLeay.xs" + STRLEN len; + unsigned char md[MD5_DIGEST_LENGTH]; + unsigned char * ret; +#line 3650 "SSLeay.c" + unsigned char * data = (unsigned char *) SvPV( ST(0), len); +#line 2269 "SSLeay.xs" + ret = MD5(data,len,md); + if (ret!=NULL) { + XSRETURN_PV((char *) md); + } else { + XSRETURN_UNDEF; + } +#line 3659 "SSLeay.c" + } + XSRETURN_EMPTY; +} + +#define REM_EOF "/* EOF - SSLeay.xs */" +#ifdef __cplusplus +extern "C" +#endif +XS(boot_Net__SSLeay) +{ + dXSARGS; + char* file = __FILE__; + items = items; + XS_VERSION_BOOTCHECK ; + + newXSproto("Net::SSLeay::constant", XS_Net__SSLeay_constant, file, "$$"); + newXSproto("Net::SSLeay::hello", XS_Net__SSLeay_hello, file, ""); + newXSproto("Net::SSLeay::CTX_new", XS_Net__SSLeay_CTX_new, file, ""); + newXSproto("Net::SSLeay::CTX_v2_new", XS_Net__SSLeay_CTX_v2_new, file, ""); + newXSproto("Net::SSLeay::CTX_v3_new", XS_Net__SSLeay_CTX_v3_new, file, ""); + newXSproto("Net::SSLeay::CTX_v23_new", XS_Net__SSLeay_CTX_v23_new, file, ""); + newXSproto("Net::SSLeay::CTX_free", XS_Net__SSLeay_CTX_free, file, "$"); + newXSproto("Net::SSLeay::CTX_add_session", XS_Net__SSLeay_CTX_add_session, file, "$$"); + newXSproto("Net::SSLeay::CTX_remove_session", XS_Net__SSLeay_CTX_remove_session, file, "$$"); + newXSproto("Net::SSLeay::CTX_flush_sessions", XS_Net__SSLeay_CTX_flush_sessions, file, "$$"); + newXSproto("Net::SSLeay::CTX_set_default_verify_paths", XS_Net__SSLeay_CTX_set_default_verify_paths, file, "$"); + newXSproto("Net::SSLeay::CTX_load_verify_locations", XS_Net__SSLeay_CTX_load_verify_locations, file, "$$$"); + newXSproto("Net::SSLeay::CTX_set_verify", XS_Net__SSLeay_CTX_set_verify, file, "$$$"); + newXSproto("Net::SSLeay::new", XS_Net__SSLeay_new, file, "$"); + newXSproto("Net::SSLeay::free", XS_Net__SSLeay_free, file, "$"); +#if XSubPPtmpAAAA + newXSproto("Net::SSLeay::debug", XS_Net__SSLeay_debug, file, "$"); +#endif + newXSproto("Net::SSLeay::accept", XS_Net__SSLeay_accept, file, "$"); + newXSproto("Net::SSLeay::clear", XS_Net__SSLeay_clear, file, "$"); + newXSproto("Net::SSLeay::connect", XS_Net__SSLeay_connect, file, "$"); +#if XSubPPtmpAAAB + newXSproto("Net::SSLeay::set_fd", XS_Net__SSLeay_set_fd, file, "$$"); + newXSproto("Net::SSLeay::set_rfd", XS_Net__SSLeay_set_rfd, file, "$$"); + newXSproto("Net::SSLeay::set_wfd", XS_Net__SSLeay_set_wfd, file, "$$"); +#endif +#if XSubPPtmpAAAC + newXSproto("Net::SSLeay::set_fd", XS_Net__SSLeay_set_fd, file, "$$"); + newXSproto("Net::SSLeay::set_rfd", XS_Net__SSLeay_set_rfd, file, "$$"); + newXSproto("Net::SSLeay::set_wfd", XS_Net__SSLeay_set_wfd, file, "$$"); +#endif + newXSproto("Net::SSLeay::get_fd", XS_Net__SSLeay_get_fd, file, "$"); + newXSproto("Net::SSLeay::read", XS_Net__SSLeay_read, file, "$;$"); + newXSproto("Net::SSLeay::write", XS_Net__SSLeay_write, file, "$$"); + newXSproto("Net::SSLeay::write_partial", XS_Net__SSLeay_write_partial, file, "$$$$"); + newXSproto("Net::SSLeay::use_RSAPrivateKey", XS_Net__SSLeay_use_RSAPrivateKey, file, "$$"); + newXSproto("Net::SSLeay::use_RSAPrivateKey_ASN1", XS_Net__SSLeay_use_RSAPrivateKey_ASN1, file, "$$$"); + newXSproto("Net::SSLeay::use_RSAPrivateKey_file", XS_Net__SSLeay_use_RSAPrivateKey_file, file, "$$$"); + newXSproto("Net::SSLeay::CTX_use_RSAPrivateKey_file", XS_Net__SSLeay_CTX_use_RSAPrivateKey_file, file, "$$$"); + newXSproto("Net::SSLeay::use_PrivateKey", XS_Net__SSLeay_use_PrivateKey, file, "$$"); + newXSproto("Net::SSLeay::use_PrivateKey_ASN1", XS_Net__SSLeay_use_PrivateKey_ASN1, file, "$$$$"); + newXSproto("Net::SSLeay::use_PrivateKey_file", XS_Net__SSLeay_use_PrivateKey_file, file, "$$$"); + newXSproto("Net::SSLeay::CTX_use_PrivateKey_file", XS_Net__SSLeay_CTX_use_PrivateKey_file, file, "$$$"); + newXSproto("Net::SSLeay::use_certificate", XS_Net__SSLeay_use_certificate, file, "$$"); + newXSproto("Net::SSLeay::use_certificate_ASN1", XS_Net__SSLeay_use_certificate_ASN1, file, "$$$"); + newXSproto("Net::SSLeay::use_certificate_file", XS_Net__SSLeay_use_certificate_file, file, "$$$"); + newXSproto("Net::SSLeay::CTX_use_certificate_file", XS_Net__SSLeay_CTX_use_certificate_file, file, "$$$"); + newXSproto("Net::SSLeay::state_string", XS_Net__SSLeay_state_string, file, "$"); + newXSproto("Net::SSLeay::rstate_string", XS_Net__SSLeay_rstate_string, file, "$"); + newXSproto("Net::SSLeay::state_string_long", XS_Net__SSLeay_state_string_long, file, "$"); + newXSproto("Net::SSLeay::rstate_string_long", XS_Net__SSLeay_rstate_string_long, file, "$"); + newXSproto("Net::SSLeay::get_time", XS_Net__SSLeay_get_time, file, "$"); + newXSproto("Net::SSLeay::set_time", XS_Net__SSLeay_set_time, file, "$$"); + newXSproto("Net::SSLeay::get_timeout", XS_Net__SSLeay_get_timeout, file, "$"); + newXSproto("Net::SSLeay::set_timeout", XS_Net__SSLeay_set_timeout, file, "$$"); + newXSproto("Net::SSLeay::copy_session_id", XS_Net__SSLeay_copy_session_id, file, "$$"); + newXSproto("Net::SSLeay::set_read_ahead", XS_Net__SSLeay_set_read_ahead, file, "$;$"); + newXSproto("Net::SSLeay::get_read_ahead", XS_Net__SSLeay_get_read_ahead, file, "$"); + newXSproto("Net::SSLeay::pending", XS_Net__SSLeay_pending, file, "$"); + newXSproto("Net::SSLeay::CTX_set_cipher_list", XS_Net__SSLeay_CTX_set_cipher_list, file, "$$"); + newXSproto("Net::SSLeay::get_cipher_list", XS_Net__SSLeay_get_cipher_list, file, "$$"); + newXSproto("Net::SSLeay::set_cipher_list", XS_Net__SSLeay_set_cipher_list, file, "$$"); + newXSproto("Net::SSLeay::get_cipher", XS_Net__SSLeay_get_cipher, file, "$"); + newXSproto("Net::SSLeay::get_shared_ciphers", XS_Net__SSLeay_get_shared_ciphers, file, "$$$"); + newXSproto("Net::SSLeay::get_peer_certificate", XS_Net__SSLeay_get_peer_certificate, file, "$"); + newXSproto("Net::SSLeay::set_verify", XS_Net__SSLeay_set_verify, file, "$$$"); + newXSproto("Net::SSLeay::set_bio", XS_Net__SSLeay_set_bio, file, "$$$"); + newXSproto("Net::SSLeay::get_rbio", XS_Net__SSLeay_get_rbio, file, "$"); + newXSproto("Net::SSLeay::get_wbio", XS_Net__SSLeay_get_wbio, file, "$"); + newXSproto("Net::SSLeay::SESSION_new", XS_Net__SSLeay_SESSION_new, file, ""); + newXSproto("Net::SSLeay::SESSION_print", XS_Net__SSLeay_SESSION_print, file, "$$"); + newXSproto("Net::SSLeay::SESSION_free", XS_Net__SSLeay_SESSION_free, file, "$"); + newXSproto("Net::SSLeay::i2d_SSL_SESSION", XS_Net__SSLeay_i2d_SSL_SESSION, file, "$$"); + newXSproto("Net::SSLeay::set_session", XS_Net__SSLeay_set_session, file, "$$"); + newXSproto("Net::SSLeay::d2i_SSL_SESSION", XS_Net__SSLeay_d2i_SSL_SESSION, file, "$$$"); + newXSproto("Net::SSLeay::get_session", XS_Net__SSLeay_get_session, file, "$"); + newXSproto("Net::SSLeay::get_certificate", XS_Net__SSLeay_get_certificate, file, "$"); + newXSproto("Net::SSLeay::get_SSL_CTX", XS_Net__SSLeay_get_SSL_CTX, file, "$"); + newXSproto("Net::SSLeay::ctrl", XS_Net__SSLeay_ctrl, file, "$$$$"); + newXSproto("Net::SSLeay::CTX_ctrl", XS_Net__SSLeay_CTX_ctrl, file, "$$$$"); + newXSproto("Net::SSLeay::get_options", XS_Net__SSLeay_get_options, file, "$"); + newXSproto("Net::SSLeay::set_options", XS_Net__SSLeay_set_options, file, "$$"); + newXSproto("Net::SSLeay::CTX_get_options", XS_Net__SSLeay_CTX_get_options, file, "$"); + newXSproto("Net::SSLeay::CTX_set_options", XS_Net__SSLeay_CTX_set_options, file, "$$"); + newXSproto("Net::SSLeay::CTX_sessions", XS_Net__SSLeay_CTX_sessions, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_number", XS_Net__SSLeay_CTX_sess_number, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_connect", XS_Net__SSLeay_CTX_sess_connect, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_connect_good", XS_Net__SSLeay_CTX_sess_connect_good, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_connect_renegotiate", XS_Net__SSLeay_CTX_sess_connect_renegotiate, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_accept", XS_Net__SSLeay_CTX_sess_accept, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_accept_renegotiate", XS_Net__SSLeay_CTX_sess_accept_renegotiate, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_accept_good", XS_Net__SSLeay_CTX_sess_accept_good, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_hits", XS_Net__SSLeay_CTX_sess_hits, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_cb_hits", XS_Net__SSLeay_CTX_sess_cb_hits, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_misses", XS_Net__SSLeay_CTX_sess_misses, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_timeouts", XS_Net__SSLeay_CTX_sess_timeouts, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_cache_full", XS_Net__SSLeay_CTX_sess_cache_full, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_get_cache_size", XS_Net__SSLeay_CTX_sess_get_cache_size, file, "$"); + newXSproto("Net::SSLeay::CTX_sess_set_cache_size", XS_Net__SSLeay_CTX_sess_set_cache_size, file, "$$"); + newXSproto("Net::SSLeay::want", XS_Net__SSLeay_want, file, "$"); + newXSproto("Net::SSLeay::state", XS_Net__SSLeay_state, file, "$"); + newXSproto("Net::SSLeay::BIO_f_ssl", XS_Net__SSLeay_BIO_f_ssl, file, ""); + newXSproto("Net::SSLeay::ERR_get_error", XS_Net__SSLeay_ERR_get_error, file, ""); + newXSproto("Net::SSLeay::ERR_peek_error", XS_Net__SSLeay_ERR_peek_error, file, ""); + newXSproto("Net::SSLeay::ERR_put_error", XS_Net__SSLeay_ERR_put_error, file, "$$$$$"); + newXSproto("Net::SSLeay::ERR_clear_error", XS_Net__SSLeay_ERR_clear_error, file, ""); + newXSproto("Net::SSLeay::ERR_error_string", XS_Net__SSLeay_ERR_error_string, file, "$;$"); + newXSproto("Net::SSLeay::load_error_strings", XS_Net__SSLeay_load_error_strings, file, ""); + newXSproto("Net::SSLeay::ERR_load_crypto_strings", XS_Net__SSLeay_ERR_load_crypto_strings, file, ""); + newXSproto("Net::SSLeay::SSLeay_add_ssl_algorithms", XS_Net__SSLeay_SSLeay_add_ssl_algorithms, file, ""); + newXSproto("Net::SSLeay::ERR_load_SSL_strings", XS_Net__SSLeay_ERR_load_SSL_strings, file, ""); + newXSproto("Net::SSLeay::RAND_seed", XS_Net__SSLeay_RAND_seed, file, "$"); + newXSproto("Net::SSLeay::RAND_cleanup", XS_Net__SSLeay_RAND_cleanup, file, ""); + newXSproto("Net::SSLeay::RAND_load_file", XS_Net__SSLeay_RAND_load_file, file, "$$"); + newXSproto("Net::SSLeay::RAND_write_file", XS_Net__SSLeay_RAND_write_file, file, "$"); + newXSproto("Net::SSLeay::X509_get_issuer_name", XS_Net__SSLeay_X509_get_issuer_name, file, "$"); + newXSproto("Net::SSLeay::X509_get_subject_name", XS_Net__SSLeay_X509_get_subject_name, file, "$"); + newXSproto("Net::SSLeay::X509_NAME_oneline", XS_Net__SSLeay_X509_NAME_oneline, file, "$"); + newXSproto("Net::SSLeay::X509_STORE_CTX_get_current_cert", XS_Net__SSLeay_X509_STORE_CTX_get_current_cert, file, "$"); + newXSproto("Net::SSLeay::X509_STORE_CTX_get_ex_data", XS_Net__SSLeay_X509_STORE_CTX_get_ex_data, file, "$$"); + newXSproto("Net::SSLeay::X509_STORE_CTX_get_error", XS_Net__SSLeay_X509_STORE_CTX_get_error, file, "$"); + newXSproto("Net::SSLeay::X509_STORE_CTX_get_error_depth", XS_Net__SSLeay_X509_STORE_CTX_get_error_depth, file, "$"); + newXSproto("Net::SSLeay::X509_STORE_CTX_set_ex_data", XS_Net__SSLeay_X509_STORE_CTX_set_ex_data, file, "$$$"); + newXSproto("Net::SSLeay::X509_STORE_CTX_set_error", XS_Net__SSLeay_X509_STORE_CTX_set_error, file, "$$"); + newXSproto("Net::SSLeay::X509_STORE_CTX_set_cert", XS_Net__SSLeay_X509_STORE_CTX_set_cert, file, "$$"); + newXSproto("Net::SSLeay::X509_get_notBefore", XS_Net__SSLeay_X509_get_notBefore, file, "$"); + newXSproto("Net::SSLeay::X509_get_notAfter", XS_Net__SSLeay_X509_get_notAfter, file, "$"); + newXSproto("Net::SSLeay::P_ASN1_UTCTIME_put2string", XS_Net__SSLeay_P_ASN1_UTCTIME_put2string, file, "$"); + newXSproto("Net::SSLeay::EVP_PKEY_copy_parameters", XS_Net__SSLeay_EVP_PKEY_copy_parameters, file, "$$"); + newXSproto("Net::SSLeay::PEM_get_string_X509", XS_Net__SSLeay_PEM_get_string_X509, file, "$"); + newXSproto("Net::SSLeay::MD5", XS_Net__SSLeay_MD5, file, "$"); + + /* Initialisation Section */ + +#if XSubPPtmpAAAA +#endif +#if XSubPPtmpAAAB +#endif +#if XSubPPtmpAAAC +#endif +#line 3815 "SSLeay.c" + + /* End of Initialisation Section */ + + XSRETURN_YES; +} + diff --git a/WebAdmin/NetSSLeay/SSLeay.pm b/WebAdmin/NetSSLeay/SSLeay.pm new file mode 100644 index 0000000..e36e168 --- /dev/null +++ b/WebAdmin/NetSSLeay/SSLeay.pm @@ -0,0 +1,1394 @@ +# Net::SSLeay.pm - Perl module for using Eric Young's implementation of SSL +# +# Copyright (c) 1996-1999 Sampo Kellomaki , All Rights Reserved. +# Version 1.04, 31.3.1999 +# 30.7.1999, Tracking OpenSSL-0.9.3a changes, --Sampo +# 31.7.1999, version 1.05 --Sampo +# +# The distribution and use of this module are subject to the conditions +# listed in COPYRIGHT file at the root of Eric Young's SSLeay-0.9.0 +# distribution (i.e. free, but mandatory attribution and NO WARRANTY). + +package Net::SSLeay; + +use strict; +use Carp; +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $AUTOLOAD); +use Socket; + +require Exporter; +require DynaLoader; +require AutoLoader; + +# 0=no warns, 1=only errors, 2=ciphers, 3=progress, 4=dump data +$Net::SSLeay::trace = 1; + +# 2 = insist on v2 SSL protocol, 3 = insist on v3 SSL, undef = guess (v23) +#$Net::SSLeay::ssl_version = 3; + +# Number of seconds to sleep after sending message and before half +# closing connection. Useful with antiquated broken servers. +$Net::SSLeay::slowly = 0; + +# RANDOM NUMBER INITIALIZATION +# +# Edit to your taste. Using /dev/random would be more secure, but may +# block if randomness is not available, thus the default is +# /dev/urandom. $how_random determines how many bits of randomness to take +# from the device. You should take enough (read SSLeay/doc/rand), but +# beware that randomness is limited resource so you should not waste +# it either or you may end up with randomness depletion (situation where +# /dev/random would block and /dev/urandom starts to return predictable +# numbers). + +$Net::SSLeay::random_device = '/dev/urandom'; +$Net::SSLeay::how_random = 512; + +$VERSION = '1.05'; +@ISA = qw(Exporter DynaLoader); +@EXPORT_OK = qw( + AT_MD5_WITH_RSA_ENCRYPTION + CB_ACCEPT_EXIT + CB_ACCEPT_LOOP + CB_CONNECT_EXIT + CB_CONNECT_LOOP + CK_DES_192_EDE3_CBC_WITH_MD5 + CK_DES_192_EDE3_CBC_WITH_SHA + CK_DES_64_CBC_WITH_MD5 + CK_DES_64_CBC_WITH_SHA + CK_DES_64_CFB64_WITH_MD5_1 + CK_IDEA_128_CBC_WITH_MD5 + CK_NULL + CK_NULL_WITH_MD5 + CK_RC2_128_CBC_EXPORT40_WITH_MD5 + CK_RC2_128_CBC_WITH_MD5 + CK_RC4_128_EXPORT40_WITH_MD5 + CK_RC4_128_WITH_MD5 + CLIENT_VERSION + CT_X509_CERTIFICATE + FILETYPE_ASN1 + FILETYPE_PEM + F_CLIENT_CERTIFICATE + F_CLIENT_HELLO + F_CLIENT_MASTER_KEY + F_D2I_SSL_SESSION + F_GET_CLIENT_FINISHED + F_GET_CLIENT_HELLO + F_GET_CLIENT_MASTER_KEY + F_GET_SERVER_FINISHED + F_GET_SERVER_HELLO + F_GET_SERVER_VERIFY + F_I2D_SSL_SESSION + F_READ_N + F_REQUEST_CERTIFICATE + F_SERVER_HELLO + F_SSL_ACCEPT + F_SSL_CERT_NEW + F_SSL_CONNECT + F_SSL_ENC_DES_CBC_INIT + F_SSL_ENC_DES_CFB_INIT + F_SSL_ENC_DES_EDE3_CBC_INIT + F_SSL_ENC_IDEA_CBC_INIT + F_SSL_ENC_NULL_INIT + F_SSL_ENC_RC2_CBC_INIT + F_SSL_ENC_RC4_INIT + F_SSL_GET_NEW_SESSION + F_SSL_MAKE_CIPHER_LIST + F_SSL_NEW + F_SSL_READ + F_SSL_RSA_PRIVATE_DECRYPT + F_SSL_RSA_PUBLIC_ENCRYPT + F_SSL_SESSION_NEW + F_SSL_SESSION_PRINT_FP + F_SSL_SET_CERTIFICATE + F_SSL_SET_FD + F_SSL_SET_RFD + F_SSL_SET_WFD + F_SSL_STARTUP + F_SSL_USE_CERTIFICATE + F_SSL_USE_CERTIFICATE_ASN1 + F_SSL_USE_CERTIFICATE_FILE + F_SSL_USE_PRIVATEKEY + F_SSL_USE_PRIVATEKEY_ASN1 + F_SSL_USE_PRIVATEKEY_FILE + F_SSL_USE_RSAPRIVATEKEY + F_SSL_USE_RSAPRIVATEKEY_ASN1 + F_SSL_USE_RSAPRIVATEKEY_FILE + F_WRITE_PENDING + MAX_MASTER_KEY_LENGTH_IN_BITS + MAX_RECORD_LENGTH_2_BYTE_HEADER + MAX_RECORD_LENGTH_3_BYTE_HEADER + MAX_SSL_SESSION_ID_LENGTH_IN_BYTES + MIN_RSA_MODULUS_LENGTH_IN_BYTES + MT_CLIENT_CERTIFICATE + MT_CLIENT_FINISHED + MT_CLIENT_HELLO + MT_CLIENT_MASTER_KEY + MT_ERROR + MT_REQUEST_CERTIFICATE + MT_SERVER_FINISHED + MT_SERVER_HELLO + MT_SERVER_VERIFY + NOTHING + PE_BAD_CERTIFICATE + PE_NO_CERTIFICATE + PE_NO_CIPHER + PE_UNSUPPORTED_CERTIFICATE_TYPE + READING + RWERR_BAD_MAC_DECODE + RWERR_BAD_WRITE_RETRY + RWERR_INTERNAL_ERROR + R_BAD_AUTHENTICATION_TYPE + R_BAD_CHECKSUM + R_BAD_MAC_DECODE + R_BAD_RESPONSE_ARGUMENT + R_BAD_SSL_FILETYPE + R_BAD_SSL_SESSION_ID_LENGTH + R_BAD_STATE + R_BAD_WRITE_RETRY + R_CHALLENGE_IS_DIFFERENT + R_CIPHER_CODE_TOO_LONG + R_CIPHER_TABLE_SRC_ERROR + R_CONECTION_ID_IS_DIFFERENT + R_INVALID_CHALLENGE_LENGTH + R_NO_CERTIFICATE_SET + R_NO_CERTIFICATE_SPECIFIED + R_NO_CIPHER_LIST + R_NO_CIPHER_MATCH + R_NO_CIPHER_WE_TRUST + R_NO_PRIVATEKEY + R_NO_PUBLICKEY + R_NO_READ_METHOD_SET + R_NO_WRITE_METHOD_SET + R_NULL_SSL_CTX + R_PEER_DID_NOT_RETURN_A_CERTIFICATE + R_PEER_ERROR + R_PEER_ERROR_CERTIFICATE + R_PEER_ERROR_NO_CIPHER + R_PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE + R_PERR_ERROR_NO_CERTIFICATE + R_PUBLIC_KEY_ENCRYPT_ERROR + R_PUBLIC_KEY_IS_NOT_RSA + R_PUBLIC_KEY_NO_RSA + R_READ_WRONG_PACKET_TYPE + R_REVERSE_KEY_ARG_LENGTH_IS_WRONG + R_REVERSE_MASTER_KEY_LENGTH_IS_WRONG + R_REVERSE_SSL_SESSION_ID_LENGTH_IS_WRONG + R_SHORT_READ + R_SSL_SESSION_ID_IS_DIFFERENT + R_UNABLE_TO_EXTRACT_PUBLIC_KEY + R_UNDEFINED_INIT_STATE + R_UNKNOWN_REMOTE_ERROR_TYPE + R_UNKNOWN_STATE + R_UNSUPORTED_CIPHER + R_WRONG_PUBLIC_KEY_TYPE + R_X509_LIB + SERVER_VERSION + SESSION + SESSION_ASN1_VERSION + ST_ACCEPT + ST_BEFORE + ST_CLIENT_START_ENCRYPTION + ST_CONNECT + ST_GET_CLIENT_FINISHED_A + ST_GET_CLIENT_FINISHED_B + ST_GET_CLIENT_HELLO_A + ST_GET_CLIENT_HELLO_B + ST_GET_CLIENT_MASTER_KEY_A + ST_GET_CLIENT_MASTER_KEY_B + ST_GET_SERVER_FINISHED_A + ST_GET_SERVER_FINISHED_B + ST_GET_SERVER_HELLO_A + ST_GET_SERVER_HELLO_B + ST_GET_SERVER_VERIFY_A + ST_GET_SERVER_VERIFY_B + ST_INIT + ST_OK + ST_READ_BODY + ST_READ_HEADER + ST_SEND_CLIENT_CERTIFICATE_A + ST_SEND_CLIENT_CERTIFICATE_B + ST_SEND_CLIENT_CERTIFICATE_C + ST_SEND_CLIENT_CERTIFICATE_D + ST_SEND_CLIENT_FINISHED_A + ST_SEND_CLIENT_FINISHED_B + ST_SEND_CLIENT_HELLO_A + ST_SEND_CLIENT_HELLO_B + ST_SEND_CLIENT_MASTER_KEY_A + ST_SEND_CLIENT_MASTER_KEY_B + ST_SEND_REQUEST_CERTIFICATE_A + ST_SEND_REQUEST_CERTIFICATE_B + ST_SEND_REQUEST_CERTIFICATE_C + ST_SEND_REQUEST_CERTIFICATE_D + ST_SEND_SERVER_FINISHED_A + ST_SEND_SERVER_FINISHED_B + ST_SEND_SERVER_HELLO_A + ST_SEND_SERVER_HELLO_B + ST_SEND_SERVER_VERIFY_A + ST_SEND_SERVER_VERIFY_B + ST_SERVER_START_ENCRYPTION + ST_X509_GET_CLIENT_CERTIFICATE + ST_X509_GET_SERVER_CERTIFICATE + TXT_DES_192_EDE3_CBC_WITH_MD5 + TXT_DES_192_EDE3_CBC_WITH_SHA + TXT_DES_64_CBC_WITH_MD5 + TXT_DES_64_CBC_WITH_SHA + TXT_DES_64_CFB64_WITH_MD5_1 + TXT_IDEA_128_CBC_WITH_MD5 + TXT_NULL + TXT_NULL_WITH_MD5 + TXT_RC2_128_CBC_EXPORT40_WITH_MD5 + TXT_RC2_128_CBC_WITH_MD5 + TXT_RC4_128_EXPORT40_WITH_MD5 + TXT_RC4_128_WITH_MD5 + VERIFY_CLIENT_ONCE + VERIFY_FAIL_IF_NO_PEER_CERT + VERIFY_NONE + VERIFY_PEER + WRITING + X509_LOOKUP + CTX_new + CTX_v2_new + CTX_v3_new + CTX_v23_new + CTX_free + new + free + accept + clear + connect + set_fd + set_rfd + set_wfd + get_fd + read + write + use_RSAPrivateKey + use_RSAPrivateKey_ASN1 + use_RSAPrivateKey_file + CTX_use_RSAPrivateKey_file + use_PrivateKey + use_PrivateKey_ASN1 + use_PrivateKey_file + use_certificate + use_certificate_ASN1 + use_certificate_file + CTX_use_certificate_file + load_error_strings + ERR_load_SSL_strings + state_string + rstate_string + state_string_long + rstate_string_long + get_time + set_time + get_timeout + set_timeout + copy_session_id + set_read_ahead + get_read_ahead + pending + get_cipher_list + set_cipher_list + get_cipher + get_shared_ciphers + get_peer_certificate + set_verify + flush_sessions + set_bio + get_rbio + get_wbio + SESSION_new + SESSION_print + SESSION_free + i2d_SSL_SESSION + set_session + add_session + remove_session + d2i_SSL_SESSION + BIO_f_ssl + ERR_get_error + ERR_error_string + err + clear_error + X509_get_issuer_name + X509_get_subject_name + X509_NAME_oneline + die_if_ssl_error + die_now + print_errs + set_server_cert_and_key + make_form + make_headers + do_https + get_https + post_https + sslcat + ssl_read_CRLF + ssl_read_all + ssl_read_until + ssl_write_CRLF + ssl_write_all + dump_peer_certificate +); + +sub AUTOLOAD { + # This AUTOLOAD is used to 'autoload' constants from the constant() + # XS function. If a constant is not found then control is passed + # to the AUTOLOAD in AutoLoader. + + my $constname; + ($constname = $AUTOLOAD) =~ s/.*:://; + my $val = constant($constname, @_ ? $_[0] : 0); + if ($! != 0) { + if ($! =~ /Invalid/) { + $AutoLoader::AUTOLOAD = $AUTOLOAD; + goto &AutoLoader::AUTOLOAD; + } + else { + croak "Your vendor has not defined SSLeay macro $constname"; + } + } + eval "sub $AUTOLOAD { $val }"; + goto &$AUTOLOAD; +} + +bootstrap Net::SSLeay $VERSION; + +# Preloaded methods go here. + +### Print SSLeay error stack + +sub print_errs { + my ($msg) = @_; + my ($count, $err) = (0,0); + my ($errs, $e); + while ($err = ERR_get_error()) { + $count ++; + $e = "$msg $$: $count - " . ERR_error_string($err) . "\n"; + $errs .= $e; + warn $e if $Net::SSLeay::trace; + } + return $errs; +} + +# Death is conditional to SSLeay errors existing, i.e. this function checks +# for errors and only dies in affirmative. +# usage: Net::SSLeay::write($ssl, "foo") or die_if_ssl_error("SSL write ($!)"); + +sub die_if_ssl_error { + my ($msg) = @_; + die "$$: $msg\n" if print_errs($msg); +} + +# Unconditional death. Used to print SSLeay errors before dying. +# usage: Net::SSLeay:connect($ssl) or die_now("Failed SSL connect ($!)"); + +sub die_now { + my ($msg) = @_; + print_errs($msg); + die "$$: $msg\n"; +} + +# Autoload methods go after =cut, and are processed by the autosplit program. + +1; +__END__ +# Documentation. Use `perl-root/pod/pod2html SSLeay.pm` to output html + +=head1 NAME + +Net::SSLeay - Perl extension for using OpenSSL + +=head1 SYNOPSIS + + use Net::SSLeay, qw(get_https post_https sslcat make_headers make_form); + + ($page) = get_https('www.bacus.pt', 443, '/'); # 1 + + ($page, $response, %reply_headers) + = get_https('www.bacus.pt', 443, '/', # 2 + make_headers( + 'User-Agent' => 'Cryptozilla/5.0b1', + 'Referer' => 'https://www.bacus.pt' + )); + + ($page, $result, %headers) = # 2b + = get_https('www.bacus.pt', 443, '/protected.html', + make_headers('Authorization' => + 'Basic ' . MIME::Base64::encode("$user:$pass")) + ); + + ($page, $response, %reply_headers) + = post_https('www.bacus.pt', 443, '/foo.cgi', '', # 3 + make_form( + 'OK' => '1', + 'name' => 'Sampo' + )); + + $reply = sslcat($host, $port, $request); # 4 + + $Net::SSLeay::trace = 2; # 0=no debugging, 1=ciphers, 2=trace, 3=dump data + +=head1 DESCRIPTION + +This module offers some high level convinience functions for accessing +web pages on SSL servers, a sslcat() function for writing your own +clients, and finally access to the SSL api of SSLeay package so you +can write servers or clients for more complicated applications. + +For high level functions it is most convinient to import them to your +main namespace as indicated in the synopsis. Case 1 demonstrates +typical invocation of get_https() to fetch an HTML page from secure +server. The first argument provides host name or ip in dotted decimal +notation of the remote server to contact. Second argument is the TCP +port at the remote end (your own port is picked arbitrarily from high +numbered ports as usual for TCP). The third argument is the URL of the +page without the host name part. If in doubt consult HTTP +specifications at + +Case 2 demonstrates full fledged use of get_https. As can be seen, +get_https parses the response and response headers and returns them as +a list, which can be captured in a hash for later reference. Also a +fourth argument to get_https is used to insert some additional headers +in the request. make_headers is a function that will convert a list or +hash to such headers. By default get_https supplies Host (make virtual +hosting easy) and Accept (reportedly needed by IIS) headers. + +Case 2b demonstrates how to get password protected page. Refer to +HTTP protocol specifications for further details (e.g. RFC2617). + +Case 3 invokes post_https to submit a HTML/CGI form to secure +server. First four arguments are equal to get_https (note that empty +string ('') is passed as header argument). The fifth argument is the +contents of the form formatted according to CGI specification. In this +case the helper function make_https() is used to do the formatting, +but you could pass any string. The post_https() automatically adds +Content-Type and Content-Length headers to the request. + +Case 4 shows the fundamental sslcat() function (inspired in spirit by +netcat utility :-). Its your swiss army knife that allows you to +easily contact servers, send some data, and then get the response. You +are responsible for formatting the data and parsing the response - +sslcat() is just a transport. + +The $trace global variable can be used to control the verbosity of high +level functions. Level 0 guarantees silence, level 1 (the default) +only emits error messages. + +=head2 Convenience routines + +To be used with Low level API + + Net::SSLeay::randomize($rn_seed_file,$additional_seed); + Net::SSLeay::set_server_cert_and_key($ctx, $cert_path, $key_path); + $cert = Net::SSLeay::dump_peer_certificate($ssl); + Net::SSLeay::ssl_write_all($ssl, $message) or die "ssl write failure"; + $got = Net::SSLeay::ssl_read_all($ssl) or die "ssl read failure"; + + $got = Net::SSLeay::ssl_read_CRLF($ssl [, $max_length]); + $got = Net::SSLeay::ssl_read_until($ssl [, $delimit [, $max_length]]); + Net::SSLeay::ssl_write_CRLF($ssl, $message); + +randomize() seeds the eay PRNG with /dev/urandom (see top of SSLeay.pm +for how to change or configure this) and optionally with user provided +data. It is very important to properly seed your random numbers, so +do not forget to call this. The high level API functions automatically +call randomize() so it is not needed with them. + +set_server_cert_and_key() takes two file names as arguments and sets +the server certificate and private key to those. + +dump_peer_certificate() allows you to get plaintext description of the +certificate the peer (usually server) presented to us. + +ssl_read_all() and ssl_write_all() provide true blocking semantics for +these operations (see limitation, below, for explanation). These are +much preferred to the low level API equivalents (which implement BSD +blocking semantics). The message argument to ssl_write_all() can be +reference. This is helpful to avoid unnecessary copy when writing +something big, e.g: + + $data = 'A' x 1000000000; + Net::SSLeay::ssl_write_all($ssl, \$data) or die "ssl write failed"; + +ssl_read_CRLF() uses ssl_read_all() to read in a line terminated with a +carriage return followed by a linefeed (CRLF). The CRLF is included in +the returned scalar. + +ssl_read_until() uses ssl_read_all() to read from the SSL input +stream until it encounters a programmer specified delimiter. +If the delimiter is undefined, $/ is used. If $/ is undefined, +\n is used. One can optionally set a maximum length of bytes to read +from the SSL input stream. + +ssl_write_CRLF() writes $message and appends CRLF to the SSL output stream. + +=head2 Low level API + +In addition to the high level functions outlined above, this module +contains straight forward access to SSL part of OpenSSL C api. Only the SSL +subpart of OpenSSL is implemented (if anyone wants to implement other +parts, feel free to submit patches). + +See ssl.h header from OpenSSL C distribution for list of low lever +SSLeay functions to call (to check if some function has been +implemented see directly in SSLeay.xs). The module strips SSLeay names +of the initial "SSL_", generally you should use Net::SSLeay:: in +place. For example: + +In C: + + #include + + err = SSL_set_verify (ssl, SSL_VERIFY_CLIENT_ONCE, + &your_call_back_here); + +In perl: + + use Net::SSLeay; + + $err = Net::SSLeay::set_verify ($ssl, + &Net::SSLeay::VERIFY_CLIENT_ONCE, + \&your_call_back_here); + +If the function does not start by SSL_ you should use the full +function name, e.g.: + + $err = &Net::SSLeay::ERR_get_error; + +Following new functions behave in perlish way: + + $got = Net::SSLeay::read($ssl); + # Performs SSL_read, but returns $got + # resized according to data received. + # Returns undef on failure. + + Net::SSLeay::write($ssl, $foo) || die; + # Performs SSL_write, but automatically + # figures out the size of $foo + +In order to use the low level API you should start your programs with +the following encantation: + + use Net::SSLeay qw(die_now die_if_ssl_error); + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); # Important! + Net::SSLeay::randomize(); + +die_now() and die_if_ssl_error() are used to conveniently print SSLeay error +stack when something goes wrong, thusly: + + Net::SSLeay:connect($ssl) or die_now("Failed SSL connect ($!)"); + Net::SSLeay::write($ssl, "foo") or die_if_ssl_error("SSL write ($!)"); + +You can also use Net::SSLeay::print_errs() to dump the error stack without +exiting the program. As can be seen, your code becomes much more readable +if you import the error reporting functions to your main name space. + +I can not emphasize enough the need to check error returns. Use these +functions even in most simple programs, they will reduce debugging +time greatly. Do not ask questions in mailing list without having +first sprinkled these in your code. + +=head2 Sockets + +Perl uses file handles for all I/O. While SSLeay has quite flexible BIO +mechanism and perl has evolved PerlIO mechanism, this module still +sticks to using file descriptors. Thus to attach SSLeay to socket you +should use fileno() to extract the underlying file descriptor: + + Net::SSLeay::set_fd($ssl, fileno(S)); # Must use fileno + +You should also use "$|=1;" to eliminate STDIO buffering so you do not +get confused if you use perl I/O functions to manipulate your socket +handle. + +If you need to select(2) on the socket, go right ahead, but be warned +that SSLeay does some internal buffering so SSL_read does not always +return data even if socket selected for reading (just keep on +selecting and trying to read). Net::SSLeay.pm is no different from the +C language OpenSSL in this respect. + +=head2 Callbacks + +WARNING: as of 1.04 the callbacks have changed and have not been tested. + +At this moment the implementation of verify_callback is crippeled in +the sense that at any given time there can be only one call back which +is shared by all SSL contexts, sessions and connections. This is +due to having to keep the reference to the perl call back in a +static variable so that the callback C glue can find it. To remove +this restriction would require either a more complex data structure +(like a hash?) in XSUB to map the call backs to their owners or, +cleaner, adding a context pointer in the SSL structure. This context would +then be passed to the C callback, which in our case would be the glue +to look up the proper Perl function from the context and call it. + +---- inaccurate ---- +The verify call back looks like this in C: + + int (*callback)(int ok,X509 *subj_cert,X509 *issuer_cert, + int depth,int errorcode,char *arg,STACK *cert_chain) + +The corresponding Perl function should be something like this: + + sub verify { + my ($ok, $subj_cert, $issuer_cert, $depth, $errorcode, + $arg, $chain) = @_; + print "Verifying certificate...\n"; + ... + return $ok; + } + +It is used like this: + + Net::SSLeay::set_verify ($ssl, Net::SSLeay::VERIFY_PEER, \&verify); + +No other callbacks are implemented. You do not need to use any +callback for simple (i.e. normal) cases where the SSLeay built-in +verify mechanism satisfies your needs. +---- end inaccurate ---- + +If you want to use callback stuff, see examples/callback.pl! Its the +only one I am able to make work reliably. + +=head2 X509 and RAND stuff + +This module largely lacks interface to the X509 and RAND routines, but +as I was lazy and needed them, the following kludges are implemented: + + $x509_name = Net::SSLeay::X509_get_subject_name($x509_cert); + $x509_name = Net::SSLeay::X509_get_issuer_name($x509_cert); + print Net::SSLeay::X509_NAME_oneline($x509_name); + Net::SSLeay::RAND_seed($buf); # Perlishly figures out buf size + Net::SSLeay::RAND_cleanup(); + Net::SSLeay::RAND_load_file($file_name, $how_many_bytes); + Net::SSLeay::RAND_write_file($file_name); + +Actually you should consider using the following helper functions: + + print Net::SSLeay::dump_peer_certificate($ssl); + Net::SSLeay::randomize(); + +=head1 EXAMPLES + +One very good example is to look at the implementation of sslcat() in the +SSLeay.pm file. + +Following is a simple SSLeay client (with too little error checking :-( + + #!/usr/local/bin/perl + use Socket; + use Net::SSLeay qw(die_now die_if_ssl_error) ; + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::randomize(); + + ($dest_serv, $port, $msg) = @ARGV; # Read command line + $port = getservbyname ($port, 'tcp') unless $port =~ /^\d+$/; + $dest_ip = gethostbyname ($dest_serv); + $dest_serv_params = sockaddr_in($port, $dest_ip); + + socket (S, &AF_INET, &SOCK_STREAM, 0) or die "socket: $!"; + connect (S, $dest_serv_params) or die "connect: $!"; + select (S); $| = 1; select (STDOUT); # Eliminate STDIO buffering + + # The network connection is now open, lets fire up SSL + + $ctx = Net::SSLeay::CTX_new() or die_now("Failed to create SSL_CTX $!"); + Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL) + and die_if_ssl_error("ssl ctx set options"); + $ssl = Net::SSLeay::new($ctx) or die_now("Failed to create SSL $!"); + Net::SSLeay::set_fd($ssl, fileno(S)); # Must use fileno + $res = Net::SSLeay::connect($ssl) and die_if_ssl_error("ssl connect"); + print "Cipher `" . Net::SSLeay::get_cipher($ssl) . "'\n"; + + # Exchange data + + $res = Net::SSLeay::write($ssl, $msg); # Perl knows how long $msg is + die_if_ssl_error("ssl write"); + shutdown S, 1; # Half close --> No more output, sends EOF to server + $got = Net::SSLeay::read($ssl); # Perl returns undef on failure + die_if_ssl_error("ssl read"); + print $got; + + Net::SSLeay::free ($ssl); # Tear down connection + Net::SSLeay::CTX_free ($ctx); + close S; + +Following is a simple SSLeay echo server (non forking): + + #!/usr/local/bin/perl -w + use Socket; + use Net::SSLeay qw(die_now die_if_ssl_error); + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::randomize(); + + $our_ip = "\0\0\0\0"; # Bind to all interfaces + $port = 1235; + $sockaddr_template = 'S n a4 x8'; + $our_serv_params = pack ($sockaddr_template, &AF_INET, $port, $our_ip); + + socket (S, &AF_INET, &SOCK_STREAM, 0) or die "socket: $!"; + bind (S, $our_serv_params) or die "bind: $!"; + listen (S, 5) or die "listen: $!"; + $ctx = Net::SSLeay::CTX_new () or die_now("CTX_new ($ctx): $!"); + Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL) + and die_if_ssl_error("ssl ctx set options"); + + # Following will ask password unless private key is not encrypted + Net::SSLeay::CTX_use_RSAPrivateKey_file ($ctx, 'plain-rsa.pem', + &Net::SSLeay::FILETYPE_PEM); + die_if_ssl_error("private key"); + Net::SSLeay::CTX_use_certificate_file ($ctx, 'plain-cert.pem', + &Net::SSLeay::FILETYPE_PEM); + die_if_ssl_error("certificate"); + + while (1) { + print "Accepting connections...\n"; + ($addr = accept (NS, S)) or die "accept: $!"; + select (NS); $| = 1; select (STDOUT); # Piping hot! + + ($af,$client_port,$client_ip) = unpack($sockaddr_template,$addr); + @inetaddr = unpack('C4',$client_ip); + print "$af connection from " . + join ('.', @inetaddr) . ":$client_port\n"; + + # We now have a network connection, lets fire up SSLeay... + + $ssl = Net::SSLeay::new($ctx) or die_now("SSL_new ($ssl): $!"); + Net::SSLeay::set_fd($ssl, fileno(NS)); + + $err = Net::SSLeay::accept($ssl) and die_if_ssl_error('ssl accept'); + print "Cipher `" . Net::SSLeay::get_cipher($ssl) . "'\n"; + + # Connected. Exchange some data. + + $got = Net::SSLeay::read($ssl); # Returns undef on fail + die_if_ssl_error("ssl read"); + print "Got `$got' (" . length ($got) . " chars)\n"; + + Net::SSLeay::write ($ssl, uc ($got)) or die "write: $!"; + die_if_ssl_error("ssl write"); + + Net::SSLeay::free ($ssl); # Tear down connection + close NS; + } + +Yet another echo server. This one runs from /etc/inetd.conf so it avoids +all the socket code overhead. Only caveat is opening rsa key file - +it had better be without any encryption or else it will not know where +to ask for the password. Note how STDIN and STDOUT are wired to SSL. + + #!/usr/local/bin/perl + # /etc/inetd.conf + # ssltst stream tcp nowait root /path/to/server.pl server.pl + # /etc/services + # ssltst 1234/tcp + + use Net::SSLeay qw(die_now die_if_ssl_error); + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::randomize(); + + chdir '/key/dir' or die "chdir: $!"; + $| = 1; # Piping hot! + open LOG, ">>/dev/console" or die "Can't open log file $!"; + select LOG; print "server.pl started\n"; + + $ctx = Net::SSLeay::CTX_new() or die_now "CTX_new ($ctx) ($!)"; + $ssl = Net::SSLeay::new($ctx) or die_now "new ($ssl) ($!)"; + Net::SSLeay::set_options($ssl, &Net::SSLeay::OP_ALL) + and die_if_ssl_error("ssl set options"); + + # We get already open network connection from inetd, now we just + # need to attach SSLeay to STDIN and STDOUT + Net::SSLeay::set_rfd($ssl, fileno(STDIN)); + Net::SSLeay::set_wfd($ssl, fileno(STDOUT)); + + Net::SSLeay::use_RSAPrivateKey_file ($ssl, 'plain-rsa.pem', + &Net::SSLeay::FILETYPE_PEM); + die_if_ssl_error("private key"); + Net::SSLeay::use_certificate_file ($ssl, 'plain-cert.pem', + &Net::SSLeay::FILETYPE_PEM); + die_if_ssl_error("certificate"); + + Net::SSLeay::accept($ssl) and die_if_ssl_err("ssl accept: $!"); + print "Cipher `" . Net::SSLeay::get_cipher($ssl) . "'\n"; + + $got = Net::SSLeay::read($ssl); + die_if_ssl_error("ssl read"); + print "Got `$got' (" . length ($got) . " chars)\n"; + + Net::SSLeay::write ($ssl, uc($got)) or die "write: $!"; + die_if_ssl_error("ssl write"); + + Net::SSLeay::free ($ssl); # Tear down the connection + Net::SSLeay::CTX_free ($ctx); + close LOG; + +There are also a number of example/test programs in the examples directory: + + sslecho.pl - A simple server, not unlike the one above + minicli.pl - Implements a client using low level SSLeay routines + sslcat.pl - Demonstrates using high level sslcat utility function + get_page.pl - Is a utility for getting html pages from secure servers + callback.pl - Demonstrates certificate verification and callback usage + stdio_bulk.pl - Does SSL over Unix pipes + ssl-inetd-serv.pl - SSL server that can be invoked from inetd.conf + httpd-proxy-snif.pl - Utility that allows you to see how a browser + sends https request to given server and what reply + it gets back (very educative :-) + makecert.pl - Creates a self signed cert (does not use this module) + +=head1 LIMITATIONS + +Net::SSLeay::read uses internal buffer of 32KB, thus no single read +will return more. In practice one read returns much less, usually +as much as fits in one network packet. To work around this, +you should use a loop like this: + + $reply = ''; + do { + $got = Net::SSLeay::read($ssl); + last if print_errs('SSL_read'); + $reply .= $got; + } while ($got); + +Although there is no built in limit in Net::SSLeay::write, the network +packet size limitation applies here as well, thus use: + + $written = 0; + do { + $written .= Net::SSLeay::write($ssl, substr($message, $written)); + last if print_errs('SSL_write'); + } while ($written < length($message)); + +Or alternatively you can just use the following convinence functions: + + Net::SSLeay::ssl_write_all($ssl, $message) or die "ssl write failure"; + $got = Net::SSLeay::ssl_read_all($ssl) or die "ssl read failure"; + +=head1 KNOWN BUGS AND CAVEATS + +Autoloader emits + + Argument "xxx" isn't numeric in entersub at blib/lib/Net/SSLeay.pm' + +warning if die_if_ssl_error is made autoloadable. If you figure out why, +drop me a line. + +Callback set using SSL_set_verify() does not appear to work. This may +well be eay problem (e.g. see ssl/ssl_lib.c line 1029). Try using +SSL_CTX_set_verify() instead and do not be surprised if even this stops +working in future versions. + +Callback and certificate verification stuff is generally too little tested. + +Random numbers are not initialized randomly enough, especially if you +do not have /dev/random and/or /dev/urandom. + +If you are using the low level API functions to communicate with other +SSL implementations, you would do well to call + + Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL) + and die_if_ssl_error("ssl ctx set options"); + +to cope with some well know bugs in some other SSL +implementations. The high level API functions always set all known +compatibility options. + +Sometimes sslcat (and the high level https functions that build on it) +is too fast in signaling the EOF to legacy https servers. This causes +the server to return empty page. To work around this problem you can +set global variable + + $Net::SSLeay::slowly = 1; # Add sleep so broken servers can keep up + +=head1 DIAGNOSTICS + +"Random number generator not seeded!!!" + This warning indicates that randomize() was not able to read + /dev/random or /dev/urandom, possibly because your system does not + have them or they are differently named. You can still use SSL, but + the encryption will not be as strong. + +"open_tcp_connection: destination host not found:`server' (port 123) ($!)" + Name lookup for host named `server' failed. + +"open_tcp_connection: failed `server', 123 ($!)" + The name was resolved, but establising the TCP connection failed. + +"msg 123: 1 - error:140770F8:SSL routines:SSL23_GET_SERVER_HELLO:unknown proto" + SSLeay error string. First (123) number is PID, second number (1) indicates + the position of the error message in SSLeay error stack. You often see + a pile of these messages as errors cascade. + +"msg 123: 1 - error:02001002::lib(2) :func(1) :reason(2)" + The same as above, but you didn't call load_error_strings() so SSLeay + couldn't verbosely explain the error. You can still find out what it + means with this command: + + /usr/local/ssl/bin/ssleay errstr 02001002 + +=head1 VERSION + +This man page documents version 1.04, released on 31.7.1999. This version +had some API changes over 1.03 but is still provisory. Expect to see +version 1.05 to get up to full speed of OpenSSL-0.9.3a and beyound. + +There are currently two perl modules for using OpenSSL C +library: Net::SSLeay (maintaned by me) and SSLeay (maintained by OpenSSL +team). This module is the Net::SSLeay variant. + +At the time of making this release, Eric's module was still quite +scetchy and could not be used for real work, thus I felt motivated to +make this maintenance release. This module is not planned to evolve to +contain any further functionality, i.e. I will concentrate on just +making a simple SSL connection over TCP socket. Presumably Eric's own +module will offer full SSLeay API one day. + +This module uses OpenSSL-0.9.3a. It does not work with any earlier version +and there is no guarantee that it will work with later versions either, +though as long as C API does not change, it should. This module +requires perl5.005 (or better?) though I believe it would build with +any perl5.002 or newer. + +=head1 AUTHOR + +Sampo Kellomaki + +Please send bug reports to the above address. General questions should be +sent either to me or to the mailing list (subscribe by sending mail +to openssl-users-request@openssl.org or using web interface at +http://www.openssl.org/support/). + +=head1 COPYRIGHT + +Copyright (c) 1996-1999 Sampo Kellomaki , All Rights Reserved. + +Distribution and use of this module is under the same terms as the +OpenSSL package itself (i.e. free, but mandatory attribution; NO +WARRANTY). Please consult COPYRIGHT file in the root of the SSLeay +distribution. + +While the source distribution of this perl module does not contain +Eric's or OpenSSL's code, if you use this module you will use OpenSSL +library. Please give Eric and OpenSSL team credit (as required by +their licenses). + +And remember, you, and nobody else but you, are responsible for +auditing this module and OpenSSL library for security problems, +backdoors, and general suitability for your application. + +=head1 SEE ALSO + + Net_SSLeay/examples - Example servers and a clients + - Net::SSLeay.pm home + - OpenSSL source, documentation, etc + openssl-users-request@openssl.org - General OpenSSL mailing list + - SSL Draft specification + - HTTP specifications + - How to send password + +=cut + +# '; + +### +### Open TCP stream to given host and port, looking up the details +### from system databases or DNS. +### + +sub open_tcp_connection { + my ($dest_serv, $port) = @_; + my ($errs); + $port = getservbyname ($port, 'tcp') unless $port =~ /^\d+$/; + my $dest_serv_ip = gethostbyname ($dest_serv); + unless (defined($dest_serv_ip)) { + $errs = "$0 $$: open_tcp_connection: destination host not found:" + . " `$dest_serv' (port $port) ($!)\n"; + warn $errs if $trace; + return wantarray ? (0, $errs) : 0; + } + my $sin = sockaddr_in($port, $dest_serv_ip); + + printf STDERR "Opening connection to $dest_serv:$port (%x)\n", + $dest_serv_ip if $trace>2; + + my $proto = getprotobyname('tcp'); + if (socket (SSLCAT_S, &PF_INET, &SOCK_STREAM, $proto)) { + warn "next connect\n" if $trace>3; + if (connect (SSLCAT_S, $sin)) { + my $old_out = select (SSLCAT_S); $| = 1; select ($old_out); + warn "connected to $dest_serv, $port\n" if $trace>3; + return wantarray ? (1, undef) : 1; # Success + } + } + $errs = "$0 $$: open_tcp_connection: failed `$dest_serv', $port ($!)\n"; + warn $errs if $trace; + close SSLCAT_S; + return wantarray ? (0, $errs) : 0; # Fail +} + +### +### read and write helpers that block +### + +sub ssl_read_all { + my ($ssl,$how_much) = @_; + $how_much = 2000000000 unless $how_much; + my ($reply, $got, $errs); + do { + $got = Net::SSLeay::read($ssl,$how_much); + last if $errs = print_errs('SSL_read'); + $how_much -= length($got); + $vm = (split ' ', `cat /proc/$$/stat`)[22] if $trace>2; # Linux Only? + warn " got " . length($got) . ':' + . length($reply) . " bytes (VM=$vm).\n" if $trace == 3; + warn " got `$got' (" . length($got) . ':' + . length($reply) . " bytes, VM=$vm)\n" if $trace>3; + $reply .= $got; + #$reply = $got; # *** DEBUG + } while ($got && $how_much > 0); + return wantarray ? ($reply, $errs) : $reply; +} + +sub ssl_write_all { + my $ssl = $_[0]; + my ($data_ref, $errs); + if (ref $_[1]) { + $data_ref = $_[1]; + } else { + $data_ref = \$_[1]; + } + my ($wrote, $written, $to_write) = (0,0, length($$data_ref)); + $vm = (split ' ', `cat /proc/$$/stat`)[22] if $trace>2; # Linux Only? + warn " write_all VM at entry=$vm\n" if $trace>2; + do { + #sleep 1; # *** DEBUG + warn "partial `$$data_ref'\n" if $trace>3; + $wrote = write_partial($ssl, $written, $to_write, $$data_ref); + $written += $wrote; + $to_write -= $wrote; + $vm = (split ' ', `cat /proc/$$/stat`)[22] if $trace>2; # Linux Only? + warn " written so far $wrote:$written bytes (VM=$vm)\n" if $trace>2; + return (wantarray ? (undef, $errs) : undef) + if $errs = print_errs('SSL_write'); + } while ($to_write); + return wantarray ? ($written, $errs) : $written; +} + +### from patch by Clinton Wong + +# ssl_read_until($ssl [, $delimit [, $max_length]]) +# if $delimit missing, use $/ if it exists, otherwise use \n +# read until delimiter reached, up to $max_length chars if defined + +sub ssl_read_until { + my ($ssl,$delimit, $max_length) = @_; + + # guess the delimit string if missing + if ( ! defined $delimit ) { + if ( defined $/ && length $/ ) { $delimit = $/ } + else { $delimit = "\n" } # Note: \n,$/ value depends on the platform + } + my $length_delimit = length $delimit; + + my ($reply, $got); + do { + $got = Net::SSLeay::read($ssl,1); + last if print_errs('SSL_read'); + $vm = (split ' ', `cat /proc/$$/stat`)[22] if $trace>1; # Linux Only? + warn " got " . length($got) . ':' + . length($reply) . " bytes (VM=$vm).\n" if $trace == 2; + warn " got `$got' (" . length($got) . ':' + . length($reply) . " bytes, VM=$vm)\n" if $trace>2; + $reply .= $got; + } while ($got && + ( $length_delimit==0 || substr($reply, length($reply)- + $length_delimit) ne $delimit + ) && + (!defined $max_length || length $reply < $max_length) + ); + return $reply; +} + +# ssl_read_CRLF($ssl [, $max_length]) +sub ssl_read_CRLF { ssl_read_until($_[0], chr(13).chr(10), $_[1]) } + +# ssl_write_CRLF($ssl, $message) writes $message and appends CRLF +sub ssl_write_CRLF { + # the next line uses less memory but might use more network packets + return ssl_write_all($_[0], $_[1]) + ssl_write_all($_[0], chr(13).chr(10)); + + # the next few lines do the same thing at the expense of memory, with + # the chance that it will use less packets, since CRLF is in the original + # message and won't be sent separately. + + #my $data_ref; + #if (ref $_[1]) { $data_ref = $_[1] } + # else { $data_ref = \$_[1] } + #my $message = $$data_ref . chr(13).chr(10); + #return ssl_write_all($_[0], \$message); +} + +### Quickly print out with whom we're talking + +sub dump_peer_certificate { + my ($ssl) = @_; + my $cert = get_peer_certificate($ssl); + return if print_errs('get_peer_certificate'); + return "Subject Name: " + . X509_NAME_oneline(X509_get_subject_name($cert)) . "\n" + . "Issuer Name: " + . X509_NAME_oneline(X509_get_issuer_name($cert)) . "\n"; +} + +### Arrange some randomness for eay PRNG + +sub randomize { + my ($rn_seed_file, $seed) = @_; + + RAND_seed(rand() + $$); # Stir it with time and pid + + unless (-r $rn_seed_file || -r $Net::SSLeay::random_device || $seed) { + warn "Random number generator not seeded!!!\n" if $trace; + } + + RAND_load_file($rn_seed_file, -s _) if -r $rn_seed_file; + RAND_seed($seed) if $seed; + RAND_load_file($Net::SSLeay::random_device, $Net::SSLeay::how_random/8) + if -r $Net::SSLeay::random_device; +} + +### +### Basic request - response primitive (don't use for https) +### + +sub sslcat { # address, port, message --> returns reply + my ($dest_serv, $port, $out_message) = @_; + my ($ctx, $ssl, $got, $errs, $written); + + ($got, $errs) = open_tcp_connection($dest_serv, $port, \$errs); + return (wantarray ? (undef, $errs) : undef) unless $got; + + ### Do SSL negotiation stuff + + warn "Creating SSL $ssl_version context...\n" if $trace>2; + load_error_strings(); # Some bloat, but I'm after ease of use + SSLeay_add_ssl_algorithms(); # and debuggability. + randomize('/etc/passwd'); + + if ($ssl_version == 2) { $ctx = CTX_v2_new(); } + elsif ($ssl_version == 3) { $ctx = CTX_v3_new(); } + else { $ctx = CTX_new(); } + + goto cleanup2 if $errs = print_errs('CTX_new') or !$ctx; + + CTX_set_options($ctx, &OP_ALL); + goto cleanup2 if $errs = print_errs('CTX_set_options'); + + warn "Creating SSL connection (context was '$ctx')...\n" if $trace>2; + $ssl = new($ctx); + goto cleanup if $errs = print_errs('SSL_new') or !$ssl; + + warn "Setting fd (ctx $ctx, con $ssl)...\n" if $trace>2; + set_fd($ssl, fileno(SSLCAT_S)); + goto cleanup if $errs = print_errs('set_fd'); + + warn "Entering SSL negotiation phase...\n" if $trace>2; + + $got = Net::SSLeay::connect($ssl); + warn "SSLeay connect returned $got\n" if $trace>2; + goto cleanup if $errs = print_errs('SSL_connect'); + + if ($trace>1) { + warn "Cipher `" . get_cipher($ssl) . "'\n"; + print_errs('get_ciper'); + warn dump_peer_certificate($ssl); + } + + ### Connected. Exchange some data (doing repeated tries if necessary). + + warn "sslcat $$: sending " . length($out_message) . " bytes...\n" + if $trace==3; + warn "sslcat $$: sending `$out_message' (" . length($out_message) + . " bytes)...\n" if $trace>3; + ($written, $errs) = ssl_write_all($ssl, $out_message); + goto cleanup unless $written; + + sleep $slowly if $slowly; # Closing too soon can abort broken servers + shutdown SSLCAT_S, 1; # Half close --> No more output, send EOF to server + + warn "waiting for reply...\n" if $trace>2; + ($got, $errs) = ssl_read_all($ssl); + warn "Got " . length($got) . " bytes.\n" if $trace==3; + warn "Got `$got' (" . length($got) . " bytes)\n" if $trace>3; + +cleanup: + free ($ssl); + $errs .= print_errs('SSL_free'); +cleanup2: + CTX_free ($ctx); + $errs .= print_errs('CTX_free'); + close SSLCAT_S; + return wantarray ? ($got, $errs) : $got; +} + +### +### Basic request - response primitive, this is different from sslcat +### because this does not shutdown the connection. +### + +sub https_cat { # address, port, message --> returns reply + my ($dest_serv, $port, $out_message) = @_; + my ($ctx, $ssl, $got, $errs, $written); + + ($got, $errs) = open_tcp_connection($dest_serv, $port, \$errs); + return (wantarray ? (undef, $errs) : undef) unless $got; + + ### Do SSL negotiation stuff + + warn "Creating SSL $ssl_version context...\n" if $trace>2; + load_error_strings(); # Some bloat, but I'm after ease of use + SSLeay_add_ssl_algorithms(); # and debuggability. + randomize('/etc/passwd'); + + if ($ssl_version == 2) { $ctx = CTX_v2_new(); } + elsif ($ssl_version == 3) { $ctx = CTX_v3_new(); } + else { $ctx = CTX_new(); } + + goto cleanup2 if $errs = print_errs('CTX_new') or !$ctx; + + CTX_set_options($ctx, &OP_ALL); + goto cleanup2 if $errs = print_errs('CTX_set_options'); + + warn "Creating SSL connection (context was '$ctx')...\n" if $trace>2; + $ssl = new($ctx); + goto cleanup if $errs = print_errs('SSL_new') or !$ssl; + + warn "Setting fd (ctx $ctx, con $ssl)...\n" if $trace>2; + set_fd($ssl, fileno(SSLCAT_S)); + goto cleanup if $errs = print_errs('set_fd'); + + warn "Entering SSL negotiation phase...\n" if $trace>2; + + $got = Net::SSLeay::connect($ssl); + warn "SSLeay connect returned $got\n" if $trace>2; + goto cleanup if $errs = print_errs('SSL_connect'); + + if ($trace>1) { + warn "Cipher `" . get_cipher($ssl) . "'\n"; + print_errs('get_ciper'); + warn dump_peer_certificate($ssl); + } + + ### Connected. Exchange some data (doing repeated tries if necessary). + + warn "sslcat $$: sending " . length($out_message) . " bytes...\n" + if $trace==3; + warn "sslcat $$: sending `$out_message' (" . length($out_message) + . " bytes)...\n" if $trace>3; + ($written, $errs) = ssl_write_all($ssl, $out_message); + goto cleanup unless $written; + + warn "waiting for reply...\n" if $trace>2; + ($got, $errs) = ssl_read_all($ssl); + warn "Got " . length($got) . " bytes.\n" if $trace==3; + warn "Got `$got' (" . length($got) . " bytes)\n" if $trace>3; + +cleanup: + free ($ssl); + $errs .= print_errs('SSL_free'); +cleanup2: + CTX_free ($ctx); + $errs .= print_errs('CTX_free'); + close SSLCAT_S; + return wantarray ? ($got, $errs) : $got; +} + +### +### Easy set up of private key and certificate +### + +sub set_server_cert_and_key { + my ($ctx, $cert_path, $key_path) = @_; + my $errs = ''; + # Following will ask password unless private key is not encrypted + CTX_use_RSAPrivateKey_file ($ctx, $key_path, &FILETYPE_PEM); + $errs .= print_errs("private key `$key_path' ($!)"); + CTX_use_certificate_file ($ctx, $cert_path, &FILETYPE_PEM); + $errs .= print_errs("certificate `$cert_path' ($!)"); + return wantarray ? (undef, $errs) : ($errs eq ''); +} + +### +### Easy https manipulation routines +### + +sub make_form { + my (@fields) = @_; + my $form; + while (@fields) { + my ($name, $data) = (shift(@fields), shift(@fields)); + $data =~ s/([^\w\-.\@\$ ])/sprintf("%%%2.2x",ord($1))/gse; + $data =~ tr[ ][+]; + $form .= "$name=$data&"; + } + chop $form; + return $form; +} + +sub make_headers { + my (@headers) = @_; + my $headers; + while (@headers) { + $headers .= shift(@headers) . ': ' . shift(@headers) . "\r\n"; + } + return $headers; +} + +# ($page, $respone_or_err, %headers) = do_https(...); + +sub do_https { + my ($site, $port, $path, $method, $headers, $content, $mime_type) = @_; + my ($response, $page, $errs, $http, $h,$v); + + if ($content) { + $mime_type = "application/x-www-form-urlencoded" unless $mime_type; + my $len = length($content); + $content = "Content-Type: $mime_type\r\n" + . "Content-Length: $len\r\n\r\n$content"; + } else { + $content = "\r\n\r\n"; + } + my $req = "$method $path HTTP/1.0\r\nHost: $site:$port\r\n" + . $headers . "Accept: */*\r\n$content"; + + ($http, $errs) = https_cat($site, $port, $req); + return (undef, "HTTP/1.0 900 NET OR SSL ERROR\r\n\r\n$errs") if $errs; + + ($headers, $page) = split /\s?\n\s?\n/, $http, 2; + ($response, $headers) = split /\s?\n/, $headers, 2; + return ($page, $response, + map( { ($h,$v)=/^(\S+)\:\s*(.*)$/; (uc($h),$v); } + split(/\s?\n/, $headers) + ) + ); +} + +sub get_https { + my ($site, $port, $path, $headers, $content, $mime) = @_; + return do_https($site, $port, $path, 'GET', $headers, $content, $mime); +} + +sub post_https { + my ($site, $port, $path, $headers, $post_str, $mime) = @_; + return do_https($site, $port, $path, 'POST', $headers, $post_str, $mime); +} + +1; +__END__ diff --git a/WebAdmin/QTSSLinkApp/QTSS Admin app.icns b/WebAdmin/QTSSLinkApp/QTSS Admin app.icns new file mode 100644 index 0000000..b7c34db Binary files /dev/null and b/WebAdmin/QTSSLinkApp/QTSS Admin app.icns differ diff --git a/WebAdmin/QTSSLinkApp/qtsslinkapp_main.m b/WebAdmin/QTSSLinkApp/qtsslinkapp_main.m new file mode 100644 index 0000000..0819b09 --- /dev/null +++ b/WebAdmin/QTSSLinkApp/qtsslinkapp_main.m @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +// +// main.m +// QuickTime Streaming Server +// +// Created by John Anderson on Thu Jul 25 2002. +// Copyright (c) 2002 Apple. All rights reserved. +// + +#import + +int main(int argc, const char *argv[]) +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://127.0.0.1:1220/"]]; + return 0; +} diff --git a/WebAdmin/StartupItems.bproj/English.lproj/Localizable.strings b/WebAdmin/StartupItems.bproj/English.lproj/Localizable.strings new file mode 100644 index 0000000..29c5933 --- /dev/null +++ b/WebAdmin/StartupItems.bproj/English.lproj/Localizable.strings @@ -0,0 +1,9 @@ + + + + + Starting QuickTime Streaming Services + Starting QuickTime Streaming Services + + + diff --git a/WebAdmin/StartupItems.bproj/QuickTimeStreamingServer b/WebAdmin/StartupItems.bproj/QuickTimeStreamingServer new file mode 100755 index 0000000..b847061 --- /dev/null +++ b/WebAdmin/StartupItems.bproj/QuickTimeStreamingServer @@ -0,0 +1,60 @@ +#!/bin/sh + +. /etc/rc.common + +PID_DIR="/private/var/run/" +QTSS_PID=$PID_DIR/QuickTimeStreamingServer.pid +WEBADMIN_PID=$PID_DIR/streamingadminserver.pid + +StartService () +{ + + /bin/rm -f /Library/QuickTimeStreaming/Playlists/*/*.pid + /bin/rm -f /Library/QuickTimeStreaming/Playlists/*/*.current + /bin/rm -f /Library/QuickTimeStreaming/Playlists/*/*.upcoming + + if [ "${QTSSRUNSERVER:=-NO-}" = "-YES-" ]; then + ConsoleMessage "Starting QuickTime Streaming Services" + + /usr/sbin/QuickTimeStreamingServer + fi + + if [ "${QTSSWEBADMIN:=-NO-}" = "-YES-" ]; then + ConsoleMessage "Starting QuickTime Streaming Web Admin" + + /usr/sbin/streamingadminserver.pl + fi + + return 0 + +} + +StopService () +{ + + + if [ -e $QTSS_PID ] ; then + ConsoleMessage "Stopping QuickTime Streaming services" + kill `cat $QTSS_PID` > /dev/null 2>&1 + /bin/rm -f $QTSS_PID + fi + + if [ -e $WEBADMIN_PID ] ; then + ConsoleMessage "Stopping QuickTime Streaming Web Admin" + kill `cat $WEBADMIN_PID` > /dev/null 2>&1 + /bin/rm -f $WEBADMIN_PID + fi + + + return 0 +} + +RestartService () +{ + StopService + StartService + return 0 + +} + +RunService "$1" diff --git a/WebAdmin/StartupItems.bproj/StartupParameters.plist b/WebAdmin/StartupItems.bproj/StartupParameters.plist new file mode 100644 index 0000000..d977b43 --- /dev/null +++ b/WebAdmin/StartupItems.bproj/StartupParameters.plist @@ -0,0 +1,11 @@ +{ + Description = "QuickTime Streaming Services"; + Provides = ("QuickTime Streaming Services"); + Requires = ("Resolver"); + OrderPreference = "None"; + Messages = + { + start = "Starting QuickTime Streaming Services"; + stop = "Stopping QuickTime Streaming Services"; + }; +} diff --git a/WebAdmin/StartupItems.bproj/dummy.c b/WebAdmin/StartupItems.bproj/dummy.c new file mode 100644 index 0000000..d521c5a --- /dev/null +++ b/WebAdmin/StartupItems.bproj/dummy.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @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@ + */ +void foo (void) +{ + + +} diff --git a/WebAdmin/WebAdminHtml/MapUTF.pl b/WebAdmin/WebAdminHtml/MapUTF.pl new file mode 100644 index 0000000..0df7b6a --- /dev/null +++ b/WebAdmin/WebAdminHtml/MapUTF.pl @@ -0,0 +1,8281 @@ +package ShiftJIS::CP932::MapUTF; + +use strict; +use vars qw($VERSION $PACKAGE @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); +use vars qw(%CP932_UTF16 %CP932_UTF8 %UTF16_CP932 %UTF8_CP932); + +use Carp; +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw(cp932_to_utf8 cp932_to_utf16 utf8_to_cp932 utf16_to_cp932); +@EXPORT_OK = qw(toUTF8 fromUTF8 toUTF16 fromUTF16); +%EXPORT_TAGS = ('all' => [ @EXPORT, @EXPORT_OK] ); + +$VERSION = '0.07'; +$PACKAGE = 'ShiftJIS::CP932::MapUTF'; # __PACKAGE__ + +my $Uchar = + '(?:[\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|' + . '\xE0[\xA0-\xBF][\x80-\xBF]|' + . '[\xE1-\xEF][\x80-\xBF][\x80-\xBF]|' + . '\xF0[\x90-\xBF][\x80-\xBF][\x80-\xBF]|' + . '[\xF1-\xF3][\x80-\xBF][\x80-\xBF][\x80-\xBF]|' + . '\xF4[\x80-\x8F][\x80-\xBF][\x80-\xBF])'; + +my $Schar = '(?:[\x81-\x9F\xE0-\xFC][\x00-\xFF]|[\x00-\xFF])'; + +my $Hchar = '(?:[\x00-\xFF][\x00-\xD7\xE0-\xFF]|' + . '[\x00-\xFF][\xD8-\xDB][\x00-\xFF][\xDC-\xDF])'; + +sub toUTF8 ($) +{ + my $u = shift; + + croak("$PACKAGE: out of range utf-8 in toUTF8") + if $u < 0 || 0x10FFFF < $u; + + croak("$PACKAGE: unpaired surrogate in toUTF8") + if 0xD800 < $u && $u < 0xDFFF; + + return + $u < 0x0080 ? chr($u) : + $u < 0x0800 ? + pack("CC", + ( ($u >> 6) | 0xc0), + ( ($u & 0x3f) | 0x80) + ) : + $u < 0x10000 ? + pack("CCC", + ( ($u >> 12) | 0xe0), + ((($u >> 6) & 0x3f) | 0x80), + ( ($u & 0x3f) | 0x80) + ) : + pack("CCCC", + ( ($u >> 18) | 0xf0), + ((($u >> 12) & 0x3f) | 0x80), + ((($u >> 6) & 0x3f) | 0x80), + ( ($u & 0x3f) | 0x80) + ); +} + +sub fromUTF8 ($) +{ + my @c = unpack 'C*', shift; + return @c == 1 ? $c[0] : @c == 2 + ? ((($c[0] & 0x1F)<<6)|($c[1] & 0x3F)) + : @c == 3 + ? ((($c[0] & 0x0F)<<12)|(($c[1] & 0x3F)<< 6)|($c[2] & 0x3F)) + : @c == 4 + ? ((($c[0] & 0x07)<<18)|(($c[1] & 0x3F)<<12)| + ( ($c[2] & 0x3F)<< 6)|( $c[3] & 0x3F)) + : croak("$PACKAGE: invalid utf-8 in fromUTF8") +} + +sub toUTF16 ($) +{ + my $n = shift; + croak("$PACKAGE: out of range utf-16 in toUTF16") + if $n < 0 || 0x10FFFF < $n; + + croak("$PACKAGE: unpaired surrogate in toUTF16") + if 0xD800 < $n && $n < 0xDFFF; + + return pack('v', $n) if $n < 0x10000; + $n -= 0x10000; + pack('vv', (($n >> 10) | 0xD800), (($n & 0x3FF) | 0xDC00)); +} + +sub fromUTF16 ($) +{ + my $h = shift; + return unpack('v', $h) if length $h == 2; + croak("$PACKAGE: invalid utf-16 in fromUTF16") if length $h != 4; + + my($high,$low) = unpack("v*",$h); + + croak("$PACKAGE: invalid utf-16 in fromUTF16") + unless 0xD800 <= $high && $high <= 0xDBFF + && 0xDC00 <= $low && $low <= 0xDFFF; + + $high &= 0x3FF; + $low &= 0x3FF; + return 0x10000 + ($high << 10 | $low); +} + +foreach(qw/ +00-0000 +01-0001 +02-0002 +03-0003 +04-0004 +05-0005 +06-0006 +07-0007 +08-0008 +09-0009 +0A-000A +0B-000B +0C-000C +0D-000D +0E-000E +0F-000F +10-0010 +11-0011 +12-0012 +13-0013 +14-0014 +15-0015 +16-0016 +17-0017 +18-0018 +19-0019 +1A-001A +1B-001B +1C-001C +1D-001D +1E-001E +1F-001F +20-0020 +21-0021 +22-0022 +23-0023 +24-0024 +25-0025 +26-0026 +27-0027 +28-0028 +29-0029 +2A-002A +2B-002B +2C-002C +2D-002D +2E-002E +2F-002F +30-0030 +31-0031 +32-0032 +33-0033 +34-0034 +35-0035 +36-0036 +37-0037 +38-0038 +39-0039 +3A-003A +3B-003B +3C-003C +3D-003D +3E-003E +3F-003F +40-0040 +41-0041 +42-0042 +43-0043 +44-0044 +45-0045 +46-0046 +47-0047 +48-0048 +49-0049 +4A-004A +4B-004B +4C-004C +4D-004D +4E-004E +4F-004F +50-0050 +51-0051 +52-0052 +53-0053 +54-0054 +55-0055 +56-0056 +57-0057 +58-0058 +59-0059 +5A-005A +5B-005B +5C-005C +5D-005D +5E-005E +5F-005F +60-0060 +61-0061 +62-0062 +63-0063 +64-0064 +65-0065 +66-0066 +67-0067 +68-0068 +69-0069 +6A-006A +6B-006B +6C-006C +6D-006D +6E-006E +6F-006F +70-0070 +71-0071 +72-0072 +73-0073 +74-0074 +75-0075 +76-0076 +77-0077 +78-0078 +79-0079 +7A-007A +7B-007B +7C-007C +7D-007D +7E-007E +7F-007F +A1-FF61 +A2-FF62 +A3-FF63 +A4-FF64 +A5-FF65 +A6-FF66 +A7-FF67 +A8-FF68 +A9-FF69 +AA-FF6A +AB-FF6B +AC-FF6C +AD-FF6D +AE-FF6E +AF-FF6F +B0-FF70 +B1-FF71 +B2-FF72 +B3-FF73 +B4-FF74 +B5-FF75 +B6-FF76 +B7-FF77 +B8-FF78 +B9-FF79 +BA-FF7A +BB-FF7B +BC-FF7C +BD-FF7D +BE-FF7E +BF-FF7F +C0-FF80 +C1-FF81 +C2-FF82 +C3-FF83 +C4-FF84 +C5-FF85 +C6-FF86 +C7-FF87 +C8-FF88 +C9-FF89 +CA-FF8A +CB-FF8B +CC-FF8C +CD-FF8D +CE-FF8E +CF-FF8F +D0-FF90 +D1-FF91 +D2-FF92 +D3-FF93 +D4-FF94 +D5-FF95 +D6-FF96 +D7-FF97 +D8-FF98 +D9-FF99 +DA-FF9A +DB-FF9B +DC-FF9C +DD-FF9D +DE-FF9E +DF-FF9F +8140-3000 +8141-3001 +8142-3002 +8143-FF0C +8144-FF0E +8145-30FB +8146-FF1A +8147-FF1B +8148-FF1F +8149-FF01 +814A-309B +814B-309C +814C-00B4 +814D-FF40 +814E-00A8 +814F-FF3E +8150-FFE3 +8151-FF3F +8152-30FD +8153-30FE +8154-309D +8155-309E +8156-3003 +8157-4EDD +8158-3005 +8159-3006 +815A-3007 +815B-30FC +815C-2015 +815D-2010 +815E-FF0F +815F-FF3C +8160-FF5E +8161-2225 +8162-FF5C +8163-2026 +8164-2025 +8165-2018 +8166-2019 +8167-201C +8168-201D +8169-FF08 +816A-FF09 +816B-3014 +816C-3015 +816D-FF3B +816E-FF3D +816F-FF5B +8170-FF5D +8171-3008 +8172-3009 +8173-300A +8174-300B +8175-300C +8176-300D +8177-300E +8178-300F +8179-3010 +817A-3011 +817B-FF0B +817C-FF0D +817D-00B1 +817E-00D7 +8180-00F7 +8181-FF1D +8182-2260 +8183-FF1C +8184-FF1E +8185-2266 +8186-2267 +8187-221E +8188-2234 +8189-2642 +818A-2640 +818B-00B0 +818C-2032 +818D-2033 +818E-2103 +818F-FFE5 +8190-FF04 +8191-FFE0 +8192-FFE1 +8193-FF05 +8194-FF03 +8195-FF06 +8196-FF0A +8197-FF20 +8198-00A7 +8199-2606 +819A-2605 +819B-25CB +819C-25CF +819D-25CE +819E-25C7 +819F-25C6 +81A0-25A1 +81A1-25A0 +81A2-25B3 +81A3-25B2 +81A4-25BD +81A5-25BC +81A6-203B +81A7-3012 +81A8-2192 +81A9-2190 +81AA-2191 +81AB-2193 +81AC-3013 +81B8-2208 +81B9-220B +81BA-2286 +81BB-2287 +81BC-2282 +81BD-2283 +81BE-222A-t +81BF-2229-t +81C8-2227 +81C9-2228 +81CA-FFE2-t +81CB-21D2 +81CC-21D4 +81CD-2200 +81CE-2203 +81DA-2220-t +81DB-22A5-t +81DC-2312 +81DD-2202 +81DE-2207 +81DF-2261-t +81E0-2252-t +81E1-226A +81E2-226B +81E3-221A-t +81E4-223D +81E5-221D +81E6-2235-t +81E7-222B-t +81E8-222C +81F0-212B +81F1-2030 +81F2-266F +81F3-266D +81F4-266A +81F5-2020 +81F6-2021 +81F7-00B6 +81FC-25EF +824F-FF10 +8250-FF11 +8251-FF12 +8252-FF13 +8253-FF14 +8254-FF15 +8255-FF16 +8256-FF17 +8257-FF18 +8258-FF19 +8260-FF21 +8261-FF22 +8262-FF23 +8263-FF24 +8264-FF25 +8265-FF26 +8266-FF27 +8267-FF28 +8268-FF29 +8269-FF2A +826A-FF2B +826B-FF2C +826C-FF2D +826D-FF2E +826E-FF2F +826F-FF30 +8270-FF31 +8271-FF32 +8272-FF33 +8273-FF34 +8274-FF35 +8275-FF36 +8276-FF37 +8277-FF38 +8278-FF39 +8279-FF3A +8281-FF41 +8282-FF42 +8283-FF43 +8284-FF44 +8285-FF45 +8286-FF46 +8287-FF47 +8288-FF48 +8289-FF49 +828A-FF4A +828B-FF4B +828C-FF4C +828D-FF4D +828E-FF4E +828F-FF4F +8290-FF50 +8291-FF51 +8292-FF52 +8293-FF53 +8294-FF54 +8295-FF55 +8296-FF56 +8297-FF57 +8298-FF58 +8299-FF59 +829A-FF5A +829F-3041 +82A0-3042 +82A1-3043 +82A2-3044 +82A3-3045 +82A4-3046 +82A5-3047 +82A6-3048 +82A7-3049 +82A8-304A +82A9-304B +82AA-304C +82AB-304D +82AC-304E +82AD-304F +82AE-3050 +82AF-3051 +82B0-3052 +82B1-3053 +82B2-3054 +82B3-3055 +82B4-3056 +82B5-3057 +82B6-3058 +82B7-3059 +82B8-305A +82B9-305B +82BA-305C +82BB-305D +82BC-305E +82BD-305F +82BE-3060 +82BF-3061 +82C0-3062 +82C1-3063 +82C2-3064 +82C3-3065 +82C4-3066 +82C5-3067 +82C6-3068 +82C7-3069 +82C8-306A +82C9-306B +82CA-306C +82CB-306D +82CC-306E +82CD-306F +82CE-3070 +82CF-3071 +82D0-3072 +82D1-3073 +82D2-3074 +82D3-3075 +82D4-3076 +82D5-3077 +82D6-3078 +82D7-3079 +82D8-307A +82D9-307B +82DA-307C +82DB-307D +82DC-307E +82DD-307F +82DE-3080 +82DF-3081 +82E0-3082 +82E1-3083 +82E2-3084 +82E3-3085 +82E4-3086 +82E5-3087 +82E6-3088 +82E7-3089 +82E8-308A +82E9-308B +82EA-308C +82EB-308D +82EC-308E +82ED-308F +82EE-3090 +82EF-3091 +82F0-3092 +82F1-3093 +8340-30A1 +8341-30A2 +8342-30A3 +8343-30A4 +8344-30A5 +8345-30A6 +8346-30A7 +8347-30A8 +8348-30A9 +8349-30AA +834A-30AB +834B-30AC +834C-30AD +834D-30AE +834E-30AF +834F-30B0 +8350-30B1 +8351-30B2 +8352-30B3 +8353-30B4 +8354-30B5 +8355-30B6 +8356-30B7 +8357-30B8 +8358-30B9 +8359-30BA +835A-30BB +835B-30BC +835C-30BD +835D-30BE +835E-30BF +835F-30C0 +8360-30C1 +8361-30C2 +8362-30C3 +8363-30C4 +8364-30C5 +8365-30C6 +8366-30C7 +8367-30C8 +8368-30C9 +8369-30CA +836A-30CB +836B-30CC +836C-30CD +836D-30CE +836E-30CF +836F-30D0 +8370-30D1 +8371-30D2 +8372-30D3 +8373-30D4 +8374-30D5 +8375-30D6 +8376-30D7 +8377-30D8 +8378-30D9 +8379-30DA +837A-30DB +837B-30DC +837C-30DD +837D-30DE +837E-30DF +8380-30E0 +8381-30E1 +8382-30E2 +8383-30E3 +8384-30E4 +8385-30E5 +8386-30E6 +8387-30E7 +8388-30E8 +8389-30E9 +838A-30EA +838B-30EB +838C-30EC +838D-30ED +838E-30EE +838F-30EF +8390-30F0 +8391-30F1 +8392-30F2 +8393-30F3 +8394-30F4 +8395-30F5 +8396-30F6 +839F-0391 +83A0-0392 +83A1-0393 +83A2-0394 +83A3-0395 +83A4-0396 +83A5-0397 +83A6-0398 +83A7-0399 +83A8-039A +83A9-039B +83AA-039C +83AB-039D +83AC-039E +83AD-039F +83AE-03A0 +83AF-03A1 +83B0-03A3 +83B1-03A4 +83B2-03A5 +83B3-03A6 +83B4-03A7 +83B5-03A8 +83B6-03A9 +83BF-03B1 +83C0-03B2 +83C1-03B3 +83C2-03B4 +83C3-03B5 +83C4-03B6 +83C5-03B7 +83C6-03B8 +83C7-03B9 +83C8-03BA +83C9-03BB +83CA-03BC +83CB-03BD +83CC-03BE +83CD-03BF +83CE-03C0 +83CF-03C1 +83D0-03C3 +83D1-03C4 +83D2-03C5 +83D3-03C6 +83D4-03C7 +83D5-03C8 +83D6-03C9 +8440-0410 +8441-0411 +8442-0412 +8443-0413 +8444-0414 +8445-0415 +8446-0401 +8447-0416 +8448-0417 +8449-0418 +844A-0419 +844B-041A +844C-041B +844D-041C +844E-041D +844F-041E +8450-041F +8451-0420 +8452-0421 +8453-0422 +8454-0423 +8455-0424 +8456-0425 +8457-0426 +8458-0427 +8459-0428 +845A-0429 +845B-042A +845C-042B +845D-042C +845E-042D +845F-042E +8460-042F +8470-0430 +8471-0431 +8472-0432 +8473-0433 +8474-0434 +8475-0435 +8476-0451 +8477-0436 +8478-0437 +8479-0438 +847A-0439 +847B-043A +847C-043B +847D-043C +847E-043D +8480-043E +8481-043F +8482-0440 +8483-0441 +8484-0442 +8485-0443 +8486-0444 +8487-0445 +8488-0446 +8489-0447 +848A-0448 +848B-0449 +848C-044A +848D-044B +848E-044C +848F-044D +8490-044E +8491-044F +849F-2500 +84A0-2502 +84A1-250C +84A2-2510 +84A3-2518 +84A4-2514 +84A5-251C +84A6-252C +84A7-2524 +84A8-2534 +84A9-253C +84AA-2501 +84AB-2503 +84AC-250F +84AD-2513 +84AE-251B +84AF-2517 +84B0-2523 +84B1-2533 +84B2-252B +84B3-253B +84B4-254B +84B5-2520 +84B6-252F +84B7-2528 +84B8-2537 +84B9-253F +84BA-251D +84BB-2530 +84BC-2525 +84BD-2538 +84BE-2542 +8740-2460 +8741-2461 +8742-2462 +8743-2463 +8744-2464 +8745-2465 +8746-2466 +8747-2467 +8748-2468 +8749-2469 +874A-246A +874B-246B +874C-246C +874D-246D +874E-246E +874F-246F +8750-2470 +8751-2471 +8752-2472 +8753-2473 +8754-2160-t +8755-2161-t +8756-2162-t +8757-2163-t +8758-2164-t +8759-2165-t +875A-2166-t +875B-2167-t +875C-2168-t +875D-2169-t +875F-3349 +8760-3314 +8761-3322 +8762-334D +8763-3318 +8764-3327 +8765-3303 +8766-3336 +8767-3351 +8768-3357 +8769-330D +876A-3326 +876B-3323 +876C-332B +876D-334A +876E-333B +876F-339C +8770-339D +8771-339E +8772-338E +8773-338F +8774-33C4 +8775-33A1 +877E-337B +8780-301D +8781-301F +8782-2116-t +8783-33CD +8784-2121-t +8785-32A4 +8786-32A5 +8787-32A6 +8788-32A7 +8789-32A8 +878A-3231-t +878B-3232 +878C-3239 +878D-337E +878E-337D +878F-337C +8790-2252-f +8791-2261-f +8792-222B-f +8793-222E +8794-2211 +8795-221A-f +8796-22A5-f +8797-2220-f +8798-221F +8799-22BF +879A-2235-f +879B-2229-f +879C-222A-f +889F-4E9C +88A0-5516 +88A1-5A03 +88A2-963F +88A3-54C0 +88A4-611B +88A5-6328 +88A6-59F6 +88A7-9022 +88A8-8475 +88A9-831C +88AA-7A50 +88AB-60AA +88AC-63E1 +88AD-6E25 +88AE-65ED +88AF-8466 +88B0-82A6 +88B1-9BF5 +88B2-6893 +88B3-5727 +88B4-65A1 +88B5-6271 +88B6-5B9B +88B7-59D0 +88B8-867B +88B9-98F4 +88BA-7D62 +88BB-7DBE +88BC-9B8E +88BD-6216 +88BE-7C9F +88BF-88B7 +88C0-5B89 +88C1-5EB5 +88C2-6309 +88C3-6697 +88C4-6848 +88C5-95C7 +88C6-978D +88C7-674F +88C8-4EE5 +88C9-4F0A +88CA-4F4D +88CB-4F9D +88CC-5049 +88CD-56F2 +88CE-5937 +88CF-59D4 +88D0-5A01 +88D1-5C09 +88D2-60DF +88D3-610F +88D4-6170 +88D5-6613 +88D6-6905 +88D7-70BA +88D8-754F +88D9-7570 +88DA-79FB +88DB-7DAD +88DC-7DEF +88DD-80C3 +88DE-840E +88DF-8863 +88E0-8B02 +88E1-9055 +88E2-907A +88E3-533B +88E4-4E95 +88E5-4EA5 +88E6-57DF +88E7-80B2 +88E8-90C1 +88E9-78EF +88EA-4E00 +88EB-58F1 +88EC-6EA2 +88ED-9038 +88EE-7A32 +88EF-8328 +88F0-828B +88F1-9C2F +88F2-5141 +88F3-5370 +88F4-54BD +88F5-54E1 +88F6-56E0 +88F7-59FB +88F8-5F15 +88F9-98F2 +88FA-6DEB +88FB-80E4 +88FC-852D +8940-9662 +8941-9670 +8942-96A0 +8943-97FB +8944-540B +8945-53F3 +8946-5B87 +8947-70CF +8948-7FBD +8949-8FC2 +894A-96E8 +894B-536F +894C-9D5C +894D-7ABA +894E-4E11 +894F-7893 +8950-81FC +8951-6E26 +8952-5618 +8953-5504 +8954-6B1D +8955-851A +8956-9C3B +8957-59E5 +8958-53A9 +8959-6D66 +895A-74DC +895B-958F +895C-5642 +895D-4E91 +895E-904B +895F-96F2 +8960-834F +8961-990C +8962-53E1 +8963-55B6 +8964-5B30 +8965-5F71 +8966-6620 +8967-66F3 +8968-6804 +8969-6C38 +896A-6CF3 +896B-6D29 +896C-745B +896D-76C8 +896E-7A4E +896F-9834 +8970-82F1 +8971-885B +8972-8A60 +8973-92ED +8974-6DB2 +8975-75AB +8976-76CA +8977-99C5 +8978-60A6 +8979-8B01 +897A-8D8A +897B-95B2 +897C-698E +897D-53AD +897E-5186 +8980-5712 +8981-5830 +8982-5944 +8983-5BB4 +8984-5EF6 +8985-6028 +8986-63A9 +8987-63F4 +8988-6CBF +8989-6F14 +898A-708E +898B-7114 +898C-7159 +898D-71D5 +898E-733F +898F-7E01 +8990-8276 +8991-82D1 +8992-8597 +8993-9060 +8994-925B +8995-9D1B +8996-5869 +8997-65BC +8998-6C5A +8999-7525 +899A-51F9 +899B-592E +899C-5965 +899D-5F80 +899E-5FDC +899F-62BC +89A0-65FA +89A1-6A2A +89A2-6B27 +89A3-6BB4 +89A4-738B +89A5-7FC1 +89A6-8956 +89A7-9D2C +89A8-9D0E +89A9-9EC4 +89AA-5CA1 +89AB-6C96 +89AC-837B +89AD-5104 +89AE-5C4B +89AF-61B6 +89B0-81C6 +89B1-6876 +89B2-7261 +89B3-4E59 +89B4-4FFA +89B5-5378 +89B6-6069 +89B7-6E29 +89B8-7A4F +89B9-97F3 +89BA-4E0B +89BB-5316 +89BC-4EEE +89BD-4F55 +89BE-4F3D +89BF-4FA1 +89C0-4F73 +89C1-52A0 +89C2-53EF +89C3-5609 +89C4-590F +89C5-5AC1 +89C6-5BB6 +89C7-5BE1 +89C8-79D1 +89C9-6687 +89CA-679C +89CB-67B6 +89CC-6B4C +89CD-6CB3 +89CE-706B +89CF-73C2 +89D0-798D +89D1-79BE +89D2-7A3C +89D3-7B87 +89D4-82B1 +89D5-82DB +89D6-8304 +89D7-8377 +89D8-83EF +89D9-83D3 +89DA-8766 +89DB-8AB2 +89DC-5629 +89DD-8CA8 +89DE-8FE6 +89DF-904E +89E0-971E +89E1-868A +89E2-4FC4 +89E3-5CE8 +89E4-6211 +89E5-7259 +89E6-753B +89E7-81E5 +89E8-82BD +89E9-86FE +89EA-8CC0 +89EB-96C5 +89EC-9913 +89ED-99D5 +89EE-4ECB +89EF-4F1A +89F0-89E3 +89F1-56DE +89F2-584A +89F3-58CA +89F4-5EFB +89F5-5FEB +89F6-602A +89F7-6094 +89F8-6062 +89F9-61D0 +89FA-6212 +89FB-62D0 +89FC-6539 +8A40-9B41 +8A41-6666 +8A42-68B0 +8A43-6D77 +8A44-7070 +8A45-754C +8A46-7686 +8A47-7D75 +8A48-82A5 +8A49-87F9 +8A4A-958B +8A4B-968E +8A4C-8C9D +8A4D-51F1 +8A4E-52BE +8A4F-5916 +8A50-54B3 +8A51-5BB3 +8A52-5D16 +8A53-6168 +8A54-6982 +8A55-6DAF +8A56-788D +8A57-84CB +8A58-8857 +8A59-8A72 +8A5A-93A7 +8A5B-9AB8 +8A5C-6D6C +8A5D-99A8 +8A5E-86D9 +8A5F-57A3 +8A60-67FF +8A61-86CE +8A62-920E +8A63-5283 +8A64-5687 +8A65-5404 +8A66-5ED3 +8A67-62E1 +8A68-64B9 +8A69-683C +8A6A-6838 +8A6B-6BBB +8A6C-7372 +8A6D-78BA +8A6E-7A6B +8A6F-899A +8A70-89D2 +8A71-8D6B +8A72-8F03 +8A73-90ED +8A74-95A3 +8A75-9694 +8A76-9769 +8A77-5B66 +8A78-5CB3 +8A79-697D +8A7A-984D +8A7B-984E +8A7C-639B +8A7D-7B20 +8A7E-6A2B +8A80-6A7F +8A81-68B6 +8A82-9C0D +8A83-6F5F +8A84-5272 +8A85-559D +8A86-6070 +8A87-62EC +8A88-6D3B +8A89-6E07 +8A8A-6ED1 +8A8B-845B +8A8C-8910 +8A8D-8F44 +8A8E-4E14 +8A8F-9C39 +8A90-53F6 +8A91-691B +8A92-6A3A +8A93-9784 +8A94-682A +8A95-515C +8A96-7AC3 +8A97-84B2 +8A98-91DC +8A99-938C +8A9A-565B +8A9B-9D28 +8A9C-6822 +8A9D-8305 +8A9E-8431 +8A9F-7CA5 +8AA0-5208 +8AA1-82C5 +8AA2-74E6 +8AA3-4E7E +8AA4-4F83 +8AA5-51A0 +8AA6-5BD2 +8AA7-520A +8AA8-52D8 +8AA9-52E7 +8AAA-5DFB +8AAB-559A +8AAC-582A +8AAD-59E6 +8AAE-5B8C +8AAF-5B98 +8AB0-5BDB +8AB1-5E72 +8AB2-5E79 +8AB3-60A3 +8AB4-611F +8AB5-6163 +8AB6-61BE +8AB7-63DB +8AB8-6562 +8AB9-67D1 +8ABA-6853 +8ABB-68FA +8ABC-6B3E +8ABD-6B53 +8ABE-6C57 +8ABF-6F22 +8AC0-6F97 +8AC1-6F45 +8AC2-74B0 +8AC3-7518 +8AC4-76E3 +8AC5-770B +8AC6-7AFF +8AC7-7BA1 +8AC8-7C21 +8AC9-7DE9 +8ACA-7F36 +8ACB-7FF0 +8ACC-809D +8ACD-8266 +8ACE-839E +8ACF-89B3 +8AD0-8ACC +8AD1-8CAB +8AD2-9084 +8AD3-9451 +8AD4-9593 +8AD5-9591 +8AD6-95A2 +8AD7-9665 +8AD8-97D3 +8AD9-9928 +8ADA-8218 +8ADB-4E38 +8ADC-542B +8ADD-5CB8 +8ADE-5DCC +8ADF-73A9 +8AE0-764C +8AE1-773C +8AE2-5CA9 +8AE3-7FEB +8AE4-8D0B +8AE5-96C1 +8AE6-9811 +8AE7-9854 +8AE8-9858 +8AE9-4F01 +8AEA-4F0E +8AEB-5371 +8AEC-559C +8AED-5668 +8AEE-57FA +8AEF-5947 +8AF0-5B09 +8AF1-5BC4 +8AF2-5C90 +8AF3-5E0C +8AF4-5E7E +8AF5-5FCC +8AF6-63EE +8AF7-673A +8AF8-65D7 +8AF9-65E2 +8AFA-671F +8AFB-68CB +8AFC-68C4 +8B40-6A5F +8B41-5E30 +8B42-6BC5 +8B43-6C17 +8B44-6C7D +8B45-757F +8B46-7948 +8B47-5B63 +8B48-7A00 +8B49-7D00 +8B4A-5FBD +8B4B-898F +8B4C-8A18 +8B4D-8CB4 +8B4E-8D77 +8B4F-8ECC +8B50-8F1D +8B51-98E2 +8B52-9A0E +8B53-9B3C +8B54-4E80 +8B55-507D +8B56-5100 +8B57-5993 +8B58-5B9C +8B59-622F +8B5A-6280 +8B5B-64EC +8B5C-6B3A +8B5D-72A0 +8B5E-7591 +8B5F-7947 +8B60-7FA9 +8B61-87FB +8B62-8ABC +8B63-8B70 +8B64-63AC +8B65-83CA +8B66-97A0 +8B67-5409 +8B68-5403 +8B69-55AB +8B6A-6854 +8B6B-6A58 +8B6C-8A70 +8B6D-7827 +8B6E-6775 +8B6F-9ECD +8B70-5374 +8B71-5BA2 +8B72-811A +8B73-8650 +8B74-9006 +8B75-4E18 +8B76-4E45 +8B77-4EC7 +8B78-4F11 +8B79-53CA +8B7A-5438 +8B7B-5BAE +8B7C-5F13 +8B7D-6025 +8B7E-6551 +8B80-673D +8B81-6C42 +8B82-6C72 +8B83-6CE3 +8B84-7078 +8B85-7403 +8B86-7A76 +8B87-7AAE +8B88-7B08 +8B89-7D1A +8B8A-7CFE +8B8B-7D66 +8B8C-65E7 +8B8D-725B +8B8E-53BB +8B8F-5C45 +8B90-5DE8 +8B91-62D2 +8B92-62E0 +8B93-6319 +8B94-6E20 +8B95-865A +8B96-8A31 +8B97-8DDD +8B98-92F8 +8B99-6F01 +8B9A-79A6 +8B9B-9B5A +8B9C-4EA8 +8B9D-4EAB +8B9E-4EAC +8B9F-4F9B +8BA0-4FA0 +8BA1-50D1 +8BA2-5147 +8BA3-7AF6 +8BA4-5171 +8BA5-51F6 +8BA6-5354 +8BA7-5321 +8BA8-537F +8BA9-53EB +8BAA-55AC +8BAB-5883 +8BAC-5CE1 +8BAD-5F37 +8BAE-5F4A +8BAF-602F +8BB0-6050 +8BB1-606D +8BB2-631F +8BB3-6559 +8BB4-6A4B +8BB5-6CC1 +8BB6-72C2 +8BB7-72ED +8BB8-77EF +8BB9-80F8 +8BBA-8105 +8BBB-8208 +8BBC-854E +8BBD-90F7 +8BBE-93E1 +8BBF-97FF +8BC0-9957 +8BC1-9A5A +8BC2-4EF0 +8BC3-51DD +8BC4-5C2D +8BC5-6681 +8BC6-696D +8BC7-5C40 +8BC8-66F2 +8BC9-6975 +8BCA-7389 +8BCB-6850 +8BCC-7C81 +8BCD-50C5 +8BCE-52E4 +8BCF-5747 +8BD0-5DFE +8BD1-9326 +8BD2-65A4 +8BD3-6B23 +8BD4-6B3D +8BD5-7434 +8BD6-7981 +8BD7-79BD +8BD8-7B4B +8BD9-7DCA +8BDA-82B9 +8BDB-83CC +8BDC-887F +8BDD-895F +8BDE-8B39 +8BDF-8FD1 +8BE0-91D1 +8BE1-541F +8BE2-9280 +8BE3-4E5D +8BE4-5036 +8BE5-53E5 +8BE6-533A +8BE7-72D7 +8BE8-7396 +8BE9-77E9 +8BEA-82E6 +8BEB-8EAF +8BEC-99C6 +8BED-99C8 +8BEE-99D2 +8BEF-5177 +8BF0-611A +8BF1-865E +8BF2-55B0 +8BF3-7A7A +8BF4-5076 +8BF5-5BD3 +8BF6-9047 +8BF7-9685 +8BF8-4E32 +8BF9-6ADB +8BFA-91E7 +8BFB-5C51 +8BFC-5C48 +8C40-6398 +8C41-7A9F +8C42-6C93 +8C43-9774 +8C44-8F61 +8C45-7AAA +8C46-718A +8C47-9688 +8C48-7C82 +8C49-6817 +8C4A-7E70 +8C4B-6851 +8C4C-936C +8C4D-52F2 +8C4E-541B +8C4F-85AB +8C50-8A13 +8C51-7FA4 +8C52-8ECD +8C53-90E1 +8C54-5366 +8C55-8888 +8C56-7941 +8C57-4FC2 +8C58-50BE +8C59-5211 +8C5A-5144 +8C5B-5553 +8C5C-572D +8C5D-73EA +8C5E-578B +8C5F-5951 +8C60-5F62 +8C61-5F84 +8C62-6075 +8C63-6176 +8C64-6167 +8C65-61A9 +8C66-63B2 +8C67-643A +8C68-656C +8C69-666F +8C6A-6842 +8C6B-6E13 +8C6C-7566 +8C6D-7A3D +8C6E-7CFB +8C6F-7D4C +8C70-7D99 +8C71-7E4B +8C72-7F6B +8C73-830E +8C74-834A +8C75-86CD +8C76-8A08 +8C77-8A63 +8C78-8B66 +8C79-8EFD +8C7A-981A +8C7B-9D8F +8C7C-82B8 +8C7D-8FCE +8C7E-9BE8 +8C80-5287 +8C81-621F +8C82-6483 +8C83-6FC0 +8C84-9699 +8C85-6841 +8C86-5091 +8C87-6B20 +8C88-6C7A +8C89-6F54 +8C8A-7A74 +8C8B-7D50 +8C8C-8840 +8C8D-8A23 +8C8E-6708 +8C8F-4EF6 +8C90-5039 +8C91-5026 +8C92-5065 +8C93-517C +8C94-5238 +8C95-5263 +8C96-55A7 +8C97-570F +8C98-5805 +8C99-5ACC +8C9A-5EFA +8C9B-61B2 +8C9C-61F8 +8C9D-62F3 +8C9E-6372 +8C9F-691C +8CA0-6A29 +8CA1-727D +8CA2-72AC +8CA3-732E +8CA4-7814 +8CA5-786F +8CA6-7D79 +8CA7-770C +8CA8-80A9 +8CA9-898B +8CAA-8B19 +8CAB-8CE2 +8CAC-8ED2 +8CAD-9063 +8CAE-9375 +8CAF-967A +8CB0-9855 +8CB1-9A13 +8CB2-9E78 +8CB3-5143 +8CB4-539F +8CB5-53B3 +8CB6-5E7B +8CB7-5F26 +8CB8-6E1B +8CB9-6E90 +8CBA-7384 +8CBB-73FE +8CBC-7D43 +8CBD-8237 +8CBE-8A00 +8CBF-8AFA +8CC0-9650 +8CC1-4E4E +8CC2-500B +8CC3-53E4 +8CC4-547C +8CC5-56FA +8CC6-59D1 +8CC7-5B64 +8CC8-5DF1 +8CC9-5EAB +8CCA-5F27 +8CCB-6238 +8CCC-6545 +8CCD-67AF +8CCE-6E56 +8CCF-72D0 +8CD0-7CCA +8CD1-88B4 +8CD2-80A1 +8CD3-80E1 +8CD4-83F0 +8CD5-864E +8CD6-8A87 +8CD7-8DE8 +8CD8-9237 +8CD9-96C7 +8CDA-9867 +8CDB-9F13 +8CDC-4E94 +8CDD-4E92 +8CDE-4F0D +8CDF-5348 +8CE0-5449 +8CE1-543E +8CE2-5A2F +8CE3-5F8C +8CE4-5FA1 +8CE5-609F +8CE6-68A7 +8CE7-6A8E +8CE8-745A +8CE9-7881 +8CEA-8A9E +8CEB-8AA4 +8CEC-8B77 +8CED-9190 +8CEE-4E5E +8CEF-9BC9 +8CF0-4EA4 +8CF1-4F7C +8CF2-4FAF +8CF3-5019 +8CF4-5016 +8CF5-5149 +8CF6-516C +8CF7-529F +8CF8-52B9 +8CF9-52FE +8CFA-539A +8CFB-53E3 +8CFC-5411 +8D40-540E +8D41-5589 +8D42-5751 +8D43-57A2 +8D44-597D +8D45-5B54 +8D46-5B5D +8D47-5B8F +8D48-5DE5 +8D49-5DE7 +8D4A-5DF7 +8D4B-5E78 +8D4C-5E83 +8D4D-5E9A +8D4E-5EB7 +8D4F-5F18 +8D50-6052 +8D51-614C +8D52-6297 +8D53-62D8 +8D54-63A7 +8D55-653B +8D56-6602 +8D57-6643 +8D58-66F4 +8D59-676D +8D5A-6821 +8D5B-6897 +8D5C-69CB +8D5D-6C5F +8D5E-6D2A +8D5F-6D69 +8D60-6E2F +8D61-6E9D +8D62-7532 +8D63-7687 +8D64-786C +8D65-7A3F +8D66-7CE0 +8D67-7D05 +8D68-7D18 +8D69-7D5E +8D6A-7DB1 +8D6B-8015 +8D6C-8003 +8D6D-80AF +8D6E-80B1 +8D6F-8154 +8D70-818F +8D71-822A +8D72-8352 +8D73-884C +8D74-8861 +8D75-8B1B +8D76-8CA2 +8D77-8CFC +8D78-90CA +8D79-9175 +8D7A-9271 +8D7B-783F +8D7C-92FC +8D7D-95A4 +8D7E-964D +8D80-9805 +8D81-9999 +8D82-9AD8 +8D83-9D3B +8D84-525B +8D85-52AB +8D86-53F7 +8D87-5408 +8D88-58D5 +8D89-62F7 +8D8A-6FE0 +8D8B-8C6A +8D8C-8F5F +8D8D-9EB9 +8D8E-514B +8D8F-523B +8D90-544A +8D91-56FD +8D92-7A40 +8D93-9177 +8D94-9D60 +8D95-9ED2 +8D96-7344 +8D97-6F09 +8D98-8170 +8D99-7511 +8D9A-5FFD +8D9B-60DA +8D9C-9AA8 +8D9D-72DB +8D9E-8FBC +8D9F-6B64 +8DA0-9803 +8DA1-4ECA +8DA2-56F0 +8DA3-5764 +8DA4-58BE +8DA5-5A5A +8DA6-6068 +8DA7-61C7 +8DA8-660F +8DA9-6606 +8DAA-6839 +8DAB-68B1 +8DAC-6DF7 +8DAD-75D5 +8DAE-7D3A +8DAF-826E +8DB0-9B42 +8DB1-4E9B +8DB2-4F50 +8DB3-53C9 +8DB4-5506 +8DB5-5D6F +8DB6-5DE6 +8DB7-5DEE +8DB8-67FB +8DB9-6C99 +8DBA-7473 +8DBB-7802 +8DBC-8A50 +8DBD-9396 +8DBE-88DF +8DBF-5750 +8DC0-5EA7 +8DC1-632B +8DC2-50B5 +8DC3-50AC +8DC4-518D +8DC5-6700 +8DC6-54C9 +8DC7-585E +8DC8-59BB +8DC9-5BB0 +8DCA-5F69 +8DCB-624D +8DCC-63A1 +8DCD-683D +8DCE-6B73 +8DCF-6E08 +8DD0-707D +8DD1-91C7 +8DD2-7280 +8DD3-7815 +8DD4-7826 +8DD5-796D +8DD6-658E +8DD7-7D30 +8DD8-83DC +8DD9-88C1 +8DDA-8F09 +8DDB-969B +8DDC-5264 +8DDD-5728 +8DDE-6750 +8DDF-7F6A +8DE0-8CA1 +8DE1-51B4 +8DE2-5742 +8DE3-962A +8DE4-583A +8DE5-698A +8DE6-80B4 +8DE7-54B2 +8DE8-5D0E +8DE9-57FC +8DEA-7895 +8DEB-9DFA +8DEC-4F5C +8DED-524A +8DEE-548B +8DEF-643E +8DF0-6628 +8DF1-6714 +8DF2-67F5 +8DF3-7A84 +8DF4-7B56 +8DF5-7D22 +8DF6-932F +8DF7-685C +8DF8-9BAD +8DF9-7B39 +8DFA-5319 +8DFB-518A +8DFC-5237 +8E40-5BDF +8E41-62F6 +8E42-64AE +8E43-64E6 +8E44-672D +8E45-6BBA +8E46-85A9 +8E47-96D1 +8E48-7690 +8E49-9BD6 +8E4A-634C +8E4B-9306 +8E4C-9BAB +8E4D-76BF +8E4E-6652 +8E4F-4E09 +8E50-5098 +8E51-53C2 +8E52-5C71 +8E53-60E8 +8E54-6492 +8E55-6563 +8E56-685F +8E57-71E6 +8E58-73CA +8E59-7523 +8E5A-7B97 +8E5B-7E82 +8E5C-8695 +8E5D-8B83 +8E5E-8CDB +8E5F-9178 +8E60-9910 +8E61-65AC +8E62-66AB +8E63-6B8B +8E64-4ED5 +8E65-4ED4 +8E66-4F3A +8E67-4F7F +8E68-523A +8E69-53F8 +8E6A-53F2 +8E6B-55E3 +8E6C-56DB +8E6D-58EB +8E6E-59CB +8E6F-59C9 +8E70-59FF +8E71-5B50 +8E72-5C4D +8E73-5E02 +8E74-5E2B +8E75-5FD7 +8E76-601D +8E77-6307 +8E78-652F +8E79-5B5C +8E7A-65AF +8E7B-65BD +8E7C-65E8 +8E7D-679D +8E7E-6B62 +8E80-6B7B +8E81-6C0F +8E82-7345 +8E83-7949 +8E84-79C1 +8E85-7CF8 +8E86-7D19 +8E87-7D2B +8E88-80A2 +8E89-8102 +8E8A-81F3 +8E8B-8996 +8E8C-8A5E +8E8D-8A69 +8E8E-8A66 +8E8F-8A8C +8E90-8AEE +8E91-8CC7 +8E92-8CDC +8E93-96CC +8E94-98FC +8E95-6B6F +8E96-4E8B +8E97-4F3C +8E98-4F8D +8E99-5150 +8E9A-5B57 +8E9B-5BFA +8E9C-6148 +8E9D-6301 +8E9E-6642 +8E9F-6B21 +8EA0-6ECB +8EA1-6CBB +8EA2-723E +8EA3-74BD +8EA4-75D4 +8EA5-78C1 +8EA6-793A +8EA7-800C +8EA8-8033 +8EA9-81EA +8EAA-8494 +8EAB-8F9E +8EAC-6C50 +8EAD-9E7F +8EAE-5F0F +8EAF-8B58 +8EB0-9D2B +8EB1-7AFA +8EB2-8EF8 +8EB3-5B8D +8EB4-96EB +8EB5-4E03 +8EB6-53F1 +8EB7-57F7 +8EB8-5931 +8EB9-5AC9 +8EBA-5BA4 +8EBB-6089 +8EBC-6E7F +8EBD-6F06 +8EBE-75BE +8EBF-8CEA +8EC0-5B9F +8EC1-8500 +8EC2-7BE0 +8EC3-5072 +8EC4-67F4 +8EC5-829D +8EC6-5C61 +8EC7-854A +8EC8-7E1E +8EC9-820E +8ECA-5199 +8ECB-5C04 +8ECC-6368 +8ECD-8D66 +8ECE-659C +8ECF-716E +8ED0-793E +8ED1-7D17 +8ED2-8005 +8ED3-8B1D +8ED4-8ECA +8ED5-906E +8ED6-86C7 +8ED7-90AA +8ED8-501F +8ED9-52FA +8EDA-5C3A +8EDB-6753 +8EDC-707C +8EDD-7235 +8EDE-914C +8EDF-91C8 +8EE0-932B +8EE1-82E5 +8EE2-5BC2 +8EE3-5F31 +8EE4-60F9 +8EE5-4E3B +8EE6-53D6 +8EE7-5B88 +8EE8-624B +8EE9-6731 +8EEA-6B8A +8EEB-72E9 +8EEC-73E0 +8EED-7A2E +8EEE-816B +8EEF-8DA3 +8EF0-9152 +8EF1-9996 +8EF2-5112 +8EF3-53D7 +8EF4-546A +8EF5-5BFF +8EF6-6388 +8EF7-6A39 +8EF8-7DAC +8EF9-9700 +8EFA-56DA +8EFB-53CE +8EFC-5468 +8F40-5B97 +8F41-5C31 +8F42-5DDE +8F43-4FEE +8F44-6101 +8F45-62FE +8F46-6D32 +8F47-79C0 +8F48-79CB +8F49-7D42 +8F4A-7E4D +8F4B-7FD2 +8F4C-81ED +8F4D-821F +8F4E-8490 +8F4F-8846 +8F50-8972 +8F51-8B90 +8F52-8E74 +8F53-8F2F +8F54-9031 +8F55-914B +8F56-916C +8F57-96C6 +8F58-919C +8F59-4EC0 +8F5A-4F4F +8F5B-5145 +8F5C-5341 +8F5D-5F93 +8F5E-620E +8F5F-67D4 +8F60-6C41 +8F61-6E0B +8F62-7363 +8F63-7E26 +8F64-91CD +8F65-9283 +8F66-53D4 +8F67-5919 +8F68-5BBF +8F69-6DD1 +8F6A-795D +8F6B-7E2E +8F6C-7C9B +8F6D-587E +8F6E-719F +8F6F-51FA +8F70-8853 +8F71-8FF0 +8F72-4FCA +8F73-5CFB +8F74-6625 +8F75-77AC +8F76-7AE3 +8F77-821C +8F78-99FF +8F79-51C6 +8F7A-5FAA +8F7B-65EC +8F7C-696F +8F7D-6B89 +8F7E-6DF3 +8F80-6E96 +8F81-6F64 +8F82-76FE +8F83-7D14 +8F84-5DE1 +8F85-9075 +8F86-9187 +8F87-9806 +8F88-51E6 +8F89-521D +8F8A-6240 +8F8B-6691 +8F8C-66D9 +8F8D-6E1A +8F8E-5EB6 +8F8F-7DD2 +8F90-7F72 +8F91-66F8 +8F92-85AF +8F93-85F7 +8F94-8AF8 +8F95-52A9 +8F96-53D9 +8F97-5973 +8F98-5E8F +8F99-5F90 +8F9A-6055 +8F9B-92E4 +8F9C-9664 +8F9D-50B7 +8F9E-511F +8F9F-52DD +8FA0-5320 +8FA1-5347 +8FA2-53EC +8FA3-54E8 +8FA4-5546 +8FA5-5531 +8FA6-5617 +8FA7-5968 +8FA8-59BE +8FA9-5A3C +8FAA-5BB5 +8FAB-5C06 +8FAC-5C0F +8FAD-5C11 +8FAE-5C1A +8FAF-5E84 +8FB0-5E8A +8FB1-5EE0 +8FB2-5F70 +8FB3-627F +8FB4-6284 +8FB5-62DB +8FB6-638C +8FB7-6377 +8FB8-6607 +8FB9-660C +8FBA-662D +8FBB-6676 +8FBC-677E +8FBD-68A2 +8FBE-6A1F +8FBF-6A35 +8FC0-6CBC +8FC1-6D88 +8FC2-6E09 +8FC3-6E58 +8FC4-713C +8FC5-7126 +8FC6-7167 +8FC7-75C7 +8FC8-7701 +8FC9-785D +8FCA-7901 +8FCB-7965 +8FCC-79F0 +8FCD-7AE0 +8FCE-7B11 +8FCF-7CA7 +8FD0-7D39 +8FD1-8096 +8FD2-83D6 +8FD3-848B +8FD4-8549 +8FD5-885D +8FD6-88F3 +8FD7-8A1F +8FD8-8A3C +8FD9-8A54 +8FDA-8A73 +8FDB-8C61 +8FDC-8CDE +8FDD-91A4 +8FDE-9266 +8FDF-937E +8FE0-9418 +8FE1-969C +8FE2-9798 +8FE3-4E0A +8FE4-4E08 +8FE5-4E1E +8FE6-4E57 +8FE7-5197 +8FE8-5270 +8FE9-57CE +8FEA-5834 +8FEB-58CC +8FEC-5B22 +8FED-5E38 +8FEE-60C5 +8FEF-64FE +8FF0-6761 +8FF1-6756 +8FF2-6D44 +8FF3-72B6 +8FF4-7573 +8FF5-7A63 +8FF6-84B8 +8FF7-8B72 +8FF8-91B8 +8FF9-9320 +8FFA-5631 +8FFB-57F4 +8FFC-98FE +9040-62ED +9041-690D +9042-6B96 +9043-71ED +9044-7E54 +9045-8077 +9046-8272 +9047-89E6 +9048-98DF +9049-8755 +904A-8FB1 +904B-5C3B +904C-4F38 +904D-4FE1 +904E-4FB5 +904F-5507 +9050-5A20 +9051-5BDD +9052-5BE9 +9053-5FC3 +9054-614E +9055-632F +9056-65B0 +9057-664B +9058-68EE +9059-699B +905A-6D78 +905B-6DF1 +905C-7533 +905D-75B9 +905E-771F +905F-795E +9060-79E6 +9061-7D33 +9062-81E3 +9063-82AF +9064-85AA +9065-89AA +9066-8A3A +9067-8EAB +9068-8F9B +9069-9032 +906A-91DD +906B-9707 +906C-4EBA +906D-4EC1 +906E-5203 +906F-5875 +9070-58EC +9071-5C0B +9072-751A +9073-5C3D +9074-814E +9075-8A0A +9076-8FC5 +9077-9663 +9078-976D +9079-7B25 +907A-8ACF +907B-9808 +907C-9162 +907D-56F3 +907E-53A8 +9080-9017 +9081-5439 +9082-5782 +9083-5E25 +9084-63A8 +9085-6C34 +9086-708A +9087-7761 +9088-7C8B +9089-7FE0 +908A-8870 +908B-9042 +908C-9154 +908D-9310 +908E-9318 +908F-968F +9090-745E +9091-9AC4 +9092-5D07 +9093-5D69 +9094-6570 +9095-67A2 +9096-8DA8 +9097-96DB +9098-636E +9099-6749 +909A-6919 +909B-83C5 +909C-9817 +909D-96C0 +909E-88FE +909F-6F84 +90A0-647A +90A1-5BF8 +90A2-4E16 +90A3-702C +90A4-755D +90A5-662F +90A6-51C4 +90A7-5236 +90A8-52E2 +90A9-59D3 +90AA-5F81 +90AB-6027 +90AC-6210 +90AD-653F +90AE-6574 +90AF-661F +90B0-6674 +90B1-68F2 +90B2-6816 +90B3-6B63 +90B4-6E05 +90B5-7272 +90B6-751F +90B7-76DB +90B8-7CBE +90B9-8056 +90BA-58F0 +90BB-88FD +90BC-897F +90BD-8AA0 +90BE-8A93 +90BF-8ACB +90C0-901D +90C1-9192 +90C2-9752 +90C3-9759 +90C4-6589 +90C5-7A0E +90C6-8106 +90C7-96BB +90C8-5E2D +90C9-60DC +90CA-621A +90CB-65A5 +90CC-6614 +90CD-6790 +90CE-77F3 +90CF-7A4D +90D0-7C4D +90D1-7E3E +90D2-810A +90D3-8CAC +90D4-8D64 +90D5-8DE1 +90D6-8E5F +90D7-78A9 +90D8-5207 +90D9-62D9 +90DA-63A5 +90DB-6442 +90DC-6298 +90DD-8A2D +90DE-7A83 +90DF-7BC0 +90E0-8AAC +90E1-96EA +90E2-7D76 +90E3-820C +90E4-8749 +90E5-4ED9 +90E6-5148 +90E7-5343 +90E8-5360 +90E9-5BA3 +90EA-5C02 +90EB-5C16 +90EC-5DDD +90ED-6226 +90EE-6247 +90EF-64B0 +90F0-6813 +90F1-6834 +90F2-6CC9 +90F3-6D45 +90F4-6D17 +90F5-67D3 +90F6-6F5C +90F7-714E +90F8-717D +90F9-65CB +90FA-7A7F +90FB-7BAD +90FC-7DDA +9140-7E4A +9141-7FA8 +9142-817A +9143-821B +9144-8239 +9145-85A6 +9146-8A6E +9147-8CCE +9148-8DF5 +9149-9078 +914A-9077 +914B-92AD +914C-9291 +914D-9583 +914E-9BAE +914F-524D +9150-5584 +9151-6F38 +9152-7136 +9153-5168 +9154-7985 +9155-7E55 +9156-81B3 +9157-7CCE +9158-564C +9159-5851 +915A-5CA8 +915B-63AA +915C-66FE +915D-66FD +915E-695A +915F-72D9 +9160-758F +9161-758E +9162-790E +9163-7956 +9164-79DF +9165-7C97 +9166-7D20 +9167-7D44 +9168-8607 +9169-8A34 +916A-963B +916B-9061 +916C-9F20 +916D-50E7 +916E-5275 +916F-53CC +9170-53E2 +9171-5009 +9172-55AA +9173-58EE +9174-594F +9175-723D +9176-5B8B +9177-5C64 +9178-531D +9179-60E3 +917A-60F3 +917B-635C +917C-6383 +917D-633F +917E-63BB +9180-64CD +9181-65E9 +9182-66F9 +9183-5DE3 +9184-69CD +9185-69FD +9186-6F15 +9187-71E5 +9188-4E89 +9189-75E9 +918A-76F8 +918B-7A93 +918C-7CDF +918D-7DCF +918E-7D9C +918F-8061 +9190-8349 +9191-8358 +9192-846C +9193-84BC +9194-85FB +9195-88C5 +9196-8D70 +9197-9001 +9198-906D +9199-9397 +919A-971C +919B-9A12 +919C-50CF +919D-5897 +919E-618E +919F-81D3 +91A0-8535 +91A1-8D08 +91A2-9020 +91A3-4FC3 +91A4-5074 +91A5-5247 +91A6-5373 +91A7-606F +91A8-6349 +91A9-675F +91AA-6E2C +91AB-8DB3 +91AC-901F +91AD-4FD7 +91AE-5C5E +91AF-8CCA +91B0-65CF +91B1-7D9A +91B2-5352 +91B3-8896 +91B4-5176 +91B5-63C3 +91B6-5B58 +91B7-5B6B +91B8-5C0A +91B9-640D +91BA-6751 +91BB-905C +91BC-4ED6 +91BD-591A +91BE-592A +91BF-6C70 +91C0-8A51 +91C1-553E +91C2-5815 +91C3-59A5 +91C4-60F0 +91C5-6253 +91C6-67C1 +91C7-8235 +91C8-6955 +91C9-9640 +91CA-99C4 +91CB-9A28 +91CC-4F53 +91CD-5806 +91CE-5BFE +91CF-8010 +91D0-5CB1 +91D1-5E2F +91D2-5F85 +91D3-6020 +91D4-614B +91D5-6234 +91D6-66FF +91D7-6CF0 +91D8-6EDE +91D9-80CE +91DA-817F +91DB-82D4 +91DC-888B +91DD-8CB8 +91DE-9000 +91DF-902E +91E0-968A +91E1-9EDB +91E2-9BDB +91E3-4EE3 +91E4-53F0 +91E5-5927 +91E6-7B2C +91E7-918D +91E8-984C +91E9-9DF9 +91EA-6EDD +91EB-7027 +91EC-5353 +91ED-5544 +91EE-5B85 +91EF-6258 +91F0-629E +91F1-62D3 +91F2-6CA2 +91F3-6FEF +91F4-7422 +91F5-8A17 +91F6-9438 +91F7-6FC1 +91F8-8AFE +91F9-8338 +91FA-51E7 +91FB-86F8 +91FC-53EA +9240-53E9 +9241-4F46 +9242-9054 +9243-8FB0 +9244-596A +9245-8131 +9246-5DFD +9247-7AEA +9248-8FBF +9249-68DA +924A-8C37 +924B-72F8 +924C-9C48 +924D-6A3D +924E-8AB0 +924F-4E39 +9250-5358 +9251-5606 +9252-5766 +9253-62C5 +9254-63A2 +9255-65E6 +9256-6B4E +9257-6DE1 +9258-6E5B +9259-70AD +925A-77ED +925B-7AEF +925C-7BAA +925D-7DBB +925E-803D +925F-80C6 +9260-86CB +9261-8A95 +9262-935B +9263-56E3 +9264-58C7 +9265-5F3E +9266-65AD +9267-6696 +9268-6A80 +9269-6BB5 +926A-7537 +926B-8AC7 +926C-5024 +926D-77E5 +926E-5730 +926F-5F1B +9270-6065 +9271-667A +9272-6C60 +9273-75F4 +9274-7A1A +9275-7F6E +9276-81F4 +9277-8718 +9278-9045 +9279-99B3 +927A-7BC9 +927B-755C +927C-7AF9 +927D-7B51 +927E-84C4 +9280-9010 +9281-79E9 +9282-7A92 +9283-8336 +9284-5AE1 +9285-7740 +9286-4E2D +9287-4EF2 +9288-5B99 +9289-5FE0 +928A-62BD +928B-663C +928C-67F1 +928D-6CE8 +928E-866B +928F-8877 +9290-8A3B +9291-914E +9292-92F3 +9293-99D0 +9294-6A17 +9295-7026 +9296-732A +9297-82E7 +9298-8457 +9299-8CAF +929A-4E01 +929B-5146 +929C-51CB +929D-558B +929E-5BF5 +929F-5E16 +92A0-5E33 +92A1-5E81 +92A2-5F14 +92A3-5F35 +92A4-5F6B +92A5-5FB4 +92A6-61F2 +92A7-6311 +92A8-66A2 +92A9-671D +92AA-6F6E +92AB-7252 +92AC-753A +92AD-773A +92AE-8074 +92AF-8139 +92B0-8178 +92B1-8776 +92B2-8ABF +92B3-8ADC +92B4-8D85 +92B5-8DF3 +92B6-929A +92B7-9577 +92B8-9802 +92B9-9CE5 +92BA-52C5 +92BB-6357 +92BC-76F4 +92BD-6715 +92BE-6C88 +92BF-73CD +92C0-8CC3 +92C1-93AE +92C2-9673 +92C3-6D25 +92C4-589C +92C5-690E +92C6-69CC +92C7-8FFD +92C8-939A +92C9-75DB +92CA-901A +92CB-585A +92CC-6802 +92CD-63B4 +92CE-69FB +92CF-4F43 +92D0-6F2C +92D1-67D8 +92D2-8FBB +92D3-8526 +92D4-7DB4 +92D5-9354 +92D6-693F +92D7-6F70 +92D8-576A +92D9-58F7 +92DA-5B2C +92DB-7D2C +92DC-722A +92DD-540A +92DE-91E3 +92DF-9DB4 +92E0-4EAD +92E1-4F4E +92E2-505C +92E3-5075 +92E4-5243 +92E5-8C9E +92E6-5448 +92E7-5824 +92E8-5B9A +92E9-5E1D +92EA-5E95 +92EB-5EAD +92EC-5EF7 +92ED-5F1F +92EE-608C +92EF-62B5 +92F0-633A +92F1-63D0 +92F2-68AF +92F3-6C40 +92F4-7887 +92F5-798E +92F6-7A0B +92F7-7DE0 +92F8-8247 +92F9-8A02 +92FA-8AE6 +92FB-8E44 +92FC-9013 +9340-90B8 +9341-912D +9342-91D8 +9343-9F0E +9344-6CE5 +9345-6458 +9346-64E2 +9347-6575 +9348-6EF4 +9349-7684 +934A-7B1B +934B-9069 +934C-93D1 +934D-6EBA +934E-54F2 +934F-5FB9 +9350-64A4 +9351-8F4D +9352-8FED +9353-9244 +9354-5178 +9355-586B +9356-5929 +9357-5C55 +9358-5E97 +9359-6DFB +935A-7E8F +935B-751C +935C-8CBC +935D-8EE2 +935E-985B +935F-70B9 +9360-4F1D +9361-6BBF +9362-6FB1 +9363-7530 +9364-96FB +9365-514E +9366-5410 +9367-5835 +9368-5857 +9369-59AC +936A-5C60 +936B-5F92 +936C-6597 +936D-675C +936E-6E21 +936F-767B +9370-83DF +9371-8CED +9372-9014 +9373-90FD +9374-934D +9375-7825 +9376-783A +9377-52AA +9378-5EA6 +9379-571F +937A-5974 +937B-6012 +937C-5012 +937D-515A +937E-51AC +9380-51CD +9381-5200 +9382-5510 +9383-5854 +9384-5858 +9385-5957 +9386-5B95 +9387-5CF6 +9388-5D8B +9389-60BC +938A-6295 +938B-642D +938C-6771 +938D-6843 +938E-68BC +938F-68DF +9390-76D7 +9391-6DD8 +9392-6E6F +9393-6D9B +9394-706F +9395-71C8 +9396-5F53 +9397-75D8 +9398-7977 +9399-7B49 +939A-7B54 +939B-7B52 +939C-7CD6 +939D-7D71 +939E-5230 +939F-8463 +93A0-8569 +93A1-85E4 +93A2-8A0E +93A3-8B04 +93A4-8C46 +93A5-8E0F +93A6-9003 +93A7-900F +93A8-9419 +93A9-9676 +93AA-982D +93AB-9A30 +93AC-95D8 +93AD-50CD +93AE-52D5 +93AF-540C +93B0-5802 +93B1-5C0E +93B2-61A7 +93B3-649E +93B4-6D1E +93B5-77B3 +93B6-7AE5 +93B7-80F4 +93B8-8404 +93B9-9053 +93BA-9285 +93BB-5CE0 +93BC-9D07 +93BD-533F +93BE-5F97 +93BF-5FB3 +93C0-6D9C +93C1-7279 +93C2-7763 +93C3-79BF +93C4-7BE4 +93C5-6BD2 +93C6-72EC +93C7-8AAD +93C8-6803 +93C9-6A61 +93CA-51F8 +93CB-7A81 +93CC-6934 +93CD-5C4A +93CE-9CF6 +93CF-82EB +93D0-5BC5 +93D1-9149 +93D2-701E +93D3-5678 +93D4-5C6F +93D5-60C7 +93D6-6566 +93D7-6C8C +93D8-8C5A +93D9-9041 +93DA-9813 +93DB-5451 +93DC-66C7 +93DD-920D +93DE-5948 +93DF-90A3 +93E0-5185 +93E1-4E4D +93E2-51EA +93E3-8599 +93E4-8B0E +93E5-7058 +93E6-637A +93E7-934B +93E8-6962 +93E9-99B4 +93EA-7E04 +93EB-7577 +93EC-5357 +93ED-6960 +93EE-8EDF +93EF-96E3 +93F0-6C5D +93F1-4E8C +93F2-5C3C +93F3-5F10 +93F4-8FE9 +93F5-5302 +93F6-8CD1 +93F7-8089 +93F8-8679 +93F9-5EFF +93FA-65E5 +93FB-4E73 +93FC-5165 +9440-5982 +9441-5C3F +9442-97EE +9443-4EFB +9444-598A +9445-5FCD +9446-8A8D +9447-6FE1 +9448-79B0 +9449-7962 +944A-5BE7 +944B-8471 +944C-732B +944D-71B1 +944E-5E74 +944F-5FF5 +9450-637B +9451-649A +9452-71C3 +9453-7C98 +9454-4E43 +9455-5EFC +9456-4E4B +9457-57DC +9458-56A2 +9459-60A9 +945A-6FC3 +945B-7D0D +945C-80FD +945D-8133 +945E-81BF +945F-8FB2 +9460-8997 +9461-86A4 +9462-5DF4 +9463-628A +9464-64AD +9465-8987 +9466-6777 +9467-6CE2 +9468-6D3E +9469-7436 +946A-7834 +946B-5A46 +946C-7F75 +946D-82AD +946E-99AC +946F-4FF3 +9470-5EC3 +9471-62DD +9472-6392 +9473-6557 +9474-676F +9475-76C3 +9476-724C +9477-80CC +9478-80BA +9479-8F29 +947A-914D +947B-500D +947C-57F9 +947D-5A92 +947E-6885 +9480-6973 +9481-7164 +9482-72FD +9483-8CB7 +9484-58F2 +9485-8CE0 +9486-966A +9487-9019 +9488-877F +9489-79E4 +948A-77E7 +948B-8429 +948C-4F2F +948D-5265 +948E-535A +948F-62CD +9490-67CF +9491-6CCA +9492-767D +9493-7B94 +9494-7C95 +9495-8236 +9496-8584 +9497-8FEB +9498-66DD +9499-6F20 +949A-7206 +949B-7E1B +949C-83AB +949D-99C1 +949E-9EA6 +949F-51FD +94A0-7BB1 +94A1-7872 +94A2-7BB8 +94A3-8087 +94A4-7B48 +94A5-6AE8 +94A6-5E61 +94A7-808C +94A8-7551 +94A9-7560 +94AA-516B +94AB-9262 +94AC-6E8C +94AD-767A +94AE-9197 +94AF-9AEA +94B0-4F10 +94B1-7F70 +94B2-629C +94B3-7B4F +94B4-95A5 +94B5-9CE9 +94B6-567A +94B7-5859 +94B8-86E4 +94B9-96BC +94BA-4F34 +94BB-5224 +94BC-534A +94BD-53CD +94BE-53DB +94BF-5E06 +94C0-642C +94C1-6591 +94C2-677F +94C3-6C3E +94C4-6C4E +94C5-7248 +94C6-72AF +94C7-73ED +94C8-7554 +94C9-7E41 +94CA-822C +94CB-85E9 +94CC-8CA9 +94CD-7BC4 +94CE-91C6 +94CF-7169 +94D0-9812 +94D1-98EF +94D2-633D +94D3-6669 +94D4-756A +94D5-76E4 +94D6-78D0 +94D7-8543 +94D8-86EE +94D9-532A +94DA-5351 +94DB-5426 +94DC-5983 +94DD-5E87 +94DE-5F7C +94DF-60B2 +94E0-6249 +94E1-6279 +94E2-62AB +94E3-6590 +94E4-6BD4 +94E5-6CCC +94E6-75B2 +94E7-76AE +94E8-7891 +94E9-79D8 +94EA-7DCB +94EB-7F77 +94EC-80A5 +94ED-88AB +94EE-8AB9 +94EF-8CBB +94F0-907F +94F1-975E +94F2-98DB +94F3-6A0B +94F4-7C38 +94F5-5099 +94F6-5C3E +94F7-5FAE +94F8-6787 +94F9-6BD8 +94FA-7435 +94FB-7709 +94FC-7F8E +9540-9F3B +9541-67CA +9542-7A17 +9543-5339 +9544-758B +9545-9AED +9546-5F66 +9547-819D +9548-83F1 +9549-8098 +954A-5F3C +954B-5FC5 +954C-7562 +954D-7B46 +954E-903C +954F-6867 +9550-59EB +9551-5A9B +9552-7D10 +9553-767E +9554-8B2C +9555-4FF5 +9556-5F6A +9557-6A19 +9558-6C37 +9559-6F02 +955A-74E2 +955B-7968 +955C-8868 +955D-8A55 +955E-8C79 +955F-5EDF +9560-63CF +9561-75C5 +9562-79D2 +9563-82D7 +9564-9328 +9565-92F2 +9566-849C +9567-86ED +9568-9C2D +9569-54C1 +956A-5F6C +956B-658C +956C-6D5C +956D-7015 +956E-8CA7 +956F-8CD3 +9570-983B +9571-654F +9572-74F6 +9573-4E0D +9574-4ED8 +9575-57E0 +9576-592B +9577-5A66 +9578-5BCC +9579-51A8 +957A-5E03 +957B-5E9C +957C-6016 +957D-6276 +957E-6577 +9580-65A7 +9581-666E +9582-6D6E +9583-7236 +9584-7B26 +9585-8150 +9586-819A +9587-8299 +9588-8B5C +9589-8CA0 +958A-8CE6 +958B-8D74 +958C-961C +958D-9644 +958E-4FAE +958F-64AB +9590-6B66 +9591-821E +9592-8461 +9593-856A +9594-90E8 +9595-5C01 +9596-6953 +9597-98A8 +9598-847A +9599-8557 +959A-4F0F +959B-526F +959C-5FA9 +959D-5E45 +959E-670D +959F-798F +95A0-8179 +95A1-8907 +95A2-8986 +95A3-6DF5 +95A4-5F17 +95A5-6255 +95A6-6CB8 +95A7-4ECF +95A8-7269 +95A9-9B92 +95AA-5206 +95AB-543B +95AC-5674 +95AD-58B3 +95AE-61A4 +95AF-626E +95B0-711A +95B1-596E +95B2-7C89 +95B3-7CDE +95B4-7D1B +95B5-96F0 +95B6-6587 +95B7-805E +95B8-4E19 +95B9-4F75 +95BA-5175 +95BB-5840 +95BC-5E63 +95BD-5E73 +95BE-5F0A +95BF-67C4 +95C0-4E26 +95C1-853D +95C2-9589 +95C3-965B +95C4-7C73 +95C5-9801 +95C6-50FB +95C7-58C1 +95C8-7656 +95C9-78A7 +95CA-5225 +95CB-77A5 +95CC-8511 +95CD-7B86 +95CE-504F +95CF-5909 +95D0-7247 +95D1-7BC7 +95D2-7DE8 +95D3-8FBA +95D4-8FD4 +95D5-904D +95D6-4FBF +95D7-52C9 +95D8-5A29 +95D9-5F01 +95DA-97AD +95DB-4FDD +95DC-8217 +95DD-92EA +95DE-5703 +95DF-6355 +95E0-6B69 +95E1-752B +95E2-88DC +95E3-8F14 +95E4-7A42 +95E5-52DF +95E6-5893 +95E7-6155 +95E8-620A +95E9-66AE +95EA-6BCD +95EB-7C3F +95EC-83E9 +95ED-5023 +95EE-4FF8 +95EF-5305 +95F0-5446 +95F1-5831 +95F2-5949 +95F3-5B9D +95F4-5CF0 +95F5-5CEF +95F6-5D29 +95F7-5E96 +95F8-62B1 +95F9-6367 +95FA-653E +95FB-65B9 +95FC-670B +9640-6CD5 +9641-6CE1 +9642-70F9 +9643-7832 +9644-7E2B +9645-80DE +9646-82B3 +9647-840C +9648-84EC +9649-8702 +964A-8912 +964B-8A2A +964C-8C4A +964D-90A6 +964E-92D2 +964F-98FD +9650-9CF3 +9651-9D6C +9652-4E4F +9653-4EA1 +9654-508D +9655-5256 +9656-574A +9657-59A8 +9658-5E3D +9659-5FD8 +965A-5FD9 +965B-623F +965C-66B4 +965D-671B +965E-67D0 +965F-68D2 +9660-5192 +9661-7D21 +9662-80AA +9663-81A8 +9664-8B00 +9665-8C8C +9666-8CBF +9667-927E +9668-9632 +9669-5420 +966A-982C +966B-5317 +966C-50D5 +966D-535C +966E-58A8 +966F-64B2 +9670-6734 +9671-7267 +9672-7766 +9673-7A46 +9674-91E6 +9675-52C3 +9676-6CA1 +9677-6B86 +9678-5800 +9679-5E4C +967A-5954 +967B-672C +967C-7FFB +967D-51E1 +967E-76C6 +9680-6469 +9681-78E8 +9682-9B54 +9683-9EBB +9684-57CB +9685-59B9 +9686-6627 +9687-679A +9688-6BCE +9689-54E9 +968A-69D9 +968B-5E55 +968C-819C +968D-6795 +968E-9BAA +968F-67FE +9690-9C52 +9691-685D +9692-4EA6 +9693-4FE3 +9694-53C8 +9695-62B9 +9696-672B +9697-6CAB +9698-8FC4 +9699-4FAD +969A-7E6D +969B-9EBF +969C-4E07 +969D-6162 +969E-6E80 +969F-6F2B +96A0-8513 +96A1-5473 +96A2-672A +96A3-9B45 +96A4-5DF3 +96A5-7B95 +96A6-5CAC +96A7-5BC6 +96A8-871C +96A9-6E4A +96AA-84D1 +96AB-7A14 +96AC-8108 +96AD-5999 +96AE-7C8D +96AF-6C11 +96B0-7720 +96B1-52D9 +96B2-5922 +96B3-7121 +96B4-725F +96B5-77DB +96B6-9727 +96B7-9D61 +96B8-690B +96B9-5A7F +96BA-5A18 +96BB-51A5 +96BC-540D +96BD-547D +96BE-660E +96BF-76DF +96C0-8FF7 +96C1-9298 +96C2-9CF4 +96C3-59EA +96C4-725D +96C5-6EC5 +96C6-514D +96C7-68C9 +96C8-7DBF +96C9-7DEC +96CA-9762 +96CB-9EBA +96CC-6478 +96CD-6A21 +96CE-8302 +96CF-5984 +96D0-5B5F +96D1-6BDB +96D2-731B +96D3-76F2 +96D4-7DB2 +96D5-8017 +96D6-8499 +96D7-5132 +96D8-6728 +96D9-9ED9 +96DA-76EE +96DB-6762 +96DC-52FF +96DD-9905 +96DE-5C24 +96DF-623B +96E0-7C7E +96E1-8CB0 +96E2-554F +96E3-60B6 +96E4-7D0B +96E5-9580 +96E6-5301 +96E7-4E5F +96E8-51B6 +96E9-591C +96EA-723A +96EB-8036 +96EC-91CE +96ED-5F25 +96EE-77E2 +96EF-5384 +96F0-5F79 +96F1-7D04 +96F2-85AC +96F3-8A33 +96F4-8E8D +96F5-9756 +96F6-67F3 +96F7-85AE +96F8-9453 +96F9-6109 +96FA-6108 +96FB-6CB9 +96FC-7652 +9740-8AED +9741-8F38 +9742-552F +9743-4F51 +9744-512A +9745-52C7 +9746-53CB +9747-5BA5 +9748-5E7D +9749-60A0 +974A-6182 +974B-63D6 +974C-6709 +974D-67DA +974E-6E67 +974F-6D8C +9750-7336 +9751-7337 +9752-7531 +9753-7950 +9754-88D5 +9755-8A98 +9756-904A +9757-9091 +9758-90F5 +9759-96C4 +975A-878D +975B-5915 +975C-4E88 +975D-4F59 +975E-4E0E +975F-8A89 +9760-8F3F +9761-9810 +9762-50AD +9763-5E7C +9764-5996 +9765-5BB9 +9766-5EB8 +9767-63DA +9768-63FA +9769-64C1 +976A-66DC +976B-694A +976C-69D8 +976D-6D0B +976E-6EB6 +976F-7194 +9770-7528 +9771-7AAF +9772-7F8A +9773-8000 +9774-8449 +9775-84C9 +9776-8981 +9777-8B21 +9778-8E0A +9779-9065 +977A-967D +977B-990A +977C-617E +977D-6291 +977E-6B32 +9780-6C83 +9781-6D74 +9782-7FCC +9783-7FFC +9784-6DC0 +9785-7F85 +9786-87BA +9787-88F8 +9788-6765 +9789-83B1 +978A-983C +978B-96F7 +978C-6D1B +978D-7D61 +978E-843D +978F-916A +9790-4E71 +9791-5375 +9792-5D50 +9793-6B04 +9794-6FEB +9795-85CD +9796-862D +9797-89A7 +9798-5229 +9799-540F +979A-5C65 +979B-674E +979C-68A8 +979D-7406 +979E-7483 +979F-75E2 +97A0-88CF +97A1-88E1 +97A2-91CC +97A3-96E2 +97A4-9678 +97A5-5F8B +97A6-7387 +97A7-7ACB +97A8-844E +97A9-63A0 +97AA-7565 +97AB-5289 +97AC-6D41 +97AD-6E9C +97AE-7409 +97AF-7559 +97B0-786B +97B1-7C92 +97B2-9686 +97B3-7ADC +97B4-9F8D +97B5-4FB6 +97B6-616E +97B7-65C5 +97B8-865C +97B9-4E86 +97BA-4EAE +97BB-50DA +97BC-4E21 +97BD-51CC +97BE-5BEE +97BF-6599 +97C0-6881 +97C1-6DBC +97C2-731F +97C3-7642 +97C4-77AD +97C5-7A1C +97C6-7CE7 +97C7-826F +97C8-8AD2 +97C9-907C +97CA-91CF +97CB-9675 +97CC-9818 +97CD-529B +97CE-7DD1 +97CF-502B +97D0-5398 +97D1-6797 +97D2-6DCB +97D3-71D0 +97D4-7433 +97D5-81E8 +97D6-8F2A +97D7-96A3 +97D8-9C57 +97D9-9E9F +97DA-7460 +97DB-5841 +97DC-6D99 +97DD-7D2F +97DE-985E +97DF-4EE4 +97E0-4F36 +97E1-4F8B +97E2-51B7 +97E3-52B1 +97E4-5DBA +97E5-601C +97E6-73B2 +97E7-793C +97E8-82D3 +97E9-9234 +97EA-96B7 +97EB-96F6 +97EC-970A +97ED-9E97 +97EE-9F62 +97EF-66A6 +97F0-6B74 +97F1-5217 +97F2-52A3 +97F3-70C8 +97F4-88C2 +97F5-5EC9 +97F6-604B +97F7-6190 +97F8-6F23 +97F9-7149 +97FA-7C3E +97FB-7DF4 +97FC-806F +9840-84EE +9841-9023 +9842-932C +9843-5442 +9844-9B6F +9845-6AD3 +9846-7089 +9847-8CC2 +9848-8DEF +9849-9732 +984A-52B4 +984B-5A41 +984C-5ECA +984D-5F04 +984E-6717 +984F-697C +9850-6994 +9851-6D6A +9852-6F0F +9853-7262 +9854-72FC +9855-7BED +9856-8001 +9857-807E +9858-874B +9859-90CE +985A-516D +985B-9E93 +985C-7984 +985D-808B +985E-9332 +985F-8AD6 +9860-502D +9861-548C +9862-8A71 +9863-6B6A +9864-8CC4 +9865-8107 +9866-60D1 +9867-67A0 +9868-9DF2 +9869-4E99 +986A-4E98 +986B-9C10 +986C-8A6B +986D-85C1 +986E-8568 +986F-6900 +9870-6E7E +9871-7897 +9872-8155 +989F-5F0C +98A0-4E10 +98A1-4E15 +98A2-4E2A +98A3-4E31 +98A4-4E36 +98A5-4E3C +98A6-4E3F +98A7-4E42 +98A8-4E56 +98A9-4E58 +98AA-4E82 +98AB-4E85 +98AC-8C6B +98AD-4E8A +98AE-8212 +98AF-5F0D +98B0-4E8E +98B1-4E9E +98B2-4E9F +98B3-4EA0 +98B4-4EA2 +98B5-4EB0 +98B6-4EB3 +98B7-4EB6 +98B8-4ECE +98B9-4ECD +98BA-4EC4 +98BB-4EC6 +98BC-4EC2 +98BD-4ED7 +98BE-4EDE +98BF-4EED +98C0-4EDF +98C1-4EF7 +98C2-4F09 +98C3-4F5A +98C4-4F30 +98C5-4F5B +98C6-4F5D +98C7-4F57 +98C8-4F47 +98C9-4F76 +98CA-4F88 +98CB-4F8F +98CC-4F98 +98CD-4F7B +98CE-4F69 +98CF-4F70 +98D0-4F91 +98D1-4F6F +98D2-4F86 +98D3-4F96 +98D4-5118 +98D5-4FD4 +98D6-4FDF +98D7-4FCE +98D8-4FD8 +98D9-4FDB +98DA-4FD1 +98DB-4FDA +98DC-4FD0 +98DD-4FE4 +98DE-4FE5 +98DF-501A +98E0-5028 +98E1-5014 +98E2-502A +98E3-5025 +98E4-5005 +98E5-4F1C +98E6-4FF6 +98E7-5021 +98E8-5029 +98E9-502C +98EA-4FFE +98EB-4FEF +98EC-5011 +98ED-5006 +98EE-5043 +98EF-5047 +98F0-6703 +98F1-5055 +98F2-5050 +98F3-5048 +98F4-505A +98F5-5056 +98F6-506C +98F7-5078 +98F8-5080 +98F9-509A +98FA-5085 +98FB-50B4 +98FC-50B2 +9940-50C9 +9941-50CA +9942-50B3 +9943-50C2 +9944-50D6 +9945-50DE +9946-50E5 +9947-50ED +9948-50E3 +9949-50EE +994A-50F9 +994B-50F5 +994C-5109 +994D-5101 +994E-5102 +994F-5116 +9950-5115 +9951-5114 +9952-511A +9953-5121 +9954-513A +9955-5137 +9956-513C +9957-513B +9958-513F +9959-5140 +995A-5152 +995B-514C +995C-5154 +995D-5162 +995E-7AF8 +995F-5169 +9960-516A +9961-516E +9962-5180 +9963-5182 +9964-56D8 +9965-518C +9966-5189 +9967-518F +9968-5191 +9969-5193 +996A-5195 +996B-5196 +996C-51A4 +996D-51A6 +996E-51A2 +996F-51A9 +9970-51AA +9971-51AB +9972-51B3 +9973-51B1 +9974-51B2 +9975-51B0 +9976-51B5 +9977-51BD +9978-51C5 +9979-51C9 +997A-51DB +997B-51E0 +997C-8655 +997D-51E9 +997E-51ED +9980-51F0 +9981-51F5 +9982-51FE +9983-5204 +9984-520B +9985-5214 +9986-520E +9987-5227 +9988-522A +9989-522E +998A-5233 +998B-5239 +998C-524F +998D-5244 +998E-524B +998F-524C +9990-525E +9991-5254 +9992-526A +9993-5274 +9994-5269 +9995-5273 +9996-527F +9997-527D +9998-528D +9999-5294 +999A-5292 +999B-5271 +999C-5288 +999D-5291 +999E-8FA8 +999F-8FA7 +99A0-52AC +99A1-52AD +99A2-52BC +99A3-52B5 +99A4-52C1 +99A5-52CD +99A6-52D7 +99A7-52DE +99A8-52E3 +99A9-52E6 +99AA-98ED +99AB-52E0 +99AC-52F3 +99AD-52F5 +99AE-52F8 +99AF-52F9 +99B0-5306 +99B1-5308 +99B2-7538 +99B3-530D +99B4-5310 +99B5-530F +99B6-5315 +99B7-531A +99B8-5323 +99B9-532F +99BA-5331 +99BB-5333 +99BC-5338 +99BD-5340 +99BE-5346 +99BF-5345 +99C0-4E17 +99C1-5349 +99C2-534D +99C3-51D6 +99C4-535E +99C5-5369 +99C6-536E +99C7-5918 +99C8-537B +99C9-5377 +99CA-5382 +99CB-5396 +99CC-53A0 +99CD-53A6 +99CE-53A5 +99CF-53AE +99D0-53B0 +99D1-53B6 +99D2-53C3 +99D3-7C12 +99D4-96D9 +99D5-53DF +99D6-66FC +99D7-71EE +99D8-53EE +99D9-53E8 +99DA-53ED +99DB-53FA +99DC-5401 +99DD-543D +99DE-5440 +99DF-542C +99E0-542D +99E1-543C +99E2-542E +99E3-5436 +99E4-5429 +99E5-541D +99E6-544E +99E7-548F +99E8-5475 +99E9-548E +99EA-545F +99EB-5471 +99EC-5477 +99ED-5470 +99EE-5492 +99EF-547B +99F0-5480 +99F1-5476 +99F2-5484 +99F3-5490 +99F4-5486 +99F5-54C7 +99F6-54A2 +99F7-54B8 +99F8-54A5 +99F9-54AC +99FA-54C4 +99FB-54C8 +99FC-54A8 +9A40-54AB +9A41-54C2 +9A42-54A4 +9A43-54BE +9A44-54BC +9A45-54D8 +9A46-54E5 +9A47-54E6 +9A48-550F +9A49-5514 +9A4A-54FD +9A4B-54EE +9A4C-54ED +9A4D-54FA +9A4E-54E2 +9A4F-5539 +9A50-5540 +9A51-5563 +9A52-554C +9A53-552E +9A54-555C +9A55-5545 +9A56-5556 +9A57-5557 +9A58-5538 +9A59-5533 +9A5A-555D +9A5B-5599 +9A5C-5580 +9A5D-54AF +9A5E-558A +9A5F-559F +9A60-557B +9A61-557E +9A62-5598 +9A63-559E +9A64-55AE +9A65-557C +9A66-5583 +9A67-55A9 +9A68-5587 +9A69-55A8 +9A6A-55DA +9A6B-55C5 +9A6C-55DF +9A6D-55C4 +9A6E-55DC +9A6F-55E4 +9A70-55D4 +9A71-5614 +9A72-55F7 +9A73-5616 +9A74-55FE +9A75-55FD +9A76-561B +9A77-55F9 +9A78-564E +9A79-5650 +9A7A-71DF +9A7B-5634 +9A7C-5636 +9A7D-5632 +9A7E-5638 +9A80-566B +9A81-5664 +9A82-562F +9A83-566C +9A84-566A +9A85-5686 +9A86-5680 +9A87-568A +9A88-56A0 +9A89-5694 +9A8A-568F +9A8B-56A5 +9A8C-56AE +9A8D-56B6 +9A8E-56B4 +9A8F-56C2 +9A90-56BC +9A91-56C1 +9A92-56C3 +9A93-56C0 +9A94-56C8 +9A95-56CE +9A96-56D1 +9A97-56D3 +9A98-56D7 +9A99-56EE +9A9A-56F9 +9A9B-5700 +9A9C-56FF +9A9D-5704 +9A9E-5709 +9A9F-5708 +9AA0-570B +9AA1-570D +9AA2-5713 +9AA3-5718 +9AA4-5716 +9AA5-55C7 +9AA6-571C +9AA7-5726 +9AA8-5737 +9AA9-5738 +9AAA-574E +9AAB-573B +9AAC-5740 +9AAD-574F +9AAE-5769 +9AAF-57C0 +9AB0-5788 +9AB1-5761 +9AB2-577F +9AB3-5789 +9AB4-5793 +9AB5-57A0 +9AB6-57B3 +9AB7-57A4 +9AB8-57AA +9AB9-57B0 +9ABA-57C3 +9ABB-57C6 +9ABC-57D4 +9ABD-57D2 +9ABE-57D3 +9ABF-580A +9AC0-57D6 +9AC1-57E3 +9AC2-580B +9AC3-5819 +9AC4-581D +9AC5-5872 +9AC6-5821 +9AC7-5862 +9AC8-584B +9AC9-5870 +9ACA-6BC0 +9ACB-5852 +9ACC-583D +9ACD-5879 +9ACE-5885 +9ACF-58B9 +9AD0-589F +9AD1-58AB +9AD2-58BA +9AD3-58DE +9AD4-58BB +9AD5-58B8 +9AD6-58AE +9AD7-58C5 +9AD8-58D3 +9AD9-58D1 +9ADA-58D7 +9ADB-58D9 +9ADC-58D8 +9ADD-58E5 +9ADE-58DC +9ADF-58E4 +9AE0-58DF +9AE1-58EF +9AE2-58FA +9AE3-58F9 +9AE4-58FB +9AE5-58FC +9AE6-58FD +9AE7-5902 +9AE8-590A +9AE9-5910 +9AEA-591B +9AEB-68A6 +9AEC-5925 +9AED-592C +9AEE-592D +9AEF-5932 +9AF0-5938 +9AF1-593E +9AF2-7AD2 +9AF3-5955 +9AF4-5950 +9AF5-594E +9AF6-595A +9AF7-5958 +9AF8-5962 +9AF9-5960 +9AFA-5967 +9AFB-596C +9AFC-5969 +9B40-5978 +9B41-5981 +9B42-599D +9B43-4F5E +9B44-4FAB +9B45-59A3 +9B46-59B2 +9B47-59C6 +9B48-59E8 +9B49-59DC +9B4A-598D +9B4B-59D9 +9B4C-59DA +9B4D-5A25 +9B4E-5A1F +9B4F-5A11 +9B50-5A1C +9B51-5A09 +9B52-5A1A +9B53-5A40 +9B54-5A6C +9B55-5A49 +9B56-5A35 +9B57-5A36 +9B58-5A62 +9B59-5A6A +9B5A-5A9A +9B5B-5ABC +9B5C-5ABE +9B5D-5ACB +9B5E-5AC2 +9B5F-5ABD +9B60-5AE3 +9B61-5AD7 +9B62-5AE6 +9B63-5AE9 +9B64-5AD6 +9B65-5AFA +9B66-5AFB +9B67-5B0C +9B68-5B0B +9B69-5B16 +9B6A-5B32 +9B6B-5AD0 +9B6C-5B2A +9B6D-5B36 +9B6E-5B3E +9B6F-5B43 +9B70-5B45 +9B71-5B40 +9B72-5B51 +9B73-5B55 +9B74-5B5A +9B75-5B5B +9B76-5B65 +9B77-5B69 +9B78-5B70 +9B79-5B73 +9B7A-5B75 +9B7B-5B78 +9B7C-6588 +9B7D-5B7A +9B7E-5B80 +9B80-5B83 +9B81-5BA6 +9B82-5BB8 +9B83-5BC3 +9B84-5BC7 +9B85-5BC9 +9B86-5BD4 +9B87-5BD0 +9B88-5BE4 +9B89-5BE6 +9B8A-5BE2 +9B8B-5BDE +9B8C-5BE5 +9B8D-5BEB +9B8E-5BF0 +9B8F-5BF6 +9B90-5BF3 +9B91-5C05 +9B92-5C07 +9B93-5C08 +9B94-5C0D +9B95-5C13 +9B96-5C20 +9B97-5C22 +9B98-5C28 +9B99-5C38 +9B9A-5C39 +9B9B-5C41 +9B9C-5C46 +9B9D-5C4E +9B9E-5C53 +9B9F-5C50 +9BA0-5C4F +9BA1-5B71 +9BA2-5C6C +9BA3-5C6E +9BA4-4E62 +9BA5-5C76 +9BA6-5C79 +9BA7-5C8C +9BA8-5C91 +9BA9-5C94 +9BAA-599B +9BAB-5CAB +9BAC-5CBB +9BAD-5CB6 +9BAE-5CBC +9BAF-5CB7 +9BB0-5CC5 +9BB1-5CBE +9BB2-5CC7 +9BB3-5CD9 +9BB4-5CE9 +9BB5-5CFD +9BB6-5CFA +9BB7-5CED +9BB8-5D8C +9BB9-5CEA +9BBA-5D0B +9BBB-5D15 +9BBC-5D17 +9BBD-5D5C +9BBE-5D1F +9BBF-5D1B +9BC0-5D11 +9BC1-5D14 +9BC2-5D22 +9BC3-5D1A +9BC4-5D19 +9BC5-5D18 +9BC6-5D4C +9BC7-5D52 +9BC8-5D4E +9BC9-5D4B +9BCA-5D6C +9BCB-5D73 +9BCC-5D76 +9BCD-5D87 +9BCE-5D84 +9BCF-5D82 +9BD0-5DA2 +9BD1-5D9D +9BD2-5DAC +9BD3-5DAE +9BD4-5DBD +9BD5-5D90 +9BD6-5DB7 +9BD7-5DBC +9BD8-5DC9 +9BD9-5DCD +9BDA-5DD3 +9BDB-5DD2 +9BDC-5DD6 +9BDD-5DDB +9BDE-5DEB +9BDF-5DF2 +9BE0-5DF5 +9BE1-5E0B +9BE2-5E1A +9BE3-5E19 +9BE4-5E11 +9BE5-5E1B +9BE6-5E36 +9BE7-5E37 +9BE8-5E44 +9BE9-5E43 +9BEA-5E40 +9BEB-5E4E +9BEC-5E57 +9BED-5E54 +9BEE-5E5F +9BEF-5E62 +9BF0-5E64 +9BF1-5E47 +9BF2-5E75 +9BF3-5E76 +9BF4-5E7A +9BF5-9EBC +9BF6-5E7F +9BF7-5EA0 +9BF8-5EC1 +9BF9-5EC2 +9BFA-5EC8 +9BFB-5ED0 +9BFC-5ECF +9C40-5ED6 +9C41-5EE3 +9C42-5EDD +9C43-5EDA +9C44-5EDB +9C45-5EE2 +9C46-5EE1 +9C47-5EE8 +9C48-5EE9 +9C49-5EEC +9C4A-5EF1 +9C4B-5EF3 +9C4C-5EF0 +9C4D-5EF4 +9C4E-5EF8 +9C4F-5EFE +9C50-5F03 +9C51-5F09 +9C52-5F5D +9C53-5F5C +9C54-5F0B +9C55-5F11 +9C56-5F16 +9C57-5F29 +9C58-5F2D +9C59-5F38 +9C5A-5F41 +9C5B-5F48 +9C5C-5F4C +9C5D-5F4E +9C5E-5F2F +9C5F-5F51 +9C60-5F56 +9C61-5F57 +9C62-5F59 +9C63-5F61 +9C64-5F6D +9C65-5F73 +9C66-5F77 +9C67-5F83 +9C68-5F82 +9C69-5F7F +9C6A-5F8A +9C6B-5F88 +9C6C-5F91 +9C6D-5F87 +9C6E-5F9E +9C6F-5F99 +9C70-5F98 +9C71-5FA0 +9C72-5FA8 +9C73-5FAD +9C74-5FBC +9C75-5FD6 +9C76-5FFB +9C77-5FE4 +9C78-5FF8 +9C79-5FF1 +9C7A-5FDD +9C7B-60B3 +9C7C-5FFF +9C7D-6021 +9C7E-6060 +9C80-6019 +9C81-6010 +9C82-6029 +9C83-600E +9C84-6031 +9C85-601B +9C86-6015 +9C87-602B +9C88-6026 +9C89-600F +9C8A-603A +9C8B-605A +9C8C-6041 +9C8D-606A +9C8E-6077 +9C8F-605F +9C90-604A +9C91-6046 +9C92-604D +9C93-6063 +9C94-6043 +9C95-6064 +9C96-6042 +9C97-606C +9C98-606B +9C99-6059 +9C9A-6081 +9C9B-608D +9C9C-60E7 +9C9D-6083 +9C9E-609A +9C9F-6084 +9CA0-609B +9CA1-6096 +9CA2-6097 +9CA3-6092 +9CA4-60A7 +9CA5-608B +9CA6-60E1 +9CA7-60B8 +9CA8-60E0 +9CA9-60D3 +9CAA-60B4 +9CAB-5FF0 +9CAC-60BD +9CAD-60C6 +9CAE-60B5 +9CAF-60D8 +9CB0-614D +9CB1-6115 +9CB2-6106 +9CB3-60F6 +9CB4-60F7 +9CB5-6100 +9CB6-60F4 +9CB7-60FA +9CB8-6103 +9CB9-6121 +9CBA-60FB +9CBB-60F1 +9CBC-610D +9CBD-610E +9CBE-6147 +9CBF-613E +9CC0-6128 +9CC1-6127 +9CC2-614A +9CC3-613F +9CC4-613C +9CC5-612C +9CC6-6134 +9CC7-613D +9CC8-6142 +9CC9-6144 +9CCA-6173 +9CCB-6177 +9CCC-6158 +9CCD-6159 +9CCE-615A +9CCF-616B +9CD0-6174 +9CD1-616F +9CD2-6165 +9CD3-6171 +9CD4-615F +9CD5-615D +9CD6-6153 +9CD7-6175 +9CD8-6199 +9CD9-6196 +9CDA-6187 +9CDB-61AC +9CDC-6194 +9CDD-619A +9CDE-618A +9CDF-6191 +9CE0-61AB +9CE1-61AE +9CE2-61CC +9CE3-61CA +9CE4-61C9 +9CE5-61F7 +9CE6-61C8 +9CE7-61C3 +9CE8-61C6 +9CE9-61BA +9CEA-61CB +9CEB-7F79 +9CEC-61CD +9CED-61E6 +9CEE-61E3 +9CEF-61F6 +9CF0-61FA +9CF1-61F4 +9CF2-61FF +9CF3-61FD +9CF4-61FC +9CF5-61FE +9CF6-6200 +9CF7-6208 +9CF8-6209 +9CF9-620D +9CFA-620C +9CFB-6214 +9CFC-621B +9D40-621E +9D41-6221 +9D42-622A +9D43-622E +9D44-6230 +9D45-6232 +9D46-6233 +9D47-6241 +9D48-624E +9D49-625E +9D4A-6263 +9D4B-625B +9D4C-6260 +9D4D-6268 +9D4E-627C +9D4F-6282 +9D50-6289 +9D51-627E +9D52-6292 +9D53-6293 +9D54-6296 +9D55-62D4 +9D56-6283 +9D57-6294 +9D58-62D7 +9D59-62D1 +9D5A-62BB +9D5B-62CF +9D5C-62FF +9D5D-62C6 +9D5E-64D4 +9D5F-62C8 +9D60-62DC +9D61-62CC +9D62-62CA +9D63-62C2 +9D64-62C7 +9D65-629B +9D66-62C9 +9D67-630C +9D68-62EE +9D69-62F1 +9D6A-6327 +9D6B-6302 +9D6C-6308 +9D6D-62EF +9D6E-62F5 +9D6F-6350 +9D70-633E +9D71-634D +9D72-641C +9D73-634F +9D74-6396 +9D75-638E +9D76-6380 +9D77-63AB +9D78-6376 +9D79-63A3 +9D7A-638F +9D7B-6389 +9D7C-639F +9D7D-63B5 +9D7E-636B +9D80-6369 +9D81-63BE +9D82-63E9 +9D83-63C0 +9D84-63C6 +9D85-63E3 +9D86-63C9 +9D87-63D2 +9D88-63F6 +9D89-63C4 +9D8A-6416 +9D8B-6434 +9D8C-6406 +9D8D-6413 +9D8E-6426 +9D8F-6436 +9D90-651D +9D91-6417 +9D92-6428 +9D93-640F +9D94-6467 +9D95-646F +9D96-6476 +9D97-644E +9D98-652A +9D99-6495 +9D9A-6493 +9D9B-64A5 +9D9C-64A9 +9D9D-6488 +9D9E-64BC +9D9F-64DA +9DA0-64D2 +9DA1-64C5 +9DA2-64C7 +9DA3-64BB +9DA4-64D8 +9DA5-64C2 +9DA6-64F1 +9DA7-64E7 +9DA8-8209 +9DA9-64E0 +9DAA-64E1 +9DAB-62AC +9DAC-64E3 +9DAD-64EF +9DAE-652C +9DAF-64F6 +9DB0-64F4 +9DB1-64F2 +9DB2-64FA +9DB3-6500 +9DB4-64FD +9DB5-6518 +9DB6-651C +9DB7-6505 +9DB8-6524 +9DB9-6523 +9DBA-652B +9DBB-6534 +9DBC-6535 +9DBD-6537 +9DBE-6536 +9DBF-6538 +9DC0-754B +9DC1-6548 +9DC2-6556 +9DC3-6555 +9DC4-654D +9DC5-6558 +9DC6-655E +9DC7-655D +9DC8-6572 +9DC9-6578 +9DCA-6582 +9DCB-6583 +9DCC-8B8A +9DCD-659B +9DCE-659F +9DCF-65AB +9DD0-65B7 +9DD1-65C3 +9DD2-65C6 +9DD3-65C1 +9DD4-65C4 +9DD5-65CC +9DD6-65D2 +9DD7-65DB +9DD8-65D9 +9DD9-65E0 +9DDA-65E1 +9DDB-65F1 +9DDC-6772 +9DDD-660A +9DDE-6603 +9DDF-65FB +9DE0-6773 +9DE1-6635 +9DE2-6636 +9DE3-6634 +9DE4-661C +9DE5-664F +9DE6-6644 +9DE7-6649 +9DE8-6641 +9DE9-665E +9DEA-665D +9DEB-6664 +9DEC-6667 +9DED-6668 +9DEE-665F +9DEF-6662 +9DF0-6670 +9DF1-6683 +9DF2-6688 +9DF3-668E +9DF4-6689 +9DF5-6684 +9DF6-6698 +9DF7-669D +9DF8-66C1 +9DF9-66B9 +9DFA-66C9 +9DFB-66BE +9DFC-66BC +9E40-66C4 +9E41-66B8 +9E42-66D6 +9E43-66DA +9E44-66E0 +9E45-663F +9E46-66E6 +9E47-66E9 +9E48-66F0 +9E49-66F5 +9E4A-66F7 +9E4B-670F +9E4C-6716 +9E4D-671E +9E4E-6726 +9E4F-6727 +9E50-9738 +9E51-672E +9E52-673F +9E53-6736 +9E54-6741 +9E55-6738 +9E56-6737 +9E57-6746 +9E58-675E +9E59-6760 +9E5A-6759 +9E5B-6763 +9E5C-6764 +9E5D-6789 +9E5E-6770 +9E5F-67A9 +9E60-677C +9E61-676A +9E62-678C +9E63-678B +9E64-67A6 +9E65-67A1 +9E66-6785 +9E67-67B7 +9E68-67EF +9E69-67B4 +9E6A-67EC +9E6B-67B3 +9E6C-67E9 +9E6D-67B8 +9E6E-67E4 +9E6F-67DE +9E70-67DD +9E71-67E2 +9E72-67EE +9E73-67B9 +9E74-67CE +9E75-67C6 +9E76-67E7 +9E77-6A9C +9E78-681E +9E79-6846 +9E7A-6829 +9E7B-6840 +9E7C-684D +9E7D-6832 +9E7E-684E +9E80-68B3 +9E81-682B +9E82-6859 +9E83-6863 +9E84-6877 +9E85-687F +9E86-689F +9E87-688F +9E88-68AD +9E89-6894 +9E8A-689D +9E8B-689B +9E8C-6883 +9E8D-6AAE +9E8E-68B9 +9E8F-6874 +9E90-68B5 +9E91-68A0 +9E92-68BA +9E93-690F +9E94-688D +9E95-687E +9E96-6901 +9E97-68CA +9E98-6908 +9E99-68D8 +9E9A-6922 +9E9B-6926 +9E9C-68E1 +9E9D-690C +9E9E-68CD +9E9F-68D4 +9EA0-68E7 +9EA1-68D5 +9EA2-6936 +9EA3-6912 +9EA4-6904 +9EA5-68D7 +9EA6-68E3 +9EA7-6925 +9EA8-68F9 +9EA9-68E0 +9EAA-68EF +9EAB-6928 +9EAC-692A +9EAD-691A +9EAE-6923 +9EAF-6921 +9EB0-68C6 +9EB1-6979 +9EB2-6977 +9EB3-695C +9EB4-6978 +9EB5-696B +9EB6-6954 +9EB7-697E +9EB8-696E +9EB9-6939 +9EBA-6974 +9EBB-693D +9EBC-6959 +9EBD-6930 +9EBE-6961 +9EBF-695E +9EC0-695D +9EC1-6981 +9EC2-696A +9EC3-69B2 +9EC4-69AE +9EC5-69D0 +9EC6-69BF +9EC7-69C1 +9EC8-69D3 +9EC9-69BE +9ECA-69CE +9ECB-5BE8 +9ECC-69CA +9ECD-69DD +9ECE-69BB +9ECF-69C3 +9ED0-69A7 +9ED1-6A2E +9ED2-6991 +9ED3-69A0 +9ED4-699C +9ED5-6995 +9ED6-69B4 +9ED7-69DE +9ED8-69E8 +9ED9-6A02 +9EDA-6A1B +9EDB-69FF +9EDC-6B0A +9EDD-69F9 +9EDE-69F2 +9EDF-69E7 +9EE0-6A05 +9EE1-69B1 +9EE2-6A1E +9EE3-69ED +9EE4-6A14 +9EE5-69EB +9EE6-6A0A +9EE7-6A12 +9EE8-6AC1 +9EE9-6A23 +9EEA-6A13 +9EEB-6A44 +9EEC-6A0C +9EED-6A72 +9EEE-6A36 +9EEF-6A78 +9EF0-6A47 +9EF1-6A62 +9EF2-6A59 +9EF3-6A66 +9EF4-6A48 +9EF5-6A38 +9EF6-6A22 +9EF7-6A90 +9EF8-6A8D +9EF9-6AA0 +9EFA-6A84 +9EFB-6AA2 +9EFC-6AA3 +9F40-6A97 +9F41-8617 +9F42-6ABB +9F43-6AC3 +9F44-6AC2 +9F45-6AB8 +9F46-6AB3 +9F47-6AAC +9F48-6ADE +9F49-6AD1 +9F4A-6ADF +9F4B-6AAA +9F4C-6ADA +9F4D-6AEA +9F4E-6AFB +9F4F-6B05 +9F50-8616 +9F51-6AFA +9F52-6B12 +9F53-6B16 +9F54-9B31 +9F55-6B1F +9F56-6B38 +9F57-6B37 +9F58-76DC +9F59-6B39 +9F5A-98EE +9F5B-6B47 +9F5C-6B43 +9F5D-6B49 +9F5E-6B50 +9F5F-6B59 +9F60-6B54 +9F61-6B5B +9F62-6B5F +9F63-6B61 +9F64-6B78 +9F65-6B79 +9F66-6B7F +9F67-6B80 +9F68-6B84 +9F69-6B83 +9F6A-6B8D +9F6B-6B98 +9F6C-6B95 +9F6D-6B9E +9F6E-6BA4 +9F6F-6BAA +9F70-6BAB +9F71-6BAF +9F72-6BB2 +9F73-6BB1 +9F74-6BB3 +9F75-6BB7 +9F76-6BBC +9F77-6BC6 +9F78-6BCB +9F79-6BD3 +9F7A-6BDF +9F7B-6BEC +9F7C-6BEB +9F7D-6BF3 +9F7E-6BEF +9F80-9EBE +9F81-6C08 +9F82-6C13 +9F83-6C14 +9F84-6C1B +9F85-6C24 +9F86-6C23 +9F87-6C5E +9F88-6C55 +9F89-6C62 +9F8A-6C6A +9F8B-6C82 +9F8C-6C8D +9F8D-6C9A +9F8E-6C81 +9F8F-6C9B +9F90-6C7E +9F91-6C68 +9F92-6C73 +9F93-6C92 +9F94-6C90 +9F95-6CC4 +9F96-6CF1 +9F97-6CD3 +9F98-6CBD +9F99-6CD7 +9F9A-6CC5 +9F9B-6CDD +9F9C-6CAE +9F9D-6CB1 +9F9E-6CBE +9F9F-6CBA +9FA0-6CDB +9FA1-6CEF +9FA2-6CD9 +9FA3-6CEA +9FA4-6D1F +9FA5-884D +9FA6-6D36 +9FA7-6D2B +9FA8-6D3D +9FA9-6D38 +9FAA-6D19 +9FAB-6D35 +9FAC-6D33 +9FAD-6D12 +9FAE-6D0C +9FAF-6D63 +9FB0-6D93 +9FB1-6D64 +9FB2-6D5A +9FB3-6D79 +9FB4-6D59 +9FB5-6D8E +9FB6-6D95 +9FB7-6FE4 +9FB8-6D85 +9FB9-6DF9 +9FBA-6E15 +9FBB-6E0A +9FBC-6DB5 +9FBD-6DC7 +9FBE-6DE6 +9FBF-6DB8 +9FC0-6DC6 +9FC1-6DEC +9FC2-6DDE +9FC3-6DCC +9FC4-6DE8 +9FC5-6DD2 +9FC6-6DC5 +9FC7-6DFA +9FC8-6DD9 +9FC9-6DE4 +9FCA-6DD5 +9FCB-6DEA +9FCC-6DEE +9FCD-6E2D +9FCE-6E6E +9FCF-6E2E +9FD0-6E19 +9FD1-6E72 +9FD2-6E5F +9FD3-6E3E +9FD4-6E23 +9FD5-6E6B +9FD6-6E2B +9FD7-6E76 +9FD8-6E4D +9FD9-6E1F +9FDA-6E43 +9FDB-6E3A +9FDC-6E4E +9FDD-6E24 +9FDE-6EFF +9FDF-6E1D +9FE0-6E38 +9FE1-6E82 +9FE2-6EAA +9FE3-6E98 +9FE4-6EC9 +9FE5-6EB7 +9FE6-6ED3 +9FE7-6EBD +9FE8-6EAF +9FE9-6EC4 +9FEA-6EB2 +9FEB-6ED4 +9FEC-6ED5 +9FED-6E8F +9FEE-6EA5 +9FEF-6EC2 +9FF0-6E9F +9FF1-6F41 +9FF2-6F11 +9FF3-704C +9FF4-6EEC +9FF5-6EF8 +9FF6-6EFE +9FF7-6F3F +9FF8-6EF2 +9FF9-6F31 +9FFA-6EEF +9FFB-6F32 +9FFC-6ECC +E040-6F3E +E041-6F13 +E042-6EF7 +E043-6F86 +E044-6F7A +E045-6F78 +E046-6F81 +E047-6F80 +E048-6F6F +E049-6F5B +E04A-6FF3 +E04B-6F6D +E04C-6F82 +E04D-6F7C +E04E-6F58 +E04F-6F8E +E050-6F91 +E051-6FC2 +E052-6F66 +E053-6FB3 +E054-6FA3 +E055-6FA1 +E056-6FA4 +E057-6FB9 +E058-6FC6 +E059-6FAA +E05A-6FDF +E05B-6FD5 +E05C-6FEC +E05D-6FD4 +E05E-6FD8 +E05F-6FF1 +E060-6FEE +E061-6FDB +E062-7009 +E063-700B +E064-6FFA +E065-7011 +E066-7001 +E067-700F +E068-6FFE +E069-701B +E06A-701A +E06B-6F74 +E06C-701D +E06D-7018 +E06E-701F +E06F-7030 +E070-703E +E071-7032 +E072-7051 +E073-7063 +E074-7099 +E075-7092 +E076-70AF +E077-70F1 +E078-70AC +E079-70B8 +E07A-70B3 +E07B-70AE +E07C-70DF +E07D-70CB +E07E-70DD +E080-70D9 +E081-7109 +E082-70FD +E083-711C +E084-7119 +E085-7165 +E086-7155 +E087-7188 +E088-7166 +E089-7162 +E08A-714C +E08B-7156 +E08C-716C +E08D-718F +E08E-71FB +E08F-7184 +E090-7195 +E091-71A8 +E092-71AC +E093-71D7 +E094-71B9 +E095-71BE +E096-71D2 +E097-71C9 +E098-71D4 +E099-71CE +E09A-71E0 +E09B-71EC +E09C-71E7 +E09D-71F5 +E09E-71FC +E09F-71F9 +E0A0-71FF +E0A1-720D +E0A2-7210 +E0A3-721B +E0A4-7228 +E0A5-722D +E0A6-722C +E0A7-7230 +E0A8-7232 +E0A9-723B +E0AA-723C +E0AB-723F +E0AC-7240 +E0AD-7246 +E0AE-724B +E0AF-7258 +E0B0-7274 +E0B1-727E +E0B2-7282 +E0B3-7281 +E0B4-7287 +E0B5-7292 +E0B6-7296 +E0B7-72A2 +E0B8-72A7 +E0B9-72B9 +E0BA-72B2 +E0BB-72C3 +E0BC-72C6 +E0BD-72C4 +E0BE-72CE +E0BF-72D2 +E0C0-72E2 +E0C1-72E0 +E0C2-72E1 +E0C3-72F9 +E0C4-72F7 +E0C5-500F +E0C6-7317 +E0C7-730A +E0C8-731C +E0C9-7316 +E0CA-731D +E0CB-7334 +E0CC-732F +E0CD-7329 +E0CE-7325 +E0CF-733E +E0D0-734E +E0D1-734F +E0D2-9ED8 +E0D3-7357 +E0D4-736A +E0D5-7368 +E0D6-7370 +E0D7-7378 +E0D8-7375 +E0D9-737B +E0DA-737A +E0DB-73C8 +E0DC-73B3 +E0DD-73CE +E0DE-73BB +E0DF-73C0 +E0E0-73E5 +E0E1-73EE +E0E2-73DE +E0E3-74A2 +E0E4-7405 +E0E5-746F +E0E6-7425 +E0E7-73F8 +E0E8-7432 +E0E9-743A +E0EA-7455 +E0EB-743F +E0EC-745F +E0ED-7459 +E0EE-7441 +E0EF-745C +E0F0-7469 +E0F1-7470 +E0F2-7463 +E0F3-746A +E0F4-7476 +E0F5-747E +E0F6-748B +E0F7-749E +E0F8-74A7 +E0F9-74CA +E0FA-74CF +E0FB-74D4 +E0FC-73F1 +E140-74E0 +E141-74E3 +E142-74E7 +E143-74E9 +E144-74EE +E145-74F2 +E146-74F0 +E147-74F1 +E148-74F8 +E149-74F7 +E14A-7504 +E14B-7503 +E14C-7505 +E14D-750C +E14E-750E +E14F-750D +E150-7515 +E151-7513 +E152-751E +E153-7526 +E154-752C +E155-753C +E156-7544 +E157-754D +E158-754A +E159-7549 +E15A-755B +E15B-7546 +E15C-755A +E15D-7569 +E15E-7564 +E15F-7567 +E160-756B +E161-756D +E162-7578 +E163-7576 +E164-7586 +E165-7587 +E166-7574 +E167-758A +E168-7589 +E169-7582 +E16A-7594 +E16B-759A +E16C-759D +E16D-75A5 +E16E-75A3 +E16F-75C2 +E170-75B3 +E171-75C3 +E172-75B5 +E173-75BD +E174-75B8 +E175-75BC +E176-75B1 +E177-75CD +E178-75CA +E179-75D2 +E17A-75D9 +E17B-75E3 +E17C-75DE +E17D-75FE +E17E-75FF +E180-75FC +E181-7601 +E182-75F0 +E183-75FA +E184-75F2 +E185-75F3 +E186-760B +E187-760D +E188-7609 +E189-761F +E18A-7627 +E18B-7620 +E18C-7621 +E18D-7622 +E18E-7624 +E18F-7634 +E190-7630 +E191-763B +E192-7647 +E193-7648 +E194-7646 +E195-765C +E196-7658 +E197-7661 +E198-7662 +E199-7668 +E19A-7669 +E19B-766A +E19C-7667 +E19D-766C +E19E-7670 +E19F-7672 +E1A0-7676 +E1A1-7678 +E1A2-767C +E1A3-7680 +E1A4-7683 +E1A5-7688 +E1A6-768B +E1A7-768E +E1A8-7696 +E1A9-7693 +E1AA-7699 +E1AB-769A +E1AC-76B0 +E1AD-76B4 +E1AE-76B8 +E1AF-76B9 +E1B0-76BA +E1B1-76C2 +E1B2-76CD +E1B3-76D6 +E1B4-76D2 +E1B5-76DE +E1B6-76E1 +E1B7-76E5 +E1B8-76E7 +E1B9-76EA +E1BA-862F +E1BB-76FB +E1BC-7708 +E1BD-7707 +E1BE-7704 +E1BF-7729 +E1C0-7724 +E1C1-771E +E1C2-7725 +E1C3-7726 +E1C4-771B +E1C5-7737 +E1C6-7738 +E1C7-7747 +E1C8-775A +E1C9-7768 +E1CA-776B +E1CB-775B +E1CC-7765 +E1CD-777F +E1CE-777E +E1CF-7779 +E1D0-778E +E1D1-778B +E1D2-7791 +E1D3-77A0 +E1D4-779E +E1D5-77B0 +E1D6-77B6 +E1D7-77B9 +E1D8-77BF +E1D9-77BC +E1DA-77BD +E1DB-77BB +E1DC-77C7 +E1DD-77CD +E1DE-77D7 +E1DF-77DA +E1E0-77DC +E1E1-77E3 +E1E2-77EE +E1E3-77FC +E1E4-780C +E1E5-7812 +E1E6-7926 +E1E7-7820 +E1E8-792A +E1E9-7845 +E1EA-788E +E1EB-7874 +E1EC-7886 +E1ED-787C +E1EE-789A +E1EF-788C +E1F0-78A3 +E1F1-78B5 +E1F2-78AA +E1F3-78AF +E1F4-78D1 +E1F5-78C6 +E1F6-78CB +E1F7-78D4 +E1F8-78BE +E1F9-78BC +E1FA-78C5 +E1FB-78CA +E1FC-78EC +E240-78E7 +E241-78DA +E242-78FD +E243-78F4 +E244-7907 +E245-7912 +E246-7911 +E247-7919 +E248-792C +E249-792B +E24A-7940 +E24B-7960 +E24C-7957 +E24D-795F +E24E-795A +E24F-7955 +E250-7953 +E251-797A +E252-797F +E253-798A +E254-799D +E255-79A7 +E256-9F4B +E257-79AA +E258-79AE +E259-79B3 +E25A-79B9 +E25B-79BA +E25C-79C9 +E25D-79D5 +E25E-79E7 +E25F-79EC +E260-79E1 +E261-79E3 +E262-7A08 +E263-7A0D +E264-7A18 +E265-7A19 +E266-7A20 +E267-7A1F +E268-7980 +E269-7A31 +E26A-7A3B +E26B-7A3E +E26C-7A37 +E26D-7A43 +E26E-7A57 +E26F-7A49 +E270-7A61 +E271-7A62 +E272-7A69 +E273-9F9D +E274-7A70 +E275-7A79 +E276-7A7D +E277-7A88 +E278-7A97 +E279-7A95 +E27A-7A98 +E27B-7A96 +E27C-7AA9 +E27D-7AC8 +E27E-7AB0 +E280-7AB6 +E281-7AC5 +E282-7AC4 +E283-7ABF +E284-9083 +E285-7AC7 +E286-7ACA +E287-7ACD +E288-7ACF +E289-7AD5 +E28A-7AD3 +E28B-7AD9 +E28C-7ADA +E28D-7ADD +E28E-7AE1 +E28F-7AE2 +E290-7AE6 +E291-7AED +E292-7AF0 +E293-7B02 +E294-7B0F +E295-7B0A +E296-7B06 +E297-7B33 +E298-7B18 +E299-7B19 +E29A-7B1E +E29B-7B35 +E29C-7B28 +E29D-7B36 +E29E-7B50 +E29F-7B7A +E2A0-7B04 +E2A1-7B4D +E2A2-7B0B +E2A3-7B4C +E2A4-7B45 +E2A5-7B75 +E2A6-7B65 +E2A7-7B74 +E2A8-7B67 +E2A9-7B70 +E2AA-7B71 +E2AB-7B6C +E2AC-7B6E +E2AD-7B9D +E2AE-7B98 +E2AF-7B9F +E2B0-7B8D +E2B1-7B9C +E2B2-7B9A +E2B3-7B8B +E2B4-7B92 +E2B5-7B8F +E2B6-7B5D +E2B7-7B99 +E2B8-7BCB +E2B9-7BC1 +E2BA-7BCC +E2BB-7BCF +E2BC-7BB4 +E2BD-7BC6 +E2BE-7BDD +E2BF-7BE9 +E2C0-7C11 +E2C1-7C14 +E2C2-7BE6 +E2C3-7BE5 +E2C4-7C60 +E2C5-7C00 +E2C6-7C07 +E2C7-7C13 +E2C8-7BF3 +E2C9-7BF7 +E2CA-7C17 +E2CB-7C0D +E2CC-7BF6 +E2CD-7C23 +E2CE-7C27 +E2CF-7C2A +E2D0-7C1F +E2D1-7C37 +E2D2-7C2B +E2D3-7C3D +E2D4-7C4C +E2D5-7C43 +E2D6-7C54 +E2D7-7C4F +E2D8-7C40 +E2D9-7C50 +E2DA-7C58 +E2DB-7C5F +E2DC-7C64 +E2DD-7C56 +E2DE-7C65 +E2DF-7C6C +E2E0-7C75 +E2E1-7C83 +E2E2-7C90 +E2E3-7CA4 +E2E4-7CAD +E2E5-7CA2 +E2E6-7CAB +E2E7-7CA1 +E2E8-7CA8 +E2E9-7CB3 +E2EA-7CB2 +E2EB-7CB1 +E2EC-7CAE +E2ED-7CB9 +E2EE-7CBD +E2EF-7CC0 +E2F0-7CC5 +E2F1-7CC2 +E2F2-7CD8 +E2F3-7CD2 +E2F4-7CDC +E2F5-7CE2 +E2F6-9B3B +E2F7-7CEF +E2F8-7CF2 +E2F9-7CF4 +E2FA-7CF6 +E2FB-7CFA +E2FC-7D06 +E340-7D02 +E341-7D1C +E342-7D15 +E343-7D0A +E344-7D45 +E345-7D4B +E346-7D2E +E347-7D32 +E348-7D3F +E349-7D35 +E34A-7D46 +E34B-7D73 +E34C-7D56 +E34D-7D4E +E34E-7D72 +E34F-7D68 +E350-7D6E +E351-7D4F +E352-7D63 +E353-7D93 +E354-7D89 +E355-7D5B +E356-7D8F +E357-7D7D +E358-7D9B +E359-7DBA +E35A-7DAE +E35B-7DA3 +E35C-7DB5 +E35D-7DC7 +E35E-7DBD +E35F-7DAB +E360-7E3D +E361-7DA2 +E362-7DAF +E363-7DDC +E364-7DB8 +E365-7D9F +E366-7DB0 +E367-7DD8 +E368-7DDD +E369-7DE4 +E36A-7DDE +E36B-7DFB +E36C-7DF2 +E36D-7DE1 +E36E-7E05 +E36F-7E0A +E370-7E23 +E371-7E21 +E372-7E12 +E373-7E31 +E374-7E1F +E375-7E09 +E376-7E0B +E377-7E22 +E378-7E46 +E379-7E66 +E37A-7E3B +E37B-7E35 +E37C-7E39 +E37D-7E43 +E37E-7E37 +E380-7E32 +E381-7E3A +E382-7E67 +E383-7E5D +E384-7E56 +E385-7E5E +E386-7E59 +E387-7E5A +E388-7E79 +E389-7E6A +E38A-7E69 +E38B-7E7C +E38C-7E7B +E38D-7E83 +E38E-7DD5 +E38F-7E7D +E390-8FAE +E391-7E7F +E392-7E88 +E393-7E89 +E394-7E8C +E395-7E92 +E396-7E90 +E397-7E93 +E398-7E94 +E399-7E96 +E39A-7E8E +E39B-7E9B +E39C-7E9C +E39D-7F38 +E39E-7F3A +E39F-7F45 +E3A0-7F4C +E3A1-7F4D +E3A2-7F4E +E3A3-7F50 +E3A4-7F51 +E3A5-7F55 +E3A6-7F54 +E3A7-7F58 +E3A8-7F5F +E3A9-7F60 +E3AA-7F68 +E3AB-7F69 +E3AC-7F67 +E3AD-7F78 +E3AE-7F82 +E3AF-7F86 +E3B0-7F83 +E3B1-7F88 +E3B2-7F87 +E3B3-7F8C +E3B4-7F94 +E3B5-7F9E +E3B6-7F9D +E3B7-7F9A +E3B8-7FA3 +E3B9-7FAF +E3BA-7FB2 +E3BB-7FB9 +E3BC-7FAE +E3BD-7FB6 +E3BE-7FB8 +E3BF-8B71 +E3C0-7FC5 +E3C1-7FC6 +E3C2-7FCA +E3C3-7FD5 +E3C4-7FD4 +E3C5-7FE1 +E3C6-7FE6 +E3C7-7FE9 +E3C8-7FF3 +E3C9-7FF9 +E3CA-98DC +E3CB-8006 +E3CC-8004 +E3CD-800B +E3CE-8012 +E3CF-8018 +E3D0-8019 +E3D1-801C +E3D2-8021 +E3D3-8028 +E3D4-803F +E3D5-803B +E3D6-804A +E3D7-8046 +E3D8-8052 +E3D9-8058 +E3DA-805A +E3DB-805F +E3DC-8062 +E3DD-8068 +E3DE-8073 +E3DF-8072 +E3E0-8070 +E3E1-8076 +E3E2-8079 +E3E3-807D +E3E4-807F +E3E5-8084 +E3E6-8086 +E3E7-8085 +E3E8-809B +E3E9-8093 +E3EA-809A +E3EB-80AD +E3EC-5190 +E3ED-80AC +E3EE-80DB +E3EF-80E5 +E3F0-80D9 +E3F1-80DD +E3F2-80C4 +E3F3-80DA +E3F4-80D6 +E3F5-8109 +E3F6-80EF +E3F7-80F1 +E3F8-811B +E3F9-8129 +E3FA-8123 +E3FB-812F +E3FC-814B +E440-968B +E441-8146 +E442-813E +E443-8153 +E444-8151 +E445-80FC +E446-8171 +E447-816E +E448-8165 +E449-8166 +E44A-8174 +E44B-8183 +E44C-8188 +E44D-818A +E44E-8180 +E44F-8182 +E450-81A0 +E451-8195 +E452-81A4 +E453-81A3 +E454-815F +E455-8193 +E456-81A9 +E457-81B0 +E458-81B5 +E459-81BE +E45A-81B8 +E45B-81BD +E45C-81C0 +E45D-81C2 +E45E-81BA +E45F-81C9 +E460-81CD +E461-81D1 +E462-81D9 +E463-81D8 +E464-81C8 +E465-81DA +E466-81DF +E467-81E0 +E468-81E7 +E469-81FA +E46A-81FB +E46B-81FE +E46C-8201 +E46D-8202 +E46E-8205 +E46F-8207 +E470-820A +E471-820D +E472-8210 +E473-8216 +E474-8229 +E475-822B +E476-8238 +E477-8233 +E478-8240 +E479-8259 +E47A-8258 +E47B-825D +E47C-825A +E47D-825F +E47E-8264 +E480-8262 +E481-8268 +E482-826A +E483-826B +E484-822E +E485-8271 +E486-8277 +E487-8278 +E488-827E +E489-828D +E48A-8292 +E48B-82AB +E48C-829F +E48D-82BB +E48E-82AC +E48F-82E1 +E490-82E3 +E491-82DF +E492-82D2 +E493-82F4 +E494-82F3 +E495-82FA +E496-8393 +E497-8303 +E498-82FB +E499-82F9 +E49A-82DE +E49B-8306 +E49C-82DC +E49D-8309 +E49E-82D9 +E49F-8335 +E4A0-8334 +E4A1-8316 +E4A2-8332 +E4A3-8331 +E4A4-8340 +E4A5-8339 +E4A6-8350 +E4A7-8345 +E4A8-832F +E4A9-832B +E4AA-8317 +E4AB-8318 +E4AC-8385 +E4AD-839A +E4AE-83AA +E4AF-839F +E4B0-83A2 +E4B1-8396 +E4B2-8323 +E4B3-838E +E4B4-8387 +E4B5-838A +E4B6-837C +E4B7-83B5 +E4B8-8373 +E4B9-8375 +E4BA-83A0 +E4BB-8389 +E4BC-83A8 +E4BD-83F4 +E4BE-8413 +E4BF-83EB +E4C0-83CE +E4C1-83FD +E4C2-8403 +E4C3-83D8 +E4C4-840B +E4C5-83C1 +E4C6-83F7 +E4C7-8407 +E4C8-83E0 +E4C9-83F2 +E4CA-840D +E4CB-8422 +E4CC-8420 +E4CD-83BD +E4CE-8438 +E4CF-8506 +E4D0-83FB +E4D1-846D +E4D2-842A +E4D3-843C +E4D4-855A +E4D5-8484 +E4D6-8477 +E4D7-846B +E4D8-84AD +E4D9-846E +E4DA-8482 +E4DB-8469 +E4DC-8446 +E4DD-842C +E4DE-846F +E4DF-8479 +E4E0-8435 +E4E1-84CA +E4E2-8462 +E4E3-84B9 +E4E4-84BF +E4E5-849F +E4E6-84D9 +E4E7-84CD +E4E8-84BB +E4E9-84DA +E4EA-84D0 +E4EB-84C1 +E4EC-84C6 +E4ED-84D6 +E4EE-84A1 +E4EF-8521 +E4F0-84FF +E4F1-84F4 +E4F2-8517 +E4F3-8518 +E4F4-852C +E4F5-851F +E4F6-8515 +E4F7-8514 +E4F8-84FC +E4F9-8540 +E4FA-8563 +E4FB-8558 +E4FC-8548 +E540-8541 +E541-8602 +E542-854B +E543-8555 +E544-8580 +E545-85A4 +E546-8588 +E547-8591 +E548-858A +E549-85A8 +E54A-856D +E54B-8594 +E54C-859B +E54D-85EA +E54E-8587 +E54F-859C +E550-8577 +E551-857E +E552-8590 +E553-85C9 +E554-85BA +E555-85CF +E556-85B9 +E557-85D0 +E558-85D5 +E559-85DD +E55A-85E5 +E55B-85DC +E55C-85F9 +E55D-860A +E55E-8613 +E55F-860B +E560-85FE +E561-85FA +E562-8606 +E563-8622 +E564-861A +E565-8630 +E566-863F +E567-864D +E568-4E55 +E569-8654 +E56A-865F +E56B-8667 +E56C-8671 +E56D-8693 +E56E-86A3 +E56F-86A9 +E570-86AA +E571-868B +E572-868C +E573-86B6 +E574-86AF +E575-86C4 +E576-86C6 +E577-86B0 +E578-86C9 +E579-8823 +E57A-86AB +E57B-86D4 +E57C-86DE +E57D-86E9 +E57E-86EC +E580-86DF +E581-86DB +E582-86EF +E583-8712 +E584-8706 +E585-8708 +E586-8700 +E587-8703 +E588-86FB +E589-8711 +E58A-8709 +E58B-870D +E58C-86F9 +E58D-870A +E58E-8734 +E58F-873F +E590-8737 +E591-873B +E592-8725 +E593-8729 +E594-871A +E595-8760 +E596-875F +E597-8778 +E598-874C +E599-874E +E59A-8774 +E59B-8757 +E59C-8768 +E59D-876E +E59E-8759 +E59F-8753 +E5A0-8763 +E5A1-876A +E5A2-8805 +E5A3-87A2 +E5A4-879F +E5A5-8782 +E5A6-87AF +E5A7-87CB +E5A8-87BD +E5A9-87C0 +E5AA-87D0 +E5AB-96D6 +E5AC-87AB +E5AD-87C4 +E5AE-87B3 +E5AF-87C7 +E5B0-87C6 +E5B1-87BB +E5B2-87EF +E5B3-87F2 +E5B4-87E0 +E5B5-880F +E5B6-880D +E5B7-87FE +E5B8-87F6 +E5B9-87F7 +E5BA-880E +E5BB-87D2 +E5BC-8811 +E5BD-8816 +E5BE-8815 +E5BF-8822 +E5C0-8821 +E5C1-8831 +E5C2-8836 +E5C3-8839 +E5C4-8827 +E5C5-883B +E5C6-8844 +E5C7-8842 +E5C8-8852 +E5C9-8859 +E5CA-885E +E5CB-8862 +E5CC-886B +E5CD-8881 +E5CE-887E +E5CF-889E +E5D0-8875 +E5D1-887D +E5D2-88B5 +E5D3-8872 +E5D4-8882 +E5D5-8897 +E5D6-8892 +E5D7-88AE +E5D8-8899 +E5D9-88A2 +E5DA-888D +E5DB-88A4 +E5DC-88B0 +E5DD-88BF +E5DE-88B1 +E5DF-88C3 +E5E0-88C4 +E5E1-88D4 +E5E2-88D8 +E5E3-88D9 +E5E4-88DD +E5E5-88F9 +E5E6-8902 +E5E7-88FC +E5E8-88F4 +E5E9-88E8 +E5EA-88F2 +E5EB-8904 +E5EC-890C +E5ED-890A +E5EE-8913 +E5EF-8943 +E5F0-891E +E5F1-8925 +E5F2-892A +E5F3-892B +E5F4-8941 +E5F5-8944 +E5F6-893B +E5F7-8936 +E5F8-8938 +E5F9-894C +E5FA-891D +E5FB-8960 +E5FC-895E +E640-8966 +E641-8964 +E642-896D +E643-896A +E644-896F +E645-8974 +E646-8977 +E647-897E +E648-8983 +E649-8988 +E64A-898A +E64B-8993 +E64C-8998 +E64D-89A1 +E64E-89A9 +E64F-89A6 +E650-89AC +E651-89AF +E652-89B2 +E653-89BA +E654-89BD +E655-89BF +E656-89C0 +E657-89DA +E658-89DC +E659-89DD +E65A-89E7 +E65B-89F4 +E65C-89F8 +E65D-8A03 +E65E-8A16 +E65F-8A10 +E660-8A0C +E661-8A1B +E662-8A1D +E663-8A25 +E664-8A36 +E665-8A41 +E666-8A5B +E667-8A52 +E668-8A46 +E669-8A48 +E66A-8A7C +E66B-8A6D +E66C-8A6C +E66D-8A62 +E66E-8A85 +E66F-8A82 +E670-8A84 +E671-8AA8 +E672-8AA1 +E673-8A91 +E674-8AA5 +E675-8AA6 +E676-8A9A +E677-8AA3 +E678-8AC4 +E679-8ACD +E67A-8AC2 +E67B-8ADA +E67C-8AEB +E67D-8AF3 +E67E-8AE7 +E680-8AE4 +E681-8AF1 +E682-8B14 +E683-8AE0 +E684-8AE2 +E685-8AF7 +E686-8ADE +E687-8ADB +E688-8B0C +E689-8B07 +E68A-8B1A +E68B-8AE1 +E68C-8B16 +E68D-8B10 +E68E-8B17 +E68F-8B20 +E690-8B33 +E691-97AB +E692-8B26 +E693-8B2B +E694-8B3E +E695-8B28 +E696-8B41 +E697-8B4C +E698-8B4F +E699-8B4E +E69A-8B49 +E69B-8B56 +E69C-8B5B +E69D-8B5A +E69E-8B6B +E69F-8B5F +E6A0-8B6C +E6A1-8B6F +E6A2-8B74 +E6A3-8B7D +E6A4-8B80 +E6A5-8B8C +E6A6-8B8E +E6A7-8B92 +E6A8-8B93 +E6A9-8B96 +E6AA-8B99 +E6AB-8B9A +E6AC-8C3A +E6AD-8C41 +E6AE-8C3F +E6AF-8C48 +E6B0-8C4C +E6B1-8C4E +E6B2-8C50 +E6B3-8C55 +E6B4-8C62 +E6B5-8C6C +E6B6-8C78 +E6B7-8C7A +E6B8-8C82 +E6B9-8C89 +E6BA-8C85 +E6BB-8C8A +E6BC-8C8D +E6BD-8C8E +E6BE-8C94 +E6BF-8C7C +E6C0-8C98 +E6C1-621D +E6C2-8CAD +E6C3-8CAA +E6C4-8CBD +E6C5-8CB2 +E6C6-8CB3 +E6C7-8CAE +E6C8-8CB6 +E6C9-8CC8 +E6CA-8CC1 +E6CB-8CE4 +E6CC-8CE3 +E6CD-8CDA +E6CE-8CFD +E6CF-8CFA +E6D0-8CFB +E6D1-8D04 +E6D2-8D05 +E6D3-8D0A +E6D4-8D07 +E6D5-8D0F +E6D6-8D0D +E6D7-8D10 +E6D8-9F4E +E6D9-8D13 +E6DA-8CCD +E6DB-8D14 +E6DC-8D16 +E6DD-8D67 +E6DE-8D6D +E6DF-8D71 +E6E0-8D73 +E6E1-8D81 +E6E2-8D99 +E6E3-8DC2 +E6E4-8DBE +E6E5-8DBA +E6E6-8DCF +E6E7-8DDA +E6E8-8DD6 +E6E9-8DCC +E6EA-8DDB +E6EB-8DCB +E6EC-8DEA +E6ED-8DEB +E6EE-8DDF +E6EF-8DE3 +E6F0-8DFC +E6F1-8E08 +E6F2-8E09 +E6F3-8DFF +E6F4-8E1D +E6F5-8E1E +E6F6-8E10 +E6F7-8E1F +E6F8-8E42 +E6F9-8E35 +E6FA-8E30 +E6FB-8E34 +E6FC-8E4A +E740-8E47 +E741-8E49 +E742-8E4C +E743-8E50 +E744-8E48 +E745-8E59 +E746-8E64 +E747-8E60 +E748-8E2A +E749-8E63 +E74A-8E55 +E74B-8E76 +E74C-8E72 +E74D-8E7C +E74E-8E81 +E74F-8E87 +E750-8E85 +E751-8E84 +E752-8E8B +E753-8E8A +E754-8E93 +E755-8E91 +E756-8E94 +E757-8E99 +E758-8EAA +E759-8EA1 +E75A-8EAC +E75B-8EB0 +E75C-8EC6 +E75D-8EB1 +E75E-8EBE +E75F-8EC5 +E760-8EC8 +E761-8ECB +E762-8EDB +E763-8EE3 +E764-8EFC +E765-8EFB +E766-8EEB +E767-8EFE +E768-8F0A +E769-8F05 +E76A-8F15 +E76B-8F12 +E76C-8F19 +E76D-8F13 +E76E-8F1C +E76F-8F1F +E770-8F1B +E771-8F0C +E772-8F26 +E773-8F33 +E774-8F3B +E775-8F39 +E776-8F45 +E777-8F42 +E778-8F3E +E779-8F4C +E77A-8F49 +E77B-8F46 +E77C-8F4E +E77D-8F57 +E77E-8F5C +E780-8F62 +E781-8F63 +E782-8F64 +E783-8F9C +E784-8F9F +E785-8FA3 +E786-8FAD +E787-8FAF +E788-8FB7 +E789-8FDA +E78A-8FE5 +E78B-8FE2 +E78C-8FEA +E78D-8FEF +E78E-9087 +E78F-8FF4 +E790-9005 +E791-8FF9 +E792-8FFA +E793-9011 +E794-9015 +E795-9021 +E796-900D +E797-901E +E798-9016 +E799-900B +E79A-9027 +E79B-9036 +E79C-9035 +E79D-9039 +E79E-8FF8 +E79F-904F +E7A0-9050 +E7A1-9051 +E7A2-9052 +E7A3-900E +E7A4-9049 +E7A5-903E +E7A6-9056 +E7A7-9058 +E7A8-905E +E7A9-9068 +E7AA-906F +E7AB-9076 +E7AC-96A8 +E7AD-9072 +E7AE-9082 +E7AF-907D +E7B0-9081 +E7B1-9080 +E7B2-908A +E7B3-9089 +E7B4-908F +E7B5-90A8 +E7B6-90AF +E7B7-90B1 +E7B8-90B5 +E7B9-90E2 +E7BA-90E4 +E7BB-6248 +E7BC-90DB +E7BD-9102 +E7BE-9112 +E7BF-9119 +E7C0-9132 +E7C1-9130 +E7C2-914A +E7C3-9156 +E7C4-9158 +E7C5-9163 +E7C6-9165 +E7C7-9169 +E7C8-9173 +E7C9-9172 +E7CA-918B +E7CB-9189 +E7CC-9182 +E7CD-91A2 +E7CE-91AB +E7CF-91AF +E7D0-91AA +E7D1-91B5 +E7D2-91B4 +E7D3-91BA +E7D4-91C0 +E7D5-91C1 +E7D6-91C9 +E7D7-91CB +E7D8-91D0 +E7D9-91D6 +E7DA-91DF +E7DB-91E1 +E7DC-91DB +E7DD-91FC +E7DE-91F5 +E7DF-91F6 +E7E0-921E +E7E1-91FF +E7E2-9214 +E7E3-922C +E7E4-9215 +E7E5-9211 +E7E6-925E +E7E7-9257 +E7E8-9245 +E7E9-9249 +E7EA-9264 +E7EB-9248 +E7EC-9295 +E7ED-923F +E7EE-924B +E7EF-9250 +E7F0-929C +E7F1-9296 +E7F2-9293 +E7F3-929B +E7F4-925A +E7F5-92CF +E7F6-92B9 +E7F7-92B7 +E7F8-92E9 +E7F9-930F +E7FA-92FA +E7FB-9344 +E7FC-932E +E840-9319 +E841-9322 +E842-931A +E843-9323 +E844-933A +E845-9335 +E846-933B +E847-935C +E848-9360 +E849-937C +E84A-936E +E84B-9356 +E84C-93B0 +E84D-93AC +E84E-93AD +E84F-9394 +E850-93B9 +E851-93D6 +E852-93D7 +E853-93E8 +E854-93E5 +E855-93D8 +E856-93C3 +E857-93DD +E858-93D0 +E859-93C8 +E85A-93E4 +E85B-941A +E85C-9414 +E85D-9413 +E85E-9403 +E85F-9407 +E860-9410 +E861-9436 +E862-942B +E863-9435 +E864-9421 +E865-943A +E866-9441 +E867-9452 +E868-9444 +E869-945B +E86A-9460 +E86B-9462 +E86C-945E +E86D-946A +E86E-9229 +E86F-9470 +E870-9475 +E871-9477 +E872-947D +E873-945A +E874-947C +E875-947E +E876-9481 +E877-947F +E878-9582 +E879-9587 +E87A-958A +E87B-9594 +E87C-9596 +E87D-9598 +E87E-9599 +E880-95A0 +E881-95A8 +E882-95A7 +E883-95AD +E884-95BC +E885-95BB +E886-95B9 +E887-95BE +E888-95CA +E889-6FF6 +E88A-95C3 +E88B-95CD +E88C-95CC +E88D-95D5 +E88E-95D4 +E88F-95D6 +E890-95DC +E891-95E1 +E892-95E5 +E893-95E2 +E894-9621 +E895-9628 +E896-962E +E897-962F +E898-9642 +E899-964C +E89A-964F +E89B-964B +E89C-9677 +E89D-965C +E89E-965E +E89F-965D +E8A0-965F +E8A1-9666 +E8A2-9672 +E8A3-966C +E8A4-968D +E8A5-9698 +E8A6-9695 +E8A7-9697 +E8A8-96AA +E8A9-96A7 +E8AA-96B1 +E8AB-96B2 +E8AC-96B0 +E8AD-96B4 +E8AE-96B6 +E8AF-96B8 +E8B0-96B9 +E8B1-96CE +E8B2-96CB +E8B3-96C9 +E8B4-96CD +E8B5-894D +E8B6-96DC +E8B7-970D +E8B8-96D5 +E8B9-96F9 +E8BA-9704 +E8BB-9706 +E8BC-9708 +E8BD-9713 +E8BE-970E +E8BF-9711 +E8C0-970F +E8C1-9716 +E8C2-9719 +E8C3-9724 +E8C4-972A +E8C5-9730 +E8C6-9739 +E8C7-973D +E8C8-973E +E8C9-9744 +E8CA-9746 +E8CB-9748 +E8CC-9742 +E8CD-9749 +E8CE-975C +E8CF-9760 +E8D0-9764 +E8D1-9766 +E8D2-9768 +E8D3-52D2 +E8D4-976B +E8D5-9771 +E8D6-9779 +E8D7-9785 +E8D8-977C +E8D9-9781 +E8DA-977A +E8DB-9786 +E8DC-978B +E8DD-978F +E8DE-9790 +E8DF-979C +E8E0-97A8 +E8E1-97A6 +E8E2-97A3 +E8E3-97B3 +E8E4-97B4 +E8E5-97C3 +E8E6-97C6 +E8E7-97C8 +E8E8-97CB +E8E9-97DC +E8EA-97ED +E8EB-9F4F +E8EC-97F2 +E8ED-7ADF +E8EE-97F6 +E8EF-97F5 +E8F0-980F +E8F1-980C +E8F2-9838 +E8F3-9824 +E8F4-9821 +E8F5-9837 +E8F6-983D +E8F7-9846 +E8F8-984F +E8F9-984B +E8FA-986B +E8FB-986F +E8FC-9870 +E940-9871 +E941-9874 +E942-9873 +E943-98AA +E944-98AF +E945-98B1 +E946-98B6 +E947-98C4 +E948-98C3 +E949-98C6 +E94A-98E9 +E94B-98EB +E94C-9903 +E94D-9909 +E94E-9912 +E94F-9914 +E950-9918 +E951-9921 +E952-991D +E953-991E +E954-9924 +E955-9920 +E956-992C +E957-992E +E958-993D +E959-993E +E95A-9942 +E95B-9949 +E95C-9945 +E95D-9950 +E95E-994B +E95F-9951 +E960-9952 +E961-994C +E962-9955 +E963-9997 +E964-9998 +E965-99A5 +E966-99AD +E967-99AE +E968-99BC +E969-99DF +E96A-99DB +E96B-99DD +E96C-99D8 +E96D-99D1 +E96E-99ED +E96F-99EE +E970-99F1 +E971-99F2 +E972-99FB +E973-99F8 +E974-9A01 +E975-9A0F +E976-9A05 +E977-99E2 +E978-9A19 +E979-9A2B +E97A-9A37 +E97B-9A45 +E97C-9A42 +E97D-9A40 +E97E-9A43 +E980-9A3E +E981-9A55 +E982-9A4D +E983-9A5B +E984-9A57 +E985-9A5F +E986-9A62 +E987-9A65 +E988-9A64 +E989-9A69 +E98A-9A6B +E98B-9A6A +E98C-9AAD +E98D-9AB0 +E98E-9ABC +E98F-9AC0 +E990-9ACF +E991-9AD1 +E992-9AD3 +E993-9AD4 +E994-9ADE +E995-9ADF +E996-9AE2 +E997-9AE3 +E998-9AE6 +E999-9AEF +E99A-9AEB +E99B-9AEE +E99C-9AF4 +E99D-9AF1 +E99E-9AF7 +E99F-9AFB +E9A0-9B06 +E9A1-9B18 +E9A2-9B1A +E9A3-9B1F +E9A4-9B22 +E9A5-9B23 +E9A6-9B25 +E9A7-9B27 +E9A8-9B28 +E9A9-9B29 +E9AA-9B2A +E9AB-9B2E +E9AC-9B2F +E9AD-9B32 +E9AE-9B44 +E9AF-9B43 +E9B0-9B4F +E9B1-9B4D +E9B2-9B4E +E9B3-9B51 +E9B4-9B58 +E9B5-9B74 +E9B6-9B93 +E9B7-9B83 +E9B8-9B91 +E9B9-9B96 +E9BA-9B97 +E9BB-9B9F +E9BC-9BA0 +E9BD-9BA8 +E9BE-9BB4 +E9BF-9BC0 +E9C0-9BCA +E9C1-9BB9 +E9C2-9BC6 +E9C3-9BCF +E9C4-9BD1 +E9C5-9BD2 +E9C6-9BE3 +E9C7-9BE2 +E9C8-9BE4 +E9C9-9BD4 +E9CA-9BE1 +E9CB-9C3A +E9CC-9BF2 +E9CD-9BF1 +E9CE-9BF0 +E9CF-9C15 +E9D0-9C14 +E9D1-9C09 +E9D2-9C13 +E9D3-9C0C +E9D4-9C06 +E9D5-9C08 +E9D6-9C12 +E9D7-9C0A +E9D8-9C04 +E9D9-9C2E +E9DA-9C1B +E9DB-9C25 +E9DC-9C24 +E9DD-9C21 +E9DE-9C30 +E9DF-9C47 +E9E0-9C32 +E9E1-9C46 +E9E2-9C3E +E9E3-9C5A +E9E4-9C60 +E9E5-9C67 +E9E6-9C76 +E9E7-9C78 +E9E8-9CE7 +E9E9-9CEC +E9EA-9CF0 +E9EB-9D09 +E9EC-9D08 +E9ED-9CEB +E9EE-9D03 +E9EF-9D06 +E9F0-9D2A +E9F1-9D26 +E9F2-9DAF +E9F3-9D23 +E9F4-9D1F +E9F5-9D44 +E9F6-9D15 +E9F7-9D12 +E9F8-9D41 +E9F9-9D3F +E9FA-9D3E +E9FB-9D46 +E9FC-9D48 +EA40-9D5D +EA41-9D5E +EA42-9D64 +EA43-9D51 +EA44-9D50 +EA45-9D59 +EA46-9D72 +EA47-9D89 +EA48-9D87 +EA49-9DAB +EA4A-9D6F +EA4B-9D7A +EA4C-9D9A +EA4D-9DA4 +EA4E-9DA9 +EA4F-9DB2 +EA50-9DC4 +EA51-9DC1 +EA52-9DBB +EA53-9DB8 +EA54-9DBA +EA55-9DC6 +EA56-9DCF +EA57-9DC2 +EA58-9DD9 +EA59-9DD3 +EA5A-9DF8 +EA5B-9DE6 +EA5C-9DED +EA5D-9DEF +EA5E-9DFD +EA5F-9E1A +EA60-9E1B +EA61-9E1E +EA62-9E75 +EA63-9E79 +EA64-9E7D +EA65-9E81 +EA66-9E88 +EA67-9E8B +EA68-9E8C +EA69-9E92 +EA6A-9E95 +EA6B-9E91 +EA6C-9E9D +EA6D-9EA5 +EA6E-9EA9 +EA6F-9EB8 +EA70-9EAA +EA71-9EAD +EA72-9761 +EA73-9ECC +EA74-9ECE +EA75-9ECF +EA76-9ED0 +EA77-9ED4 +EA78-9EDC +EA79-9EDE +EA7A-9EDD +EA7B-9EE0 +EA7C-9EE5 +EA7D-9EE8 +EA7E-9EEF +EA80-9EF4 +EA81-9EF6 +EA82-9EF7 +EA83-9EF9 +EA84-9EFB +EA85-9EFC +EA86-9EFD +EA87-9F07 +EA88-9F08 +EA89-76B7 +EA8A-9F15 +EA8B-9F21 +EA8C-9F2C +EA8D-9F3E +EA8E-9F4A +EA8F-9F52 +EA90-9F54 +EA91-9F63 +EA92-9F5F +EA93-9F60 +EA94-9F61 +EA95-9F66 +EA96-9F67 +EA97-9F6C +EA98-9F6A +EA99-9F77 +EA9A-9F72 +EA9B-9F76 +EA9C-9F95 +EA9D-9F9C +EA9E-9FA0 +EA9F-582F +EAA0-69C7 +EAA1-9059 +EAA2-7464 +EAA3-51DC +EAA4-7199 +ED40-7E8A-f +ED41-891C-f +ED42-9348-f +ED43-9288-f +ED44-84DC-f +ED45-4FC9-f +ED46-70BB-f +ED47-6631-f +ED48-68C8-f +ED49-92F9-f +ED4A-66FB-f +ED4B-5F45-f +ED4C-4E28-f +ED4D-4EE1-f +ED4E-4EFC-f +ED4F-4F00-f +ED50-4F03-f +ED51-4F39-f +ED52-4F56-f +ED53-4F92-f +ED54-4F8A-f +ED55-4F9A-f +ED56-4F94-f +ED57-4FCD-f +ED58-5040-f +ED59-5022-f +ED5A-4FFF-f +ED5B-501E-f +ED5C-5046-f +ED5D-5070-f +ED5E-5042-f +ED5F-5094-f +ED60-50F4-f +ED61-50D8-f +ED62-514A-f +ED63-5164-f +ED64-519D-f +ED65-51BE-f +ED66-51EC-f +ED67-5215-f +ED68-529C-f +ED69-52A6-f +ED6A-52C0-f +ED6B-52DB-f +ED6C-5300-f +ED6D-5307-f +ED6E-5324-f +ED6F-5372-f +ED70-5393-f +ED71-53B2-f +ED72-53DD-f +ED73-FA0E-f +ED74-549C-f +ED75-548A-f +ED76-54A9-f +ED77-54FF-f +ED78-5586-f +ED79-5759-f +ED7A-5765-f +ED7B-57AC-f +ED7C-57C8-f +ED7D-57C7-f +ED7E-FA0F-f +ED80-FA10-f +ED81-589E-f +ED82-58B2-f +ED83-590B-f +ED84-5953-f +ED85-595B-f +ED86-595D-f +ED87-5963-f +ED88-59A4-f +ED89-59BA-f +ED8A-5B56-f +ED8B-5BC0-f +ED8C-752F-f +ED8D-5BD8-f +ED8E-5BEC-f +ED8F-5C1E-f +ED90-5CA6-f +ED91-5CBA-f +ED92-5CF5-f +ED93-5D27-f +ED94-5D53-f +ED95-FA11-f +ED96-5D42-f +ED97-5D6D-f +ED98-5DB8-f +ED99-5DB9-f +ED9A-5DD0-f +ED9B-5F21-f +ED9C-5F34-f +ED9D-5F67-f +ED9E-5FB7-f +ED9F-5FDE-f +EDA0-605D-f +EDA1-6085-f +EDA2-608A-f +EDA3-60DE-f +EDA4-60D5-f +EDA5-6120-f +EDA6-60F2-f +EDA7-6111-f +EDA8-6137-f +EDA9-6130-f +EDAA-6198-f +EDAB-6213-f +EDAC-62A6-f +EDAD-63F5-f +EDAE-6460-f +EDAF-649D-f +EDB0-64CE-f +EDB1-654E-f +EDB2-6600-f +EDB3-6615-f +EDB4-663B-f +EDB5-6609-f +EDB6-662E-f +EDB7-661E-f +EDB8-6624-f +EDB9-6665-f +EDBA-6657-f +EDBB-6659-f +EDBC-FA12-f +EDBD-6673-f +EDBE-6699-f +EDBF-66A0-f +EDC0-66B2-f +EDC1-66BF-f +EDC2-66FA-f +EDC3-670E-f +EDC4-F929-f +EDC5-6766-f +EDC6-67BB-f +EDC7-6852-f +EDC8-67C0-f +EDC9-6801-f +EDCA-6844-f +EDCB-68CF-f +EDCC-FA13-f +EDCD-6968-f +EDCE-FA14-f +EDCF-6998-f +EDD0-69E2-f +EDD1-6A30-f +EDD2-6A6B-f +EDD3-6A46-f +EDD4-6A73-f +EDD5-6A7E-f +EDD6-6AE2-f +EDD7-6AE4-f +EDD8-6BD6-f +EDD9-6C3F-f +EDDA-6C5C-f +EDDB-6C86-f +EDDC-6C6F-f +EDDD-6CDA-f +EDDE-6D04-f +EDDF-6D87-f +EDE0-6D6F-f +EDE1-6D96-f +EDE2-6DAC-f +EDE3-6DCF-f +EDE4-6DF8-f +EDE5-6DF2-f +EDE6-6DFC-f +EDE7-6E39-f +EDE8-6E5C-f +EDE9-6E27-f +EDEA-6E3C-f +EDEB-6EBF-f +EDEC-6F88-f +EDED-6FB5-f +EDEE-6FF5-f +EDEF-7005-f +EDF0-7007-f +EDF1-7028-f +EDF2-7085-f +EDF3-70AB-f +EDF4-710F-f +EDF5-7104-f +EDF6-715C-f +EDF7-7146-f +EDF8-7147-f +EDF9-FA15-f +EDFA-71C1-f +EDFB-71FE-f +EDFC-72B1-f +EE40-72BE-f +EE41-7324-f +EE42-FA16-f +EE43-7377-f +EE44-73BD-f +EE45-73C9-f +EE46-73D6-f +EE47-73E3-f +EE48-73D2-f +EE49-7407-f +EE4A-73F5-f +EE4B-7426-f +EE4C-742A-f +EE4D-7429-f +EE4E-742E-f +EE4F-7462-f +EE50-7489-f +EE51-749F-f +EE52-7501-f +EE53-756F-f +EE54-7682-f +EE55-769C-f +EE56-769E-f +EE57-769B-f +EE58-76A6-f +EE59-FA17-f +EE5A-7746-f +EE5B-52AF-f +EE5C-7821-f +EE5D-784E-f +EE5E-7864-f +EE5F-787A-f +EE60-7930-f +EE61-FA18-f +EE62-FA19-f +EE63-FA1A-f +EE64-7994-f +EE65-FA1B-f +EE66-799B-f +EE67-7AD1-f +EE68-7AE7-f +EE69-FA1C-f +EE6A-7AEB-f +EE6B-7B9E-f +EE6C-FA1D-f +EE6D-7D48-f +EE6E-7D5C-f +EE6F-7DB7-f +EE70-7DA0-f +EE71-7DD6-f +EE72-7E52-f +EE73-7F47-f +EE74-7FA1-f +EE75-FA1E-f +EE76-8301-f +EE77-8362-f +EE78-837F-f +EE79-83C7-f +EE7A-83F6-f +EE7B-8448-f +EE7C-84B4-f +EE7D-8553-f +EE7E-8559-f +EE80-856B-f +EE81-FA1F-f +EE82-85B0-f +EE83-FA20-f +EE84-FA21-f +EE85-8807-f +EE86-88F5-f +EE87-8A12-f +EE88-8A37-f +EE89-8A79-f +EE8A-8AA7-f +EE8B-8ABE-f +EE8C-8ADF-f +EE8D-FA22-f +EE8E-8AF6-f +EE8F-8B53-f +EE90-8B7F-f +EE91-8CF0-f +EE92-8CF4-f +EE93-8D12-f +EE94-8D76-f +EE95-FA23-f +EE96-8ECF-f +EE97-FA24-f +EE98-FA25-f +EE99-9067-f +EE9A-90DE-f +EE9B-FA26-f +EE9C-9115-f +EE9D-9127-f +EE9E-91DA-f +EE9F-91D7-f +EEA0-91DE-f +EEA1-91ED-f +EEA2-91EE-f +EEA3-91E4-f +EEA4-91E5-f +EEA5-9206-f +EEA6-9210-f +EEA7-920A-f +EEA8-923A-f +EEA9-9240-f +EEAA-923C-f +EEAB-924E-f +EEAC-9259-f +EEAD-9251-f +EEAE-9239-f +EEAF-9267-f +EEB0-92A7-f +EEB1-9277-f +EEB2-9278-f +EEB3-92E7-f +EEB4-92D7-f +EEB5-92D9-f +EEB6-92D0-f +EEB7-FA27-f +EEB8-92D5-f +EEB9-92E0-f +EEBA-92D3-f +EEBB-9325-f +EEBC-9321-f +EEBD-92FB-f +EEBE-FA28-f +EEBF-931E-f +EEC0-92FF-f +EEC1-931D-f +EEC2-9302-f +EEC3-9370-f +EEC4-9357-f +EEC5-93A4-f +EEC6-93C6-f +EEC7-93DE-f +EEC8-93F8-f +EEC9-9431-f +EECA-9445-f +EECB-9448-f +EECC-9592-f +EECD-F9DC-f +EECE-FA29-f +EECF-969D-f +EED0-96AF-f +EED1-9733-f +EED2-973B-f +EED3-9743-f +EED4-974D-f +EED5-974F-f +EED6-9751-f +EED7-9755-f +EED8-9857-f +EED9-9865-f +EEDA-FA2A-f +EEDB-FA2B-f +EEDC-9927-f +EEDD-FA2C-f +EEDE-999E-f +EEDF-9A4E-f +EEE0-9AD9-f +EEE1-9ADC-f +EEE2-9B75-f +EEE3-9B72-f +EEE4-9B8F-f +EEE5-9BB1-f +EEE6-9BBB-f +EEE7-9C00-f +EEE8-9D70-f +EEE9-9D6B-f +EEEA-FA2D-f +EEEB-9E19-f +EEEC-9ED1-f +EEEF-2170-f +EEF0-2171-f +EEF1-2172-f +EEF2-2173-f +EEF3-2174-f +EEF4-2175-f +EEF5-2176-f +EEF6-2177-f +EEF7-2178-f +EEF8-2179-f +EEF9-FFE2-f +EEFA-FFE4-f +EEFB-FF07-f +EEFC-FF02-f +FA40-2170-t +FA41-2171-t +FA42-2172-t +FA43-2173-t +FA44-2174-t +FA45-2175-t +FA46-2176-t +FA47-2177-t +FA48-2178-t +FA49-2179-t +FA4A-2160-f +FA4B-2161-f +FA4C-2162-f +FA4D-2163-f +FA4E-2164-f +FA4F-2165-f +FA50-2166-f +FA51-2167-f +FA52-2168-f +FA53-2169-f +FA54-FFE2-f +FA55-FFE4-t +FA56-FF07-t +FA57-FF02-t +FA58-3231-f +FA59-2116-f +FA5A-2121-f +FA5B-2235-f +FA5C-7E8A-t +FA5D-891C-t +FA5E-9348-t +FA5F-9288-t +FA60-84DC-t +FA61-4FC9-t +FA62-70BB-t +FA63-6631-t +FA64-68C8-t +FA65-92F9-t +FA66-66FB-t +FA67-5F45-t +FA68-4E28-t +FA69-4EE1-t +FA6A-4EFC-t +FA6B-4F00-t +FA6C-4F03-t +FA6D-4F39-t +FA6E-4F56-t +FA6F-4F92-t +FA70-4F8A-t +FA71-4F9A-t +FA72-4F94-t +FA73-4FCD-t +FA74-5040-t +FA75-5022-t +FA76-4FFF-t +FA77-501E-t +FA78-5046-t +FA79-5070-t +FA7A-5042-t +FA7B-5094-t +FA7C-50F4-t +FA7D-50D8-t +FA7E-514A-t +FA80-5164-t +FA81-519D-t +FA82-51BE-t +FA83-51EC-t +FA84-5215-t +FA85-529C-t +FA86-52A6-t +FA87-52C0-t +FA88-52DB-t +FA89-5300-t +FA8A-5307-t +FA8B-5324-t +FA8C-5372-t +FA8D-5393-t +FA8E-53B2-t +FA8F-53DD-t +FA90-FA0E-t +FA91-549C-t +FA92-548A-t +FA93-54A9-t +FA94-54FF-t +FA95-5586-t +FA96-5759-t +FA97-5765-t +FA98-57AC-t +FA99-57C8-t +FA9A-57C7-t +FA9B-FA0F-t +FA9C-FA10-t +FA9D-589E-t +FA9E-58B2-t +FA9F-590B-t +FAA0-5953-t +FAA1-595B-t +FAA2-595D-t +FAA3-5963-t +FAA4-59A4-t +FAA5-59BA-t +FAA6-5B56-t +FAA7-5BC0-t +FAA8-752F-t +FAA9-5BD8-t +FAAA-5BEC-t +FAAB-5C1E-t +FAAC-5CA6-t +FAAD-5CBA-t +FAAE-5CF5-t +FAAF-5D27-t +FAB0-5D53-t +FAB1-FA11-t +FAB2-5D42-t +FAB3-5D6D-t +FAB4-5DB8-t +FAB5-5DB9-t +FAB6-5DD0-t +FAB7-5F21-t +FAB8-5F34-t +FAB9-5F67-t +FABA-5FB7-t +FABB-5FDE-t +FABC-605D-t +FABD-6085-t +FABE-608A-t +FABF-60DE-t +FAC0-60D5-t +FAC1-6120-t +FAC2-60F2-t +FAC3-6111-t +FAC4-6137-t +FAC5-6130-t +FAC6-6198-t +FAC7-6213-t +FAC8-62A6-t +FAC9-63F5-t +FACA-6460-t +FACB-649D-t +FACC-64CE-t +FACD-654E-t +FACE-6600-t +FACF-6615-t +FAD0-663B-t +FAD1-6609-t +FAD2-662E-t +FAD3-661E-t +FAD4-6624-t +FAD5-6665-t +FAD6-6657-t +FAD7-6659-t +FAD8-FA12-t +FAD9-6673-t +FADA-6699-t +FADB-66A0-t +FADC-66B2-t +FADD-66BF-t +FADE-66FA-t +FADF-670E-t +FAE0-F929-t +FAE1-6766-t +FAE2-67BB-t +FAE3-6852-t +FAE4-67C0-t +FAE5-6801-t +FAE6-6844-t +FAE7-68CF-t +FAE8-FA13-t +FAE9-6968-t +FAEA-FA14-t +FAEB-6998-t +FAEC-69E2-t +FAED-6A30-t +FAEE-6A6B-t +FAEF-6A46-t +FAF0-6A73-t +FAF1-6A7E-t +FAF2-6AE2-t +FAF3-6AE4-t +FAF4-6BD6-t +FAF5-6C3F-t +FAF6-6C5C-t +FAF7-6C86-t +FAF8-6C6F-t +FAF9-6CDA-t +FAFA-6D04-t +FAFB-6D87-t +FAFC-6D6F-t +FB40-6D96-t +FB41-6DAC-t +FB42-6DCF-t +FB43-6DF8-t +FB44-6DF2-t +FB45-6DFC-t +FB46-6E39-t +FB47-6E5C-t +FB48-6E27-t +FB49-6E3C-t +FB4A-6EBF-t +FB4B-6F88-t +FB4C-6FB5-t +FB4D-6FF5-t +FB4E-7005-t +FB4F-7007-t +FB50-7028-t +FB51-7085-t +FB52-70AB-t +FB53-710F-t +FB54-7104-t +FB55-715C-t +FB56-7146-t +FB57-7147-t +FB58-FA15-t +FB59-71C1-t +FB5A-71FE-t +FB5B-72B1-t +FB5C-72BE-t +FB5D-7324-t +FB5E-FA16-t +FB5F-7377-t +FB60-73BD-t +FB61-73C9-t +FB62-73D6-t +FB63-73E3-t +FB64-73D2-t +FB65-7407-t +FB66-73F5-t +FB67-7426-t +FB68-742A-t +FB69-7429-t +FB6A-742E-t +FB6B-7462-t +FB6C-7489-t +FB6D-749F-t +FB6E-7501-t +FB6F-756F-t +FB70-7682-t +FB71-769C-t +FB72-769E-t +FB73-769B-t +FB74-76A6-t +FB75-FA17-t +FB76-7746-t +FB77-52AF-t +FB78-7821-t +FB79-784E-t +FB7A-7864-t +FB7B-787A-t +FB7C-7930-t +FB7D-FA18-t +FB7E-FA19-t +FB80-FA1A-t +FB81-7994-t +FB82-FA1B-t +FB83-799B-t +FB84-7AD1-t +FB85-7AE7-t +FB86-FA1C-t +FB87-7AEB-t +FB88-7B9E-t +FB89-FA1D-t +FB8A-7D48-t +FB8B-7D5C-t +FB8C-7DB7-t +FB8D-7DA0-t +FB8E-7DD6-t +FB8F-7E52-t +FB90-7F47-t +FB91-7FA1-t +FB92-FA1E-t +FB93-8301-t +FB94-8362-t +FB95-837F-t +FB96-83C7-t +FB97-83F6-t +FB98-8448-t +FB99-84B4-t +FB9A-8553-t +FB9B-8559-t +FB9C-856B-t +FB9D-FA1F-t +FB9E-85B0-t +FB9F-FA20-t +FBA0-FA21-t +FBA1-8807-t +FBA2-88F5-t +FBA3-8A12-t +FBA4-8A37-t +FBA5-8A79-t +FBA6-8AA7-t +FBA7-8ABE-t +FBA8-8ADF-t +FBA9-FA22-t +FBAA-8AF6-t +FBAB-8B53-t +FBAC-8B7F-t +FBAD-8CF0-t +FBAE-8CF4-t +FBAF-8D12-t +FBB0-8D76-t +FBB1-FA23-t +FBB2-8ECF-t +FBB3-FA24-t +FBB4-FA25-t +FBB5-9067-t +FBB6-90DE-t +FBB7-FA26-t +FBB8-9115-t +FBB9-9127-t +FBBA-91DA-t +FBBB-91D7-t +FBBC-91DE-t +FBBD-91ED-t +FBBE-91EE-t +FBBF-91E4-t +FBC0-91E5-t +FBC1-9206-t +FBC2-9210-t +FBC3-920A-t +FBC4-923A-t +FBC5-9240-t +FBC6-923C-t +FBC7-924E-t +FBC8-9259-t +FBC9-9251-t +FBCA-9239-t +FBCB-9267-t +FBCC-92A7-t +FBCD-9277-t +FBCE-9278-t +FBCF-92E7-t +FBD0-92D7-t +FBD1-92D9-t +FBD2-92D0-t +FBD3-FA27-t +FBD4-92D5-t +FBD5-92E0-t +FBD6-92D3-t +FBD7-9325-t +FBD8-9321-t +FBD9-92FB-t +FBDA-FA28-t +FBDB-931E-t +FBDC-92FF-t +FBDD-931D-t +FBDE-9302-t +FBDF-9370-t +FBE0-9357-t +FBE1-93A4-t +FBE2-93C6-t +FBE3-93DE-t +FBE4-93F8-t +FBE5-9431-t +FBE6-9445-t +FBE7-9448-t +FBE8-9592-t +FBE9-F9DC-t +FBEA-FA29-t +FBEB-969D-t +FBEC-96AF-t +FBED-9733-t +FBEE-973B-t +FBEF-9743-t +FBF0-974D-t +FBF1-974F-t +FBF2-9751-t +FBF3-9755-t +FBF4-9857-t +FBF5-9865-t +FBF6-FA2A-t +FBF7-FA2B-t +FBF8-9927-t +FBF9-FA2C-t +FBFA-999E-t +FBFB-9A4E-t +FBFC-9AD9-t +FC40-9ADC-t +FC41-9B75-t +FC42-9B72-t +FC43-9B8F-t +FC44-9BB1-t +FC45-9BBB-t +FC46-9C00-t +FC47-9D70-t +FC48-9D6B-t +FC49-FA2D-t +FC4A-9E19-t +FC4B-9ED1-t +/){ + my @t = split /-/, $_, 3; + $t[0] = hex $t[0]; + $t[1] = hex $t[1]; + my $cp932 = $t[0] > 0xFF ? pack('n', $t[0]) : chr($t[0]); + my $utf16 = pack('v',$t[1]); # no char > 0x10000. + my $utf8 = toUTF8($t[1]); + if(!$t[2] or $t[2] eq 't'){ + $CP932_UTF8{$cp932} = $utf8; + $CP932_UTF16{$cp932} = $utf16; + $UTF8_CP932{$utf8} = $UTF16_CP932{$utf16} = $cp932; + } else { + $CP932_UTF8{$cp932} = $utf8; + $CP932_UTF16{$cp932} = $utf16; + } +} + +$UTF16_CP932{"\xFF\xFE"} = ''; + +sub cp932_to_utf8 { + my $coderef; + if(ref $_[0] eq 'CODE'){ $coderef = shift } + my $str = shift; + defined $coderef + ? $str =~ s/($Schar)/ + exists $CP932_UTF8{$1} ? $CP932_UTF8{$1} : &$coderef($1) + /geo + : $str =~ s/($Schar)/$CP932_UTF8{$1}/go; + return $str; +} + +sub utf8_to_cp932 { + my $coderef; + if(ref $_[0] eq 'CODE'){ $coderef = shift } + my $str = shift; + defined $coderef + ? $str =~ s/($Uchar)/ + exists $UTF8_CP932{$1} ? $UTF8_CP932{$1} : &$coderef(fromUTF8($1)) + /geo + : $str =~ s/($Uchar)/$UTF8_CP932{$1}/go; + return $str; +} + +sub cp932_to_utf16 { + my $coderef; + if(ref $_[0] eq 'CODE'){ $coderef = shift } + my $str = shift; + defined $coderef + ? $str =~ s/($Schar)/ + exists $CP932_UTF16{$1} ? $CP932_UTF16{$1} : &$coderef($1) + /geo + : $str =~ s/($Schar)/$CP932_UTF16{$1}/go; + return $str; +} + +sub utf16_to_cp932 { + my $coderef; + if(ref $_[0] eq 'CODE'){ $coderef = shift } + my $str = shift; + defined $coderef + ? $str =~ s/($Hchar)/ + exists $UTF16_CP932{$1} ? $UTF16_CP932{$1} : &$coderef(fromUTF16($1)) + /geo + : $str =~ s/($Hchar)/$UTF16_CP932{$1}/go; + return $str; +} + +1; +__END__ + +=head1 NAME + +ShiftJIS::CP932::MapUTF - Conversion between CP-932 and Unicode + +=head1 SYNOPSIS + + use ShiftJIS::CP932::MapUTF; + + $utf8_string = cp932_to_utf8($cp932_string); + +=head1 DESCRIPTION + +The Microsoft CodePage 932 (CP-932) table comprises 7915 characters: + + JIS X 0201-1976 single-byte characters (191 characters), + JIS X 0208-1990 double-byte characters (6879 characters), + NEC special characters (83 characters from SJIS row 13), + NEC-selected IBM extended characters (374 characters from SJIS row 89 to 92), + and IBM extended characters (388 characters from SJIS row 115 to 119). + +It contains duplicates that do not round trip +map. These duplicates are due to the characters defined +by vendors, NEC and IBM. + +For example, there are two characters that are mapped to U+2252, +namely, 0x81e0 (JIS X 0208) and 0x8790 (NEC special character). + +This module provides some functions to maps +from CP-932 to Unicode, and vice versa. + +=head2 FUNCTIONS EXPORTED BY DEFAULT + +=over 4 + +=item C + +=item C + +Converts CP-932 to UTF-8. + +For example, converts C<\x81\xe0> or C<\x87\x90> to C +in the UTF-8 encoding. + +If C is not specified, characters that aren't mapped to Unicode +are deleted. +If C is specified, characters that aren't mapped to Unicode +are converted using C from the CP-932 character string. + +For example, converts C<\x82\xf2> to C, HIRAGANA LETTER VU, +in the UTF-8 encoding. + + cp932_to_utf8( + sub { $_[0] eq "\x82\xf2" ? toUTF8(0x3094) : "" }, + $cp932_string); + +=item C + +=item C + +Converts CP-932 to UTF-16LE. + +For example, converts C<\x81\xe0> or C<\x87\x90> to C +in the UTF-16LE encoding. + +If C is not specified, characters that aren't mapped to Unicode +are deleted. +If C is specified, characters that aren't mapped to Unicode +are converted using C from the CP-932 character string. + +For example, converts C<\x82\xf2> to C, HIRAGANA LETTER VU, +in the UTF-16LE encoding. + + cp932_to_utf16( + sub { $_[0] eq "\x82\xf2" ? toUTF16(0x3094) : "" }, + $cp932_string); + +=item C + +=item C + +Converts UTF-8 to CP-932 (normalized). + +For example, C in the UTF-8 encoding is converted +to C<\x81\xe0>, not to C<\x87\x90>. + +If C is not specified, characters that aren't mapped to CP-932 +are deleted. +If C is specified, characters that aren't mapped to CP-932 +are converted using C from its Unicode code point (integer). + +For example, characters that aren't mapped to CP-932 are +converted to numerical character references for HTML 4.01. + + utf8_to_cp932(sub {sprintf "&#x%04x;", shift}, $utf8_string); + +=item C + +=item C + +Converts C to C (normalized). + +For example, C in the C encoding is converted +to C<\x81\xe0>, not to C<\x87\x90>. + +If C is not specified, characters that aren't mapped to CP-932 +are deleted. +If C is specified, characters that aren't mapped to CP-932 +are converted using C from its Unicode code point (integer). + +For example, characters that aren't mapped to CP-932 are +converted to numerical character references for HTML 4.01. + + utf16_to_cp932(sub {sprintf "&#x%04x;", shift}, $utf16LE_string); + +=back + +=head2 FUNCTIONS EXPORTED ON REQUEST + +=over 4 + +=item C + +Returns a utf-8 string from codepoint integer. + +Valid unicode codepoints are C<0> to C<0x10ffff>, +excepting C<0xD800> to C<0xDFFF>. + +If an invalid codepoint is passed in, croaked. + +=item C + +Returns a utf-16-le string from codepoint integer. + +Valid unicode codepoints are C<0> to C<0x10ffff>, +excepting C<0xD800> to C<0xDFFF>. + +If an invalid codepoint is passed in, croaked. + +=item C + +Returns the codepoint from a utf-8 string corresponding to B. + +If invalid, croaked. + +=item C + +Returns the codepoint from a utf-16-le string corresponding to B. + +If invalid, croaked. + +=back + +=head1 CAVEAT + +This module doesn't understand any logically wide characters (see perlunicode). +Use utf8::decode/utf8::encode (see utf8) on Perl 5.7 or later if necessary. + +=head1 AUTHOR + +Tomoyuki SADAHIRO + + bqw10602@nifty.com + http://homepage1.nifty.com/nomenclator/perl/ + + Copyright(C) 2001, SADAHIRO Tomoyuki. Japan. All rights reserved. + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=head1 SEE ALSO + +=over 4 + +=item 1 + +Microsoft PRB: Conversion Problem Between Shift-JIS and Unicode +(Article ID: Q170559) + +=item 2 + +Unicode.org + +ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT + +=item 3 + +L + +=back + +=cut diff --git a/WebAdmin/WebAdminHtml/access_log.html b/WebAdmin/WebAdminHtml/access_log.html new file mode 100644 index 0000000..118f04a --- /dev/null +++ b/WebAdmin/WebAdminHtml/access_log.html @@ -0,0 +1,76 @@ + + + + + + + + + + +
+ + + + +
+ + +
+ + + + + + + +
+ +
+ +
+
+
+ + + + +
+ + + + +
+ +
+ + + + + + + + + +
+ + + +
+ + + +
+
+
+ + + + diff --git a/WebAdmin/WebAdminHtml/adminprotocol-lib.pl b/WebAdmin/WebAdminHtml/adminprotocol-lib.pl new file mode 100644 index 0000000..9375855 --- /dev/null +++ b/WebAdmin/WebAdminHtml/adminprotocol-lib.pl @@ -0,0 +1,1770 @@ +# adminprotocol-lib.pl +# Common functions for talking to the admin module of QTSS +#---------------------------------------------------------- +# +# @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@ +# +# +#--------------------------------------------------------- + +require ('playlist-lib.pl'); + +package adminprotolib; + +# Vital libraries +#use IO::Socket; +use Socket; + +@weekdayStr = ( "SunStr", "MonStr", "TueStr", "WedStr", "ThuStr", "FriStr", "SatStr" ); +@monthStr = ( "JanStr", "FebStr", "MarStr", "AprStr", "MayStr", "JunStr", "JulStr", "AugStr", "SepStr", "OctStr", "NovStr", "DecStr" ); + +$enMessageHash = $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"}; +$deMessageHash = $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"}; +$jaMessageHash = $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"}; +$frMessageHash = $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"}; + +# GetMessageHash() +# Returns the messages hash given the language +sub GetMessageHash +{ + return $ENV{"QTSSADMINSERVER_EN_MESSAGEHASH"}; +} + +# GetGenreArray() +# Returns the messages hash given the language +sub GetGenreArray +{ + my $lang = $ENV{"LANGUAGE"}; + @genreArray = (); + my $genreFilename = $ENV{'SERVER_ROOT'} . "/html_en/" . $ENV{"GENREFILE"}; + + open(GENRES, $genreFilename) or die "Couldn't find the $lang genre file $genreFilename!"; + while($genreLine = ) { + $genreLine =~ s/[\r\n]//g; + push(@genreArray, $genreLine); + } + + return \@genreArray; +} + +my $confPath = $ENV{"QTSSADMINSERVER_CONFIG"}; + +# GetData(data, messageHash, authheader, serverName, port, uri) +# Does an HTTP GET to a server and puts the body in a scalar variable +# Returns the status code of the response from the server +sub GetData +{ + my ($messHash, $authheader, $remote,$port, $iaddr, $paddr, $proto, $uri); + $messHash = $_[1]; + $authheader = $_[2]; + $remote = $_[3]; + $port = $_[4]; + $uri = $_[5]; + + my %messages = %$messHash; + + my $status = 500; + if(!($iaddr = inet_aton($remote))) { + $_[0] = "$messages{'NoHostError'}: $remote"; + return $status; + } + $paddr = sockaddr_in($port, $iaddr); + $proto = getprotobyname('tcp'); + if(!socket(CLIENT_SOCK, PF_INET, SOCK_STREAM, $proto)) { + $_[0] = "$messages{'SocketFailedError'}: $!"; + return $status; + } + if(!connect(CLIENT_SOCK, $paddr)) { + $_[0] = "$messages{'ConnectFailedError'}: $!"; + close (CLIENT_SOCK); + return $status; + } + + # send request + $request = "GET $uri HTTP/1.1\r\nUser-Agent: PerlScript\r\nAccept: */*\r\nConnection: close\r\n" . "$authheader\r\n"; + my $bytesSent = 0; + while($bytesSent < length($request)) { + $partOfRequest = substr($request, $bytesSent); + if(!($bytes = send(CLIENT_SOCK, $partOfRequest, 0))) { + $_[0] = "$messages{'SendFailedError'}: $!"; + close (CLIENT_SOCK); + return $status; + } + $bytesSent += $bytes; + } + + # read response + my $partOfResponse; + $response = ""; + while(1) { + + $partOfResponse = ""; + #if($^O eq "MSWin32") { + # ($servipaddr = recv(CLIENT_SOCK, $partOfResponse, 1024, 0)) || last; + #} + #else { + ($numBytesRead = read(CLIENT_SOCK, $partOfResponse, 1024)) || last; + #} + $response .= $partOfResponse; + } + + # read response headers + my @lines = split /\n/m, $response; + my $line = shift @lines; + + # Check the status code of the response + if ($line =~ m/^(\S*?)(\s)(.*?)(\s)(\S*?)(\s*)$/) { + $status = $3; + } + + # Go through the rest of the headers + while(@lines) { + $line = shift @lines; + if ($line =~ m/^\s*$/) { last; } + if($line =~ /^(\S+):\s+(.*)$/) { + if(lc($1) eq "www-authenticate") { + $challenge = $line; + } + } + } + + # Read the response body + if ($status == 200) { + $responseText = ""; + while(@lines) { + $line = shift @lines; + $responseText .= "$line\n"; + } + $_[0] = $responseText; + } + elsif ($status == 401) { + $_[0] = $challenge; + } + + close (CLIENT_SOCK); + return $status; +} + +# EchoData(data, messageHash, authheader, serverName, port, uri, param) +# Uses GetData to fetch the uri and parses the value in param=value +# into a scalar variable. +# Returns the value as a scalar +sub EchoData { + my $messHash = $_[1]; + my $authheader = $_[2]; + my $serverName = $_[3]; + my $serverPort = $_[4]; + my $uri = $_[5]; + my $param = $_[6]; + my $responseText = ""; + my $status = GetData($responseText, $messHash, $authheader, $serverName, $serverPort, $uri); + if($status != 200) { + $_[0] = $responseText; + return $status; + } + + my $paramName = ""; + my $paramValue = ""; + if ($param =~ m/^(.*)\/(\w+)$/) { + $paramName = $2; + } + else { + $paramName = $param; + } + + my @lines = split /\n/, $responseText; + my $line; + + while(@lines) { + $line = shift @lines; + if($line =~ m/^$paramName=\"(.*)\"(\s*)$/) { + $paramValue = $1; + } + } + if($paramValue eq "") { + undef($paramValue); + } + $_[0] = $paramValue; + return $status; +} + +# GetMovieDir(dirname, messageHash, authheader, serverName, port) +# Uses GetData to fetch the location of the Movies +# directory from the QTSS server. +# Returns the value as a scalar in the first parameter. +# Also returns an error code. +sub GetMovieDir { + my $messHash = $_[1]; + my $authheader = $_[2]; + my $server = $_[3]; + my $port = $_[4]; + my $uri = "/modules/admin/server/qtssSvrPreferences/movie_folder"; + my $param = "/server/qtssSvrPreferences/movie_folder"; + my $dirname = ""; + my $status = EchoData($dirname, $messHash, $authheader, $server, $port, $uri, $param); + + if ($status eq "401") { + $dirname = "Authorization_Failure"; + } + elsif ($dirname eq "") { + if ($^O =~/[Dd]arwin/) { + $dirname = "/Library/QuickTimeStreaming/Movies"; + } + elsif ($^O eq "MSWin32") { + $dirname = "c:\\Program Files\\Darwin Streaming Server\\Movies"; + } + else { + $dirname = "/usr/local/movies/"; + } + } + $_[0] = $dirname; + return $status; +} + +# MakeArray(text, name, [size]) +# Parses the scalar text for the container name and +# returns an array of all the values. If size is given +# it looks for size number of elements else it finds all +sub MakeArray +{ + my $text = $_[0]; + my $name = $_[1]; + my $size = 0; + my $count = 0; + + if($_[3]) { $size = $_[3]; } + + my @lines = split /\r|\n/, $text; + my $line; + my @arr; + $#arr = $size - 1; # pre-grow the array if we know its size + + while(@lines) { + $line = shift @lines; +# if($line =~ m/^Container=\"(.*)$name\"(\s*)$/) { + if($line =~ m/^Container=\"(.*)\"$/) { + while(1) { + $line = shift @lines; + if($line =~ m/^$count=\"(.*)\"(\s*)$/) { + $arr[$count] = $1; + $count++; + if(($size != 0) && ($size == $count)) { last; } + } + else { last; } + } + last; + } + elsif($line =~ m/^(.*?)=\"(.*?)\"/) { + $arr[0] = $2; + last; + } + } + return \@arr; +} + + +#sub FormatArray (\@arrName, beginIndex, endIndex, prefix, suffix) +# Formats the elements of the array by applying the +# prefix and the suffix to each element in the array +# returns the formatted text +sub FormatArray { + my $arRef = $_[0]; + my @arr = @$arRef; + my $index = $_[1]; + my $endIndex = ($_[2] == -1) ? $#arr : $_[2]; + my $prefix = $_[3]; + my $suffix = $_[4]; + + local $responseText= ""; + for($index; $index <= $endIndex; $index++) { + $responseText .= $prefix.$arr[$index].$suffix; + } + return scalar $responseText; +} + +# sub HasValue (\@arrOfHash, value, ["num" | alpha"]) +# Returns 1 if the array contains the value, 0 otherwise. +sub HasValue { + my $arRef = $_[0]; + my @arr = @$arRef; + my $value = $_[1]; + my $type = $_[2]; + + if($type eq "num") { + for($i = 0; $i <= $#arr; $i++) { + if($arr[$i] == $value) { + return 1; + } + } + } + elsif($type eq "alpha") { + for($i = 0; $i <= $#arr; $i++) { + if($arr[$i] eq $value) { + return 1; + } + } + } + return 0; +} + +# sub SetAttribute (data, messageHash, authheader, server, port, fullpath, value, [type]) +# Sends an admin protocol set command and returns the error value +sub SetAttribute { + # get the value + $valueStr = $_[6]; + # encode value as percent-escaped string + $valueStr =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; + + my $uri = "/modules".$_[5]."?command=set+value="."\"$valueStr\""; + my $code = 400; + if($_[7]) { + $uri .= "+type=$_[7]"; + } + my $data = ""; + $status = GetData($data, $_[1], $_[2], $_[3], $_[4], $uri); + if($status == 200) { + if($data =~ m/^error:\((.*)\)/) { + $code = $1; + } + } + else { + $code = $status; + $_[0] = $data; + } + return $code; +} + +# sub SetPassword (data, messageHash, authheader, server, port, qtssUsersFileAttr, value, qtssPasswdName, qtssAdmin) +# Sends an admin protocol set command for the admin password and returns the error value +sub SetPassword { + my $code = 200; + my $data = ""; + my $password = ""; + + if($_[6] eq ""){ + $code = 200; + $_[0] = "No password given"; + return; + } + + #if($_[6] !~ /^[a-zA-Z_0-9\t\r\n\f]+$/) + #{ + # # the password contains other than alphanumeric ascii characters + # $code = 500; + # $_[0] = "The password contains other than alphanumeric ascii characters."; + # return $code; + #} + + #if($_[6] =~ /\s+/) { + # $password = qq("). $_[6] . qq("); + #} + #else { + # $password = $_[6]; + #} + + #Get the name of the default users file from QTSS + # that's where the current username:password record is + my $uri = "/modules/admin". $_[5]; + my $status = EchoData($data, $_[1], $_[2], $_[3], $_[4], $uri, $_[5]); + + if ($status != 200) { + $code = $status; + $_[0] = $data; + } + else { + if ($^O eq "MSWin32") + { + # for windows, we need to use double quotes around the args and not single quotes + $programArgs = "\"$_[7]\" -f \"$data\" -p \"$_[6]\" \"$_[8]\""; + } + else + { + # for macosx and other unixes, we need to use single quotes around the args + $programArgs = "\"$_[7]\" -f \"$data\" -p \'$_[6]\' \'$_[8]\'"; + } + + if($^O ne "MSWin32") { + if(system($programArgs) == 0) { + $code = 200; + } + else { + $code = 500; + $_[0] = "Error running password application."; + return $code; + } + $_[0] = ""; + } + else { + $progName = qq($_[7]); + eval "require Win32::Process"; + if(!$@) { + Win32::Process::Create( + $processObj, + $progName, + $programArgs, + 1, + DETACHED_PROCESS, + ".") || return $code; + + $processObj->SetPriorityClass(NORMAL_PRIORITY_CLASS); + $processObj->Wait(0); + sleep(2); + $_[0] = "Password Set"; + $code = 200; + } + } + } + return $code; +} + +# Runs qtpasswd to delete the username record from the users file +# DeleteUsername( outputresultstring, messagesHash, authHeader, QTSSIP, QTSSport, UsersFileAttribute, QTPasswdpath, oldUsername) +sub DeleteUsername { + my $code = 200; + my $data = ""; + my $password = ""; + + if($_[7] eq ""){ + $code = 200; + $_[0] = "No username given"; + return; + } + + #Get the name of the default users file from QTSS + # that's where the current username:password record is + my $uri = "/modules/admin". $_[5]; + my $status = EchoData($data, $_[1], $_[2], $_[3], $_[4], $uri, $_[5]); + + if ($status != 200) { + $code = $status; + $_[0] = $data; + } + else { + # commandWithArgs is used on macosx and other unixes + # so enclose args in single quotes + my $commandWithArgs = "\"$_[6]\" -f \"$data\" -F -d \'$_[7]\'"; + + # args is used on windows + # so enclose args in double quotes + my $args = "-f \"$data\" -F -d \"$_[7]\""; + if($^O ne "MSWin32") { + if(system($commandWithArgs) == 0) { + $code = 200; + } + else { + $code = 500; + $_[0] = "Error running password application."; + return $code; + } + $_[0] = ""; + } + else { + &playlistlib::LaunchWin32Process($_[6], "\"$_[6]\"", $args, 1); + sleep(2); + $_[0] = "Username deleted"; + $code = 200; + } + } + return $code; +} + +# sub AddValueToAttribute (data, messageHash, authheader, server, port, fullpath, value) +sub AddValueToAttribute { + my $uri = "/modules".$_[5]."?command=add+value="."\"$_[6]\""; + my $code = 0; + my $data = ""; + $status = GetData($data, $_[1], $_[2], $_[3], $_[4], $uri); + if($status == 200) { + if($data =~ m/^error:\((.*?)\)$/) { + $code = $1; + } + } + else { + $code = $status; + $_[0] = $data; + } + return $code; +} + +# sub DeleteValueFromAttribute (data, messageHash, authheader, server, port, fullpath, value) +sub DeleteValueFromAttribute { + my $server = $_[3]; + my $port = $_[4]; + my $fullpath = $_[5]; + my $value = $_[6]; + my $code = 0; + my $data = ""; + my $status = GetData($data, $_[1], $_[2], $server, $port, "/modules".$fullpath."/*"); + if($status != 200) { + $code = $status; + $_[0] = $data; + return $code; + } + my $arRef = MakeArray($data, $fullpath."/"); + my @arr = @$arRef; + my $index = -1; + for($i = 0; $i <= $#arr; $i++) { + if($arr[$i] eq $value) { + $index = $i; + last; + } + } + if($index != -1) { + my $uri = "/modules".$fullpath."/$index"."?command=del"; + $data = ""; + $status = GetData($data, $_[1], $_[2], $server, $port, $uri); + if($status == 200) { + if($data =~ m/^error:\((.*?)\)$/) { + $code = $1; + } + } + else { + $code = $status; + $_[0] = $data; + } + } + return $code; +} + +# sub ParseFile (data, authheader, server, port, filename, [func], [param], [value]....) +# Parses the file for all the server side includes and processes them +# returns the processed file data in a scalar +# The multiple sets of arguments func, param, and value are for taking +# some input in the cgi for some server side includes +# return values: 200 - parsing okay and qtss returned values +# data returned is the output +# 401 - parsing okay but qtss returned authorization failed +# data returned is the auth challenge http headers +# 500 - qtss is not responding +# data - must be empty +sub ParseFile { + my $authheader = $_[1]; + my $server = $_[2]; + my $port = $_[3]; + my $filename = $_[4]; + my %funcparam; + my $fkey; + my $fvalue; + for($i = 5; $i <= $#_; $i = $i + 3) { + $fkey = $_[$i] . ":" . $_[$i+1]; + $fvalue = $_[$i+2]; + $funcparam{$fkey} = $fvalue; + } + + my $messHash = GetMessageHash(); + my %messages = %$messHash; + + local (*TEMPFILE, $_); + # Open the file + if(!open(TEMPFILE, $filename)) { + $_[0] = "$messages{'FileOpenError'} $filename: $!\n"; + return; + } + # Read the entire file into a buffer and close file handle + read(TEMPFILE, $_, -s $filename); + close(TEMPFILE); + my %varHash = (); + my $data = ""; + my $status; + + # Look for <%% Func param%%> tags + while(/^(.*?)<%%(.*?)%%>(.*)$/s) { + $_[0] .= $1; + $_ = $3; + $tag = $2; + if($tag =~ m/^ECHODATA\s+(.*)/s) { + @params = split /\s+/, $1; + $uri = "/modules/admin".$params[0]; + $data = ""; + $status = EchoData($data, $messHash, $authheader, $server, $port, $uri, $params[0]); + if($status == 401) { + $_[0] = $data; + return $status; + } + elsif($status == 500) { + $data = ""; + } + $_[0] .= $data; + } + elsif($tag =~ m/^GETDATA\s+(.*)/s) { + #storing the retrieved text in a variable hashed to the name + @params = split /\s+/, $1; + $uri = "/modules/admin".$params[1]; + $data = ""; + $status = GetData($data, $messHash, $authheader, $server, $port, $uri); + if($status == 401) { + $_[0] = $data; + return $status; + } + elsif($status == 500) { + undef($data); + } + $varHash{$params[0]} = $data; + } + elsif($tag =~ m/^GETVALUE\s+(.*)/s) { + #extract the value from the retreived text and store in a variable for later use + @params = split /\s+/, $1; + $uri = "/modules/admin".$params[1]; + $data = ""; + $status = EchoData($data, $messHash, $authheader, $server, $port, $uri, $params[1]); + if($status == 401) { + $_[0] = $data; + return $status; + } + elsif($status == 500) { + undef($data); + } + $varHash{$params[0]} = $data; + } + elsif($tag =~ m/^MAKEARRAY\s+(.*)/s) { + #storing the array reference returned hashed to the name + @params = split /\s+/, $1; + if(defined($varHash{$params[2]})) { + $arRef = MakeArray($varHash{$params[2]}, $params[1]); + $varHash{$params[0]} = $arRef; + } + else { + undef($varHash{$params[0]}); + } + } + elsif($tag =~ m/^HASVALUE\s+(.*)/s) { + #returning 1 if the value exists in the array, 0 otherwise + @params = split /\s+/, $1; + $params[2] = ($params[2] =~ m/^\'(.*)\'/) ? $1 : $params[2]; + if(defined($varHash{$params[1]})) { + $found = HasValue($varHash{$params[1]}, $params[2], $params[3]); + $varHash{$params[0]} = $found; + } + else { + undef($varHash{$params[0]}); + } + } + elsif($tag =~ m/^IFVALUEEQUALS\s+(.*)/s) { + #returning 1 if the value exists in the array, 0 otherwise + @params = split /\s+/, $1; + $params[2] = ($params[2] =~ m/^\'(.*)\'/) ? $1 : $params[2]; + if(!defined($varHash{$params[1]})) { + undef($found); + } + elsif($varHash{$params[1]} eq $params[2]) { + $found = 1; + } + else { $found = 0; } + $varHash{$params[0]} = $found; + } + elsif($tag =~ m/^CONVERTTOLOCALTIME\s+(\S+)/) { + my $timeval = $varHash{$1}; + if(!defined($timeval)) { + $_[0] .= ""; + } + else { + my @tm = localtime($timeval/1000); + my $lang = $ENV{"LANGUAGE"}; + if($lang eq "de") { + $_[0] .= sprintf "%s, %d %s %d %2.2d:%2.2d:%2.2d", + $messages{$weekdayStr[$tm[6]]}, $tm[3], $messages{$monthStr[$tm[4]]}, $tm[5]+1900, + $tm[2], $tm[1], $tm[0]; + } + elsif($lang eq "ja") { + $_[0] .= sprintf "%d %s %d %s, %2.2d:%2.2d:%2.2d", + $tm[5]+1900, $messages{$monthStr[$tm[4]]}, $tm[3], $messages{$weekdayStr[$tm[6]]}, + $tm[2], $tm[1], $tm[0]; + } + elsif($lang eq "fr") { + $_[0] .= sprintf "%s %d %s %d %2.2d:%2.2d:%2.2d", + $messages{$weekdayStr[$tm[6]]}, $tm[3], $messages{$monthStr[$tm[4]]}, $tm[5]+1900, + $tm[2], $tm[1], $tm[0]; + } + else { + $_[0] .= sprintf "%s, %d. %s %d %2.2d:%2.2d:%2.2d", + $messages{$weekdayStr[$tm[6]]}, $tm[3], $messages{$monthStr[$tm[4]]}, $tm[5]+1900, + $tm[2], $tm[1], $tm[0]; + } + + } + } + elsif($tag =~ m/^ACTIONONDATA\s+(\S+)\s+(\S+)\s+(\S+)\s+\'(.*?)\'(\s*)/s) { + $refKey = $1; + if(defined($varHash{$2}) && defined($varHash{$3})) { + $varHash{$refKey} = eval($varHash{$2} . $4 . $varHash{$3}); + } + else { + undef($varHash{$refKey}); + } + } + elsif($tag =~ m/^FORMATFLOAT\s+(\S+)/s) { + if(defined($varHash{$1})) { + $_[0] .= sprintf "%3.2f", $varHash{$1}; + } + else { + $_[0] .= ""; + } + } + elsif($tag =~ m/^CONVERTMSECTIMETOSTR\s+(\S+)/) { + if(defined($varHash{$1})) { + my $timeStr = ConvertTimeToStr($varHash{$1}, $messHash); + $_[0] .= $timeStr; + } + else { + $_[0] .= ""; + } + } + elsif($tag =~ m/^MODIFYDATA\s+(\S+?)\s+\'(.*?)\'\s+\'(.*?)\'/s) { + $value = $varHash{$1}; + $condition = $2; + $action = $3; + if(defined($value)) { + $newVal = ModifyData($value, $condition, $action); + $_[0] .= $newVal; + } + else { + $_[0] .= ""; + } + } + elsif($tag =~ m/^PRINTFILE\s+(\S+?)\s+(\S+)/s) { + if(!defined($varHash{$1}) || !defined($varHash{$2})) { + $_[0] .= ""; + } + else { + $_[0] .= GetFile($varHash{$1}, $varHash{$2}, $messHash); + } + } + elsif($tag =~ m/^PRINTHTMLFORMATFILE\s+(\S+?)\s+(\S+)/s) { + if(!defined($varHash{$1}) || !defined($varHash{$2})) { + $_[0] .= ""; + } + else { + $_[0] .= GetFormattedFile($varHash{$1}, $varHash{$2}, $messHash); + } + } + elsif($tag =~ m/PROCESSFILE\s+(\S+?)\s+(\S+?)\s+(\S+?)\s+(\S+)/s) { + my $ref = $1; + if(defined($varHash{$2}) && defined($varHash{$2})){ + $varHash{$ref} = ProcessFile($varHash{$2}, $varHash{$3}, $4); + } + else { + undef($varHash{$ref}); + } + } + elsif($tag =~ m/^HTMLIZE\s+(\S+)/s) { + $text = $varHash{$1}; + if(defined($text)) { + $htmlText = HtmlEscape($text); + $_[0] .= $htmlText; + } + else { + $_[0] .= ""; + } + } + elsif($tag =~ m/^PRINTDATA\s+(\S+)\s+\'(.*?)\'\s+\'(.*?)\'/s) { + if(!defined($varHash{$1})) { + $_[0] .= ""; + } elsif( $varHash{$1} == 1) { + $_[0] .= $2; + } else { + $_[0] .= $3; + } + } + elsif($tag =~ m/^CONVERTTOVERSIONSTR\s+(\S+)/s) { + if(defined($varHash{$1})) { + $_[0] .= ConvertToVersionString($varHash{$1}); + } + else { + $_[0] .= ""; + } + } + elsif($tag =~ m/^CREATESELECTFROMINPUTWITHFUNC\s+(\S+?)\s+(\S+?)\s+(.*)/s) { + my $name = $1; + $value = $funcparam{"CREATESELECTFROMINPUTWITHFUNC:$name"}; + if(defined($value)) { + my $handler = $2; + my $optstr = $3; + my @options = (); + my $i = 0; + + while($optstr =~ m/\'(.*?)\'\s+(.*)/) { + $options[$i] = "