commit af3619d4fae11545655743dd4588c7f57f0f95d2 Author: Darren VanBuren Date: Tue Mar 7 14:51:44 2017 -0800 Very rough first start, not even everything added diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5761abc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.o diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/APICommonCode/QTAccessFile.cpp b/APICommonCode/QTAccessFile.cpp new file mode 100644 index 0000000..57b3715 --- /dev/null +++ b/APICommonCode/QTAccessFile.cpp @@ -0,0 +1,681 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: QTAccessFile.cpp + + Contains: This file contains the implementation for finding and parsing qtaccess files. + + +*/ +#include +#include +#include +#include "SafeStdLib.h" +#include "QTSS.h" +#include "OSHeaders.h" +#include "StrPtrLen.h" +#include "StringParser.h" +#include "QTSSModuleUtils.h" +#include "OSFileSource.h" +#include "OSMemory.h" +#include "OSHeaders.h" +#include "QTAccessFile.h" +#include "OSArrayObjectDeleter.h" + + +#include +#if __MacOSX__ + #include +#endif +#include +#include +#include + +#define DEBUG_QTACCESS 0 +#define debug_printf if (DEBUG_QTACCESS) qtss_printf + +char* QTAccessFile::sAccessValidUser = "require valid-user\n"; +char* QTAccessFile::sAccessAnyUser = "require any-user\n"; + +UInt8 QTAccessFile::sWhitespaceAndGreaterThanMask[] = +{ + 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, 1, 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 +}; + +char* QTAccessFile::sQTAccessFileName = "qtaccess"; +Bool16 QTAccessFile::sAllocatedName = false; +OSMutex* QTAccessFile::sAccessFileMutex = NULL;//QTAccessFile isn't reentrant +const int kBuffLen = 512; + +void QTAccessFile::Initialize() // called by server at initialize never call again +{ + if (NULL == sAccessFileMutex) + { sAccessFileMutex = NEW OSMutex(); + } +} + +void QTAccessFile::SetAccessFileName(const char *inQTAccessFileName) +{ + OSMutexLocker locker(sAccessFileMutex); + if (NULL == inQTAccessFileName) + { Assert(NULL != inQTAccessFileName); + return; + } + + if (sAllocatedName) + { delete [] sQTAccessFileName; + } + + sAllocatedName = true; + sQTAccessFileName = NEW char[strlen(inQTAccessFileName)+1]; + ::strcpy(sQTAccessFileName, inQTAccessFileName); + +} + + +Bool16 QTAccessFile::HaveUser(char *userName, void* extraDataPtr) +{ + Bool16 result = false; + + if (NULL != userName && 0 != userName[0]) + result = true; + + return result; +} + +Bool16 QTAccessFile::HaveGroups( char** groupArray, UInt32 numGroups, void* extraDataPtr) +{ + Bool16 result = false; + + if (numGroups > 0 && groupArray != NULL) + result = true; + + return result; +} + +Bool16 QTAccessFile::HaveRealm( char *userName, StrPtrLen* ioRealmNameStr, void *extraData ) +{ + Bool16 result = false; + + if (ioRealmNameStr != NULL && ioRealmNameStr->Ptr != NULL && ioRealmNameStr->Len > 0) + result = true; + + return result; +} + +void QTAccessFile::GetRealm(StrPtrLen* accessRealm, StrPtrLen* ioRealmNameStr, char *userName,void *extraDataPtr ) +{ + + if (ioRealmNameStr->Len <= accessRealm->Len) + accessRealm->Len = ioRealmNameStr->Len -1; // just copy what we can + ::memcpy(ioRealmNameStr->Ptr, accessRealm->Ptr,accessRealm->Len); + ioRealmNameStr->Ptr[accessRealm->Len] = 0; + +} + +Bool16 QTAccessFile::TestUser(StrPtrLen* accessUser, char *userName,void *extraDataPtr ) +{ + Bool16 result = false; + + if ( accessUser->Equal(userName) ) + result = true; + + return result; +} + +Bool16 QTAccessFile::TestGroup( StrPtrLen* accessGroup, char *userName, char**groupArray, UInt32 numGroups, void *extraDataPtr) +{ + + for (UInt32 index = 0; index < numGroups; index ++) + { if(accessGroup->Equal(groupArray[index])) + return true; + } + + return false; + +} + +Bool16 QTAccessFile::TestExtraData( StrPtrLen* wordPtr, StringParser* lineParserPtr, void* extraDataPtr) +{ + return false; +} + + +Bool16 QTAccessFile::AccessAllowed ( char *userName, char**groupArray, UInt32 numGroups, StrPtrLen *accessFileBufPtr, + QTSS_ActionFlags inFlags,StrPtrLen* ioRealmNameStr, Bool16 *outAllowAnyUserPtr, void *extraDataPtr + ) +{ + if (NULL == accessFileBufPtr || NULL == accessFileBufPtr->Ptr || 0 == accessFileBufPtr->Len) + return true; // nothing to check + + if (ioRealmNameStr != NULL && ioRealmNameStr->Ptr != NULL && ioRealmNameStr->Len > 0) + ioRealmNameStr->Ptr[0] = 0; + + StringParser accessFileParser(accessFileBufPtr); + QTSS_ActionFlags currentFlags = qtssActionFlagsRead; + StrPtrLen line; + StrPtrLen word; + Bool16 haveUserName = false; + Bool16 haveRealmResultBuffer = false; + Bool16 haveGroups = false; + + *outAllowAnyUserPtr = false; + + haveUserName = HaveUser(userName, extraDataPtr); + + haveGroups = HaveGroups(groupArray, numGroups, extraDataPtr); + + haveRealmResultBuffer = HaveRealm(userName, ioRealmNameStr, extraDataPtr ); + + while( accessFileParser.GetDataRemaining() != 0 ) + { + accessFileParser.GetThruEOL(&line); // Read each line + 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 == '#') || (firstChar == '\0') ) + continue; //skip over comments and blank lines... + + lineParser.ConsumeUntilWhitespace(&word); + if ( word.Equal(" or + while (word.Len != 0) // compare each word in the line + { + if (word.Equal("WRITE") ) + { currentFlags |= inFlags & qtssActionFlagsWrite; // accept following lines if inFlags has write + } + + if (word.Equal("READ") ) + { currentFlags |= inFlags & qtssActionFlagsRead; // accept following lines if inFlags has read + } + lineParser.ConsumeWhitespace(); + lineParser.ConsumeUntil(&word, sWhitespaceAndGreaterThanMask); + } + continue; //done with limit line + } + if ( word.Equal("") ) + currentFlags = qtssActionFlagsRead; // set the current access state to the default of read access + + if (0 == (currentFlags & inFlags)) + continue; // ignore lines because inFlags doesn't match the current access state + + if (haveRealmResultBuffer && word.Equal("AuthName") ) //realm name + { + lineParser.ConsumeWhitespace(); + lineParser.GetThruEOL(&word); + StringParser::UnQuote(&word);// if the parsed string is surrounded by quotes then remove them. + + GetRealm(&word, ioRealmNameStr, userName, extraDataPtr ); + + // we don't change the buffer len ioRealmNameStr->Len because we might have another AuthName tag to copy + continue; // done with AuthName (realm) + } + + + if (word.Equal("require") ) + { + lineParser.ConsumeWhitespace(); + lineParser.ConsumeUntilWhitespace(&word); + + if (haveUserName && word.Equal("valid-user") ) + { return true; + } + + if ( word.Equal("any-user") ) + { + *outAllowAnyUserPtr = true; + return true; + } + + if (!haveUserName) + continue; + + if (word.Equal("user") ) + { + lineParser.ConsumeWhitespace(); + lineParser.ConsumeUntilWhitespace(&word); + + while (word.Len != 0) // compare each word in the line + { + if (TestUser(&word, userName, extraDataPtr )) + return true; + + lineParser.ConsumeWhitespace(); + lineParser.ConsumeUntilWhitespace(&word); + } + continue; // done with "require user" line + } + + if (haveGroups && word.Equal("group")) // check if we have groups for the user + { + lineParser.ConsumeWhitespace(); + lineParser.ConsumeUntilWhitespace(&word); + + while (word.Len != 0) // compare each word in the line + { + if (TestGroup(&word, userName, groupArray, numGroups, extraDataPtr) ) + return true; + + lineParser.ConsumeWhitespace(); + lineParser.ConsumeUntilWhitespace(&word); + } + continue; // done with "require group" line + } + + if (TestExtraData(&word, &lineParser, extraDataPtr)) + return true; + + + continue; // done with unparsed "require" line + } + + } + + return false; // user or group not found +} + +char* QTAccessFile::GetAccessFile_Copy( const char* movieRootDir, const char* dirPath) +{ + OSMutexLocker locker(sAccessFileMutex); + + char* currentDir= NULL; + char* lastSlash = NULL; + int movieRootDirLen = ::strlen(movieRootDir); + int maxLen = strlen(dirPath)+strlen(sQTAccessFileName) + strlen(kPathDelimiterString) + 1; + currentDir = NEW char[maxLen]; + + ::strcpy(currentDir, dirPath); + + //strip off filename + lastSlash = ::strrchr(currentDir, kPathDelimiterChar); + if (lastSlash != NULL) + lastSlash[0] = '\0'; + + //check qtaccess files + + while ( true ) //walk backward up the dir tree. + { + int curLen = strlen(currentDir) + strlen(sQTAccessFileName) + strlen(kPathDelimiterString); + + if ( curLen >= maxLen ) + break; + + ::strcat(currentDir, kPathDelimiterString); + ::strcat(currentDir, sQTAccessFileName); + + QTSS_Object fileObject = NULL; + if( QTSS_OpenFileObject(currentDir, qtssOpenFileNoFlags, &fileObject) == QTSS_NoErr) + { + (void)QTSS_CloseFileObject(fileObject); + return currentDir; + } + + //strip off the "/qtaccess" + lastSlash = ::strrchr(currentDir, kPathDelimiterChar); + lastSlash[0] = '\0'; + + //strip of the tailing directory + lastSlash = ::strrchr(currentDir, kPathDelimiterChar); + if (lastSlash == NULL) + break; + else + lastSlash[0] = '\0'; + + if ( (lastSlash-currentDir) < movieRootDirLen ) //bail if we start eating our way out of fMovieRootDir + break; + } + + delete[] currentDir; + return NULL; +} + +// allocates memory for outUsersFilePath and outGroupsFilePath - remember to delete +// returns the auth scheme +QTSS_AuthScheme QTAccessFile::FindUsersAndGroupsFilesAndAuthScheme(char* inAccessFilePath, QTSS_ActionFlags inAction, char** outUsersFilePath, char** outGroupsFilePath) +{ + QTSS_AuthScheme authScheme = qtssAuthNone; + QTSS_ActionFlags currentFlags = qtssActionFlagsRead; + + if (inAccessFilePath == NULL) + return authScheme; + + *outUsersFilePath = NULL; + *outGroupsFilePath = NULL; + //Assert(outUsersFilePath == NULL); + //Assert(outGroupsFilePath == NULL); + + StrPtrLen accessFileBuf; + (void)QTSSModuleUtils::ReadEntireFile(inAccessFilePath, &accessFileBuf); + OSCharArrayDeleter accessFileBufDeleter(accessFileBuf.Ptr); + + StringParser accessFileParser(&accessFileBuf); + StrPtrLen line; + StrPtrLen word; + + while( accessFileParser.GetDataRemaining() != 0 ) + { + accessFileParser.GetThruEOL(&line); // Read each line + 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 == '#') || (firstChar == '\0') ) + continue; //skip over comments and blank lines... + + lineParser.ConsumeUntilWhitespace(&word); + + if ( word.Equal(" or + while (word.Len != 0) // compare each word in the line + { + if (word.Equal("WRITE") ) + { + currentFlags |= inAction & qtssActionFlagsWrite; // accept following lines if inFlags has write + } + + if (word.Equal("READ") ) + { + currentFlags |= inAction & qtssActionFlagsRead; // accept following lines if inFlags has read + } + + lineParser.ConsumeWhitespace(); + lineParser.ConsumeUntil(&word, sWhitespaceAndGreaterThanMask); + } + continue; //done with limit line + } + + if ( word.Equal("") ) + currentFlags = qtssActionFlagsRead; // set the current access state to the default of read access + + if (0 == (currentFlags & inAction)) + continue; // ignore lines because inFlags doesn't match the current access state + + if (word.Equal("AuthUserFile") ) + { + lineParser.ConsumeWhitespace(); + lineParser.GetThruEOL(&word); + StringParser::UnQuote(&word);// if the parsed string is surrounded by quotes then remove them. + + if(*outUsersFilePath != NULL) // we are encountering the AuthUserFile keyword twice! + delete[] *outUsersFilePath; // The last one found takes precedence...delete the previous path + + *outUsersFilePath = word.GetAsCString(); + continue; + } + + if (word.Equal("AuthGroupFile") ) + { + lineParser.ConsumeWhitespace(); + lineParser.GetThruEOL(&word); + StringParser::UnQuote(&word);// if the parsed string is surrounded by quotes then remove them. + + if(*outGroupsFilePath != NULL) // we are encountering the AuthGroupFile keyword twice! + delete[] *outGroupsFilePath; // The last one found takes precedence...delete the previous path + + *outGroupsFilePath = word.GetAsCString(); + + continue; + } + + if (word.Equal("AuthScheme") ) + { + lineParser.ConsumeWhitespace(); + lineParser.GetThruEOL(&word); + StringParser::UnQuote(&word);// if the parsed string is surrounded by quotes then remove them. + + if (word.Equal("basic")) + authScheme = qtssAuthBasic; + else if (word.Equal("digest")) + authScheme = qtssAuthDigest; + + continue; + } + } + + return authScheme; +} + +QTSS_Error QTAccessFile::AuthorizeRequest(QTSS_StandardRTSP_Params* inParams, Bool16 allowNoAccessFiles, QTSS_ActionFlags noAction, QTSS_ActionFlags authorizeAction, Bool16 *outAuthorizedPtr, Bool16 *outAllowAnyUserPtr) +{ + if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest) || (NULL == outAllowAnyUserPtr) || (NULL == outAuthorizedPtr) ) + return QTSS_RequestFailed; + + *outAllowAnyUserPtr = false; + *outAuthorizedPtr = false; + + QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; + + // get the type of request + // Don't touch write requests + QTSS_ActionFlags action = QTSSModuleUtils::GetRequestActions(theRTSPRequest); + if(action == qtssActionFlagsNoFlags) + return QTSS_RequestFailed; + + if( (action & noAction) != 0) + return QTSS_NoErr; // we don't handle + + //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; + + QTSS_UserProfileObject theUserProfile = QTSSModuleUtils::GetUserProfileObject(theRTSPRequest); + if (NULL == theUserProfile) + return QTSS_RequestFailed; + + char* accessFilePath = QTAccessFile::GetAccessFile_Copy(movieRootDirStr, pathBuffStr); + OSCharArrayDeleter accessFilePathDeleter(accessFilePath); + + char* username = QTSSModuleUtils::GetUserName_Copy(theUserProfile); + OSCharArrayDeleter usernameDeleter(username); + + UInt32 numGroups = 0; + char** groupCharPtrArray = QTSSModuleUtils::GetGroupsArray_Copy(theUserProfile, &numGroups); + OSCharPointerArrayDeleter groupCharPtrArrayDeleter(groupCharPtrArray); + + StrPtrLen accessFileBuf; + (void)QTSSModuleUtils::ReadEntireFile(accessFilePath, &accessFileBuf); + OSCharArrayDeleter accessFileBufDeleter(accessFileBuf.Ptr); + + if (accessFileBuf.Len == 0 && !allowNoAccessFiles) + { accessFileBuf.Set(sAccessValidUser); + if (DEBUG_QTACCESS) + qtss_printf("QTAccessFile::AuthorizeRequest SET Accessfile valid user for no accessfile %s\n", sAccessValidUser); + } + + if (accessFileBuf.Len == 0 && allowNoAccessFiles) + { accessFileBuf.Set(sAccessAnyUser); + if (DEBUG_QTACCESS) + qtss_printf("QTAccessFile::AuthorizeRequest SET Accessfile any user for no access file %s\n", sAccessAnyUser); + } + + char realmName[kBuffLen] = { 0 }; + StrPtrLen realmNameStr(realmName,kBuffLen -1); + + //check if this user is allowed to see this movie + Bool16 allowRequest = this->AccessAllowed(username, groupCharPtrArray, numGroups, &accessFileBuf, authorizeAction,&realmNameStr, outAllowAnyUserPtr ); + debug_printf("accessFile.AccessAllowed for user=%s returned %d\n", username, allowRequest); + + // Get the auth scheme + QTSS_AuthScheme theAuthScheme = qtssAuthNone; + UInt32 len = sizeof(theAuthScheme); + QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&theAuthScheme, &len); + Assert(len == sizeof(theAuthScheme)); + if(theErr != QTSS_NoErr) + return theErr; + + // If auth scheme is basic and the realm is present in the access file, use it + if((theAuthScheme == qtssAuthBasic) && (realmNameStr.Ptr[0] != '\0')) //set the realm if we have one + (void) QTSS_SetValue(theRTSPRequest,qtssRTSPReqURLRealm, 0, realmNameStr.Ptr, ::strlen(realmNameStr.Ptr)); + else // if auth scheme is basic and no realm is present, or if the auth scheme is digest, use the realm from the users file + { + char* userRealm = NULL; + (void) QTSS_GetValueAsString(theUserProfile, qtssUserRealm, 0, &userRealm); + if(userRealm != NULL) + { + OSCharArrayDeleter userRealmDeleter(userRealm); + (void) QTSS_SetValue(theRTSPRequest,qtssRTSPReqURLRealm, 0, userRealm, ::strlen(userRealm)); + } + } + + *outAuthorizedPtr = allowRequest; + + Bool16 founduser = this->HaveUser(username, NULL); + Bool16 authContinue = true; + char nameBuff[256]; + StrPtrLen reqNameStr(nameBuff, kBuffLen); + StrPtrLen profileNameStr(username); + theErr = QTSS_GetValue (theRTSPRequest,qtssRTSPReqUserName,0, (void *) reqNameStr.Ptr, &reqNameStr.Len); + + +if (DEBUG_QTACCESS) +{ qtss_printf("QTAccessFile::AuthorizeRequest qtaccess profile user =%s ", username); + reqNameStr.PrintStr("request user=","\n"); + qtss_printf("QTAccessFile::AuthorizeRequest allowRequest=%d founduser=%d authContinue=%d\n", allowRequest, founduser, authContinue); +} + if (allowRequest && founduser) + theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &founduser,&authContinue); + if (!allowRequest && !founduser) + theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &founduser,&authContinue); + +if (DEBUG_QTACCESS) +{ qtss_printf("QTAccessFile::AuthorizeRequest QTSSModuleUtils::AuthorizeRequest qtaccess profile user =%s ", username); + reqNameStr.PrintStr("request user=","\n"); + qtss_printf("QTAccessFile::AuthorizeRequest allowRequest=%d founduser=%d authContinue=%d\n", allowRequest, founduser, authContinue); +} + + return theErr; +} + + +bool DSAccessFile::CheckGroupMembership(const char* inUsername, const char* inGroupName) +{ + struct passwd *user = NULL; + struct group *group = NULL; +#if __MacOSX__ + uuid_t userID; + uuid_t groupID; + int isMember = 0; +#else + int numGroups = 50; + gid_t *groupList; + groupList = (gid_t *) malloc(numGroups * sizeof(gid_t)); +#endif + + // Look up the user using the POSIX APIs: only care about the UID. + user = getpwnam(inUsername); + if ( user == NULL ) + return false; +#if __MacOSX__ + uuid_clear(userID); + if ( mbr_uid_to_uuid(user->pw_uid, userID) ) + return false; +#endif + // Look up the group using the POSIX APIs: only care about the GID. + group = getgrnam(inGroupName); + endgrent(); + if ( group == NULL ) + return false; +#if __MacOSX__ + uuid_clear(groupID); + if ( mbr_gid_to_uuid(group->gr_gid, groupID) ) + return false; + + // In Tiger, group membership is painfully simple: we ask memberd for it! + // mbr_check_membership() returns 0 on success and error code on failure. + if ( mbr_check_membership(userID, groupID, &isMember) ) + return false; + return (bool)isMember; +#else + if(getgrouplist(inUsername, user->pw_gid, groupList, &numGroups) == -1) { + qtss_printf("QTAccessFile::CheckGroupMembership getgrouplist for user %s returned -1 and numGroups = %d", inUsername, numGroups); + return false; + } + for(int i = 0; i < numGroups; i++) { + if(groupList[i] == group->gr_gid) { + return true; + } + } + return false; +#endif +} + +Bool16 DSAccessFile::ValidUser( char*userName, void* extraDataPtr) +{ + struct passwd *user = getpwnam(userName); + Bool16 result =true; + if ( user == NULL ) + { + return result; + } + + return true; +} + + + diff --git a/APICommonCode/QTAccessFile.h b/APICommonCode/QTAccessFile.h new file mode 100644 index 0000000..f6fe160 --- /dev/null +++ b/APICommonCode/QTAccessFile.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: QTAccessFile.h + + Contains: This object contains an interface for finding and parsing qtaccess files. + + +*/ +#ifndef _QT_ACCESS_FILE_H_ +#define _QT_ACCESS_FILE_H_ + +#include +#include "SafeStdLib.h" +#include "QTSS.h" +#include "StrPtrLen.h" +#include "OSHeaders.h" +#include "StringParser.h" +#include "OSMutex.h" + +class QTAccessFile +{ + public: + static UInt8 sWhitespaceAndGreaterThanMask[]; + static void Initialize(); + + static char * GetUserNameCopy(QTSS_UserProfileObject inUserProfile); + + //GetGroupsArrayCopy + // + // GetGroupsArrayCopy allocates outGroupCharPtrArray. Caller must "delete [] outGroupCharPtrArray" when done. + static char* GetAccessFile_Copy( const char* movieRootDir, const char* dirPath); + + //over ride these in a sub class + virtual Bool16 HaveUser(char *userName, void* extraDataPtr); + virtual Bool16 HaveGroups( char** groupArray, UInt32 numGroups, void* extraDataPtr); + virtual Bool16 HaveRealm( char *userName, StrPtrLen* ioRealmNameStr, void *extraData ); + virtual Bool16 TestUser(StrPtrLen* accessUser, char *userName,void *extraDataPtr ); + virtual Bool16 TestGroup( StrPtrLen* accessGroup, char *userName, char**groupArray, UInt32 numGroups, void *extraDataPtr); + virtual Bool16 TestExtraData( StrPtrLen* wordPtr, StringParser* lineParserPtr, void* extraDataPtr); + virtual void GetRealm(StrPtrLen* accessRealm, StrPtrLen* ioRealmNameStr, char *userName,void *extraDataPtr ); + virtual Bool16 ValidUser(char* userName, void* extraDataPtr) { return false; }; + + //AccessAllowed + // + // This routine is used to get the Realm to send back to a user and to check if a user has access + // userName: may be null. + // accessFileBufPtr:If accessFileBufPtr is NULL or contains a NULL PTR or 0 LEN then false is returned + // ioRealmNameStr: ioRealmNameStr and ioRealmNameStr->Ptr may be null. + // To get a returned ioRealmNameStr value the ioRealmNameStr and ioRealmNameStr->Ptr must be non-NULL + // valid pointers. The ioRealmNameStr.Len should be set to the ioRealmNameStr->Ptr's allocated len. + // numGroups: The number of groups in the groupArray. Use GetGroupsArrayCopy to create the groupArray. + Bool16 AccessAllowed ( char *userName, char**groupArray, UInt32 numGroups, + StrPtrLen *accessFileBufPtr,QTSS_ActionFlags inFlags,StrPtrLen* ioRealmNameStr, + Bool16* outAllowAnyUserPtr, + void *extraDataPtr = NULL + ); + + static void SetAccessFileName(const char *inQTAccessFileName); //makes a copy and stores it + static char* GetAccessFileName() { return sQTAccessFileName; }; // a reference. Don't delete! + + // allocates memory for outUsersFilePath and outGroupsFilePath - remember to delete + // returns the auth scheme + static QTSS_AuthScheme FindUsersAndGroupsFilesAndAuthScheme(char* inAccessFilePath, QTSS_ActionFlags inAction, char** outUsersFilePath, char** outGroupsFilePath); + + QTSS_Error AuthorizeRequest(QTSS_StandardRTSP_Params* inParams, Bool16 allowNoAccessFiles, QTSS_ActionFlags noAction, QTSS_ActionFlags authorizeAction, Bool16 *outAuthorizedPtr, Bool16* outAllowAnyUserPtr = NULL); + virtual ~QTAccessFile() {}; + + private: + static char* sQTAccessFileName; // managed by the QTAccess module + static Bool16 sAllocatedName; + static OSMutex* sAccessFileMutex; + static char* sAccessValidUser; + static char* sAccessAnyUser; + + +}; + +class DSAccessFile : public QTAccessFile +{ + public: + virtual ~DSAccessFile() {} + virtual Bool16 HaveGroups( char** groupArray, UInt32 numGroups, void* extraDataPtr) { return true; } + virtual Bool16 TestGroup( StrPtrLen* accessGroup, char *userName, char**groupArray, UInt32 numGroups, void *extraDataPtr) + { StrPtrLenDel deleter( accessGroup->GetAsCString() ); + return this->CheckGroupMembership(userName, deleter.Ptr ); + } + virtual Bool16 ValidUser(char* userName, void* extraDataPtr); + bool CheckGroupMembership(const char* inUsername, const char* inGroupName); + +}; + + +#endif //_QT_ACCESS_FILE_H_ + diff --git a/APICommonCode/QTSS3GPPModuleUtils.cpp b/APICommonCode/QTSS3GPPModuleUtils.cpp new file mode 100644 index 0000000..b55e09d --- /dev/null +++ b/APICommonCode/QTSS3GPPModuleUtils.cpp @@ -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: QTSS3GPPModuleUtils.cpp + + Contains: Implements utility routines defined in QTSS3GPPModuleUtils.h. + +*/ + +#include "QTSS3GPPModuleUtils.h" +#include "QTSSModuleUtils.h" +#include "QTSS_Private.h" + +#include "StrPtrLen.h" +#include "OSArrayObjectDeleter.h" +#include "OSMemory.h" +#include "MyAssert.h" +#include "StringFormatter.h" +#include "ResizeableStringFormatter.h" +#include "StringParser.h" +#include "SafeStdLib.h" + + +QTSS_TextMessagesObject QTSS3GPPModuleUtils::sMessages = NULL; +QTSS_ServerObject QTSS3GPPModuleUtils::sServer = NULL; +QTSS_PrefsObject QTSS3GPPModuleUtils::sServerPrefs = NULL; +QTSS_StreamRef QTSS3GPPModuleUtils::sErrorLog = NULL; +StrPtrLen QTSS3GPPModuleUtils::s3gppBitRateAdaptationSDPStr("a=3GPP-Adaptation-Support:"); +const char* k3gppRateAdaptationReportFreqPrefName = "3gpp_protocol_rate_adaptation_report_frequency"; + +Bool16 QTSS3GPPModuleUtils::s3gppEnabled = false; +Bool16 QTSS3GPPModuleUtils::s3gppRateAdaptationEnabled = false; +UInt16 QTSS3GPPModuleUtils::s3gppRateAdaptationReportFrequency = 1; + +void QTSS3GPPModuleUtils::Initialize(QTSS_Initialize_Params* initParams) +{ + if (NULL == initParams) + return; + + sServer = initParams->inServer; + sServerPrefs = initParams->inPrefs; + sMessages = initParams->inMessages; + sErrorLog = initParams->inErrorLogStream; + +} + +void QTSS3GPPModuleUtils::ValidatePrefs() +{ + +// min and max values per 3gpp rel-6 A26234 5.3.3.5 + if (s3gppRateAdaptationReportFrequency < 1 || s3gppRateAdaptationReportFrequency > 9) + QTSSModuleUtils::LogPrefErrorStr( qtssWarningVerbosity, (char*) k3gppRateAdaptationReportFreqPrefName, "has an invalid value: valid range is [1..9]"); + + if (s3gppRateAdaptationReportFrequency < 1) + s3gppRateAdaptationReportFrequency = 1; + + if (s3gppRateAdaptationReportFrequency > 9) + s3gppRateAdaptationReportFrequency = 9; + + +} + + + +void QTSS3GPPModuleUtils::ReadPrefs() +{ + + const Bool16 kDefaultEnable = true; + const UInt16 kDefaultReportFreq = 1; + QTSSModuleUtils::GetAttribute(sServerPrefs,"enable_3gpp_protocol", qtssAttrDataTypeBool16, + &s3gppEnabled,(void *)&kDefaultEnable, sizeof(s3gppEnabled)); + + QTSSModuleUtils::GetAttribute(sServerPrefs,"enable_3gpp_protocol_rate_adaptation", qtssAttrDataTypeBool16, + &s3gppRateAdaptationEnabled,(void *)&kDefaultEnable, sizeof(s3gppRateAdaptationEnabled)); + + QTSSModuleUtils::GetAttribute(sServerPrefs, (char *) k3gppRateAdaptationReportFreqPrefName, qtssAttrDataTypeUInt16, + &s3gppRateAdaptationReportFrequency,(void *)&kDefaultReportFreq, sizeof(s3gppRateAdaptationReportFrequency)); + + + QTSS3GPPModuleUtils::ValidatePrefs(); +} + +SDPContainer* QTSS3GPPModuleUtils::Get3GPPSDPFeatureListCopy(ResizeableStringFormatter &buffer) +{ + SDPContainer* resultList = NEW SDPContainer; + StrPtrLen theLinePtr; + + + if (s3gppEnabled) + { + if (s3gppRateAdaptationEnabled) + { + buffer.Put(s3gppBitRateAdaptationSDPStr); + buffer.Put((SInt32) s3gppRateAdaptationReportFrequency); + buffer.PutEOL(); + theLinePtr.Set(buffer.GetBufPtr(),buffer.GetBytesWritten()); + resultList->AddHeaderLine(&theLinePtr); + buffer.Reset(); + } + + } + + + return resultList; +} + diff --git a/APICommonCode/QTSS3GPPModuleUtils.h b/APICommonCode/QTSS3GPPModuleUtils.h new file mode 100644 index 0000000..e841522 --- /dev/null +++ b/APICommonCode/QTSS3GPPModuleUtils.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: QTSS3GPPModuleUtils.h + + Contains: Utility routines for 3GPP modules to use. + +*/ + + +#ifndef _QTSS_3GPP_MODULE_UTILS_H_ +#define _QTSS_3GPP_MODULE_UTILS_H_ + +#include +#include "SafeStdLib.h" +#include "QTSS.h" +#include "StrPtrLen.h" +#include "SDPUtils.h" + +class QTSS3GPPModuleUtils +{ + public: + + + static void Initialize(QTSS_Initialize_Params* initParams); + static void ReadPrefs(); + + + static SDPContainer* Get3GPPSDPFeatureListCopy(ResizeableStringFormatter &buffer); + + private: + + static void ValidatePrefs(); + + // + // Used in the implementation of the above functions + + static QTSS_TextMessagesObject sMessages; + static QTSS_ServerObject sServer; + static QTSS_PrefsObject sServerPrefs; + static QTSS_StreamRef sErrorLog; + static StrPtrLen s3gppBitRateAdaptationSDPStr; + static Bool16 s3gppEnabled; + static Bool16 s3gppRateAdaptationEnabled; + static UInt16 s3gppRateAdaptationReportFrequency; + + + +}; + + +#endif //_QTSS_3GPP_MODULE_UTILS_H_ diff --git a/APICommonCode/QTSSMemoryDeleter.h b/APICommonCode/QTSSMemoryDeleter.h new file mode 100644 index 0000000..d707439 --- /dev/null +++ b/APICommonCode/QTSSMemoryDeleter.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: QTSSMemoryDeleter.h + + Contains: Auto object for deleting memory allocated by QTSS API callbacks, + such as QTSS_GetValueAsString + +*/ + +#ifndef __QTSS_MEMORY_DELETER_H__ +#define __QTSS_MEMORY_DELETER_H__ + +#include "MyAssert.h" +#include "QTSS.h" + +template +class QTSSMemoryDeleter +{ + public: + QTSSMemoryDeleter(T* victim) : fT(victim) {} + ~QTSSMemoryDeleter() { QTSS_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; +}; + +typedef QTSSMemoryDeleter QTSSCharArrayDeleter; + +#endif //__QTSS_MEMORY_DELETER_H__ + + diff --git a/APICommonCode/QTSSModuleUtils.cpp b/APICommonCode/QTSSModuleUtils.cpp new file mode 100644 index 0000000..4231c60 --- /dev/null +++ b/APICommonCode/QTSSModuleUtils.cpp @@ -0,0 +1,1132 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: QTSSModuleUtils.cpp + + Contains: Implements utility routines defined in QTSSModuleUtils.h. + +*/ + +#include "QTSSModuleUtils.h" +#include "QTSS_Private.h" + +#include "StrPtrLen.h" +#include "OSArrayObjectDeleter.h" +#include "OSMemory.h" +#include "MyAssert.h" +#include "StringFormatter.h" +#include "ResizeableStringFormatter.h" +#include "QTAccessFile.h" +#include "StringParser.h" +#include "SafeStdLib.h" +#ifndef __Win32__ +#include +#endif + +#ifdef __solaris__ +#include +#endif + +QTSS_TextMessagesObject QTSSModuleUtils::sMessages = NULL; +QTSS_ServerObject QTSSModuleUtils::sServer = NULL; +QTSS_StreamRef QTSSModuleUtils::sErrorLog = NULL; +Bool16 QTSSModuleUtils::sEnableRTSPErrorMsg = false; +QTSS_ErrorVerbosity QTSSModuleUtils::sMissingPrefVerbosity = qtssMessageVerbosity; + +void QTSSModuleUtils::Initialize(QTSS_TextMessagesObject inMessages, + QTSS_ServerObject inServer, + QTSS_StreamRef inErrorLog) +{ + sMessages = inMessages; + sServer = inServer; + sErrorLog = inErrorLog; +} + +QTSS_Error QTSSModuleUtils::ReadEntireFile(char* inPath, StrPtrLen* outData, QTSS_TimeVal inModDate, QTSS_TimeVal* outModDate) +{ + + QTSS_Object theFileObject = NULL; + QTSS_Error theErr = QTSS_NoErr; + + outData->Ptr = NULL; + outData->Len = 0; + + do { + + // Use the QTSS file system API to read the file + theErr = QTSS_OpenFileObject(inPath, 0, &theFileObject); + if (theErr != QTSS_NoErr) + break; + + UInt32 theParamLen = 0; + QTSS_TimeVal* theModDate = NULL; + theErr = QTSS_GetValuePtr(theFileObject, qtssFlObjModDate, 0, (void**)&theModDate, &theParamLen); + Assert(theParamLen == sizeof(QTSS_TimeVal)); + if(theParamLen != sizeof(QTSS_TimeVal)) + break; + if(outModDate != NULL) + *outModDate = (QTSS_TimeVal)*theModDate; + + if(inModDate != -1) { + // If file hasn't been modified since inModDate, don't have to read the file + if(*theModDate <= inModDate) + break; + } + + theParamLen = 0; + UInt64* theLength = NULL; + theErr = QTSS_GetValuePtr(theFileObject, qtssFlObjLength, 0, (void**)&theLength, &theParamLen); + if (theParamLen != sizeof(UInt64)) + break; + + if (*theLength > kSInt32_Max) + break; + + // Allocate memory for the file data + outData->Ptr = NEW char[ (SInt32) (*theLength + 1) ]; + outData->Len = (SInt32) *theLength; + outData->Ptr[outData->Len] = 0; + + // Read the data + UInt32 recvLen = 0; + theErr = QTSS_Read(theFileObject, outData->Ptr, outData->Len, &recvLen); + if (theErr != QTSS_NoErr) + { + outData->Delete(); + break; + } + Assert(outData->Len == recvLen); + + }while(false); + + // Close the file + if(theFileObject != NULL) { + theErr = QTSS_CloseFileObject(theFileObject); + } + + return theErr; +} + +void QTSSModuleUtils::SetupSupportedMethods(QTSS_Object inServer, QTSS_RTSPMethod* inMethodArray, UInt32 inNumMethods) +{ + // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN + UInt32 theNumMethods = 0; + (void)QTSS_GetNumValues(inServer, qtssSvrHandledMethods, &theNumMethods); + + for (UInt32 x = 0; x < inNumMethods; x++) + (void)QTSS_SetValue(inServer, qtssSvrHandledMethods, theNumMethods++, (void*)&inMethodArray[x], sizeof(inMethodArray[x])); +} + +void QTSSModuleUtils::LogError( QTSS_ErrorVerbosity inVerbosity, + QTSS_AttributeID inTextMessage, + UInt32 /*inErrNumber*/, + char* inArgument, + char* inArg2) +{ + static char* sEmptyArg = ""; + + if (sMessages == NULL) + return; + + // Retrieve the specified text message from the text messages dictionary. + + StrPtrLen theMessage; + (void)QTSS_GetValuePtr(sMessages, inTextMessage, 0, (void**)&theMessage.Ptr, &theMessage.Len); + if ((theMessage.Ptr == NULL) || (theMessage.Len == 0)) + (void)QTSS_GetValuePtr(sMessages, qtssMsgNoMessage, 0, (void**)&theMessage.Ptr, &theMessage.Len); + + if ((theMessage.Ptr == NULL) || (theMessage.Len == 0)) + return; + + // qtss_sprintf and ::strlen will crash if inArgument is NULL + if (inArgument == NULL) + inArgument = sEmptyArg; + if (inArg2 == NULL) + inArg2 = sEmptyArg; + + // Create a new string, and put the argument into the new string. + + UInt32 theMessageLen = theMessage.Len + ::strlen(inArgument) + ::strlen(inArg2); + + OSCharArrayDeleter theLogString(NEW char[theMessageLen + 1]); + qtss_sprintf(theLogString.GetObject(), theMessage.Ptr, inArgument, inArg2); + Assert(theMessageLen >= ::strlen(theLogString.GetObject())); + + (void)QTSS_Write(sErrorLog, theLogString.GetObject(), ::strlen(theLogString.GetObject()), + NULL, inVerbosity); +} + +void QTSSModuleUtils::LogErrorStr( QTSS_ErrorVerbosity inVerbosity, char* inMessage) +{ + if (inMessage == NULL) return; + (void)QTSS_Write(sErrorLog, inMessage, ::strlen(inMessage), NULL, inVerbosity); +} + + +void QTSSModuleUtils::LogPrefErrorStr( QTSS_ErrorVerbosity inVerbosity, char* preference, char* inMessage) +{ + if (inMessage == NULL || preference == NULL) + { Assert(0); + return; + } + char buffer[1024]; + + qtss_snprintf(buffer,sizeof(buffer), "Server preference %s %s", preference, inMessage); + + (void)QTSS_Write(sErrorLog, buffer, ::strlen(buffer), NULL, inVerbosity); +} + + +char* QTSSModuleUtils::GetFullPath( QTSS_RTSPRequestObject inRequest, + QTSS_AttributeID whichFileType, + UInt32* outLen, + StrPtrLen* suffix) +{ + Assert(outLen != NULL); + + (void)QTSS_LockObject(inRequest); + // Get the proper file path attribute. This may return an error if + // the file type is qtssFilePathTrunc attr, because there may be no path + // once its truncated. That's ok. In that case, we just won't append a path. + StrPtrLen theFilePath; + (void)QTSS_GetValuePtr(inRequest, whichFileType, 0, (void**)&theFilePath.Ptr, &theFilePath.Len); + + StrPtrLen theRootDir; + QTSS_Error theErr = QTSS_GetValuePtr(inRequest, qtssRTSPReqRootDir, 0, (void**)&theRootDir.Ptr, &theRootDir.Len); + Assert(theErr == QTSS_NoErr); + + + //trim off extra / characters before concatenating + // so root/ + /path instead of becoming root//path is now root/path as it should be. + + if (theRootDir.Len && theRootDir.Ptr[theRootDir.Len -1] == kPathDelimiterChar + && theFilePath.Len && theFilePath.Ptr[0] == kPathDelimiterChar) + { + char *thePathEnd = &(theFilePath.Ptr[theFilePath.Len]); + while (theFilePath.Ptr != thePathEnd) + { + if (*theFilePath.Ptr != kPathDelimiterChar) + break; + + theFilePath.Ptr ++; + theFilePath.Len --; + } + } + + //construct a full path out of the root dir path for this request, + //and the url path. + *outLen = theFilePath.Len + theRootDir.Len + 2; + if (suffix != NULL) + *outLen += suffix->Len; + + char* theFullPath = NEW char[*outLen]; + + //write all the pieces of the path into this new buffer. + StringFormatter thePathFormatter(theFullPath, *outLen); + thePathFormatter.Put(theRootDir); + thePathFormatter.Put(theFilePath); + if (suffix != NULL) + thePathFormatter.Put(*suffix); + thePathFormatter.PutTerminator(); + + *outLen = *outLen - 2; + + (void)QTSS_UnlockObject(inRequest); + + return theFullPath; +} + +QTSS_Error QTSSModuleUtils::AppendRTPMetaInfoHeader( QTSS_RTSPRequestObject inRequest, + StrPtrLen* inRTPMetaInfoHeader, + RTPMetaInfoPacket::FieldID* inFieldIDArray) +{ + // + // For formatting the response header + char tempBuffer[128]; + ResizeableStringFormatter theFormatter(tempBuffer, 128); + + StrPtrLen theHeader(*inRTPMetaInfoHeader); + + // + // For marking which fields were requested by the client + Bool16 foundFieldArray[RTPMetaInfoPacket::kNumFields]; + ::memset(foundFieldArray, 0, sizeof(Bool16) * RTPMetaInfoPacket::kNumFields); + + char* theEndP = theHeader.Ptr + theHeader.Len; + UInt16 fieldNameValue = 0; + + while (theHeader.Ptr <= (theEndP - sizeof(RTPMetaInfoPacket::FieldName))) + { + RTPMetaInfoPacket::FieldName* theFieldName = (RTPMetaInfoPacket::FieldName*)theHeader.Ptr; + ::memcpy (&fieldNameValue, theFieldName, sizeof(UInt16)); + + RTPMetaInfoPacket::FieldIndex theFieldIndex = RTPMetaInfoPacket::GetFieldIndexForName(ntohs(fieldNameValue)); + + // + // This field is not supported (not in the field ID array), so + // don't put it in the response + if ((theFieldIndex == RTPMetaInfoPacket::kIllegalField) || + (inFieldIDArray[theFieldIndex] == RTPMetaInfoPacket::kFieldNotUsed)) + { + theHeader.Ptr += 3; + continue; + } + + // + // Mark that this field has been requested by the client + foundFieldArray[theFieldIndex] = true; + + // + // This field is good to go... put it in the response + theFormatter.Put(theHeader.Ptr, sizeof(RTPMetaInfoPacket::FieldName)); + + if (inFieldIDArray[theFieldIndex] != RTPMetaInfoPacket::kUncompressed) + { + // + // If the caller wants this field to be compressed (there + // is an ID associated with the field), put the ID in the response + theFormatter.PutChar('='); + theFormatter.Put(inFieldIDArray[theFieldIndex]); + } + + // + // Field separator + theFormatter.PutChar(';'); + + // + // Skip onto the next field name in the header + theHeader.Ptr += 3; + } + + // + // Go through the caller's FieldID array, and turn off the fields + // that were not requested by the client. + for (UInt32 x = 0; x < RTPMetaInfoPacket::kNumFields; x++) + { + if (!foundFieldArray[x]) + inFieldIDArray[x] = RTPMetaInfoPacket::kFieldNotUsed; + } + + // + // No intersection between requested headers and supported headers! + if (theFormatter.GetCurrentOffset() == 0) + return QTSS_ValueNotFound; // Not really the greatest error! + + // + // When appending the header to the response, strip off the last ';'. + // It's not needed. + return QTSS_AppendRTSPHeader(inRequest, qtssXRTPMetaInfoHeader, theFormatter.GetBufPtr(), theFormatter.GetCurrentOffset() - 1); +} + +QTSS_Error QTSSModuleUtils::SendErrorResponse( QTSS_RTSPRequestObject inRequest, + QTSS_RTSPStatusCode inStatusCode, + QTSS_AttributeID inTextMessage, + StrPtrLen* inStringArg) +{ + static Bool16 sFalse = false; + + //set RTSP headers necessary for this error response message + (void)QTSS_SetValue(inRequest, qtssRTSPReqStatusCode, 0, &inStatusCode, sizeof(inStatusCode)); + (void)QTSS_SetValue(inRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse)); + StringFormatter theErrorMsgFormatter(NULL, 0); + char *messageBuffPtr = NULL; + + if (sEnableRTSPErrorMsg) + { + // Retrieve the specified message out of the text messages dictionary. + StrPtrLen theMessage; + (void)QTSS_GetValuePtr(sMessages, inTextMessage, 0, (void**)&theMessage.Ptr, &theMessage.Len); + + if ((theMessage.Ptr == NULL) || (theMessage.Len == 0)) + { + // If we couldn't find the specified message, get the default + // "No Message" message, and return that to the client instead. + + (void)QTSS_GetValuePtr(sMessages, qtssMsgNoMessage, 0, (void**)&theMessage.Ptr, &theMessage.Len); + } + Assert(theMessage.Ptr != NULL); + Assert(theMessage.Len > 0); + + // Allocate a temporary buffer for the error message, and format the error message + // into that buffer + UInt32 theMsgLen = 256; + if (inStringArg != NULL) + theMsgLen += inStringArg->Len; + + messageBuffPtr = NEW char[theMsgLen]; + messageBuffPtr[0] = 0; + theErrorMsgFormatter.Set(messageBuffPtr, theMsgLen); + // + // Look for a %s in the string, and if one exists, replace it with the + // argument passed into this function. + + //we can safely assume that message is in fact NULL terminated + char* stringLocation = ::strstr(theMessage.Ptr, "%s"); + if (stringLocation != NULL) + { + //write first chunk + theErrorMsgFormatter.Put(theMessage.Ptr, stringLocation - theMessage.Ptr); + + if (inStringArg != NULL && inStringArg->Len > 0) + { + //write string arg if it exists + theErrorMsgFormatter.Put(inStringArg->Ptr, inStringArg->Len); + stringLocation += 2; + } + //write last chunk + theErrorMsgFormatter.Put(stringLocation, (theMessage.Ptr + theMessage.Len) - stringLocation); + } + else + theErrorMsgFormatter.Put(theMessage); + + + char buff[32]; + qtss_sprintf(buff,"%"_U32BITARG_"",theErrorMsgFormatter.GetBytesWritten()); + (void)QTSS_AppendRTSPHeader(inRequest, qtssContentLengthHeader, buff, ::strlen(buff)); + } + + //send the response header. In all situations where errors could happen, we + //don't really care, cause there's nothing we can do anyway! + (void)QTSS_SendRTSPHeaders(inRequest); + + // + // Now that we've formatted the message into the temporary buffer, + // write it out to the request stream and the Client Session object + (void)QTSS_Write(inRequest, theErrorMsgFormatter.GetBufPtr(), theErrorMsgFormatter.GetBytesWritten(), NULL, 0); + (void)QTSS_SetValue(inRequest, qtssRTSPReqRespMsg, 0, theErrorMsgFormatter.GetBufPtr(), theErrorMsgFormatter.GetBytesWritten()); + + delete [] messageBuffPtr; + return QTSS_RequestFailed; +} + +QTSS_Error QTSSModuleUtils::SendErrorResponseWithMessage( QTSS_RTSPRequestObject inRequest, + QTSS_RTSPStatusCode inStatusCode, + StrPtrLen* inErrorMessagePtr) +{ + static Bool16 sFalse = false; + + //set RTSP headers necessary for this error response message + (void)QTSS_SetValue(inRequest, qtssRTSPReqStatusCode, 0, &inStatusCode, sizeof(inStatusCode)); + (void)QTSS_SetValue(inRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse)); + StrPtrLen theErrorMessage(NULL, 0); + + if (sEnableRTSPErrorMsg) + { + Assert(inErrorMessagePtr != NULL); + //Assert(inErrorMessagePtr->Ptr != NULL); + //Assert(inErrorMessagePtr->Len != 0); + theErrorMessage.Set(inErrorMessagePtr->Ptr, inErrorMessagePtr->Len); + + char buff[32]; + qtss_sprintf(buff,"%"_U32BITARG_"",inErrorMessagePtr->Len); + (void)QTSS_AppendRTSPHeader(inRequest, qtssContentLengthHeader, buff, ::strlen(buff)); + } + + //send the response header. In all situations where errors could happen, we + //don't really care, cause there's nothing we can do anyway! + (void)QTSS_SendRTSPHeaders(inRequest); + + // + // Now that we've formatted the message into the temporary buffer, + // write it out to the request stream and the Client Session object + (void)QTSS_Write(inRequest, theErrorMessage.Ptr, theErrorMessage.Len, NULL, 0); + (void)QTSS_SetValue(inRequest, qtssRTSPReqRespMsg, 0, theErrorMessage.Ptr, theErrorMessage.Len); + + return QTSS_RequestFailed; +} + + +QTSS_Error QTSSModuleUtils::SendHTTPErrorResponse( QTSS_RTSPRequestObject inRequest, + QTSS_SessionStatusCode inStatusCode, + Bool16 inKillSession, + char *errorMessage) +{ + static Bool16 sFalse = false; + + //set status code for access log + (void)QTSS_SetValue(inRequest, qtssRTSPReqStatusCode, 0, &inStatusCode, sizeof(inStatusCode)); + + if (inKillSession) // tell the server to end the session + (void)QTSS_SetValue(inRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse)); + + ResizeableStringFormatter theErrorMessage(NULL, 0); //allocates and deletes memory + ResizeableStringFormatter bodyMessage(NULL,0); //allocates and deletes memory + + char messageLineBuffer[64]; // used for each line + static const int maxMessageBufferChars = sizeof(messageLineBuffer) -1; + messageLineBuffer[maxMessageBufferChars] = 0; // guarantee termination + + // ToDo: put in a more meaningful http error message for each error. Not required by spec. + // ToDo: maybe use the HTTP protcol class static error strings. + char* errorMsg = "error"; + + DateBuffer theDate; + DateTranslator::UpdateDateBuffer(&theDate, 0); // get the current GMT date and time + + UInt32 realCode = 0; + UInt32 len = sizeof(realCode); + (void) QTSS_GetValue(inRequest, qtssRTSPReqRealStatusCode, 0, (void*)&realCode,&len); + + char serverHeaderBuffer[64]; // the qtss Server: header field + len = sizeof(serverHeaderBuffer) -1; // leave room for terminator + (void) QTSS_GetValue(sServer, qtssSvrRTSPServerHeader, 0, (void*)serverHeaderBuffer,&len); + serverHeaderBuffer[len] = 0; // terminate. + + qtss_snprintf(messageLineBuffer,maxMessageBufferChars, "HTTP/1.1 %"_U32BITARG_" %s",realCode, errorMsg); + theErrorMessage.Put(messageLineBuffer,::strlen(messageLineBuffer)); + theErrorMessage.PutEOL(); + + theErrorMessage.Put(serverHeaderBuffer,::strlen(serverHeaderBuffer)); + theErrorMessage.PutEOL(); + + qtss_snprintf(messageLineBuffer,maxMessageBufferChars, "Date: %s",theDate.GetDateBuffer()); + theErrorMessage.Put(messageLineBuffer,::strlen(messageLineBuffer)); + theErrorMessage.PutEOL(); + + Bool16 addBody = (errorMessage != NULL && ::strlen(errorMessage) != 0); // body error message so add body headers + if (addBody) // body error message so add body headers + { + // first create the html body + static const StrPtrLen htmlBodyStart("\n"); + bodyMessage.Put(htmlBodyStart.Ptr,htmlBodyStart.Len); + + //

errorMessage

\n + static const StrPtrLen hStart("

"); + bodyMessage.Put(hStart.Ptr,hStart.Len); + + bodyMessage.Put(errorMessage,::strlen(errorMessage)); + + static const StrPtrLen hTerm("

\n"); + bodyMessage.Put(hTerm.Ptr,hTerm.Len); + + static const StrPtrLen htmlBodyTerm("\n"); + bodyMessage.Put(htmlBodyTerm.Ptr,htmlBodyTerm.Len); + + // write body headers + static const StrPtrLen bodyHeaderType("Content-Type: text/html"); + theErrorMessage.Put(bodyHeaderType.Ptr,bodyHeaderType.Len); + theErrorMessage.PutEOL(); + + qtss_snprintf(messageLineBuffer,maxMessageBufferChars, "Content-Length: %"_U32BITARG_"", bodyMessage.GetBytesWritten()); + theErrorMessage.Put(messageLineBuffer,::strlen(messageLineBuffer)); + theErrorMessage.PutEOL(); + } + + static const StrPtrLen headerClose("Connection: close"); + theErrorMessage.Put(headerClose.Ptr,headerClose.Len); + theErrorMessage.PutEOL(); + + theErrorMessage.PutEOL(); // terminate headers with empty line + + if (addBody) // add html body + { + theErrorMessage.Put(bodyMessage.GetBufPtr(),bodyMessage.GetBytesWritten()); + } + + // + // Now that we've formatted the message into the temporary buffer, + // write it out to the request stream and the Client Session object + (void)QTSS_Write(inRequest, theErrorMessage.GetBufPtr(), theErrorMessage.GetBytesWritten(), NULL, 0); + (void)QTSS_SetValue(inRequest, qtssRTSPReqRespMsg, 0, theErrorMessage.GetBufPtr(), theErrorMessage.GetBytesWritten()); + + return QTSS_RequestFailed; +} + +void QTSSModuleUtils::SendDescribeResponse(QTSS_RTSPRequestObject inRequest, + QTSS_ClientSessionObject inSession, + iovec* describeData, + UInt32 inNumVectors, + UInt32 inTotalLength) +{ + //write content size header + char buf[32]; + qtss_sprintf(buf, "%"_S32BITARG_"", inTotalLength); + (void)QTSS_AppendRTSPHeader(inRequest, qtssContentLengthHeader, &buf[0], ::strlen(&buf[0])); + + (void)QTSS_SendStandardRTSPResponse(inRequest, inSession, 0); + + // On solaris, the maximum # of vectors is very low (= 16) so to ensure that we are still able to + // send the SDP if we have a number greater than the maximum allowed, we coalesce the vectors into + // a single big buffer +#ifdef __solaris__ + if (inNumVectors > IOV_MAX ) + { + char* describeDataBuffer = QTSSModuleUtils::CoalesceVectors(describeData, inNumVectors, inTotalLength); + (void)QTSS_Write(inRequest, (void *)describeDataBuffer, inTotalLength, NULL, qtssWriteFlagsNoFlags); + // deleting memory allocated by the CoalesceVectors call + delete [] describeDataBuffer; + } + else + (void)QTSS_WriteV(inRequest, describeData, inNumVectors, inTotalLength, NULL); +#else + (void)QTSS_WriteV(inRequest, describeData, inNumVectors, inTotalLength, NULL); +#endif + +} + +char* QTSSModuleUtils::CoalesceVectors(iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength) +{ + if (inTotalLength == 0) + return NULL; + + char* buffer = NEW char[inTotalLength]; + UInt32 bufferOffset = 0; + + for (UInt32 index = 0; index < inNumVectors; index++) + { + ::memcpy (buffer + bufferOffset, inVec[index].iov_base, inVec[index].iov_len); + bufferOffset += inVec[index].iov_len; + } + + Assert (bufferOffset == inTotalLength); + + return buffer; +} + +QTSS_ModulePrefsObject QTSSModuleUtils::GetModulePrefsObject(QTSS_ModuleObject inModObject) +{ + QTSS_ModulePrefsObject thePrefsObject = NULL; + UInt32 theLen = sizeof(thePrefsObject); + QTSS_Error theErr = QTSS_GetValue(inModObject, qtssModPrefs, 0, &thePrefsObject, &theLen); + Assert(theErr == QTSS_NoErr); + + return thePrefsObject; +} + +QTSS_Object QTSSModuleUtils::GetModuleAttributesObject(QTSS_ModuleObject inModObject) +{ + QTSS_Object theAttributesObject = NULL; + UInt32 theLen = sizeof(theAttributesObject); + QTSS_Error theErr = QTSS_GetValue(inModObject, qtssModAttributes, 0, &theAttributesObject, &theLen); + Assert(theErr == QTSS_NoErr); + + return theAttributesObject; +} + +QTSS_ModulePrefsObject QTSSModuleUtils::GetModuleObjectByName(const StrPtrLen& inModuleName) +{ + QTSS_ModuleObject theModule = NULL; + UInt32 theLen = sizeof(theModule); + + for (int x = 0; QTSS_GetValue(sServer, qtssSvrModuleObjects, x, &theModule, &theLen) == QTSS_NoErr; x++) + { + Assert(theModule != NULL); + Assert(theLen == sizeof(theModule)); + + StrPtrLen theName; + QTSS_Error theErr = QTSS_GetValuePtr(theModule, qtssModName, 0, (void**)&theName.Ptr, &theName.Len); + Assert(theErr == QTSS_NoErr); + + if (inModuleName.Equal(theName)) + return theModule; + +#if DEBUG + theModule = NULL; + theLen = sizeof(theModule); +#endif + } + return NULL; +} + +void QTSSModuleUtils::GetAttribute(QTSS_Object inObject, char* inAttributeName, QTSS_AttrDataType inType, + void* ioBuffer, void* inDefaultValue, UInt32 inBufferLen) +{ + // + // Check to make sure this attribute is the right type. If it's not, this will coerce + // it to be the right type. This also returns the id of the attribute + QTSS_AttributeID theID = QTSSModuleUtils::CheckAttributeDataType(inObject, inAttributeName, inType, inDefaultValue, inBufferLen); + + // + // Get the attribute value. + QTSS_Error theErr = QTSS_GetValue(inObject, theID, 0, ioBuffer, &inBufferLen); + + // + // Caller should KNOW how big this attribute is + Assert(theErr != QTSS_NotEnoughSpace); + + if (theErr != QTSS_NoErr) + { + // + // If we couldn't get the attribute value for whatever reason, just use the + // default if it was provided. + ::memcpy(ioBuffer, inDefaultValue, inBufferLen); + + if (inBufferLen > 0) + { + // + // Log an error for this pref only if there was a default value provided. + char* theValueAsString = NULL; + theErr = QTSS_ValueToString(inDefaultValue, inBufferLen, inType, &theValueAsString); + Assert(theErr == QTSS_NoErr); + OSCharArrayDeleter theValueStr(theValueAsString); + QTSSModuleUtils::LogError( sMissingPrefVerbosity, + qtssServerPrefMissing, + 0, + inAttributeName, + theValueStr.GetObject()); + } + + // + // Create an entry for this attribute + QTSSModuleUtils::CreateAttribute(inObject, inAttributeName, inType, inDefaultValue, inBufferLen); + } +} + +char* QTSSModuleUtils::GetStringAttribute(QTSS_Object inObject, char* inAttributeName, char* inDefaultValue) +{ + UInt32 theDefaultValLen = 0; + if (inDefaultValue != NULL) + theDefaultValLen = ::strlen(inDefaultValue); + + // + // Check to make sure this attribute is the right type. If it's not, this will coerce + // it to be the right type + QTSS_AttributeID theID = QTSSModuleUtils::CheckAttributeDataType(inObject, inAttributeName, qtssAttrDataTypeCharArray, inDefaultValue, theDefaultValLen); + + char* theString = NULL; + (void)QTSS_GetValueAsString(inObject, theID, 0, &theString); + if (theString != NULL) + return theString; + + // + // If we get here the attribute must be missing, so create it and log + // an error. + + QTSSModuleUtils::CreateAttribute(inObject, inAttributeName, qtssAttrDataTypeCharArray, inDefaultValue, theDefaultValLen); + + // + // Return the default if it was provided. Only log an error if the default value was provided + if (theDefaultValLen > 0) + { + QTSSModuleUtils::LogError( sMissingPrefVerbosity, + qtssServerPrefMissing, + 0, + inAttributeName, + inDefaultValue); + } + + if (inDefaultValue != NULL) + { + // + // Whether to return the default value or not from this function is dependent + // solely on whether the caller passed in a non-NULL pointer or not. + // This ensures that if the caller wants an empty-string returned as a default + // value, it can do that. + theString = NEW char[theDefaultValLen + 1]; + ::strcpy(theString, inDefaultValue); + return theString; + } + return NULL; +} + +void QTSSModuleUtils::GetIOAttribute(QTSS_Object inObject, char* inAttributeName, QTSS_AttrDataType inType, + void* ioDefaultResultBuffer, UInt32 inBufferLen) +{ + char *defaultBuffPtr = NEW char[inBufferLen]; + ::memcpy(defaultBuffPtr,ioDefaultResultBuffer,inBufferLen); + QTSSModuleUtils::GetAttribute(inObject, inAttributeName, inType, ioDefaultResultBuffer, defaultBuffPtr, inBufferLen); + delete [] defaultBuffPtr; + +} + + +QTSS_AttributeID QTSSModuleUtils::GetAttrID(QTSS_Object inObject, char* inAttributeName) +{ + // + // Get the attribute ID of this attribute. + QTSS_Object theAttrInfo = NULL; + QTSS_Error theErr = QTSS_GetAttrInfoByName(inObject, inAttributeName, &theAttrInfo); + if (theErr != QTSS_NoErr) + return qtssIllegalAttrID; + + QTSS_AttributeID theID = qtssIllegalAttrID; + UInt32 theLen = sizeof(theID); + theErr = QTSS_GetValue(theAttrInfo, qtssAttrID, 0, &theID, &theLen); + Assert(theErr == QTSS_NoErr); + + return theID; +} + +QTSS_AttributeID QTSSModuleUtils::CheckAttributeDataType(QTSS_Object inObject, char* inAttributeName, QTSS_AttrDataType inType, void* inDefaultValue, UInt32 inBufferLen) +{ + // + // Get the attribute type of this attribute. + QTSS_Object theAttrInfo = NULL; + QTSS_Error theErr = QTSS_GetAttrInfoByName(inObject, inAttributeName, &theAttrInfo); + if (theErr != QTSS_NoErr) + return qtssIllegalAttrID; + + QTSS_AttrDataType theAttributeType = qtssAttrDataTypeUnknown; + UInt32 theLen = sizeof(theAttributeType); + theErr = QTSS_GetValue(theAttrInfo, qtssAttrDataType, 0, &theAttributeType, &theLen); + Assert(theErr == QTSS_NoErr); + + QTSS_AttributeID theID = qtssIllegalAttrID; + theLen = sizeof(theID); + theErr = QTSS_GetValue(theAttrInfo, qtssAttrID, 0, &theID, &theLen); + Assert(theErr == QTSS_NoErr); + + if (theAttributeType != inType) + { + char* theValueAsString = NULL; + theErr = QTSS_ValueToString(inDefaultValue, inBufferLen, inType, &theValueAsString); + Assert(theErr == QTSS_NoErr); + OSCharArrayDeleter theValueStr(theValueAsString); + QTSSModuleUtils::LogError( qtssWarningVerbosity, + qtssServerPrefWrongType, + 0, + inAttributeName, + theValueStr.GetObject()); + + theErr = QTSS_RemoveInstanceAttribute( inObject, theID ); + Assert(theErr == QTSS_NoErr); + return QTSSModuleUtils::CreateAttribute(inObject, inAttributeName, inType, inDefaultValue, inBufferLen); + } + return theID; +} + +QTSS_AttributeID QTSSModuleUtils::CreateAttribute(QTSS_Object inObject, char* inAttributeName, QTSS_AttrDataType inType, void* inDefaultValue, UInt32 inBufferLen) +{ + QTSS_Error theErr = QTSS_AddInstanceAttribute(inObject, inAttributeName, NULL, inType); + Assert((theErr == QTSS_NoErr) || (theErr == QTSS_AttrNameExists)); + + QTSS_AttributeID theID = QTSSModuleUtils::GetAttrID(inObject, inAttributeName); + Assert(theID != qtssIllegalAttrID); + + // + // Caller can pass in NULL for inDefaultValue, in which case we don't add the default + if (inDefaultValue != NULL) + { + theErr = QTSS_SetValue(inObject, theID, 0, inDefaultValue, inBufferLen); + Assert(theErr == QTSS_NoErr); + } + return theID; +} + +QTSS_ActionFlags QTSSModuleUtils::GetRequestActions(QTSS_RTSPRequestObject theRTSPRequest) +{ + // Don't touch write requests + QTSS_ActionFlags action = qtssActionFlagsNoFlags; + UInt32 len = sizeof(QTSS_ActionFlags); + QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAction, 0, (void*)&action, &len); + Assert(theErr == QTSS_NoErr); + Assert(len == sizeof(QTSS_ActionFlags)); + return action; +} + +char* QTSSModuleUtils::GetLocalPath_Copy(QTSS_RTSPRequestObject theRTSPRequest) +{ char* pathBuffStr = NULL; + QTSS_Error theErr = QTSS_GetValueAsString(theRTSPRequest, qtssRTSPReqLocalPath, 0, &pathBuffStr); + Assert(theErr == QTSS_NoErr); + return pathBuffStr; +} + +char* QTSSModuleUtils::GetMoviesRootDir_Copy(QTSS_RTSPRequestObject theRTSPRequest) +{ char* movieRootDirStr = NULL; + QTSS_Error theErr = QTSS_GetValueAsString(theRTSPRequest,qtssRTSPReqRootDir, 0, &movieRootDirStr); + Assert(theErr == QTSS_NoErr); + return movieRootDirStr; +} + +QTSS_UserProfileObject QTSSModuleUtils::GetUserProfileObject(QTSS_RTSPRequestObject theRTSPRequest) +{ QTSS_UserProfileObject theUserProfile = NULL; + UInt32 len = sizeof(QTSS_UserProfileObject); + QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqUserProfile, 0, (void*)&theUserProfile, &len); + Assert(theErr == QTSS_NoErr); + return theUserProfile; +} + +char *QTSSModuleUtils::GetUserName_Copy(QTSS_UserProfileObject inUserProfile) +{ + char* username = NULL; + (void) QTSS_GetValueAsString(inUserProfile, qtssUserName, 0, &username); + return username; +} + +char** QTSSModuleUtils::GetGroupsArray_Copy(QTSS_UserProfileObject inUserProfile, UInt32 *outNumGroupsPtr) +{ + Assert(NULL != outNumGroupsPtr); + + char** outGroupCharPtrArray = NULL; + *outNumGroupsPtr = 0; + + if (NULL == inUserProfile) + return NULL; + + QTSS_Error theErr = QTSS_GetNumValues (inUserProfile,qtssUserGroups, outNumGroupsPtr); + if (theErr != QTSS_NoErr || *outNumGroupsPtr == 0) + return NULL; + + outGroupCharPtrArray = NEW char*[*outNumGroupsPtr]; // array of char * + UInt32 len = 0; + for (UInt32 index = 0; index < *outNumGroupsPtr; index++) + { outGroupCharPtrArray[index] = NULL; + QTSS_GetValuePtr(inUserProfile, qtssUserGroups, index,(void **) &outGroupCharPtrArray[index], &len); + } + + return outGroupCharPtrArray; +} + +Bool16 QTSSModuleUtils::UserInGroup(QTSS_UserProfileObject inUserProfile, char* inGroup, UInt32 inGroupLen) +{ + if (NULL == inUserProfile || NULL == inGroup || inGroupLen == 0) + return false; + + char *userName = NULL; + UInt32 len = 0; + QTSS_GetValuePtr(inUserProfile, qtssUserName, 0, (void **)&userName, &len); + if (len == 0 || userName == NULL || userName[0] == 0) // no user to check + return false; + + UInt32 numGroups = 0; + QTSS_GetNumValues (inUserProfile,qtssUserGroups, &numGroups); + if (numGroups == 0) // no groups to check + return false; + + Bool16 result = false; + char* userGroup = NULL; + StrPtrLenDel userGroupStr; //deletes pointer in destructor + + for (UInt32 index = 0; index < numGroups; index++) + { + userGroup = NULL; + QTSS_GetValueAsString(inUserProfile, qtssUserGroups, index, &userGroup); //allocates string + userGroupStr.Delete(); + userGroupStr.Set(userGroup); + if(userGroupStr.Equal(inGroup)) + { + result = true; + break; + } + } + + return result; + +} + + +Bool16 QTSSModuleUtils::AddressInList(QTSS_Object inObject, QTSS_AttributeID listID, StrPtrLen *inAddressPtr) +{ + StrPtrLenDel strDeleter; + char* theAttributeString = NULL; + IPComponentStr inAddress(inAddressPtr); + IPComponentStr addressFromList; + + if (!inAddress.Valid()) + return false; + + UInt32 numValues = 0; + (void) QTSS_GetNumValues(inObject, listID, &numValues); + + for (UInt32 index = 0; index < numValues; index ++) + { + strDeleter.Delete(); + (void) QTSS_GetValueAsString(inObject, listID, index, &theAttributeString); + strDeleter.Set(theAttributeString); + + addressFromList.Set(&strDeleter); + if (addressFromList.Equal(&inAddress)) + return true; + } + + return false; +} + +Bool16 QTSSModuleUtils::FindStringInAttributeList(QTSS_Object inObject, QTSS_AttributeID listID, StrPtrLen *inStrPtr) +{ + StrPtrLenDel tempString; + + if (NULL == inStrPtr || NULL == inStrPtr->Ptr || 0 == inStrPtr->Len) + return false; + + UInt32 numValues = 0; + (void) QTSS_GetNumValues(inObject, listID, &numValues); + + for (UInt32 index = 0; index < numValues; index ++) + { + tempString.Delete(); + (void) QTSS_GetValueAsString(inObject, listID, index, &tempString.Ptr); + tempString.Set(tempString.Ptr); + if (tempString.Ptr == NULL) + return false; + + if (tempString.Equal(StrPtrLen("*",1))) + return true; + + if (inStrPtr->FindString(tempString.Ptr)) + return true; + + } + + return false; +} + +Bool16 QTSSModuleUtils::HavePlayerProfile(QTSS_PrefsObject inPrefObjectToCheck, QTSS_StandardRTSP_Params* inParams, UInt32 feature) +{ + StrPtrLenDel userAgentStr; + (void)QTSS_GetValueAsString(inParams->inClientSession, qtssCliSesFirstUserAgent, 0, &userAgentStr.Ptr); + userAgentStr.Set(userAgentStr.Ptr); + + switch (feature) + { + case QTSSModuleUtils::kRequiresRTPInfoSeqAndTime: + { + return QTSSModuleUtils::FindStringInAttributeList(inPrefObjectToCheck, qtssPrefsPlayersReqRTPHeader, &userAgentStr); + + } + break; + + case QTSSModuleUtils::kAdjustBandwidth: + { + return QTSSModuleUtils::FindStringInAttributeList(inPrefObjectToCheck, qtssPrefsPlayersReqBandAdjust, &userAgentStr); + } + break; + + case QTSSModuleUtils::kDisablePauseAdjustedRTPTime: + { + return QTSSModuleUtils::FindStringInAttributeList(inPrefObjectToCheck, qtssPrefsPlayersReqNoPauseTimeAdjust, &userAgentStr); + } + break; + + case QTSSModuleUtils::kDelayRTPStreamsUntilAfterRTSPResponse: + { + return QTSSModuleUtils::FindStringInAttributeList(inPrefObjectToCheck, qtssPrefsPlayersReqRTPStartTimeAdjust, &userAgentStr); + } + break; + + case QTSSModuleUtils::kDisable3gppRateAdaptation: + { + return QTSSModuleUtils::FindStringInAttributeList(inPrefObjectToCheck, qtssPrefsPlayersReqDisable3gppRateAdapt, &userAgentStr); + } + break; + + + case QTSSModuleUtils::kAdjust3gppTargetTime: + { + return QTSSModuleUtils::FindStringInAttributeList(inPrefObjectToCheck, qtssPrefsPlayersReq3GPPTargetTime, &userAgentStr); + } + break; + + case QTSSModuleUtils::kDisableThinning: + { + return QTSSModuleUtils::FindStringInAttributeList(inPrefObjectToCheck, qtssPrefsPlayersReqDisableThinning, &userAgentStr); + } + break; + + } + + return false; +} + + +QTSS_Error QTSSModuleUtils::AuthorizeRequest(QTSS_RTSPRequestObject theRTSPRequest, Bool16* allowed, Bool16*foundUser, Bool16 *authContinue) +{ + QTSS_Error theErr = QTSS_NoErr; + //printf("QTSSModuleUtils::AuthorizeRequest allowed=%d foundUser=%d authContinue=%d\n", *allowed, *foundUser, *authContinue); + + if (NULL != allowed) + theErr = QTSS_SetValue(theRTSPRequest,qtssRTSPReqUserAllowed, 0, allowed, sizeof(Bool16)); + if (QTSS_NoErr != theErr) + return theErr; + + if (NULL != foundUser) + theErr = QTSS_SetValue(theRTSPRequest,qtssRTSPReqUserFound, 0, foundUser, sizeof(Bool16)); + if (QTSS_NoErr != theErr) + return theErr; + + if (NULL != authContinue) + theErr = QTSS_SetValue(theRTSPRequest,qtssRTSPReqAuthHandled, 0, authContinue, sizeof(Bool16)); + + return theErr; +} + + + + + +IPComponentStr IPComponentStr::sLocalIPCompStr("127.0.0.*"); + +IPComponentStr::IPComponentStr(char *theAddressPtr) +{ + StrPtrLen sourceStr(theAddressPtr); + (void) this->Set(&sourceStr); +} + +IPComponentStr::IPComponentStr(StrPtrLen *sourceStrPtr) +{ + (void) this->Set(sourceStrPtr); +} + +Bool16 IPComponentStr::Set(StrPtrLen *theAddressStrPtr) +{ + fIsValid = false; + + StringParser IP_Paser(theAddressStrPtr); + StrPtrLen *piecePtr = &fAddressComponent[0]; + + while (IP_Paser.GetDataRemaining() > 0) + { + IP_Paser.ConsumeUntil(piecePtr,'.'); + if (piecePtr->Len == 0) + break; + + IP_Paser.ConsumeLength(NULL, 1); + if (piecePtr == &fAddressComponent[IPComponentStr::kNumComponents -1]) + { + fIsValid = true; + break; + } + + piecePtr++; + }; + + return fIsValid; +} + + +Bool16 IPComponentStr::Equal(IPComponentStr *testAddressPtr) +{ + if (testAddressPtr == NULL) + return false; + + if ( !this->Valid() || !testAddressPtr->Valid() ) + return false; + + for (UInt16 component= 0 ; component < IPComponentStr::kNumComponents ; component ++) + { + StrPtrLen *allowedPtr = this->GetComponent(component); + StrPtrLen *testPtr = testAddressPtr->GetComponent(component); + + if ( testPtr->Equal("*") || allowedPtr->Equal("*") ) + continue; + + if (!testPtr->Equal(*allowedPtr) ) + return false; + }; + + return true; +} + + diff --git a/APICommonCode/QTSSModuleUtils.h b/APICommonCode/QTSSModuleUtils.h new file mode 100644 index 0000000..3d434e5 --- /dev/null +++ b/APICommonCode/QTSSModuleUtils.h @@ -0,0 +1,291 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr); + + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: QTSSModuleUtils.h + + Contains: Utility routines for modules to use. + +*/ + + +#ifndef _QTSS_MODULE_UTILS_H_ +#define _QTSS_MODULE_UTILS_H_ + +#include +#include "SafeStdLib.h" +#include "QTSS.h" +#include "StrPtrLen.h" +#include "RTPMetaInfoPacket.h" + +class QTSSModuleUtils +{ + public: + // compatibiltiy features for certain players + + enum { + kRequiresRTPInfoSeqAndTime = 0, + kAdjustBandwidth = 1, + kDisablePauseAdjustedRTPTime= 2, + kDelayRTPStreamsUntilAfterRTSPResponse = 3, + kDisable3gppRateAdaptation =4, + kAdjust3gppTargetTime = 5, + kDisableThinning =6, + }; + + + static void Initialize( QTSS_TextMessagesObject inMessages, + QTSS_ServerObject inServer, + QTSS_StreamRef inErrorLog); + + // Read the complete contents of the file at inPath into the StrPtrLen. + // This function allocates memory for the file data. + static QTSS_Error ReadEntireFile(char* inPath, StrPtrLen* outData, QTSS_TimeVal inModDate = -1, QTSS_TimeVal* outModDate = NULL); + + // If your module supports RTSP methods, call this function from your QTSS_Initialize + // role to tell the server what those methods are. + static void SetupSupportedMethods( QTSS_Object inServer, + QTSS_RTSPMethod* inMethodArray, + UInt32 inNumMethods); + + // Using a message out of the text messages dictionary is a common + // way to log errors to the error log. Here is a function to + // make that process very easy. + + static void LogError( QTSS_ErrorVerbosity inVerbosity, + QTSS_AttributeID inTextMessage, + UInt32 inErrNumber, + char* inArgument = NULL, + char* inArg2 = NULL); + + static void LogErrorStr( QTSS_ErrorVerbosity inVerbosity, char* inMessage); + static void LogPrefErrorStr( QTSS_ErrorVerbosity inVerbosity, char* preference, char* inMessage); + + // This function constructs a C-string of the full path to the file being requested. + // You may opt to append an optional suffix, or pass in NULL. You are responsible + // for disposing this memory + + static char* GetFullPath( QTSS_RTSPRequestObject inRequest, + QTSS_AttributeID whichFileType, + UInt32* outLen, + StrPtrLen* suffix = NULL); + + // + // This function does 2 things: + // 1. Compares the enabled fields in the field ID array with the fields in the + // x-RTP-Meta-Info header. Turns off the fields in the array that aren't in the request. + // + // 2. Appends the x-RTP-Meta-Info header to the response, using the proper + // fields from the array, as well as the IDs provided in the array + static QTSS_Error AppendRTPMetaInfoHeader( QTSS_RTSPRequestObject inRequest, + StrPtrLen* inRTPMetaInfoHeader, + RTPMetaInfoPacket::FieldID* inFieldIDArray); + + // This function sends an error to the RTSP client. You must provide a + // status code for the error, and a text message ID to describe the error. + // + // It always returns QTSS_RequestFailed. + + static QTSS_Error SendErrorResponse( QTSS_RTSPRequestObject inRequest, + QTSS_RTSPStatusCode inStatusCode, + QTSS_AttributeID inTextMessage, + StrPtrLen* inStringArg = NULL); + + // This function sends an error to the RTSP client. You don't have to provide + // a text message ID, but instead you need to provide the error message in a + // string + // + // It always returns QTSS_RequestFailed + static QTSS_Error SendErrorResponseWithMessage( QTSS_RTSPRequestObject inRequest, + QTSS_RTSPStatusCode inStatusCode, + StrPtrLen* inErrorMessageStr); + + // Sends and HTTP 1.1 error message with an error message in HTML if errorMessage != NULL. + // The session must be flagged by KillSession set to true to kill. + // Use the QTSS_RTSPStatusCodes for the inStatusCode, for now they are the same as HTTP. + // + // It always returns QTSS_RequestFailed + static QTSS_Error SendHTTPErrorResponse( QTSS_RTSPRequestObject inRequest, + QTSS_SessionStatusCode inStatusCode, + Bool16 inKillSession, + char *errorMessage); + + //Modules most certainly don't NEED to use this function, but it is awfully handy + //if they want to take advantage of it. Using the SDP data provided in the iovec, + //this function sends a standard describe response. + //NOTE: THE FIRST ENTRY OF THE IOVEC MUST BE EMPTY!!!! + static void SendDescribeResponse(QTSS_RTSPRequestObject inRequest, + QTSS_ClientSessionObject inSession, + iovec* describeData, + UInt32 inNumVectors, + UInt32 inTotalLength); + + + // Called by SendDescribeResponse to coalesce iovec to a buffer + // Allocates memory - remember to delete it! + static char* CoalesceVectors(iovec* inVec, UInt32 inNumVectors, UInt32 inTotalLength); + + // + // SEARCH FOR A SPECIFIC MODULE OBJECT + static QTSS_ModulePrefsObject GetModuleObjectByName(const StrPtrLen& inModuleName); + + // + // GET MODULE PREFS OBJECT + static QTSS_ModulePrefsObject GetModulePrefsObject(QTSS_ModuleObject inModObject); + + // GET MODULE ATTRIBUTES OBJECT + static QTSS_Object GetModuleAttributesObject(QTSS_ModuleObject inModObject); + + // + // GET ATTRIBUTE + // + // This function retrieves an attribute + // (from any QTSS_Object, including the QTSS_ModulePrefsObject) + // with the specified name and type + // out of the specified object. + // + // Caller should pass in a buffer for ioBuffer that is large enough + // to hold the attribute value. inBufferLen should be set to the length + // of this buffer. + // + // Pass in a buffer containing a default value to use for the attribute + // in the inDefaultValue parameter. If the attribute isn't found, or is + // of the wrong type, the default value will be copied into ioBuffer. + // Also, this function adds the default value to object if it is not + // found or is of the wrong type. If no default value is provided, the + // attribute is still added but no value is assigned to it. + // + // Pass in NULL for the default value or 0 for the default value length if it is not known. + // + // This function logs an error if there was a default value provided. + static void GetAttribute(QTSS_Object inObject, char* inAttributeName, QTSS_AttrDataType inType, + void* ioBuffer, void* inDefaultValue, UInt32 inBufferLen); + + static void GetIOAttribute(QTSS_Object inObject, char* inAttributeName, QTSS_AttrDataType inType, + void* ioDefaultResultBuffer, UInt32 inBufferLen); + // + // GET STRING ATTRIBUTE + // + // Does the same thing as GetAttribute, but does it for string attribute. Returns a newly + // allocated buffer with the attribute value inside it. + // + // Pass in NULL for the default value or an empty string if the default is not known. + static char* GetStringAttribute(QTSS_Object inObject, char* inAttributeName, char* inDefaultValue); + + // + // GET ATTR ID + // + // Given an attribute in an object, returns its attribute ID + // or qtssIllegalAttrID if it isn't found. + static QTSS_AttributeID GetAttrID(QTSS_Object inObject, char* inAttributeName); + + // + // + // + /// Get the type of request. Returns qtssActionFlagsNoFlags on failure. + // Result is a bitmap of flags + // + static QTSS_ActionFlags GetRequestActions(QTSS_RTSPRequestObject theRTSPRequest); + + static char* GetLocalPath_Copy(QTSS_RTSPRequestObject theRTSPRequest); + static char* GetMoviesRootDir_Copy(QTSS_RTSPRequestObject theRTSPRequest); + static QTSS_UserProfileObject GetUserProfileObject(QTSS_RTSPRequestObject theRTSPRequest); + static QTSS_AttrRights GetRights(QTSS_UserProfileObject theUserProfileObject); + static char* GetExtendedRights(QTSS_UserProfileObject theUserProfileObject, UInt32 index); + + static char* GetUserName_Copy(QTSS_UserProfileObject inUserProfile); + static char** GetGroupsArray_Copy(QTSS_UserProfileObject inUserProfile, UInt32 *outNumGroupsPtr); + static Bool16 UserInGroup(QTSS_UserProfileObject inUserProfile, char* inGroupName, UInt32 inGroupNameLen); + + static void SetEnableRTSPErrorMsg(Bool16 enable) {QTSSModuleUtils::sEnableRTSPErrorMsg = enable; } + + static QTSS_AttributeID CreateAttribute(QTSS_Object inObject, char* inAttributeName, QTSS_AttrDataType inType, void* inDefaultValue, UInt32 inBufferLen); + + static Bool16 AddressInList(QTSS_Object inObject, QTSS_AttributeID listID, StrPtrLen *theAddressPtr); + + static void SetMisingPrefLogVerbosity(QTSS_ErrorVerbosity verbosityLevel) { QTSSModuleUtils::sMissingPrefVerbosity = verbosityLevel;} + static QTSS_ErrorVerbosity GetMisingPrefLogVerbosity() { return QTSSModuleUtils::sMissingPrefVerbosity;} + + static Bool16 FindStringInAttributeList(QTSS_Object inObject, QTSS_AttributeID listID, StrPtrLen *inStrPtr); + + static Bool16 HavePlayerProfile(QTSS_PrefsObject inPrefObjectToCheck, QTSS_StandardRTSP_Params* inParams, UInt32 feature); + + static QTSS_Error AuthorizeRequest(QTSS_RTSPRequestObject theRTSPRequest, Bool16* allowed, Bool16*haveUser,Bool16 *authContinue); + + + private: + + // + // Used in the implementation of the above functions + static QTSS_AttributeID CheckAttributeDataType(QTSS_Object inObject, char* inAttributeName, QTSS_AttrDataType inType, void* inDefaultValue, UInt32 inBufferLen); + + static QTSS_TextMessagesObject sMessages; + static QTSS_ServerObject sServer; + static QTSS_StreamRef sErrorLog; + static Bool16 sEnableRTSPErrorMsg; + static QTSS_ErrorVerbosity sMissingPrefVerbosity; +}; + + +class IPComponentStr +{ + public: + enum { kNumComponents = 4 }; + + StrPtrLen fAddressComponent[kNumComponents]; + Bool16 fIsValid; + static IPComponentStr sLocalIPCompStr; + + IPComponentStr() : fIsValid(false) {} + IPComponentStr(char *theAddress); + IPComponentStr(StrPtrLen *sourceStrPtr); + +inline StrPtrLen* GetComponent(UInt16 which); + Bool16 Equal(IPComponentStr *testAddressPtr); + Bool16 Set(StrPtrLen *theAddressStrPtr); + Bool16 Valid() { return fIsValid; } +inline Bool16 IsLocal(); + +}; + + +Bool16 IPComponentStr::IsLocal() +{ + if (this->Equal(&sLocalIPCompStr)) + return true; + + return false; +} + +StrPtrLen* IPComponentStr::GetComponent(UInt16 which) +{ + if (which < IPComponentStr::kNumComponents) + return &fAddressComponent[which]; + + Assert(0); + return NULL; +} + +#endif //_QTSS_MODULE_UTILS_H_ diff --git a/APICommonCode/QTSSRollingLog.cpp b/APICommonCode/QTSSRollingLog.cpp new file mode 100644 index 0000000..115cd5e --- /dev/null +++ b/APICommonCode/QTSSRollingLog.cpp @@ -0,0 +1,550 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: QTSSRollingLog.cpp + + Contains: Implements object defined in .h file + + +*/ + +#include +#include +#include "SafeStdLib.h" +#include +#include +#include +#include +#include +#ifndef __Win32__ +#include +#endif +#include "QTSSRollingLog.h" +#include "OS.h" +#include "OSMemory.h" +#include "OSArrayObjectDeleter.h" +#include "ResizeableStringFormatter.h" + +static Bool16 sCloseOnWrite = true; + + QTSSRollingLog::QTSSRollingLog() : + fLog(NULL), + fLogCreateTime(-1), + fLogFullPath(NULL), + fAppendDotLog(true), + fLogging(true) +{ + this->SetTaskName("QTSSRollingLog"); +} + +QTSSRollingLog::~QTSSRollingLog() +{ + // + // Log should already be closed, but just in case... + this->CloseLog(); + delete [] fLogFullPath; +} + +// Set this to true to get the log to close the file between writes. +void QTSSRollingLog::SetCloseOnWrite(Bool16 closeOnWrite) +{ + sCloseOnWrite = closeOnWrite; +} + +Bool16 QTSSRollingLog::IsLogEnabled() +{ + return sCloseOnWrite || (fLog != NULL); +} + +void QTSSRollingLog::WriteToLog(char* inLogData, Bool16 allowLogToRoll) +{ + OSMutexLocker locker(&fMutex); + + if (fLogging == false) + return; + + if (sCloseOnWrite && fLog == NULL) + this->EnableLog(fAppendDotLog ); //re-open log file before we write + + if (allowLogToRoll) + (void)this->CheckRollLog(); + + if (fLog != NULL) + { + qtss_fprintf(fLog, "%s", inLogData); + ::fflush(fLog); + } + + if (sCloseOnWrite) + this->CloseLog( false ); +} + +Bool16 QTSSRollingLog::RollLog() +{ + OSMutexLocker locker(&fMutex); + + //returns false if an error occurred, true otherwise + + //close the old file. + if (fLog != NULL) + this->CloseLog(); + + if (fLogging == false) + return false; + + //rename the old file + Bool16 result = this->RenameLogFile(fLogFullPath); + if (result) + this->EnableLog(fAppendDotLog);//re-opens log file + + return result; +} + +char* QTSSRollingLog::GetLogPath(char *extension) +{ + char *thePath = NULL; + + OSCharArrayDeleter logDir(this->GetLogDir()); //The string passed into this function is a copy + OSCharArrayDeleter logName(this->GetLogName()); //The string passed into this function is a copy + + ResizeableStringFormatter formatPath(NULL,0); //allocate the buffer + formatPath.PutFilePath(logDir, logName); + + if ( extension != NULL) + formatPath.Put(extension); + + formatPath.PutTerminator(); + thePath = formatPath.GetBufPtr(); + + formatPath.Set(NULL,0); //don't delete buffer we are returning the path as a result + + return thePath; +} + +void QTSSRollingLog::EnableLog( Bool16 appendDotLog ) +{ + // + // Start this object running! + this->Signal(Task::kStartEvent); + + OSMutexLocker locker(&fMutex); + + fAppendDotLog = appendDotLog; + + if (fLogging == false) + return; + + char *extension = ".log"; + if (!appendDotLog) + extension = NULL; + + delete[] fLogFullPath; + fLogFullPath = this->GetLogPath(extension); + + //we need to make sure that when we create a new log file, we write the + //log header at the top + Bool16 logExists = this->DoesFileExist(fLogFullPath); + + //create the log directory if it doesn't already exist + if (!logExists) + { + OSCharArrayDeleter tempDir(this->GetLogDir()); + OS::RecursiveMakeDir(tempDir.GetObject()); + } + + fLog = ::fopen(fLogFullPath, "a+");//open for "append" + if (NULL != fLog) + { + if (!logExists) //the file is new, write a log header with the create time of the file. + { fLogCreateTime = this->WriteLogHeader(fLog); +#if __MacOSX__ + (void) ::chown(fLogFullPath, 76, (gid_t)-1);//set owner to user qtss. +#endif + } + else //the file is old, read the log header to find the create time of the file. + fLogCreateTime = this->ReadLogHeader(fLog); + } +} + +void QTSSRollingLog::CloseLog( Bool16 leaveEnabled ) +{ + OSMutexLocker locker(&fMutex); + + if (leaveEnabled) + sCloseOnWrite = true; + + if (fLog != NULL) + { + ::fclose(fLog); + fLog = NULL; + } +} + +//returns false if some error has occurred +Bool16 QTSSRollingLog::FormatDate(char *ioDateBuffer, Bool16 logTimeInGMT) +{ + Assert(NULL != ioDateBuffer); + + //use ansi routines for getting the date. + time_t calendarTime = ::time(NULL); + Assert(-1 != calendarTime); + if (-1 == calendarTime) + return false; + + struct tm* theTime = NULL; + struct tm timeResult; + + if (logTimeInGMT) + theTime = ::qtss_gmtime(&calendarTime, &timeResult); + else + theTime = qtss_localtime(&calendarTime, &timeResult); + + Assert(NULL != theTime); + + if (NULL == theTime) + return false; + + // date time needs to look like this for extended log file format: 2001-03-16 23:34:54 + // this wonderful ANSI routine just does it for you. + // the format is YYYY-MM-DD HH:MM:SS + // the date time is in GMT, unless logTimeInGMT is false, in which case + // the time logged is local time + //qtss_strftime(ioDateBuffer, kMaxDateBufferSize, "%d/%b/%Y:%H:%M:%S", theLocalTime); + qtss_strftime(ioDateBuffer, kMaxDateBufferSizeInBytes, "%Y-%m-%d %H:%M:%S", theTime); + return true; +} + +Bool16 QTSSRollingLog::CheckRollLog() +{ + //returns false if an error occurred, true otherwise + if (fLog == NULL) + return true; + + //first check to see if log rolling should happen because of a date interval. + //This takes precedence over size based log rolling + // this is only if a client connects just between 00:00:00 and 00:01:00 + // since the task object runs every minute + + // when an entry is written to the log file, only the file size must be checked + // to see if it exceeded the limits + + // the roll interval should be monitored in a task object + // and rolled at midnight if the creation time has exceeded. + if ((-1 != fLogCreateTime) && (0 != this->GetRollIntervalInDays())) + { + time_t logCreateTimeMidnight = -1; + QTSSRollingLog::ResetToMidnight(&fLogCreateTime, &logCreateTimeMidnight); + Assert(logCreateTimeMidnight != -1); + + time_t calendarTime = ::time(NULL); + + Assert(-1 != calendarTime); + if (-1 != calendarTime) + { + double theExactInterval = ::difftime(calendarTime, logCreateTimeMidnight); + SInt32 theCurInterval = (SInt32)::floor(theExactInterval); + + //transfer_roll_interval is in days, theCurInterval is in seconds + SInt32 theRollInterval = this->GetRollIntervalInDays() * 60 * 60 * 24; + if (theCurInterval > theRollInterval) + return this->RollLog(); + } + } + + + //now check size based log rolling + UInt32 theCurrentPos = ::ftell(fLog); + //max_transfer_log_size being 0 is a signal to ignore the setting. + if ((this->GetMaxLogBytes() != 0) && + (theCurrentPos > this->GetMaxLogBytes())) + return this->RollLog(); + return true; +} + +Bool16 QTSSRollingLog::RenameLogFile(const char* inFileName) +{ + //returns false if an error occurred, true otherwise + + //this function takes care of renaming a log file from "myLogFile.log" to + //"myLogFile.981217000.log" or if that is already taken, myLogFile.981217001.log", etc + + //fix 2287086. Rolled log name can be different than original log name + //GetLogDir returns a copy of the log dir + OSCharArrayDeleter logDirectory(this->GetLogDir()); + + //create the log directory if it doesn't already exist + OS::RecursiveMakeDir(logDirectory.GetObject()); + + //GetLogName returns a copy of the log name + OSCharArrayDeleter logBaseName(this->GetLogName()); + + //QTStreamingServer.981217003.log + //format the new file name + OSCharArrayDeleter theNewNameBuffer(NEW char[::strlen(logDirectory) + kMaxFilenameLengthInBytes + 3]); + + //copy over the directory - append a '/' if it's missing + ::strcpy(theNewNameBuffer, logDirectory); + if (theNewNameBuffer[::strlen(theNewNameBuffer)-1] != kPathDelimiterChar) + { + ::strcat(theNewNameBuffer, kPathDelimiterString); + } + + //copy over the base filename + ::strcat(theNewNameBuffer, logBaseName.GetObject()); + + //append the date the file was created + struct tm timeResult; + struct tm* theLocalTime = qtss_localtime(&fLogCreateTime, &timeResult); + char timeString[10]; + qtss_strftime(timeString, 10, ".%y%m%d", theLocalTime); + ::strcat(theNewNameBuffer, timeString); + + SInt32 theBaseNameLength = ::strlen(theNewNameBuffer); + + + //loop until we find a unique name to rename this file + //and append the log number and suffix + SInt32 theErr = 0; + for (SInt32 x = 0; (theErr == 0) && (x<=1000); x++) + { + if (x == 1000) //we don't have any digits left, so just reuse the "---" until tomorrow... + { + //add a bogus log number and exit the loop + qtss_sprintf(theNewNameBuffer + theBaseNameLength, "---.log"); + break; + } + + //add the log number & suffix + qtss_sprintf(theNewNameBuffer + theBaseNameLength, "%03"_S32BITARG_".log", x); + + //assume that when ::stat returns an error, it is becase + //the file doesnt exist. Once that happens, we have a unique name + // csl - shouldn't you watch for a ENOENT result? + struct stat theIdontCare; + theErr = ::stat(theNewNameBuffer, &theIdontCare); + WarnV((theErr == 0 || OSThread::GetErrno() == ENOENT), "unexpected stat error in RenameLogFile"); + + } + + //rename the file. Use posix rename function + int result = ::rename(inFileName, theNewNameBuffer); + if (result == -1) + theErr = (SInt32)OSThread::GetErrno(); + else + theErr = 0; + + WarnV(theErr == 0 , "unexpected rename error in RenameLogFile"); + + + if (theErr != 0) + return false; + else + return true; +} + +Bool16 QTSSRollingLog::DoesFileExist(const char *inPath) +{ + struct stat theStat; + int theErr = ::stat(inPath, &theStat); + if (theErr != 0) + return false; + else + return true; +} + +time_t QTSSRollingLog::WriteLogHeader(FILE* inFile) +{ + OSMutexLocker locker(&fMutex); + + //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; + + // + // Files are always created at hour 0 (we don't care about the time, we always + // want them to roll at midnight. + //theLocalTime->tm_hour = 0; + //theLocalTime->tm_min = 0; + //theLocalTime->tm_sec = 0; + + char tempbuf[1024]; + qtss_strftime(tempbuf, sizeof(tempbuf), "#Log File Created On: %m/%d/%Y %H:%M:%S\n", theLocalTime); + //qtss_sprintf(tempbuf, "#Log File Created On: %d/%d/%d %d:%d:%d %d:%d:%d GMT\n", + // theLocalTime->tm_mon, theLocalTime->tm_mday, theLocalTime->tm_year, + // theLocalTime->tm_hour, theLocalTime->tm_min, theLocalTime->tm_sec, + // theLocalTime->tm_yday, theLocalTime->tm_wday, theLocalTime->tm_isdst); + this->WriteToLog(tempbuf, !kAllowLogToRoll); + + return this->ReadLogHeader(inFile); +} + +time_t QTSSRollingLog::ReadLogHeader(FILE* inFile) +{ + OSMutexLocker locker(&fMutex); + + //This function reads the header in a log file, returning the time stored + //at the beginning of this file. This value is used to determine when to + //roll the log. + //Returns -1 if the header is bogus. In that case, just ignore time based log rolling + + //first seek to the beginning of the file + SInt32 theCurrentPos = ::ftell(inFile); + if (theCurrentPos == -1) + return -1; + (void)::rewind(inFile); + + const UInt32 kMaxHeaderLength = 500; + char theFirstLine[kMaxHeaderLength]; + + if (NULL == ::fgets(theFirstLine, kMaxHeaderLength, inFile)) + { + ::fseek(inFile, 0, SEEK_END); + return -1; + } + ::fseek(inFile, 0, SEEK_END); + + struct tm theFileCreateTime; + + // Zero out fields we will not be using + theFileCreateTime.tm_isdst = -1; + theFileCreateTime.tm_wday = 0; + theFileCreateTime.tm_yday = 0; + + //if (EOF == ::sscanf(theFirstLine, "#Log File Created On: %d/%d/%d %d:%d:%d\n", + // &theFileCreateTime.tm_mon, &theFileCreateTime.tm_mday, &theFileCreateTime.tm_year, + // &theFileCreateTime.tm_hour, &theFileCreateTime.tm_min, &theFileCreateTime.tm_sec)) + // return -1; + + // + // We always want to roll at hour 0, so ignore the time of creation + + if (EOF == ::sscanf(theFirstLine, "#Log File Created On: %d/%d/%d %d:%d:%d\n", + &theFileCreateTime.tm_mon, &theFileCreateTime.tm_mday, &theFileCreateTime.tm_year, + &theFileCreateTime.tm_hour, &theFileCreateTime.tm_min, &theFileCreateTime.tm_sec)) + return -1; + + // + // It should be like this anyway, but if the log file is legacy, then... + // No! The log file will have the actual time in it but we shall return the exact time + //theFileCreateTime.tm_hour = 0; + //theFileCreateTime.tm_min = 0; + //theFileCreateTime.tm_sec = 0; + + // Actually, it seems like all platforms need this. +//#ifdef __Win32__ + // Win32 has slightly different atime basis than UNIX. + theFileCreateTime.tm_yday--; + theFileCreateTime.tm_mon--; + theFileCreateTime.tm_year -= 1900; +//#endif + +#if 0 + //use ansi routines for getting the date. + time_t calendarTime = ::time(NULL); + Assert(-1 != calendarTime); + if (-1 == calendarTime) + return false; + + struct tm timeResult; + struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult); + Assert(NULL != theLocalTime); + if (NULL == theLocalTime) + return false; +#endif + + //ok, we should have a filled in tm struct. Convert it to a time_t. + //time_t thePoopTime = ::mktime(theLocalTime); + time_t theTime = ::mktime(&theFileCreateTime); + return theTime; +} + +SInt64 QTSSRollingLog::Run() +{ + // + // If we are going away, just return + EventFlags events = this->GetEvents(); + if (events & Task::kKillEvent) + return -1; + + OSMutexLocker locker(&fMutex); + + UInt32 theRollInterval = (this->GetRollIntervalInDays()) * 60 * 60 * 24; + + if((fLogCreateTime != -1) && (fLog != NULL)) + { + time_t logRollTimeMidnight = -1; + this->ResetToMidnight(&fLogCreateTime, &logRollTimeMidnight); + Assert(logRollTimeMidnight != -1); + + if(theRollInterval != 0) + { + time_t calendarTime = ::time(NULL); + Assert(-1 != calendarTime); + double theExactInterval = ::difftime(calendarTime, logRollTimeMidnight); + if(theExactInterval > 0) { + UInt32 theCurInterval = (UInt32)::floor(theExactInterval); + if (theCurInterval >= theRollInterval) + this->RollLog(); + } + } + } + return 60 * 1000; +} + +void QTSSRollingLog::ResetToMidnight(time_t* inTimePtr, time_t* outTimePtr) +{ + if(*inTimePtr == -1) + { + *outTimePtr = -1; + return; + } + + struct tm timeResult; + struct tm* theLocalTime = qtss_localtime(inTimePtr, &timeResult); + Assert(theLocalTime != NULL); + + theLocalTime->tm_hour = 0; + theLocalTime->tm_min = 0; + theLocalTime->tm_sec = 0; + + // some weird stuff + //theLocalTime->tm_yday--; + //theLocalTime->tm_mon--; + //theLocalTime->tm_year -= 1900; + + *outTimePtr = ::mktime(theLocalTime); + +} diff --git a/APICommonCode/QTSSRollingLog.h b/APICommonCode/QTSSRollingLog.h new file mode 100644 index 0000000..4b6ecef --- /dev/null +++ b/APICommonCode/QTSSRollingLog.h @@ -0,0 +1,142 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: QTSSRollingLog.h + + Contains: A log toolkit, log can roll either by time or by size, clients + must derive off of this object ot provide configuration information. + + + +*/ + +#ifndef __QTSS_ROLLINGLOG_H__ +#define __QTSS_ROLLINGLOG_H__ + +#include +#include +#ifndef __Win32__ +#include +#endif +#include "OSHeaders.h" +#include "OSMutex.h" +#include "Task.h" + +const Bool16 kAllowLogToRoll = true; + +class QTSSRollingLog : public Task +{ + public: + + //pass in whether you'd like the log roller to log errors. + QTSSRollingLog(); + + // + // Call this to delete. Closes the log and sends a kill event + void Delete() + { CloseLog(false); this->Signal(Task::kKillEvent); } + + // + // Write a log message + void WriteToLog(char* inLogData, Bool16 allowLogToRoll); + + //log rolls automatically based on the configuration criteria, + //but you may roll the log manually by calling this function. + //Returns true if no error, false otherwise + Bool16 RollLog(); + + // + // Call this to open the log file and begin logging + void EnableLog( Bool16 appendDotLog = true); + + // + // Call this to close the log + // (pass leaveEnabled as true when we are temporarily closing.) + void CloseLog( Bool16 leaveEnabled = false); + + // + //mainly to check and see if errors occurred + Bool16 IsLogEnabled(); + + //master switch + Bool16 IsLogging() { return fLogging; } + void SetLoggingEnabled( Bool16 logState ) { fLogging = logState; } + + //General purpose utility function + //returns false if some error has occurred + static Bool16 FormatDate(char *ioDateBuffer, Bool16 logTimeInGMT); + + // Check the log to see if it needs to roll + // (rolls the log if necessary) + Bool16 CheckRollLog(); + + // Set this to true to get the log to close the file between writes. + static void SetCloseOnWrite(Bool16 closeOnWrite); + + enum + { + kMaxDateBufferSizeInBytes = 30, //UInt32 + kMaxFilenameLengthInBytes = 31 //UInt32 + }; + + protected: + + // + // Task object. Do not delete directly + virtual ~QTSSRollingLog(); + + //Derived class must provide a way to get the log & rolled log name + virtual char* GetLogName() = 0; + virtual char* GetLogDir() = 0; + virtual UInt32 GetRollIntervalInDays() = 0;//0 means no interval + virtual UInt32 GetMaxLogBytes() = 0;//0 means unlimited + + //to record the time the file was created (for time based rolling) + virtual time_t WriteLogHeader(FILE *inFile); + time_t ReadLogHeader(FILE* inFile); + + private: + + // + // Run function to roll log right at midnight + virtual SInt64 Run(); + + FILE* fLog; + time_t fLogCreateTime; + char* fLogFullPath; + Bool16 fAppendDotLog; + Bool16 fLogging; + Bool16 RenameLogFile(const char* inFileName); + Bool16 DoesFileExist(const char *inPath); + static void ResetToMidnight(time_t* inTimePtr, time_t* outTimePtr); + char* GetLogPath(char *extension); + + // To make sure what happens in Run doesn't also happen at the same time + // in the public functions. + OSMutex fMutex; +}; + +#endif // __QTSS_ROLLINGLOG_H__ + diff --git a/APICommonCode/SDPSourceInfo.cpp b/APICommonCode/SDPSourceInfo.cpp new file mode 100644 index 0000000..3fcd4ab --- /dev/null +++ b/APICommonCode/SDPSourceInfo.cpp @@ -0,0 +1,437 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: SDPSourceInfo.cpp + + Contains: Implementation of object defined in .h file + + +*/ + +#include "SDPSourceInfo.h" + +#include "StringParser.h" +#include "StringFormatter.h" +#include "OSMemory.h" +#include "SocketUtils.h" +#include "StrPtrLen.h" +#include "SDPUtils.h" +#include "OSArrayObjectDeleter.h" + +static StrPtrLen sCLine("c=IN IP4 0.0.0.0"); +static StrPtrLen sControlLine("a=control:*"); +static StrPtrLen sVideoStr("video"); +static StrPtrLen sAudioStr("audio"); +static StrPtrLen sRtpMapStr("rtpmap"); +static StrPtrLen sControlStr("control"); +static StrPtrLen sBufferDelayStr("x-bufferdelay"); +static StrPtrLen sBroadcastControlStr("x-broadcastcontrol"); +static StrPtrLen sAutoDisconnect("RTSP"); +static StrPtrLen sAutoDisconnectTime("TIME"); + +SDPSourceInfo::~SDPSourceInfo() +{ + // Not reqd as the destructor of the + // base class will take care of delete the stream array + // and output array if allocated + /* + if (fStreamArray != NULL) + { + char* theCharArray = (char*)fStreamArray; + delete [] theCharArray; + } + */ + + fSDPData.Delete(); +} + +char* SDPSourceInfo::GetLocalSDP(UInt32* newSDPLen) +{ + Assert(fSDPData.Ptr != NULL); + + Bool16 appendCLine = true; + UInt32 trackIndex = 0; + + char *localSDP = NEW char[fSDPData.Len * 2]; + OSCharArrayDeleter charArrayPathDeleter(localSDP); + StringFormatter localSDPFormatter(localSDP, fSDPData.Len * 2); + + StrPtrLen sdpLine; + StringParser sdpParser(&fSDPData); + char trackIndexBuffer[50]; + + // Only generate our own trackIDs if this file doesn't have 'em. + // Our assumption here is that either the file has them, or it doesn't. + // A file with some trackIDs, and some not, won't work. + Bool16 hasControlLine = 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;//ignore connection information + case 'm': + { + //append new connection information right before the first 'm' + if (appendCLine) + { + localSDPFormatter.Put(sCLine); + localSDPFormatter.PutEOL(); + + if (!hasControlLine) + { + localSDPFormatter.Put(sControlLine); + localSDPFormatter.PutEOL(); + } + + appendCLine = false; + } + //the last "a=" for each m should be the control a= + if ((trackIndex > 0) && (!hasControlLine)) + { + qtss_sprintf(trackIndexBuffer, "a=control:trackID=%"_S32BITARG_"\r\n",trackIndex); + localSDPFormatter.Put(trackIndexBuffer, ::strlen(trackIndexBuffer)); + } + //now write the 'm' line, but strip off the port information + StringParser mParser(&sdpLine); + StrPtrLen mPrefix; + mParser.ConsumeUntil(&mPrefix, StringParser::sDigitMask); + localSDPFormatter.Put(mPrefix); + localSDPFormatter.Put("0", 1); + (void)mParser.ConsumeInteger(NULL); + localSDPFormatter.Put(mParser.GetCurrentPosition(), mParser.GetDataRemaining()); + localSDPFormatter.PutEOL(); + trackIndex++; + break; + } + case 'a': + { + StringParser aParser(&sdpLine); + aParser.ConsumeLength(NULL, 2);//go past 'a=' + StrPtrLen aLineType; + aParser.ConsumeWord(&aLineType); + if (aLineType.Equal(sControlStr)) + { + aParser.ConsumeUntil(NULL, '='); + aParser.ConsumeUntil(NULL, StringParser::sDigitMask); + + StrPtrLen aDigitType; + (void)aParser.ConsumeInteger(&aDigitType); + if (aDigitType.Len > 0) + { + localSDPFormatter.Put("a=control:trackID=", 18); + localSDPFormatter.Put(aDigitType); + localSDPFormatter.PutEOL(); + hasControlLine = true; + break; + } + } + + localSDPFormatter.Put(sdpLine); + localSDPFormatter.PutEOL(); + break; + } + default: + { + localSDPFormatter.Put(sdpLine); + localSDPFormatter.PutEOL(); + } + } + } + + if ((trackIndex > 0) && (!hasControlLine)) + { + qtss_sprintf(trackIndexBuffer, "a=control:trackID=%"_S32BITARG_"\r\n",trackIndex); + localSDPFormatter.Put(trackIndexBuffer, ::strlen(trackIndexBuffer)); + } + *newSDPLen = (UInt32)localSDPFormatter.GetCurrentOffset(); + + StrPtrLen theSDPStr(localSDP, *newSDPLen);//localSDP is not 0 terminated so initialize theSDPStr with the len. + SDPContainer rawSDPContainer; + (void) rawSDPContainer.SetSDPBuffer( &theSDPStr ); + SDPLineSorter sortedSDP(&rawSDPContainer); + + return sortedSDP.GetSortedSDPCopy(); // return a new copy of the sorted SDP +} + + +void SDPSourceInfo::Parse(char* sdpData, UInt32 sdpLen) +{ + // + // There are some situations in which Parse can be called twice. + // If that happens, just return and don't do anything the second time. + if (fSDPData.Ptr != NULL) + return; + + Assert(fStreamArray == NULL); + + char *sdpDataCopy = NEW char[sdpLen]; + Assert(sdpDataCopy != NULL); + + memcpy(sdpDataCopy,sdpData, sdpLen); + fSDPData.Set(sdpDataCopy, sdpLen); + + // If there is no trackID information in this SDP, we make the track IDs start + // at 1 -> N + UInt32 currentTrack = 1; + + Bool16 hasGlobalStreamInfo = false; + StreamInfo theGlobalStreamInfo; //needed if there is one c= header independent of + //individual streams + + StrPtrLen sdpLine; + StringParser trackCounter(&fSDPData); + StringParser sdpParser(&fSDPData); + UInt32 theStreamIndex = 0; + + //walk through the SDP, counting up the number of tracks + // Repeat until there's no more data in the SDP + while (trackCounter.GetDataRemaining() > 0) + { + //each 'm' line in the SDP file corresponds to another track. + trackCounter.GetThruEOL(&sdpLine); + if ((sdpLine.Len > 0) && (sdpLine.Ptr[0] == 'm')) + fNumStreams++; + } + + //We should scale the # of StreamInfos to the # of trax, but we can't because + //of an annoying compiler bug... + + fStreamArray = NEW StreamInfo[fNumStreams]; + ::memset(fStreamArray, 0, sizeof(StreamInfo) * fNumStreams); + + // set the default destination as our default IP address and set the default ttl + theGlobalStreamInfo.fDestIPAddr = INADDR_ANY; + theGlobalStreamInfo.fTimeToLive = kDefaultTTL; + + //Set bufferdelay to default of 3 + theGlobalStreamInfo.fBufferDelay = (Float32) eDefaultBufferDelay; + + //Now actually get all the data on all the streams + while (sdpParser.GetDataRemaining() > 0) + { + sdpParser.GetThruEOL(&sdpLine); + if (sdpLine.Len == 0) + continue;//skip over any blank lines + + switch (*sdpLine.Ptr) + { + case 't': + { + StringParser mParser(&sdpLine); + + mParser.ConsumeUntil(NULL, StringParser::sDigitMask); + UInt32 ntpStart = mParser.ConsumeInteger(NULL); + + mParser.ConsumeUntil(NULL, StringParser::sDigitMask); + UInt32 ntpEnd = mParser.ConsumeInteger(NULL); + + SetActiveNTPTimes(ntpStart,ntpEnd); + } + break; + + case 'm': + { + if (hasGlobalStreamInfo) + { + fStreamArray[theStreamIndex].fDestIPAddr = theGlobalStreamInfo.fDestIPAddr; + fStreamArray[theStreamIndex].fTimeToLive = theGlobalStreamInfo.fTimeToLive; + } + fStreamArray[theStreamIndex].fTrackID = currentTrack; + currentTrack++; + + StringParser mParser(&sdpLine); + + //find out what type of track this is + mParser.ConsumeLength(NULL, 2);//go past 'm=' + StrPtrLen theStreamType; + mParser.ConsumeWord(&theStreamType); + if (theStreamType.Equal(sVideoStr)) + fStreamArray[theStreamIndex].fPayloadType = qtssVideoPayloadType; + else if (theStreamType.Equal(sAudioStr)) + fStreamArray[theStreamIndex].fPayloadType = qtssAudioPayloadType; + + //find the port for this stream + mParser.ConsumeUntil(NULL, StringParser::sDigitMask); + SInt32 tempPort = mParser.ConsumeInteger(NULL); + if ((tempPort > 0) && (tempPort < 65536)) + fStreamArray[theStreamIndex].fPort = (UInt16) tempPort; + + // find out whether this is TCP or UDP + mParser.ConsumeWhitespace(); + StrPtrLen transportID; + mParser.ConsumeWord(&transportID); + + static const StrPtrLen kTCPTransportStr("RTP/AVP/TCP"); + if (transportID.Equal(kTCPTransportStr)) + fStreamArray[theStreamIndex].fIsTCP = true; + + theStreamIndex++; + } + break; + case 'a': + { + StringParser aParser(&sdpLine); + + aParser.ConsumeLength(NULL, 2);//go past 'a=' + + StrPtrLen aLineType; + + aParser.ConsumeWord(&aLineType); + + + + if (aLineType.Equal(sBroadcastControlStr)) + + { // found a control line for the broadcast (delete at time or delete at end of broadcast/server startup) + + // qtss_printf("found =%s\n",sBroadcastControlStr); + + aParser.ConsumeUntil(NULL,StringParser::sWordMask); + + StrPtrLen sessionControlType; + + aParser.ConsumeWord(&sessionControlType); + + if (sessionControlType.Equal(sAutoDisconnect)) + { + fSessionControlType = kRTSPSessionControl; + } + else if (sessionControlType.Equal(sAutoDisconnectTime)) + { + fSessionControlType = kSDPTimeControl; + } + + + } + + //if we haven't even hit an 'm' line yet, just ignore all 'a' lines + if (theStreamIndex == 0) + break; + + if (aLineType.Equal(sRtpMapStr)) + { + //mark the codec type if this line has a codec name on it. If we already + //have a codec type for this track, just ignore this line + if ((fStreamArray[theStreamIndex - 1].fPayloadName.Len == 0) && + (aParser.GetThru(NULL, ' '))) + { + StrPtrLen payloadNameFromParser; + (void)aParser.GetThruEOL(&payloadNameFromParser); + char* temp = payloadNameFromParser.GetAsCString(); +// qtss_printf("payloadNameFromParser (%x) = %s\n", temp, temp); + (fStreamArray[theStreamIndex - 1].fPayloadName).Set(temp, payloadNameFromParser.Len); +// qtss_printf("%s\n", fStreamArray[theStreamIndex - 1].fPayloadName.Ptr); + } + } + else if (aLineType.Equal(sControlStr)) + { + //mark the trackID if that's what this line has + aParser.ConsumeUntil(NULL, '='); + aParser.ConsumeUntil(NULL, StringParser::sDigitMask); + fStreamArray[theStreamIndex - 1].fTrackID = aParser.ConsumeInteger(NULL); + } + else if (aLineType.Equal(sBufferDelayStr)) + { // if a BufferDelay is found then set all of the streams to the same buffer delay (it's global) + aParser.ConsumeUntil(NULL, StringParser::sDigitMask); + theGlobalStreamInfo.fBufferDelay = aParser.ConsumeFloat(); + } + + } + break; + case 'c': + { + //get the IP address off this header + StringParser cParser(&sdpLine); + cParser.ConsumeLength(NULL, 9);//strip off "c=in ip4 " + UInt32 tempIPAddr = SDPSourceInfo::GetIPAddr(&cParser, '/'); + + //grab the ttl + SInt32 tempTtl = kDefaultTTL; + if (cParser.GetThru(NULL, '/')) + { + tempTtl = cParser.ConsumeInteger(NULL); + Assert(tempTtl >= 0); + Assert(tempTtl < 65536); + } + + if (theStreamIndex > 0) + { + //if this c= line is part of a stream, it overrides the + //global stream information + fStreamArray[theStreamIndex - 1].fDestIPAddr = tempIPAddr; + fStreamArray[theStreamIndex - 1].fTimeToLive = (UInt16) tempTtl; + } else { + theGlobalStreamInfo.fDestIPAddr = tempIPAddr; + theGlobalStreamInfo.fTimeToLive = (UInt16) tempTtl; + hasGlobalStreamInfo = true; + } + } + } + } + + // Add the default buffer delay + Float32 bufferDelay = (Float32) eDefaultBufferDelay; + if (theGlobalStreamInfo.fBufferDelay != (Float32) eDefaultBufferDelay) + bufferDelay = theGlobalStreamInfo.fBufferDelay; + + UInt32 count = 0; + while (count < fNumStreams) + { fStreamArray[count].fBufferDelay = bufferDelay; + count ++; + } + +} + +UInt32 SDPSourceInfo::GetIPAddr(StringParser* inParser, char inStopChar) +{ + StrPtrLen ipAddrStr; + + // Get the IP addr str + inParser->ConsumeUntil(&ipAddrStr, inStopChar); + + if (ipAddrStr.Len == 0) + return 0; + + // NULL terminate it + char endChar = ipAddrStr.Ptr[ipAddrStr.Len]; + ipAddrStr.Ptr[ipAddrStr.Len] = '\0'; + + //inet_addr returns numeric IP addr in network byte order, make + //sure to convert to host order. + UInt32 ipAddr = SocketUtils::ConvertStringToAddr(ipAddrStr.Ptr); + + // Make sure to put the old char back! + ipAddrStr.Ptr[ipAddrStr.Len] = endChar; + + return ipAddr; +} + diff --git a/APICommonCode/SDPSourceInfo.h b/APICommonCode/SDPSourceInfo.h new file mode 100644 index 0000000..e90d912 --- /dev/null +++ b/APICommonCode/SDPSourceInfo.h @@ -0,0 +1,77 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: SDPSourceInfo.h + + Contains: This object takes input SDP data, and uses it to support the SourceInfo + API. + + Works only for QTSS + +*/ + +#ifndef __SDP_SOURCE_INFO_H__ +#define __SDP_SOURCE_INFO_H__ + +#include "StrPtrLen.h" +#include "SourceInfo.h" +#include "StringParser.h" + +class SDPSourceInfo : public SourceInfo +{ + public: + + // Uses the SDP Data to build up the StreamInfo structures + SDPSourceInfo(char* sdpData, UInt32 sdpLen) { Parse(sdpData, sdpLen); } + SDPSourceInfo() {} + virtual ~SDPSourceInfo(); + + // Parses out the SDP file provided, sets up the StreamInfo structures + void Parse(char* sdpData, UInt32 sdpLen); + + // 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); + + // Returns the SDP data + StrPtrLen* GetSDPData() { return &fSDPData; } + + // Utility routines + + // Assuming the parser is currently pointing at the beginning of an dotted- + // decimal IP address, this consumes it (stopping at inStopChar), and returns + // the IP address (host ordered) as a UInt32 + static UInt32 GetIPAddr(StringParser* inParser, char inStopChar); + + private: + + enum + { + kDefaultTTL = 15 //UInt16 + }; + StrPtrLen fSDPData; +}; +#endif // __SDP_SOURCE_INFO_H__ + diff --git a/APICommonCode/SourceInfo.cpp b/APICommonCode/SourceInfo.cpp new file mode 100644 index 0000000..11a73be --- /dev/null +++ b/APICommonCode/SourceInfo.cpp @@ -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@ + * + */ +/* + File: SourceInfo.cpp + + Contains: Implements object defined in .h file. + + +*/ + +#include "SourceInfo.h" +#include "SocketUtils.h" +#include "SDPSourceInfo.h" +#include "OSMemory.h" +#include "StringParser.h" + +SourceInfo::SourceInfo(const SourceInfo& copy) +: fStreamArray(NULL), fNumStreams(copy.fNumStreams), + fOutputArray(NULL), fNumOutputs(copy.fNumOutputs), + fTimeSet(copy.fTimeSet),fStartTimeUnixSecs(copy.fStartTimeUnixSecs), + fEndTimeUnixSecs(copy.fEndTimeUnixSecs), fSessionControlType(copy.fSessionControlType), + fHasValidTime(false) +{ + + if(copy.fStreamArray != NULL && fNumStreams != 0) + { + fStreamArray = NEW StreamInfo[fNumStreams]; + for (UInt32 index=0; index < fNumStreams; index++) + fStreamArray[index].Copy(copy.fStreamArray[index]); + } + + if(copy.fOutputArray != NULL && fNumOutputs != 0) + { + fOutputArray = NEW OutputInfo[fNumOutputs]; + for (UInt32 index2=0; index2 < fNumOutputs; index2++) + fOutputArray[index2].Copy(copy.fOutputArray[index2]); + } + +} + +SourceInfo::~SourceInfo() +{ + if(fStreamArray != NULL) + delete [] fStreamArray; + + if(fOutputArray != NULL) + delete [] fOutputArray; + +} + +Bool16 SourceInfo::IsReflectable() +{ + if (fStreamArray == NULL) + return false; + if (fNumStreams == 0) + return false; + + //each stream's info must meet certain criteria + for (UInt32 x = 0; x < fNumStreams; x++) + { + if (fStreamArray[x].fIsTCP) + continue; + + if ((!this->IsReflectableIPAddr(fStreamArray[x].fDestIPAddr)) || + (fStreamArray[x].fTimeToLive == 0)) + return false; + } + return true; +} + +Bool16 SourceInfo::IsReflectableIPAddr(UInt32 inIPAddr) +{ + if (SocketUtils::IsMulticastIPAddr(inIPAddr) || SocketUtils::IsLocalIPAddr(inIPAddr)) + return true; + return false; +} + +Bool16 SourceInfo::HasTCPStreams() +{ + //each stream's info must meet certain criteria + for (UInt32 x = 0; x < fNumStreams; x++) + { + if (fStreamArray[x].fIsTCP) + return true; + } + return false; +} + +Bool16 SourceInfo::HasIncomingBroacast() +{ + //each stream's info must meet certain criteria + for (UInt32 x = 0; x < fNumStreams; x++) + { + if (fStreamArray[x].fSetupToReceive) + return true; + } + return false; +} +SourceInfo::StreamInfo* SourceInfo::GetStreamInfo(UInt32 inIndex) +{ + Assert(inIndex < fNumStreams); + if (fStreamArray == NULL) + return NULL; + if (inIndex < fNumStreams) + return &fStreamArray[inIndex]; + else + return NULL; +} + +SourceInfo::StreamInfo* SourceInfo::GetStreamInfoByTrackID(UInt32 inTrackID) +{ + if (fStreamArray == NULL) + return NULL; + for (UInt32 x = 0; x < fNumStreams; x++) + { + if (fStreamArray[x].fTrackID == inTrackID) + return &fStreamArray[x]; + } + return NULL; +} + +SourceInfo::OutputInfo* SourceInfo::GetOutputInfo(UInt32 inIndex) +{ + Assert(inIndex < fNumOutputs); + if (fOutputArray == NULL) + return NULL; + if (inIndex < fNumOutputs) + return &fOutputArray[inIndex]; + else + return NULL; +} + +UInt32 SourceInfo::GetNumNewOutputs() +{ + UInt32 theNumNewOutputs = 0; + for (UInt32 x = 0; x < fNumOutputs; x++) + { + if (!fOutputArray[x].fAlreadySetup) + theNumNewOutputs++; + } + return theNumNewOutputs; +} + +Bool16 SourceInfo::SetActiveNTPTimes(UInt32 startTimeNTP,UInt32 endTimeNTP) +{ // right now only handles earliest start and latest end time. + + //qtss_printf("SourceInfo::SetActiveNTPTimes start=%"_U32BITARG_" end=%"_U32BITARG_"\n",startTimeNTP,endTimeNTP); + Bool16 accepted = false; + do + { + if ((startTimeNTP > 0) && (endTimeNTP > 0) && (endTimeNTP < startTimeNTP)) break; // not valid NTP time + + UInt32 startTimeUnixSecs = 0; + UInt32 endTimeUnixSecs = 0; + + if (startTimeNTP != 0 && IsValidNTPSecs(startTimeNTP)) // allow anything less than 1970 + startTimeUnixSecs = NTPSecs_to_UnixSecs(startTimeNTP);// convert to 1970 time + + if (endTimeNTP != 0 && !IsValidNTPSecs(endTimeNTP)) // don't allow anything less than 1970 + break; + + if (endTimeNTP != 0) // convert to 1970 time + endTimeUnixSecs = NTPSecs_to_UnixSecs(endTimeNTP); + + fStartTimeUnixSecs = startTimeUnixSecs; + fEndTimeUnixSecs = endTimeUnixSecs; + accepted = true; + + } while(0); + + //char buffer[kTimeStrSize]; + //qtss_printf("SourceInfo::SetActiveNTPTimes fStartTimeUnixSecs=%"_U32BITARG_" fEndTimeUnixSecs=%"_U32BITARG_"\n",fStartTimeUnixSecs,fEndTimeUnixSecs); + //qtss_printf("SourceInfo::SetActiveNTPTimes start time = %s",qtss_ctime(&fStartTimeUnixSecs, buffer, sizeof(buffer)) ); + //qtss_printf("SourceInfo::SetActiveNTPTimes end time = %s",qtss_ctime(&fEndTimeUnixSecs, buffer, sizeof(buffer)) ); + fHasValidTime = accepted; + return accepted; +} + +Bool16 SourceInfo::IsActiveTime(time_t unixTimeSecs) +{ + // order of tests are important here + // we do it this way because of the special case time value of 0 for end time + // start - 0 = unbounded + // 0 - 0 = permanent + if (false == fHasValidTime) + return false; + + if (unixTimeSecs < 0) //check valid value + return false; + + if (IsPermanentSource()) //check for 0 0 + return true; + + if (unixTimeSecs < fStartTimeUnixSecs) + return false; //too early + + if (fEndTimeUnixSecs == 0) + return true;// accept any time after start + + if (unixTimeSecs > fEndTimeUnixSecs) + return false; // too late + + return true; // ok start <= time <= end + +} + + +UInt32 SourceInfo::GetDurationSecs() +{ + + if (fEndTimeUnixSecs == 0) // unbounded time + return (UInt32) ~0; // max time + + time_t timeNow = OS::UnixTime_Secs(); + if (fEndTimeUnixSecs <= timeNow) // the active time has past or duration is 0 so return the minimum duration + return (UInt32) 0; + + if (fStartTimeUnixSecs == 0) // relative duration = from "now" to end time + return fEndTimeUnixSecs - timeNow; + + return fEndTimeUnixSecs - fStartTimeUnixSecs; // this must be a duration because of test for endtime above + +} + +Bool16 SourceInfo::Equal(SourceInfo* inInfo) +{ + // Check to make sure the # of streams matches up + if (this->GetNumStreams() != inInfo->GetNumStreams()) + return false; + + // Check the src & dest addr, and port of each stream. + for (UInt32 x = 0; x < this->GetNumStreams(); x++) + { + if (GetStreamInfo(x)->fDestIPAddr != inInfo->GetStreamInfo(x)->fDestIPAddr) + return false; + if (GetStreamInfo(x)->fSrcIPAddr != inInfo->GetStreamInfo(x)->fSrcIPAddr) + return false; + + // If either one of the comparators is 0 (the "wildcard" port), then we know at this point + // they are equivalent + if ((GetStreamInfo(x)->fPort == 0) || (inInfo->GetStreamInfo(x)->fPort == 0)) + return true; + + // Neither one is the wildcard port, so they must be the same + if (GetStreamInfo(x)->fPort != inInfo->GetStreamInfo(x)->fPort) + return false; + } + return true; +} + +void SourceInfo::StreamInfo::Copy(const StreamInfo& copy) +{ + fSrcIPAddr = copy.fSrcIPAddr; + fDestIPAddr = copy.fDestIPAddr; + fPort = copy.fPort; + fTimeToLive = copy.fTimeToLive; + fPayloadType = copy.fPayloadType; + if ((copy.fPayloadName).Ptr != NULL) + fPayloadName.Set((copy.fPayloadName).GetAsCString(), (copy.fPayloadName).Len); + fTrackID = copy.fTrackID; + fBufferDelay = copy.fBufferDelay; + fIsTCP = copy.fIsTCP; + fSetupToReceive = copy.fSetupToReceive; + fTimeScale = copy.fTimeScale; +} + +SourceInfo::StreamInfo::~StreamInfo() +{ + if (fPayloadName.Ptr != NULL) + delete fPayloadName.Ptr; + fPayloadName.Len = 0; +} + +void SourceInfo::OutputInfo::Copy(const OutputInfo& copy) +{ + fDestAddr = copy.fDestAddr; + fLocalAddr = copy.fLocalAddr; + fTimeToLive = copy.fTimeToLive; + fNumPorts = copy.fNumPorts; + if(fNumPorts != 0) + { + fPortArray = NEW UInt16[fNumPorts]; + ::memcpy(fPortArray, copy.fPortArray, fNumPorts * sizeof(UInt16)); + } + fBasePort = copy.fBasePort; + fAlreadySetup = copy.fAlreadySetup; +} + +SourceInfo::OutputInfo::~OutputInfo() +{ + if (fPortArray != NULL) + delete [] fPortArray; +} + +Bool16 SourceInfo::OutputInfo::Equal(const OutputInfo& info) +{ + if ((fDestAddr == info.fDestAddr) && (fLocalAddr == info.fLocalAddr) && (fTimeToLive == info.fTimeToLive)) + { + if ((fBasePort != 0) && (fBasePort == info.fBasePort)) + return true; + else if ((fNumPorts == 0) || ((fNumPorts == info.fNumPorts) && (fPortArray[0] == info.fPortArray[0]))) + return true; + } + return false; +} + + diff --git a/APICommonCode/SourceInfo.h b/APICommonCode/SourceInfo.h new file mode 100644 index 0000000..fd0667c --- /dev/null +++ b/APICommonCode/SourceInfo.h @@ -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@ + * + */ +/* + File: SourceInfo.h + + Contains: This object contains an interface for getting at any bit + of "interesting" information regarding a content source in a + format - independent manner. + + For instance, the derived object SDPSourceInfo parses an + SDP file and retrieves all the SourceInfo information from that file. + + + +*/ + +#ifndef __SOURCE_INFO_H__ +#define __SOURCE_INFO_H__ + +#include "QTSS.h" +#include "StrPtrLen.h" +#include "OSQueue.h" +#include "OS.h" + +class SourceInfo +{ + public: + + SourceInfo() : fStreamArray(NULL), fNumStreams(0), + fOutputArray(NULL), fNumOutputs(0), + fTimeSet(false),fStartTimeUnixSecs(0),fEndTimeUnixSecs(0), + fSessionControlType(kRTSPSessionControl) {} + SourceInfo(const SourceInfo& copy);// Does copy dynamically allocated data + virtual ~SourceInfo(); // Deletes the dynamically allocated data + + enum + { + eDefaultBufferDelay = 3 + }; + + // Returns whether this source is reflectable. + Bool16 IsReflectable(); + + // Each source is comprised of a set of streams. Those streams have + // the following metadata. + struct StreamInfo + { + StreamInfo() : fSrcIPAddr(0), fDestIPAddr(0), fPort(0), fTimeToLive(0), fPayloadType(0), fPayloadName(NULL), fTrackID(0), fBufferDelay((Float32) eDefaultBufferDelay), fIsTCP(false),fSetupToReceive(false), fTimeScale(0){} + ~StreamInfo(); // Deletes the memory allocated for the fPayloadName string + + void Copy(const StreamInfo& copy);// Does copy dynamically allocated data + + UInt32 fSrcIPAddr; // Src IP address of content (this may be 0 if not known for sure) + UInt32 fDestIPAddr; // Dest IP address of content (destination IP addr for source broadcast!) + UInt16 fPort; // Dest (RTP) port of source content + UInt16 fTimeToLive; // Ttl for this stream + QTSS_RTPPayloadType fPayloadType; // Payload type of this stream + StrPtrLen fPayloadName; // Payload name of this stream + UInt32 fTrackID; // ID of this stream + Float32 fBufferDelay; // buffer delay (default is 3 seconds) + Bool16 fIsTCP; // Is this a TCP broadcast? If this is the case, the port and ttl are not valid + Bool16 fSetupToReceive; // If true then a push to the server is setup on this stream. + UInt32 fTimeScale; + }; + + // Returns the number of StreamInfo objects (number of Streams in this source) + UInt32 GetNumStreams() { return fNumStreams; } + StreamInfo* GetStreamInfo(UInt32 inStreamIndex); + StreamInfo* GetStreamInfoByTrackID(UInt32 inTrackID); + + // If this source is to be Relayed, it may have "Output" information. This + // tells the reader where to forward the incoming streams onto. There may be + // 0 -> N OutputInfo objects in this SourceInfo. Each OutputInfo refers to a + // single, complete copy of ALL the input streams. The fPortArray field + // contains one RTP port for each incoming stream. + struct OutputInfo + { + OutputInfo() : fDestAddr(0), fLocalAddr(0), fTimeToLive(0), fPortArray(NULL), fNumPorts(0), fBasePort(0), fAlreadySetup(false) {} + ~OutputInfo(); // Deletes the memory allocated for fPortArray + + // Returns true if the two are equal + Bool16 Equal(const OutputInfo& info); + + void Copy(const OutputInfo& copy);// Does copy dynamically allocated data + + UInt32 fDestAddr; // Destination address to forward the input onto + UInt32 fLocalAddr; // Address of local interface to send out on (may be 0) + UInt16 fTimeToLive; // Time to live for resulting output (if multicast) + UInt16* fPortArray; // 1 destination RTP port for each Stream. + UInt32 fNumPorts; // Size of the fPortArray (usually equal to fNumStreams) + UInt16 fBasePort; // The base destination RTP port - for i=1 to fNumStreams fPortArray[i] = fPortArray[i-1] + 2 + Bool16 fAlreadySetup; // A flag used in QTSSReflectorModule.cpp + }; + + // Returns the number of OutputInfo objects. + UInt32 GetNumOutputs() { return fNumOutputs; } + UInt32 GetNumNewOutputs(); // Returns # of outputs not already setup + + OutputInfo* GetOutputInfo(UInt32 inOutputIndex); + + // GetLocalSDP. This may or may not be supported by sources. Typically, if + // the source is reflectable, this must be supported. It returns a newly + // allocated buffer (that the caller is responsible for) containing an SDP + // description of the source, stripped of all network info. + virtual char* GetLocalSDP(UInt32* /*newSDPLen*/) { return NULL; } + + // This is only supported by the RTSPSourceInfo sub class + virtual Bool16 IsRTSPSourceInfo() { return false; } + + // This is only supported by the RCFSourceInfo sub class and its derived classes + virtual char* Name() { return NULL; } + + virtual Bool16 Equal(SourceInfo* inInfo); + + // SDP scheduled times supports earliest start and latest end -- doesn't handle repeat times or multiple active times. + #define kNTP_Offset_From_1970 2208988800LU + time_t NTPSecs_to_UnixSecs(time_t time) {return (time_t) (time - (UInt32)kNTP_Offset_From_1970);} + UInt32 UnixSecs_to_NTPSecs(time_t time) {return (UInt32) (time + (UInt32)kNTP_Offset_From_1970);} + Bool16 SetActiveNTPTimes(UInt32 startNTPTime,UInt32 endNTPTime); + Bool16 IsValidNTPSecs(UInt32 time) {return time >= (UInt32) kNTP_Offset_From_1970 ? true : false;} + Bool16 IsPermanentSource() { return ((fStartTimeUnixSecs == 0) && (fEndTimeUnixSecs == 0)) ? true : false; } + Bool16 IsActiveTime(time_t unixTimeSecs); + Bool16 IsActiveNow() { return IsActiveTime(OS::UnixTime_Secs()); } + Bool16 IsRTSPControlled() {return (fSessionControlType == kRTSPSessionControl) ? true : false; } + Bool16 HasTCPStreams(); + Bool16 HasIncomingBroacast(); + time_t GetStartTimeUnixSecs() {return fStartTimeUnixSecs; } + time_t GetEndTimeUnixSecs() {return fEndTimeUnixSecs; } + UInt32 GetDurationSecs(); + enum {kSDPTimeControl, kRTSPSessionControl}; + protected: + + //utility function used by IsReflectable + Bool16 IsReflectableIPAddr(UInt32 inIPAddr); + + StreamInfo* fStreamArray; + UInt32 fNumStreams; + + OutputInfo* fOutputArray; + UInt32 fNumOutputs; + + Bool16 fTimeSet; + time_t fStartTimeUnixSecs; + time_t fEndTimeUnixSecs; + + UInt32 fSessionControlType; + Bool16 fHasValidTime; +}; + + + +#endif //__SOURCE_INFO_H__ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..22c9786 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,503 @@ +cmake_minimum_required(VERSION 3.6) +project(DarwinStreamingServer C CXX) + +include_directories(APICommonCode + APIModules/QTSSAccessLogModule + APIModules/QTSSAccessModule + APIModules/QTSSAdminModule + APIModules/QTSSDemoAuthorizationModule.bproj + APIModules/QTSSDemoRedirectModule.bproj + APIModules/QTSSDemoSMILModule.bproj + APIModules/QTSSDSAuthModule + APIModules/QTSSDSAuthModule/DSWrappers + APIModules/QTSSFileModule + APIModules/QTSSFilePrivsModule.bproj + APIModules/QTSSFlowControlModule + APIModules/QTSSHomeDirectoryModule + APIModules/QTSSHttpFileModule + APIModules/QTSSMP3StreamingModule + APIModules/QTSSPOSIXFileSysModule + APIModules/QTSSProxyModule + APIModules/QTSSRawFileModule.bproj + APIModules/QTSSReflectorModule + APIModules/QTSSRefMovieModule + APIModules/QTSSRTPFileModule + APIModules/QTSSSpamDefenseModule.bproj + APIModules/QTSSWebDebugModule + APIModules/QTSSWebStatsModule + APIStubLib + AtomicLib + broadcasterctl + CommonUtilitiesLib + HTTPUtilitiesLib + MP3Broadcaster + PlaylistBroadcaster.tproj + PrefsSourceLib + QTFileLib + QTFileTools/RTPFileGen.tproj + RTCPUtilitiesLib + RTPMetaInfoLib + RTSPClientLib + Server.tproj + StreamingProxy.tproj) + +add_definitions(-D_REENTRANT -D__USE_POSIX -D__linux__ -D__PTHREADS_MUTEXES__ -D__PTHREADS__) +#target_link_libraries(DarwinStreamingServer -lpthread -ldl -lstdc++ -lm -lcrypt) + +set(DSS_SOURCE_FILES + APICommonCode/QTAccessFile.cpp + APICommonCode/QTAccessFile.h + APICommonCode/QTSS3GPPModuleUtils.cpp + APICommonCode/QTSS3GPPModuleUtils.h + APICommonCode/QTSSMemoryDeleter.h + APICommonCode/QTSSModuleUtils.cpp + APICommonCode/QTSSModuleUtils.h + APICommonCode/QTSSRollingLog.cpp + APICommonCode/QTSSRollingLog.h + APICommonCode/SDPSourceInfo.cpp + APICommonCode/SDPSourceInfo.h + APICommonCode/SourceInfo.cpp + APICommonCode/SourceInfo.h + APIModules/OSMemory_Modules/OSMemory_Modules.cpp + APIModules/QTSSAccessLogModule/QTSSAccessLogModule.cpp + APIModules/QTSSAccessLogModule/QTSSAccessLogModule.h + APIModules/QTSSAccessModule/AccessChecker.cpp + APIModules/QTSSAccessModule/AccessChecker.h + APIModules/QTSSAccessModule/QTSSAccessModule.cpp + APIModules/QTSSAccessModule/QTSSAccessModule.h + APIModules/QTSSAdminModule/AdminElementNode.cpp + APIModules/QTSSAdminModule/AdminElementNode.h + APIModules/QTSSAdminModule/AdminQuery.cpp + APIModules/QTSSAdminModule/AdminQuery.h + APIModules/QTSSAdminModule/QTSSAdminModule.cpp + APIModules/QTSSAdminModule/QTSSAdminModule.h + APIModules/QTSSDemoAuthorizationModule.bproj/QTSSDemoAuthorizationModule.cpp + APIModules/QTSSDemoAuthorizationModule.bproj/QTSSDemoAuthorizationModule.h + APIModules/QTSSDemoRedirectModule.bproj/QTSSDemoRedirectModule.cpp + APIModules/QTSSDemoRedirectModule.bproj/QTSSDemoRedirectModule.h + APIModules/QTSSDemoSMILModule.bproj/QTSSDemoSMILModule.cpp + APIModules/QTSSDemoSMILModule.bproj/QTSSDemoSMILModule.h +# APIModules/QTSSDSAuthModule/DSWrappers/CDirService.cpp +# APIModules/QTSSDSAuthModule/DSWrappers/CDirService.h +# APIModules/QTSSDSAuthModule/DSWrappers/DSBuffer.cpp +# APIModules/QTSSDSAuthModule/DSWrappers/DSBuffer.h +# APIModules/QTSSDSAuthModule/DSWrappers/DSDataList.h +# APIModules/QTSSDSAuthModule/DSWrappers/DSDataNode.h +# APIModules/QTSSDSAuthModule/DSAccessChecker.cpp +# APIModules/QTSSDSAuthModule/DSAccessChecker.h +# APIModules/QTSSDSAuthModule/QTSSDSAuthModule.cpp +# APIModules/QTSSDSAuthModule/QTSSDSAuthModule.h + APIModules/QTSSFileModule/QTSSFileModule.cpp + APIModules/QTSSFileModule/QTSSFileModule.h + APIModules/QTSSFilePrivsModule.bproj/QTSSFilePrivsModule.cpp + APIModules/QTSSFilePrivsModule.bproj/QTSSFilePrivsModule.h + APIModules/QTSSFlowControlModule/QTSSFlowControlModule.cpp + APIModules/QTSSFlowControlModule/QTSSFlowControlModule.h + APIModules/QTSSHomeDirectoryModule/DirectoryInfo.cpp + APIModules/QTSSHomeDirectoryModule/DirectoryInfo.h + APIModules/QTSSHomeDirectoryModule/QTSSHomeDirectoryModule.cpp + APIModules/QTSSHomeDirectoryModule/QTSSHomeDirectoryModule.h + APIModules/QTSSHttpFileModule/QTSSHttpFileModule.cpp + APIModules/QTSSHttpFileModule/QTSSHttpFileModule.h + APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.cpp + APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.h + APIModules/QTSSPOSIXFileSysModule/QTSSPosixFileSysModule.cpp + APIModules/QTSSPOSIXFileSysModule/QTSSPosixFileSysModule.h + APIModules/QTSSProxyModule/QTSSProxyModule.cpp + APIModules/QTSSProxyModule/QTSSProxyModule.h + APIModules/QTSSRawFileModule.bproj/QTSSRawFileModule.cpp + APIModules/QTSSRawFileModule.bproj/QTSSRawFileModule.h + APIModules/QTSSReflectorModule/QTSSReflectorModule.cpp + APIModules/QTSSReflectorModule/QTSSReflectorModule.h + APIModules/QTSSReflectorModule/QTSSRelayModule.cpp + APIModules/QTSSReflectorModule/QTSSRelayModule.h +# APIModules/QTSSReflectorModule/QTSSSplitterModule.cpp +# APIModules/QTSSReflectorModule/QTSSSplitterModule.h + APIModules/QTSSReflectorModule/RCFSourceInfo.cpp + APIModules/QTSSReflectorModule/RCFSourceInfo.h + APIModules/QTSSReflectorModule/ReflectorOutput.h + APIModules/QTSSReflectorModule/ReflectorSession.cpp + APIModules/QTSSReflectorModule/ReflectorSession.h + APIModules/QTSSReflectorModule/ReflectorStream.cpp + APIModules/QTSSReflectorModule/ReflectorStream.h + APIModules/QTSSReflectorModule/RelayOutput.cpp + APIModules/QTSSReflectorModule/RelayOutput.h + APIModules/QTSSReflectorModule/RelaySDPSourceInfo.cpp + APIModules/QTSSReflectorModule/RelaySDPSourceInfo.h + APIModules/QTSSReflectorModule/RelaySession.cpp + APIModules/QTSSReflectorModule/RelaySession.h + APIModules/QTSSReflectorModule/RTPSessionOutput.cpp + APIModules/QTSSReflectorModule/RTPSessionOutput.h + APIModules/QTSSReflectorModule/RTSPSourceInfo.cpp + APIModules/QTSSReflectorModule/RTSPSourceInfo.h + APIModules/QTSSReflectorModule/SequenceNumberMap.cpp + APIModules/QTSSReflectorModule/SequenceNumberMap.h + APIModules/QTSSRefMovieModule/QTSSRefMovieModule.cpp + APIModules/QTSSRefMovieModule/QTSSRefMovieModule.h + APIModules/QTSSRTPFileModule/QTSSRTPFileModule.cpp + APIModules/QTSSRTPFileModule/QTSSRTPFileModule.h + APIModules/QTSSRTPFileModule/RTPFileSession.cpp + APIModules/QTSSRTPFileModule/RTPFileSession.h + APIModules/QTSSSpamDefenseModule.bproj/QTSSSpamDefenseModule.cpp + APIModules/QTSSSpamDefenseModule.bproj/QTSSSpamDefenseModule.h + APIModules/QTSSWebDebugModule/QTSSWebDebugModule.cpp + APIModules/QTSSWebDebugModule/QTSSWebDebugModule.h + APIModules/QTSSWebStatsModule/QTSSWebStatsModule.cpp + APIModules/QTSSWebStatsModule/QTSSWebStatsModule.h + APIStubLib/QTSS.h + APIStubLib/QTSS_Private.cpp + APIStubLib/QTSS_Private.h + APIStubLib/QTSSRTSPProtocol.h + AtomicLib/atomic.h + AtomicLib/hmi.c + AtomicLib/timescale.c + AtomicLib/timestamp.h + broadcasterctl/BroadcasterAdminController.h + broadcasterctl/BroadcasterRemoteAdmin.h + CommonUtilitiesLib/atomic.cpp + CommonUtilitiesLib/atomic.h + CommonUtilitiesLib/base64.c + CommonUtilitiesLib/base64.h + CommonUtilitiesLib/ConfParser.cpp + CommonUtilitiesLib/ConfParser.h + CommonUtilitiesLib/daemon.c + CommonUtilitiesLib/daemon.h + CommonUtilitiesLib/DateTranslator.cpp + CommonUtilitiesLib/DateTranslator.h + CommonUtilitiesLib/DssStopwatch.h + CommonUtilitiesLib/ev.cpp + CommonUtilitiesLib/ev.h + CommonUtilitiesLib/EventContext.cpp + CommonUtilitiesLib/EventContext.h + CommonUtilitiesLib/FastCopyMacros.h + CommonUtilitiesLib/getopt.c + CommonUtilitiesLib/getopt.h + CommonUtilitiesLib/GetWord.c + CommonUtilitiesLib/GetWord.h + CommonUtilitiesLib/IdleTask.cpp + CommonUtilitiesLib/IdleTask.h + CommonUtilitiesLib/MakeDir.c + CommonUtilitiesLib/MakeDir.h + CommonUtilitiesLib/md5.c + CommonUtilitiesLib/md5.h + CommonUtilitiesLib/md5digest.cpp + CommonUtilitiesLib/md5digest.h + CommonUtilitiesLib/MyAssert.cpp + CommonUtilitiesLib/MyAssert.h +# CommonUtilitiesLib/mycondition.cpp +# CommonUtilitiesLib/mycondition.h +# CommonUtilitiesLib/mymutex.cpp +# CommonUtilitiesLib/mymutex.h + CommonUtilitiesLib/OS.cpp + CommonUtilitiesLib/OS.h + CommonUtilitiesLib/OSArrayObjectDeleter.h + CommonUtilitiesLib/OSBufferPool.cpp + CommonUtilitiesLib/OSBufferPool.h + CommonUtilitiesLib/OSCodeFragment.cpp + CommonUtilitiesLib/OSCodeFragment.h + CommonUtilitiesLib/OSCond.cpp + CommonUtilitiesLib/OSCond.h + CommonUtilitiesLib/OSFileSource.cpp + CommonUtilitiesLib/OSFileSource.h + CommonUtilitiesLib/OSHashTable.h + CommonUtilitiesLib/OSHeaders.c + CommonUtilitiesLib/OSHeaders.h + CommonUtilitiesLib/OSHeap.cpp + CommonUtilitiesLib/OSHeap.h + CommonUtilitiesLib/OSMemory.h + CommonUtilitiesLib/OSMutex.cpp + CommonUtilitiesLib/OSMutex.h + CommonUtilitiesLib/OSMutexRW.cpp + CommonUtilitiesLib/OSMutexRW.h + CommonUtilitiesLib/OSQueue.cpp + CommonUtilitiesLib/OSQueue.h + CommonUtilitiesLib/OSRef.cpp + CommonUtilitiesLib/OSRef.h + CommonUtilitiesLib/OSThread.cpp + CommonUtilitiesLib/OSThread.h + CommonUtilitiesLib/PathDelimiter.h + CommonUtilitiesLib/PLDoubleLinkedList.h + CommonUtilitiesLib/QueryParamList.cpp + CommonUtilitiesLib/QueryParamList.h + CommonUtilitiesLib/ResizeableStringFormatter.cpp + CommonUtilitiesLib/ResizeableStringFormatter.h + CommonUtilitiesLib/SafeStdLib.h + CommonUtilitiesLib/SDPUtils.cpp + CommonUtilitiesLib/SDPUtils.h + CommonUtilitiesLib/Socket.cpp + CommonUtilitiesLib/Socket.h + CommonUtilitiesLib/SocketUtils.cpp + CommonUtilitiesLib/SocketUtils.h + CommonUtilitiesLib/StopWatch.h + CommonUtilitiesLib/StringFormatter.cpp + CommonUtilitiesLib/StringFormatter.h + CommonUtilitiesLib/StringParser.cpp + CommonUtilitiesLib/StringParser.h + CommonUtilitiesLib/StringTranslator.cpp + CommonUtilitiesLib/StringTranslator.h + CommonUtilitiesLib/StrPtrLen.cpp + CommonUtilitiesLib/StrPtrLen.h + CommonUtilitiesLib/SVector.h + CommonUtilitiesLib/Task.cpp + CommonUtilitiesLib/Task.h + CommonUtilitiesLib/TCPListenerSocket.cpp + CommonUtilitiesLib/TCPListenerSocket.h + CommonUtilitiesLib/TCPSocket.cpp + CommonUtilitiesLib/TCPSocket.h + CommonUtilitiesLib/tempcalls.h + CommonUtilitiesLib/TimeoutTask.cpp + CommonUtilitiesLib/TimeoutTask.h + CommonUtilitiesLib/Trim.c + CommonUtilitiesLib/Trim.h + CommonUtilitiesLib/UDPDemuxer.cpp + CommonUtilitiesLib/UDPDemuxer.h + CommonUtilitiesLib/UDPSocket.cpp + CommonUtilitiesLib/UDPSocket.h + CommonUtilitiesLib/UDPSocketPool.cpp + CommonUtilitiesLib/UDPSocketPool.h + CommonUtilitiesLib/UserAgentParser.cpp + CommonUtilitiesLib/UserAgentParser.h +# CommonUtilitiesLib/win32ev.cpp + HTTPUtilitiesLib/HTTPProtocol.cpp + HTTPUtilitiesLib/HTTPProtocol.h + HTTPUtilitiesLib/HTTPRequest.cpp + HTTPUtilitiesLib/HTTPRequest.h + OSMemoryLib/OSMemory.cpp + PrefsSourceLib/FilePrefsSource.cpp + PrefsSourceLib/FilePrefsSource.h +# PrefsSourceLib/NetInfoPrefsSource.cpp +# PrefsSourceLib/NetInfoPrefsSource.h +# PrefsSourceLib/nilib2.c +# PrefsSourceLib/nilib2.h + PrefsSourceLib/PrefsSource.h + PrefsSourceLib/XMLParser.cpp + PrefsSourceLib/XMLParser.h + PrefsSourceLib/XMLPrefsParser.cpp + PrefsSourceLib/XMLPrefsParser.h + QTFileLib/QTAtom.cpp + QTFileLib/QTAtom.h + QTFileLib/QTAtom_dref.cpp + QTFileLib/QTAtom_dref.h + QTFileLib/QTAtom_elst.cpp + QTFileLib/QTAtom_elst.h + QTFileLib/QTAtom_hinf.cpp + QTFileLib/QTAtom_hinf.h + QTFileLib/QTAtom_mdhd.cpp + QTFileLib/QTAtom_mdhd.h + QTFileLib/QTAtom_mvhd.cpp + QTFileLib/QTAtom_mvhd.h + QTFileLib/QTAtom_stco.cpp + QTFileLib/QTAtom_stco.h + QTFileLib/QTAtom_stsc.cpp + QTFileLib/QTAtom_stsc.h + QTFileLib/QTAtom_stsd.cpp + QTFileLib/QTAtom_stsd.h + QTFileLib/QTAtom_stss.cpp + QTFileLib/QTAtom_stss.h + QTFileLib/QTAtom_stsz.cpp + QTFileLib/QTAtom_stsz.h + QTFileLib/QTAtom_stts.cpp + QTFileLib/QTAtom_stts.h + QTFileLib/QTAtom_tkhd.cpp + QTFileLib/QTAtom_tkhd.h + QTFileLib/QTAtom_tref.cpp + QTFileLib/QTAtom_tref.h + QTFileLib/QTFile.cpp + QTFileLib/QTFile.h + QTFileLib/QTFile_FileControlBlock.cpp + QTFileLib/QTFile_FileControlBlock.h + QTFileLib/QTHintTrack.cpp + QTFileLib/QTHintTrack.h + QTFileLib/QTRTPFile.cpp + QTFileLib/QTRTPFile.h + QTFileLib/QTTrack.cpp + QTFileLib/QTTrack.h + QTFileTools/QTFileTest.tproj/QTFileTest.cpp + QTFileTools/QTRTPFileTest.tproj/QTRTPFileTest.cpp + QTFileTools/QTRTPGen.tproj/QTRTPGen.cpp + QTFileTools/QTSampleLister.tproj/QTSampleLister.cpp + QTFileTools/QTSDPGen.tproj/QTSDPGen.cpp + QTFileTools/QTTrackInfo.tproj/QTTrackInfo.cpp + QTFileTools/RTPFileGen.tproj/RTPFileDefs.h + QTFileTools/RTPFileGen.tproj/RTPFileGen.cpp + qtpasswd.tproj/QTSSPasswd.cpp + RTCPUtilitiesLib/RTCPAckPacket.cpp + RTCPUtilitiesLib/RTCPAckPacket.h + RTCPUtilitiesLib/RTCPAckPacketFmt.h + RTCPUtilitiesLib/RTCPAPPNADUPacket.cpp + RTCPUtilitiesLib/RTCPAPPNADUPacket.h + RTCPUtilitiesLib/RTCPAPPPacket.cpp + RTCPUtilitiesLib/RTCPAPPPacket.h + RTCPUtilitiesLib/RTCPAPPQTSSPacket.cpp + RTCPUtilitiesLib/RTCPAPPQTSSPacket.h + RTCPUtilitiesLib/RTCPNADUPacketFmt.h + RTCPUtilitiesLib/RTCPPacket.cpp + RTCPUtilitiesLib/RTCPPacket.h + RTCPUtilitiesLib/RTCPRRPacket.h + RTCPUtilitiesLib/RTCPSRPacket.cpp + RTCPUtilitiesLib/RTCPSRPacket.h + RTPMetaInfoLib/RTPMetaInfoPacket.cpp + RTPMetaInfoLib/RTPMetaInfoPacket.h + RTSPClientLib/ClientSession.cpp + RTSPClientLib/ClientSession.h + RTSPClientLib/ClientSocket.cpp + RTSPClientLib/ClientSocket.h + RTSPClientLib/PlayerSimulator.h + RTSPClientLib/RTPPacket.h + RTSPClientLib/RTSPClient.cpp + RTSPClientLib/RTSPClient.h + SafeStdLib/DynamicModuleStdLib.cpp + SafeStdLib/InternalStdLib.cpp + Server.tproj/GenerateXMLPrefs.cpp + Server.tproj/GenerateXMLPrefs.h + Server.tproj/main.cpp + Server.tproj/QTSSCallbacks.cpp + Server.tproj/QTSSCallbacks.h + Server.tproj/QTSSDataConverter.cpp + Server.tproj/QTSSDataConverter.h + Server.tproj/QTSSDictionary.cpp + Server.tproj/QTSSDictionary.h + Server.tproj/QTSSErrorLogModule.cpp + Server.tproj/QTSSErrorLogModule.h + Server.tproj/QTSServer.cpp + Server.tproj/QTSServer.h + Server.tproj/QTSServerInterface.cpp + Server.tproj/QTSServerInterface.h + Server.tproj/QTSServerPrefs.cpp + Server.tproj/QTSServerPrefs.h + Server.tproj/QTSSExpirationDate.cpp + Server.tproj/QTSSExpirationDate.h + Server.tproj/QTSSFile.cpp + Server.tproj/QTSSFile.h + Server.tproj/QTSSMessages.cpp + Server.tproj/QTSSMessages.h + Server.tproj/QTSSModule.cpp + Server.tproj/QTSSModule.h + Server.tproj/QTSSPrefs.cpp + Server.tproj/QTSSPrefs.h + Server.tproj/QTSSSocket.cpp + Server.tproj/QTSSSocket.h + Server.tproj/QTSSStream.h + Server.tproj/QTSSUserProfile.cpp + Server.tproj/QTSSUserProfile.h + Server.tproj/RTCPTask.cpp + Server.tproj/RTCPTask.h + Server.tproj/RTPBandwidthTracker.cpp + Server.tproj/RTPBandwidthTracker.h + Server.tproj/RTPOverbufferWindow.cpp + Server.tproj/RTPOverbufferWindow.h + Server.tproj/RTPPacketResender.cpp + Server.tproj/RTPPacketResender.h + Server.tproj/RTPSession.cpp + Server.tproj/RTPSession.h + Server.tproj/RTPSession3GPP.cpp + Server.tproj/RTPSession3GPP.h + Server.tproj/RTPSessionInterface.cpp + Server.tproj/RTPSessionInterface.h + Server.tproj/RTPStream.cpp + Server.tproj/RTPStream.h + Server.tproj/RTPStream3GPP.cpp + Server.tproj/RTPStream3GPP.h + Server.tproj/RTSPProtocol.cpp + Server.tproj/RTSPProtocol.h + Server.tproj/RTSPRequest.cpp + Server.tproj/RTSPRequest.h + Server.tproj/RTSPRequest3GPP.cpp + Server.tproj/RTSPRequest3GPP.h + Server.tproj/RTSPRequestInterface.cpp + Server.tproj/RTSPRequestInterface.h + Server.tproj/RTSPRequestStream.cpp + Server.tproj/RTSPRequestStream.h + Server.tproj/RTSPResponseStream.cpp + Server.tproj/RTSPResponseStream.h + Server.tproj/RTSPSession.cpp + Server.tproj/RTSPSession.h + Server.tproj/RTSPSession3GPP.cpp + Server.tproj/RTSPSession3GPP.h + Server.tproj/RTSPSessionInterface.cpp + Server.tproj/RTSPSessionInterface.h + Server.tproj/RunServer.cpp + Server.tproj/RunServer.h + StreamingProxy.tproj/get_opt.c + StreamingProxy.tproj/get_opt.h + StreamingProxy.tproj/proxy.c + StreamingProxy.tproj/proxy.h + StreamingProxy.tproj/proxy_plat.h + StreamingProxy.tproj/proxy_unix.c + StreamingProxy.tproj/shared_udp.c + StreamingProxy.tproj/shared_udp.h + StreamingProxy.tproj/util.c + StreamingProxy.tproj/util.h + defaultPaths.h + PlatformHeader.h + revision.h) + +add_executable(DarwinStreamingServer ${DSS_SOURCE_FILES}) + +add_executable(StreamingLoadTool StreamingLoadTool/StreamingLoadTool.cpp) + +set(PLAYLIST_BROADCASTER_SOURCE_FILES + PlaylistBroadcaster.tproj/BCasterTracker.cpp + PlaylistBroadcaster.tproj/BCasterTracker.h + PlaylistBroadcaster.tproj/BroadcasterSession.cpp + PlaylistBroadcaster.tproj/BroadcasterSession.h + PlaylistBroadcaster.tproj/BroadcastLog.cpp + PlaylistBroadcaster.tproj/BroadcastLog.h + PlaylistBroadcaster.tproj/GetLocalIPAddressString.c + PlaylistBroadcaster.tproj/GetLocalIPAddressString.h + PlaylistBroadcaster.tproj/NoRepeat.cpp + PlaylistBroadcaster.tproj/NoRepeat.h + PlaylistBroadcaster.tproj/notes.c + PlaylistBroadcaster.tproj/PickerFromFile.cpp + PlaylistBroadcaster.tproj/PickerFromFile.h + PlaylistBroadcaster.tproj/playlist_array.h + PlaylistBroadcaster.tproj/playlist_broadcaster.cpp + PlaylistBroadcaster.tproj/playlist_broadcaster.h + PlaylistBroadcaster.tproj/playlist_elements.cpp + PlaylistBroadcaster.tproj/playlist_elements.h + PlaylistBroadcaster.tproj/playlist_lists.cpp + PlaylistBroadcaster.tproj/playlist_lists.h + PlaylistBroadcaster.tproj/playlist_parsers.cpp + PlaylistBroadcaster.tproj/playlist_parsers.h + PlaylistBroadcaster.tproj/playlist_QTRTPBroadcastFile.cpp + PlaylistBroadcaster.tproj/playlist_QTRTPBroadcastFile.h + PlaylistBroadcaster.tproj/playlist_SDPGen.cpp + PlaylistBroadcaster.tproj/playlist_SDPGen.h + PlaylistBroadcaster.tproj/playlist_SimpleParse.cpp + PlaylistBroadcaster.tproj/playlist_SimpleParse.h + PlaylistBroadcaster.tproj/playlist_timestamp.h + PlaylistBroadcaster.tproj/playlist_utils.cpp + PlaylistBroadcaster.tproj/playlist_utils.h + PlaylistBroadcaster.tproj/PlaylistBroadcaster.cpp + PlaylistBroadcaster.tproj/PlaylistPicker.cpp + PlaylistBroadcaster.tproj/PlaylistPicker.h + PlaylistBroadcaster.tproj/PLBroadcastDef.cpp + PlaylistBroadcaster.tproj/PLBroadcastDef.h + PlaylistBroadcaster.tproj/SimplePlayListElement.h + PlaylistBroadcaster.tproj/StSmartArrayPointer.h + PlaylistBroadcaster.tproj/tailor.h + PlaylistBroadcaster.tproj/TrackingElement.h) + +add_executable(PlaylistBroadcaster ${PLAYLIST_BROADCASTER_SOURCE_FILES}) + +set(MP3_BROADCASTER_SOURCE_FILES + MP3Broadcaster/BroadcasterMain.cpp + MP3Broadcaster/MP3Broadcaster.cpp + MP3Broadcaster/MP3Broadcaster.h + MP3Broadcaster/MP3BroadcasterLog.cpp + MP3Broadcaster/MP3BroadcasterLog.h + MP3Broadcaster/MP3FileBroadcaster.cpp + MP3Broadcaster/MP3FileBroadcaster.h + MP3Broadcaster/MP3MetaInfoUpdater.cpp + MP3Broadcaster/MP3MetaInfoUpdater.h) + +add_executable(MP3Broadcaster ${MP3_BROADCASTER_SOURCE_FILES}) + +add_executable(QTBroadcaster QTFileTools/QTBroadcaster.tproj/QTBroadcaster.cpp) + +set(QTFILEINFO_SOURCE_FILES + QTFileLib/QTFile.cpp + QTFileLib/QTFile.h + QTFileTools/QTFileInfo.tproj/QTFileInfo.cpp) + +add_executable(QTFileInfo ${QTFILEINFO_SOURCE_FILES}) \ No newline at end of file diff --git a/HTTPUtilitiesLib/HTTPProtocol.cpp b/HTTPUtilitiesLib/HTTPProtocol.cpp new file mode 100644 index 0000000..7e15eab --- /dev/null +++ b/HTTPUtilitiesLib/HTTPProtocol.cpp @@ -0,0 +1,313 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "HTTPProtocol.h" + +StrPtrLen HTTPProtocol::sMethods[] = +{ + StrPtrLen("GET"), + StrPtrLen("HEAD"), + StrPtrLen("POST"), + StrPtrLen("OPTIONS"), + StrPtrLen("PUT"), + StrPtrLen("DELETE"), + StrPtrLen("TRACE"), + StrPtrLen("CONNECT"), +}; + +HTTPMethod HTTPProtocol::GetMethod(const StrPtrLen* inMethodStr) +{ + HTTPMethod theMethod = httpIllegalMethod; + + if (inMethodStr->Len == 0) + return httpIllegalMethod; + + switch((inMethodStr->Ptr)[0]) + { + case 'G': theMethod = httpGetMethod; break; + case 'H': theMethod = httpHeadMethod; break; + case 'P': theMethod = httpPostMethod; break; // Most likely POST and not PUT + case 'O': theMethod = httpOptionsMethod; break; + case 'D': theMethod = httpDeleteMethod; break; + case 'T': theMethod = httpTraceMethod; break; + case 'C': theMethod = httpConnectMethod; break; + } + + if ( (theMethod != httpIllegalMethod) && (inMethodStr->Equal(sMethods[theMethod])) ) + return theMethod; + + // Check for remaining methods (Only PUT method is left) + if ( inMethodStr->Equal(sMethods[httpPutMethod]) ) + return httpPutMethod; + + return httpIllegalMethod; +} + +StrPtrLen HTTPProtocol::sHeaders[] = +{ + StrPtrLen("Connection"), + StrPtrLen("Date"), + StrPtrLen("Authorization"), + StrPtrLen("If-Modified-Since"), + StrPtrLen("Server"), + StrPtrLen("WWW-Authenticate"), + StrPtrLen("Expires"), + StrPtrLen("Last-Modified"), + + StrPtrLen("Cache-Control"), + StrPtrLen("Pragma"), + StrPtrLen("Trailer"), + StrPtrLen("Transfer-Encoding"), + StrPtrLen("Upgrade"), + StrPtrLen("Via"), + StrPtrLen("Warning"), + + StrPtrLen("Accept"), + StrPtrLen("Accept-Charset"), + StrPtrLen("Accept-Encoding"), + StrPtrLen("Accept-Language"), + StrPtrLen("Expect"), + StrPtrLen("From"), + StrPtrLen("Host"), + StrPtrLen("If-Match"), + StrPtrLen("If-None-Match"), + StrPtrLen("If-Range"), + StrPtrLen("If-Unmodified-Since"), + StrPtrLen("Max-Forwards"), + StrPtrLen("Proxy-Authorization"), + StrPtrLen("Range"), + StrPtrLen("Referer"), + StrPtrLen("TE"), + StrPtrLen("User-Agent"), + + StrPtrLen("Accept-Ranges"), + StrPtrLen("Age"), + StrPtrLen("ETag"), + StrPtrLen("Location"), + StrPtrLen("Proxy-Authenticate"), + StrPtrLen("Retry-After"), + StrPtrLen("Vary"), + + StrPtrLen("Allow"), + StrPtrLen("Content-Encoding"), + StrPtrLen("Content-Language"), + StrPtrLen("Content-Length"), + StrPtrLen("Content-Location"), + StrPtrLen("Content-MD5"), + StrPtrLen("Content-Range"), + StrPtrLen("Content-Type"), + + StrPtrLen("X-SessionCookie"), + StrPtrLen("X-Server-IP-Address"), + + StrPtrLen(" ,") +}; + +HTTPHeader HTTPProtocol::GetHeader(const StrPtrLen* inHeaderStr) +{ + if (inHeaderStr->Len == 0) + return httpIllegalHeader; + + HTTPHeader theHeader = httpIllegalHeader; + + //chances are this is one of our selected "VIP" headers. so check for this. + switch((inHeaderStr->Ptr)[0]) + { + case 'C': case 'c': theHeader = httpConnectionHeader; break; + case 'S': case 's': theHeader = httpServerHeader; break; + case 'D': case 'd': theHeader = httpDateHeader; break; + case 'A': case 'a': theHeader = httpAuthorizationHeader; break; + case 'W': case 'w': theHeader = httpWWWAuthenticateHeader; break; + case 'I': case 'i': theHeader = httpIfModifiedSinceHeader; break; + case 'E': case 'e': theHeader = httpExpiresHeader; break; + case 'L': case 'l': theHeader = httpLastModifiedHeader; break; + // Added this to optimize for HTTP tunnelling in the server (Not really a VIP header) + case 'X': case 'x': theHeader = httpSessionCookieHeader; break; + } + + if ((theHeader != httpIllegalHeader) && + (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 = httpNumVIPHeaders; x < httpNumHeaders; x++) + if (inHeaderStr->EqualIgnoreCase(sHeaders[x].Ptr, sHeaders[x].Len)) + return x; + return httpIllegalHeader; +} + +StrPtrLen HTTPProtocol::sStatusCodeStrings[] = +{ + StrPtrLen("Continue"), //kContinue + StrPtrLen("Switching Protocols"), //kSwitchingProtocols + StrPtrLen("OK"), //kOK + StrPtrLen("Created"), //kCreated + StrPtrLen("Accepted"), //kAccepted + StrPtrLen("Non Authoritative Information"), //kNonAuthoritativeInformation + StrPtrLen("No Content"), //kNoContent + StrPtrLen("Reset Content"), //kResetContent + StrPtrLen("Partial Content"), //kPartialContent + StrPtrLen("Multiple Choices"), //kMultipleChoices + StrPtrLen("Moved Permanently"), //kMovedPermanently + StrPtrLen("Found"), //kFound + StrPtrLen("See Other"), //kSeeOther + StrPtrLen("Not Modified"), //kNotModified + StrPtrLen("Use Proxy"), //kUseProxy + StrPtrLen("Temporary Redirect"), //kTemporaryRedirect + StrPtrLen("Bad Request"), //kBadRequest + StrPtrLen("Unauthorized"), //kUnAuthorized + StrPtrLen("Payment Required"), //kPaymentRequired + StrPtrLen("Forbidden"), //kForbidden + StrPtrLen("Not Found"), //kNotFound + StrPtrLen("Method Not Allowed"), //kMethodNotAllowed + StrPtrLen("Not Acceptable"), //kNotAcceptable + StrPtrLen("Proxy Authentication Required"), //kProxyAuthenticationRequired + StrPtrLen("Request Time-out"), //kRequestTimeout + StrPtrLen("Conflict"), //kConflict + 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("Request Range Not Satisfiable"), //kRequestRangeNotSatisfiable + StrPtrLen("Expectation Failed"), //kExpectationFailed + StrPtrLen("Internal Server Error"), //kInternalServerError + StrPtrLen("Not Implemented"), //kNotImplemented + StrPtrLen("Bad Gateway"), //kBadGateway + StrPtrLen("Service Unavailable"), //kServiceUnavailable + StrPtrLen("Gateway Timeout"), //kGatewayTimeout + StrPtrLen("HTTP Version not supported") //kHTTPVersionNotSupported +}; + +SInt32 HTTPProtocol::sStatusCodes[] = +{ + 100, //kContinue + 101, //kSwitchingProtocols + 200, //kOK + 201, //kCreated + 202, //kAccepted + 203, //kNonAuthoritativeInformation + 204, //kNoContent + 205, //kResetContent + 206, //kPartialContent + 300, //kMultipleChoices + 301, //kMovedPermanently + 302, //kFound + 303, //kSeeOther + 304, //kNotModified + 305, //kUseProxy + 307, //kTemporaryRedirect + 400, //kBadRequest + 401, //kUnAuthorized + 402, //kPaymentRequired + 403, //kForbidden + 404, //kNotFound + 405, //kMethodNotAllowed + 406, //kNotAcceptable + 407, //kProxyAuthenticationRequired + 408, //kRequestTimeout + 409, //kConflict + 410, //kGone + 411, //kLengthRequired + 412, //kPreconditionFailed + 413, //kRequestEntityTooLarge + 414, //kRequestURITooLarge + 415, //kUnsupportedMediaType + 416, //kRequestRangeNotSatisfiable + 417, //kExpectationFailed + 500, //kInternalServerError + 501, //kNotImplemented + 502, //kBadGateway + 503, //kServiceUnavailable + 504, //kGatewayTimeout + 505 //kHTTPVersionNotSupported +}; + +StrPtrLen HTTPProtocol::sStatusCodeAsStrings[] = +{ + StrPtrLen("100"), //kContinue + StrPtrLen("101"), //kSwitchingProtocols + StrPtrLen("200"), //kOK + StrPtrLen("201"), //kCreated + StrPtrLen("202"), //kAccepted + StrPtrLen("203"), //kNonAuthoritativeInformation + StrPtrLen("204"), //kNoContent + StrPtrLen("205"), //kResetContent + StrPtrLen("206"), //kPartialContent + StrPtrLen("300"), //kMultipleChoices + StrPtrLen("301"), //kMovedPermanently + StrPtrLen("302"), //kFound + StrPtrLen("303"), //kSeeOther + StrPtrLen("304"), //kNotModified + StrPtrLen("305"), //kUseProxy + StrPtrLen("307"), //kTemporaryRedirect + StrPtrLen("400"), //kBadRequest + StrPtrLen("401"), //kUnAuthorized + StrPtrLen("402"), //kPaymentRequired + StrPtrLen("403"), //kForbidden + StrPtrLen("404"), //kNotFound + StrPtrLen("405"), //kMethodNotAllowed + StrPtrLen("406"), //kNotAcceptable + StrPtrLen("407"), //kProxyAuthenticationRequired + StrPtrLen("408"), //kRequestTimeout + StrPtrLen("409"), //kConflict + StrPtrLen("410"), //kGone + StrPtrLen("411"), //kLengthRequired + StrPtrLen("412"), //kPreconditionFailed + StrPtrLen("413"), //kRequestEntityTooLarge + StrPtrLen("414"), //kRequestURITooLarge + StrPtrLen("415"), //kUnsupportedMediaType + StrPtrLen("416"), //kRequestRangeNotSatisfiable + StrPtrLen("417"), //kExpectationFailed + StrPtrLen("500"), //kInternalServerError + StrPtrLen("501"), //kNotImplemented + StrPtrLen("502"), //kBadGateway + StrPtrLen("503"), //kServiceUnavailable + StrPtrLen("504"), //kGatewayTimeout + StrPtrLen("505") //kHTTPVersionNotSupported +}; + +StrPtrLen HTTPProtocol::sVersionStrings[] = +{ + StrPtrLen("HTTP/0.9"), + StrPtrLen("HTTP/1.0"), + StrPtrLen("HTTP/1.1") +}; + +HTTPVersion HTTPProtocol::GetVersion(StrPtrLen* versionStr) +{ + if (versionStr->Len != 8) + return httpIllegalVersion; + SInt32 limit = httpNumVersions; + for (SInt32 x = 0; x < limit; x++) + { + if (versionStr->EqualIgnoreCase(sVersionStrings[x].Ptr, sVersionStrings[x].Len)) + return x; + } + + return httpIllegalVersion; +} + diff --git a/HTTPUtilitiesLib/HTTPProtocol.h b/HTTPUtilitiesLib/HTTPProtocol.h new file mode 100644 index 0000000..59f3a19 --- /dev/null +++ b/HTTPUtilitiesLib/HTTPProtocol.h @@ -0,0 +1,204 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 __HTTPPROTOCOL_H__ +#define __HTTPPROTOCOL_H__ + +#include "OSHeaders.h" +#include "StrPtrLen.h" + +// Versions +enum +{ + http09version = 0, + http10Version = 1, + http11Version = 2, + + httpNumVersions = 3, + httpIllegalVersion = 3 +}; +typedef UInt32 HTTPVersion; + +// Methods +enum +{ + httpGetMethod = 0, + httpHeadMethod = 1, + httpPostMethod = 2, + httpOptionsMethod = 3, + httpPutMethod = 4, + httpDeleteMethod = 5, + httpTraceMethod = 6, + httpConnectMethod = 7, + + httpNumMethods = 8, + httpIllegalMethod = 8 +}; +typedef UInt32 HTTPMethod; + +// Headers +enum +{ + // VIP headers + httpConnectionHeader = 0, // general header + httpDateHeader = 1, // general header + httpAuthorizationHeader = 2, // request header + httpIfModifiedSinceHeader = 3, // request header + httpServerHeader = 4, // response header + httpWWWAuthenticateHeader = 5, // response header + httpExpiresHeader = 6, // entity header + httpLastModifiedHeader = 7, // entity header + httpNumVIPHeaders = 8, + + //Other general http headers + httpCacheControlHeader = 8, + httpPragmaHeader = 9, + httpTrailerHeader = 10, + httpTransferEncodingHeader = 11, + httpUpgradeHeader = 12, + httpViaHeader = 13, + httpWarningHeader = 14, + + // Other request headers + httpAcceptHeader = 15, + httpAcceptCharsetHeader = 16, + httpAcceptEncodingHeader = 17, + httpAcceptLanguageHeader = 18, + httpExpectHeader = 19, + httpFromHeader = 20, + httpHostHeader = 21, + httpIfMatchHeader = 22, + httpIfNoneMatchHeader = 23, + httpIfRangeHeader = 24, + httpIfUnmodifiedSinceHeader = 25, + httpMaxForwardsHeader = 26, + httpProxyAuthorizationHeader = 27, + httpRangeHeader = 28, + httpRefererHeader = 29, + httpTEHeader = 30, + httpUserAgentHeader = 31, + + // Other response headers + httpAcceptRangesHeader = 32, + httpAgeHeader = 33, + httpETagHeader = 34, + httpLocationHeader = 35, + httpProxyAuthenticateHeader = 36, + httpRetryAfterHeader = 37, + httpVaryHeader = 38, + + // Other entity headers + httpAllowHeader = 39, + httpContentEncodingHeader = 40, + httpContentLanguageHeader = 41, + httpContentLengthHeader = 42, + httpContentLocationHeader = 43, + httpContentMD5Header = 44, + httpContentRangeHeader = 45, + httpContentTypeHeader = 46, + + // QTSS Specific headers + // Add headers that are not part of the HTTP spec here + // Make sure and up the number of headers and httpIllegalHeader number + httpSessionCookieHeader = 47, // Used for HTTP tunnelling + httpServerIPAddressHeader = 48, + + httpNumHeaders = 49, + httpIllegalHeader = 49 +}; +typedef UInt32 HTTPHeader; + +// Status codes +enum +{ + httpContinue = 0, //100 + httpSwitchingProtocols = 1, //101 + httpOK = 2, //200 + httpCreated = 3, //201 + httpAccepted = 4, //202 + httpNonAuthoritativeInformation = 5, //203 + httpNoContent = 6, //204 + httpResetContent = 7, //205 + httpPartialContent = 8, //206 + httpMultipleChoices = 9, //300 + httpMovedPermanently = 10, //301 + httpFound = 11, //302 + httpSeeOther = 12, //303 + httpNotModified = 13, //304 + httpUseProxy = 14, //305 + httpTemporaryRedirect = 15, //307 + httpBadRequest = 16, //400 + httpUnAuthorized = 17, //401 + httpPaymentRequired = 18, //402 + httpForbidden = 19, //403 + httpNotFound = 20, //404 + httpMethodNotAllowed = 21, //405 + httpNotAcceptable = 22, //406 + httpProxyAuthenticationRequired = 23, //407 + httpRequestTimeout = 24, //408 + httpConflict = 25, //409 + httpGone = 26, //410 + httpLengthRequired = 27, //411 + httpPreconditionFailed = 28, //412 + httpRequestEntityTooLarge = 29, //413 + httpRequestURITooLarge = 30, //414 + httpUnsupportedMediaType = 31, //415 + httpRequestRangeNotSatisfiable = 32, //416 + httpExpectationFailed = 33, //417 + httpInternalServerError = 34, //500 + httpNotImplemented = 35, //501 + httpBadGateway = 36, //502 + httpServiceUnavailable = 37, //503 + httpGatewayTimeout = 38, //504 + httpHTTPVersionNotSupported = 39, //505 + httpNumStatusCodes = 40 +}; +typedef UInt32 HTTPStatusCode; + +class HTTPProtocol +{ +public: + // Methods + static HTTPMethod GetMethod(const StrPtrLen* inMethodStr); + static StrPtrLen* GetMethodString(HTTPMethod inMethod) { return &sMethods[inMethod]; } + // Headers + static HTTPHeader GetHeader(const StrPtrLen* inHeaderStr); + static StrPtrLen* GetHeaderString(HTTPHeader inHeader) { return &sHeaders[inHeader]; } + // Status codes + static StrPtrLen* GetStatusCodeString(HTTPStatusCode inStat) { return &sStatusCodeStrings[inStat]; } + static SInt32 GetStatusCode(HTTPStatusCode inStat) { return sStatusCodes[inStat]; } + static StrPtrLen* GetStatusCodeAsString(HTTPStatusCode inStat) { return &sStatusCodeAsStrings[inStat]; } + // Versions + static HTTPVersion GetVersion(StrPtrLen* versionStr); + static StrPtrLen* GetVersionString(HTTPVersion version) { return &sVersionStrings[version]; } + +private: + static StrPtrLen sMethods[]; + static StrPtrLen sHeaders[]; + static StrPtrLen sStatusCodeStrings[]; + static StrPtrLen sStatusCodeAsStrings[]; + static SInt32 sStatusCodes[]; + static StrPtrLen sVersionStrings[]; +}; +#endif // __HTTPPROTOCOL_H__ diff --git a/HTTPUtilitiesLib/HTTPRequest.cpp b/HTTPUtilitiesLib/HTTPRequest.cpp new file mode 100644 index 0000000..c9b428e --- /dev/null +++ b/HTTPUtilitiesLib/HTTPRequest.cpp @@ -0,0 +1,419 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "HTTPRequest.h" +#include "HTTPProtocol.h" +#include "OSMemory.h" +#include "StringParser.h" +#include "StringTranslator.h" +#include "ResizeableStringFormatter.h" +#include "DateTranslator.h" + + +StrPtrLen HTTPRequest::sColonSpace(": ", 2); +static Bool16 sFalse = false; +static Bool16 sTrue = true; +static StrPtrLen sCloseString("close", 5); +static StrPtrLen sKeepAliveString("keep-alive", 10); +static StrPtrLen sDefaultRealm("Streaming Server", 19); +UInt8 HTTPRequest::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, 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 +}; + + +// Constructor +HTTPRequest::HTTPRequest(StrPtrLen* serverHeader, StrPtrLen* requestPtr) +{ + // Store the pointer to the server header field + fSvrHeader = *serverHeader; + + // Set initial state + fRequestHeader = *requestPtr; + fResponseHeader = NULL; + fResponseFormatter = NULL; + fMethod = httpIllegalMethod; + fVersion = httpIllegalVersion; + fAbsoluteURI = NULL; + fRelativeURI = NULL; + fAbsoluteURIScheme = NULL; + fHostHeader = NULL; + fRequestPath = NULL; + fStatusCode = httpOK; + fRequestKeepAlive = false; // Default value when there is no version string +} + +// Constructor for creating a response only +HTTPRequest::HTTPRequest(StrPtrLen* serverHeader) +{ + // Store the pointer to the server header field + fSvrHeader = *serverHeader; + + // We do not require any of these: + fRequestHeader = NULL; + + fMethod = httpIllegalMethod; + fVersion = httpIllegalVersion; + fRequestLine = NULL; + fAbsoluteURI = NULL; + fRelativeURI = NULL; + fAbsoluteURIScheme = NULL; + fHostHeader = NULL; + fRequestPath = NULL; + fStatusCode = 0; + fRequestKeepAlive = false; + + // We require the response but we allocate memory only when we call + // CreateResponseHeader + fResponseHeader = NULL; + fResponseFormatter = NULL; +} + +// Destructor + HTTPRequest::~HTTPRequest() + { + if (fResponseHeader != NULL) + { + if (fResponseHeader->Ptr != NULL) + delete fResponseHeader->Ptr; + delete fResponseHeader; + } + if (fResponseFormatter != NULL) + delete fResponseFormatter; + if (fRequestPath != NULL) + delete [] fRequestPath; + } +//Parses the request +QTSS_Error HTTPRequest::Parse() +{ + Assert(fRequestHeader.Ptr != NULL); + StringParser parser(&fRequestHeader); + + // Store the request line (used for logging) + // (ex: GET /index.html HTTP/1.0) + StringParser requestLineParser(&fRequestHeader); + requestLineParser.ConsumeUntil(&fRequestLine, StringParser::sEOLMask); + + // Parse request line returns an error if there is an error in the + // request URI or the formatting of the request line. + // If the method or version are not found, they are set + // to httpIllegalMethod or httpIllegalVersion respectively, + // and QTSS_NoErr is returned. + QTSS_Error err = ParseRequestLine(&parser); + if (err != QTSS_NoErr) + return err; + + // Parse headers and set values of headers into fFieldValues array + err = ParseHeaders(&parser); + if (err != QTSS_NoErr) + return err; + + return QTSS_NoErr; +} + +QTSS_Error HTTPRequest::ParseRequestLine(StringParser* parser) +{ + // Get the method - If the method is not one of the defined methods + // then it doesn't return an error but sets fMethod to httpIllegalMethod + StrPtrLen theParsedData; + parser->ConsumeWord(&theParsedData); + fMethod = HTTPProtocol::GetMethod(&theParsedData); + + // Consume whitespace + parser->ConsumeWhitespace(); + + // Parse the URI - If it fails returns an error after setting + // the fStatusCode to the appropriate error code + QTSS_Error err = ParseURI(parser); + if (err != QTSS_NoErr) + return err; + + // 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 = HTTPProtocol::GetVersion(&versionStr); + + // Go past the end of line + if (!parser->ExpectEOL()) + { + fStatusCode = httpBadRequest; + return QTSS_BadArgument; // Request line is not properly formatted! + } + + return QTSS_NoErr; +} + +QTSS_Error HTTPRequest::ParseURI(StringParser* parser) +{ + + // read in the complete URL into fRequestAbsURI + parser->ConsumeUntil(&fAbsoluteURI, sURLStopConditions); + + StringParser urlParser(&fAbsoluteURI); + + // we always should have a slash before the URI + // If not, that indicates this is a full URI + if (fAbsoluteURI.Ptr[0] != '/') + { + //if it is a full URL, store the scheme and host name + urlParser.ConsumeLength(&fAbsoluteURIScheme, 7); //consume "http://" + urlParser.ConsumeUntil(&fHostHeader, '/'); + } + + // whatever is in this position is the relative URI + StrPtrLen relativeURI(urlParser.GetCurrentPosition(), urlParser.GetDataReceivedLen() - urlParser.GetDataParsedLen()); + // read this URI into fRequestRelURI + fRelativeURI = relativeURI; + + // Allocate memory for fRequestPath + UInt32 len = fRelativeURI.Len; + len++; + char* relativeURIDecoded = NEW char[len]; + + SInt32 theBytesWritten = StringTranslator::DecodeURL(fRelativeURI.Ptr, fRelativeURI.Len, + relativeURIDecoded, len); + + //if negative, an error occurred, reported as an QTSS_Error + //we also need to leave room for a terminator. + if ((theBytesWritten < 0) || ((UInt32)theBytesWritten == len)) + { + fStatusCode = httpBadRequest; + return QTSS_BadArgument; + } + fRequestPath = NEW char[theBytesWritten + 1]; + ::memcpy(fRequestPath, relativeURIDecoded + 1, theBytesWritten); + delete relativeURIDecoded; + fRequestPath[theBytesWritten] = '\0'; + return QTSS_NoErr; +} + +// Parses the Connection header and makes sure that request is properly terminated +QTSS_Error HTTPRequest::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) + { // No colon after header! + fStatusCode = httpBadRequest; + return QTSS_BadArgument; + } + + if (parser->PeekFast() == ' ') + { // handle space, if any + isStreamOK = parser->Expect(' '); + Assert(isStreamOK); + } + + //Look up the proper header enumeration based on the header string. + HTTPHeader theHeader = HTTPProtocol::GetHeader(&theKeyWord); + + StrPtrLen theHeaderVal; + isStreamOK = parser->GetThruEOL(&theHeaderVal); + + if (!isStreamOK) + { // No EOL after header! + fStatusCode = httpBadRequest; + return QTSS_BadArgument; + } + + // If this is the connection header + if ( theHeader == httpConnectionHeader ) + { // Set the keep alive boolean based on the connection header value + SetKeepAlive(&theHeaderVal); + } + + // Have the header field and the value; Add value to the array + // If the field is invalid (or unrecognized) just skip over gracefully + if ( theHeader != httpIllegalHeader ) + fFieldValues[theHeader] = theHeaderVal; + + } + + isStreamOK = parser->ExpectEOL(); + Assert(isStreamOK); + + return QTSS_NoErr; +} + +void HTTPRequest::SetKeepAlive(StrPtrLen *keepAliveValue) +{ + if ( sCloseString.EqualIgnoreCase(keepAliveValue->Ptr, keepAliveValue->Len) ) + fRequestKeepAlive = sFalse; + else + { + Assert( sKeepAliveString.EqualIgnoreCase(keepAliveValue->Ptr, keepAliveValue->Len) ); + fRequestKeepAlive = sTrue; + } +} + +void HTTPRequest::PutStatusLine(StringFormatter* putStream, HTTPStatusCode status, + HTTPVersion version) +{ + putStream->Put(*(HTTPProtocol::GetVersionString(version))); + putStream->PutSpace(); + putStream->Put(*(HTTPProtocol::GetStatusCodeAsString(status))); + putStream->PutSpace(); + putStream->Put(*(HTTPProtocol::GetStatusCodeString(status))); + putStream->PutEOL(); +} + +StrPtrLen* HTTPRequest::GetHeaderValue(HTTPHeader inHeader) +{ + if ( inHeader != httpIllegalHeader ) + return &fFieldValues[inHeader]; + return NULL; +} + +void HTTPRequest:: CreateResponseHeader(HTTPVersion version, HTTPStatusCode statusCode) +{ + // If we are creating a second response for the same request, make sure and + // deallocate memory for old response and allocate fresh memory + if (fResponseFormatter != NULL) + { + if(fResponseHeader->Ptr != NULL) + delete fResponseHeader->Ptr; + delete fResponseHeader; + delete fResponseFormatter; + } + + // Allocate memory for the response when you first create it + char* responseString = NEW char[kMinHeaderSizeInBytes]; + fResponseHeader = NEW StrPtrLen(responseString, kMinHeaderSizeInBytes); + fResponseFormatter = NEW ResizeableStringFormatter(fResponseHeader->Ptr, fResponseHeader->Len); + + //make a partial header for the given version and status code + PutStatusLine(fResponseFormatter, statusCode, version); + Assert(fSvrHeader.Ptr != NULL); + fResponseFormatter->Put(fSvrHeader); + fResponseFormatter->PutEOL(); + fResponseHeader->Len = fResponseFormatter->GetCurrentOffset(); +} + +StrPtrLen* HTTPRequest::GetCompleteResponseHeader() +{ + fResponseFormatter->PutEOL(); + fResponseHeader->Len = fResponseFormatter->GetCurrentOffset(); + return fResponseHeader; +} + +void HTTPRequest::AppendResponseHeader(HTTPHeader inHeader, StrPtrLen* inValue) +{ + fResponseFormatter->Put(*(HTTPProtocol::GetHeaderString(inHeader))); + fResponseFormatter->Put(sColonSpace); + fResponseFormatter->Put(*inValue); + fResponseFormatter->PutEOL(); + fResponseHeader->Len = fResponseFormatter->GetCurrentOffset(); +} + +void HTTPRequest::AppendContentLengthHeader(UInt64 length_64bit) +{ + char* contentLength = NEW char[256]; + qtss_sprintf(contentLength, "%"_64BITARG_"d", length_64bit); + StrPtrLen contentLengthPtr(contentLength); + AppendResponseHeader(httpContentLengthHeader, &contentLengthPtr); +} + +void HTTPRequest::AppendContentLengthHeader(UInt32 length_32bit) +{ + char* contentLength = NEW char[256]; + qtss_sprintf(contentLength, "%"_U32BITARG_"", length_32bit); + StrPtrLen contentLengthPtr(contentLength); + AppendResponseHeader(httpContentLengthHeader, &contentLengthPtr); +} + +void HTTPRequest::AppendConnectionCloseHeader() +{ + AppendResponseHeader(httpConnectionHeader, &sCloseString); +} + +void HTTPRequest::AppendConnectionKeepAliveHeader() +{ + AppendResponseHeader(httpConnectionHeader, &sKeepAliveString); +} + +void HTTPRequest::AppendDateAndExpiresFields() +{ + 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->AppendResponseHeader(httpDateHeader, &theDate); + this->AppendResponseHeader(httpExpiresHeader, &theDate); +} + +void HTTPRequest::AppendDateField() +{ + 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 date + this->AppendResponseHeader(httpDateHeader, &theDate); +} + +time_t HTTPRequest::ParseIfModSinceHeader() +{ + time_t theIfModSinceDate = (time_t) DateTranslator::ParseDate(&fFieldValues[httpIfModifiedSinceHeader]); + return theIfModSinceDate; +} diff --git a/HTTPUtilitiesLib/HTTPRequest.h b/HTTPUtilitiesLib/HTTPRequest.h new file mode 100644 index 0000000..ca73224 --- /dev/null +++ b/HTTPUtilitiesLib/HTTPRequest.h @@ -0,0 +1,137 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 __HTTPREQUEST_H__ +#define __HTTPREQUEST_H__ + +#include "HTTPProtocol.h" +#include "StrPtrLen.h" +#include "StringParser.h" +#include "ResizeableStringFormatter.h" +#include "OSHeaders.h" +#include "QTSS.h" + +class HTTPRequest +{ +public: + // Constructor + HTTPRequest(StrPtrLen* serverHeader, StrPtrLen* requestPtr); + + // This cosntructor is used when the request has been parsed and thrown away + // and the response has to be created + HTTPRequest(StrPtrLen* serverHeader); + + // Destructor + virtual ~HTTPRequest(); + + // Should be called before accessing anything in the request header + // Calls ParseRequestLine and ParseHeaders + QTSS_Error Parse(); + + // Basic access methods for the HTTP method, the absolute request URI, + // the host name from URI, the relative request URI, the request file path, + // the HTTP version, the Status code, the keep-alive tag. + HTTPMethod GetMethod(){ return fMethod; } + StrPtrLen* GetRequestLine(){ return &fRequestLine; } + StrPtrLen* GetRequestAbsoluteURI(){ return &fAbsoluteURI; } + StrPtrLen* GetSchemefromAbsoluteURI(){ return &fAbsoluteURIScheme; } + StrPtrLen* GetHostfromAbsoluteURI(){ return &fHostHeader; } + StrPtrLen* GetRequestRelativeURI(){ return &fRelativeURI; } + char* GetRequestPath(){ return fRequestPath; } + HTTPVersion GetVersion(){ return fVersion; } + HTTPStatusCode GetStatusCode(){ return fStatusCode; } + Bool16 IsRequestKeepAlive(){ return fRequestKeepAlive; } + + // If header field exists in the request, it will be found in the dictionary + // and the value returned. Otherwise, NULL is returned. + StrPtrLen* GetHeaderValue(HTTPHeader inHeader); + + // Creates a header with the corresponding version and status code + void CreateResponseHeader(HTTPVersion version, HTTPStatusCode statusCode); + + // To append response header fields as appropriate + void AppendResponseHeader(HTTPHeader inHeader, StrPtrLen* inValue); + void AppendDateAndExpiresFields(); + void AppendDateField(); + void AppendConnectionCloseHeader(); + void AppendConnectionKeepAliveHeader(); + void AppendContentLengthHeader(UInt64 length_64bit); + void AppendContentLengthHeader(UInt32 length_32bit); + + // Returns the completed response header by appending CRLF to the end of the header + // fields buffer + StrPtrLen* GetCompleteResponseHeader(); + + // Parse if-modified-since header + time_t ParseIfModSinceHeader(); + +private: + enum { kMinHeaderSizeInBytes = 512 }; + + // Gets the method, version and calls ParseURI + QTSS_Error ParseRequestLine(StringParser* parser); + // Parses the URI to get absolute and relative URIs, the host name and the file path + QTSS_Error ParseURI(StringParser* parser); + // Parses the headers and adds them into a dictionary + // Also calls SetKeepAlive with the Connection header field's value if it exists + QTSS_Error ParseHeaders(StringParser* parser); + + // Sets fRequestKeepAlive + void SetKeepAlive(StrPtrLen* keepAliveValue); + // Used in initialize and CreateResponseHeader + void PutStatusLine(StringFormatter* putStream, HTTPStatusCode status, HTTPVersion version); + //For writing into the premade headers + StrPtrLen* GetServerHeader(){ return &fSvrHeader; } + + // Complete request and response headers + StrPtrLen fRequestHeader; + ResizeableStringFormatter* fResponseFormatter; + StrPtrLen* fResponseHeader; + + // Private members + HTTPMethod fMethod; + HTTPVersion fVersion; + + StrPtrLen fRequestLine; + + // For the URI (fAbsoluteURI and fRelativeURI are the same if the URI is of the form "/path") + StrPtrLen fAbsoluteURI; // If it is of the form "http://foo.bar.com/path" + StrPtrLen fRelativeURI; // If it is of the form "/path" + + // If it is an absolute URI, these fields will be filled in + // "http://foo.bar.com/path" => fAbsoluteURIScheme = "http", fHostHeader = "foo.bar.com", + // fRequestPath = "path" + StrPtrLen fAbsoluteURIScheme; + StrPtrLen fHostHeader; // If the full url is given in the request line + char* fRequestPath; // Also contains the query string + + HTTPStatusCode fStatusCode; + Bool16 fRequestKeepAlive; // Keep-alive information in the client request + StrPtrLen fFieldValues[httpNumHeaders]; // Array of header field values parsed from the request + StrPtrLen fSvrHeader; // Server header set up at initialization + static StrPtrLen sColonSpace; + static UInt8 sURLStopConditions[]; +}; + +#endif // __HTTPREQUEST_H__ diff --git a/PlatformHeader.h b/PlatformHeader.h new file mode 100644 index 0000000..7993b84 --- /dev/null +++ b/PlatformHeader.h @@ -0,0 +1,236 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +// Build flags. How do you want your server built? +#define DEBUG 0 +#define ASSERT 1 +#define MEMORY_DEBUGGING 0 //enable this to turn on really fancy debugging of memory leaks, etc... +#define QTFILE_MEMORY_DEBUGGING 0 + +#if __MacOSX__ + #define PLATFORM_SERVER_BIN_NAME "QuickTimeStreamingServer" + #define PLATFORM_SERVER_TEXT_NAME "QuickTime Streaming Server" +#else + #define PLATFORM_SERVER_BIN_NAME "DarwinStreamingServer" + #define PLATFORM_SERVER_TEXT_NAME "Darwin Streaming Server" + #define MMAP_TABLES 0 +#endif + + +// Platform-specific switches +#if __MacOSX__ + +#define USE_ATOMICLIB 0 +//#define MACOSXEVENTQUEUE 1 +#define __PTHREADS__ 1 +#define __PTHREADS_MUTEXES__ 1 + +#include +#include +#if BYTE_ORDER == BIG_ENDIAN + #define BIGENDIAN 1 +#else + #define BIGENDIAN 0 +#endif + +#define ALLOW_NON_WORD_ALIGN_ACCESS 1 +#define USE_THREAD 0 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 0 +#define USE_THR_YIELD 0 +#define kPlatformNameString "MacOSX" +#define EXPORT +#define MACOSX_PUBLICBETA 0 +#define USE_DEFAULT_STD_LIB 1 + +#ifdef __LP64__ + #define MACOSXEVENTQUEUE 1 + #define EVENTS_KQUEUE 0 // future + #define EVENTS_SELECT 0 // future + #define EVENTS_OSXEVENTQUEUE 0 // future + #define SET_SELECT_SIZE 1024 + #define MMAP_TABLES 0 +#else + #define MACOSXEVENTQUEUE 1 + #define EVENTS_KQUEUE 0 + #define EVENTS_SELECT 0 + #define EVENTS_OSXEVENTQUEUE 1 + #define SET_SELECT_SIZE 0 + #define MMAP_TABLES 0 +#endif + + +#elif __Win32__ + +#define USE_ATOMICLIB 0 +#define MACOSXEVENTQUEUE 0 +#define __PTHREADS__ 0 +#define __PTHREADS_MUTEXES__ 0 +//#define BIGENDIAN 0 // Defined equivalently inside windows +#define ALLOW_NON_WORD_ALIGN_ACCESS 1 +#define USE_THREAD 0 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 0 +#define USE_THR_YIELD 0 +#define kPlatformNameString "Win32" +#define EXPORT __declspec(dllexport) +#ifndef USE_DEFAULT_STD_LIB + #define USE_DEFAULT_STD_LIB 1 +#endif + +#elif __linux__ + +#include +#if __BYTE_ORDER == BIG_ENDIAN + #define BIGENDIAN 1 +#else + #define BIGENDIAN 0 +#endif + +#define USE_ATOMICLIB 0 +#define MACOSXEVENTQUEUE 0 +#define __PTHREADS__ 1 +#define __PTHREADS_MUTEXES__ 1 +#define ALLOW_NON_WORD_ALIGN_ACCESS 1 +#define USE_THREAD 0 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 0 +#define USE_THR_YIELD 0 +#define kPlatformNameString "Linux" +#define EXPORT +#define _REENTRANT 1 + +#elif __linuxppc__ + +#include +#if __BYTE_ORDER == BIG_ENDIAN + #define BIGENDIAN 1 +#else + #define BIGENDIAN 0 +#endif + +#define USE_ATOMICLIB 0 +#define MACOSXEVENTQUEUE 0 +#define __PTHREADS__ 1 +#define __PTHREADS_MUTEXES__ 1 +#define ALLOW_NON_WORD_ALIGN_ACCESS 1 +#define USE_THREAD 0 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 0 +#define USE_THR_YIELD 0 +#define kPlatformNameString "LinuxPPC" +#define EXPORT +#define _REENTRANT 1 + +#elif __FreeBSD__ + +#include +#if BYTE_ORDER == BIG_ENDIAN + #define BIGENDIAN 1 +#else + #define BIGENDIAN 0 +#endif + +#define USE_ATOMICLIB 0 +#define MACOSXEVENTQUEUE 0 +#define __PTHREADS__ 1 +#define __PTHREADS_MUTEXES__ 1 +#define ALLOW_NON_WORD_ALIGN_ACCESS 1 +#define USE_THREAD 1 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 1 +#define USE_THR_YIELD 0 +#define kPlatformNameString "FreeBSD" +#define EXPORT +#define _REENTRANT 1 + +#elif __solaris__ + +#ifdef sparc + #define BIGENDIAN 1 +#endif +#ifdef _M_IX86 + #define BIGENDIAN 0 +#endif +#ifdef _M_ALPHA + #define BIGENDIAN 0 +#endif +#ifndef BIGENDIAN + #error NEED BIGENDIAN DEFINITION 0 OR 1 FOR PLATFORM +#endif + +#define USE_ATOMICLIB 0 +#define MACOSXEVENTQUEUE 0 +#define __PTHREADS__ 1 +#define __PTHREADS_MUTEXES__ 1 +#define ALLOW_NON_WORD_ALIGN_ACCESS 0 +#define USE_THREAD 1 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 0 +#define USE_THR_YIELD 0 +#define kPlatformNameString "Solaris" +#define EXPORT +#define _REENTRANT 1 + +#elif __sgi__ + +#define USE_ATOMICLIB 0 +#define MACOSXEVENTQUEUE 0 +#define __PTHREADS__ 1 +#define __PTHREADS_MUTEXES__ 1 +#define BIGENDIAN 1 +#define ALLOW_NON_WORD_ALIGN_ACCESS 0 +#define USE_THREAD 1 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 0 +#define USE_THR_YIELD 0 +#define kPlatformNameString "IRIX" +#define EXPORT +#define _REENTRANT 1 + +#elif __hpux__ + +#define USE_ATOMICLIB 0 +#define MACOSXEVENTQUEUE 0 +#define __PTHREADS__ 1 +#define __PTHREADS_MUTEXES__ 1 +#define BIGENDIAN 1 +#define ALLOW_NON_WORD_ALIGN_ACCESS 0 +#define USE_THREAD 1 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 0 +#define USE_THR_YIELD 0 +#define kPlatformNameString "HP-UX" +#define EXPORT +#define _REENTRANT 1 + +#elif defined(__osf__) + +#define __osf__ 1 +#define USE_ATOMICLIB 0 +#define MACOSXEVENTQUEUE 0 +#define __PTHREADS__ 1 +#define __PTHREADS_MUTEXES__ 1 +#define BIGENDIAN 0 +#define ALLOW_NON_WORD_ALIGN_ACCESS 0 +#define USE_THREAD 1 //Flag used in QTProxy +#define THREADING_IS_COOPERATIVE 0 +#define USE_THR_YIELD 0 +#define kPlatformNameString "Tru64UNIX" +#define EXPORT + +#endif diff --git a/PlaylistBroadcaster.tproj/BCasterTracker.cpp b/PlaylistBroadcaster.tproj/BCasterTracker.cpp new file mode 100644 index 0000000..e549568 --- /dev/null +++ b/PlaylistBroadcaster.tproj/BCasterTracker.cpp @@ -0,0 +1,578 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + 8.30.99 - changes for linux version + - IsProcessRunning changed + - fputs difference. + + 8.2.99 rt + - changed BCasterTracker::BCasterTracker to 5 second open timer. + - no longer lists broadcasts that are not running. file is + cleaned up on next "stop" +*/ + + +#include "BCasterTracker.h" +#include "MyAssert.h" + +#include "Trim.h" +#include "GetWord.h" +#include "OSThread.h" + +#include +#include "SafeStdLib.h" +#include +#include +#include +#include +#include +#include +#if !(defined(__solaris__) || defined(__osf__) || defined(__hpux__)) + #include +#endif +#include +#include + +char gTrackerFileTempDataPath[256]; + +void TestBCasterTracker(int x ) +{ + + BCasterTracker tracker( "trackerfile" ); + + + if ( tracker.IsOpen() ) + { + if ( x > - 1 ) + { int error; + + error = tracker.Remove( x ); + + if ( error ) // remove the xth item from the list. + qtss_printf( "Playlist Broadcast (%li) not found.\n", (SInt32)x ); + else + tracker.Save(); + } + else + { + tracker.Show(); + } + } + else + qtss_printf("PlaylistBroadcaster trackerfile open FAILED.\n"); +} + +static void ShowElement( PLDoubleLinkedListNode* ten, void* userData) +{ + int* showIndex = (int*)userData; + char *info; + + if ( BCasterTracker::IsProcessRunning( ten->fElement->mPID ) ) + info = ""; + else + info = ", (not running)"; + + + //qtss_printf( "[%li] %li %s%s\n", (SInt32)*showIndex, (SInt32)ten->fElement->mPID, ten->fElement->mName, info ); + qtss_printf( "[%3li] %s; pid: %li%s\n", (SInt32)*showIndex, ten->fElement->mName, (SInt32)ten->fElement->mPID, info ); + + + *(int*)userData = *showIndex + 1; + + +} + + +void BCasterTracker::Show() +{ + int showIndex = 1; + + if ( mTrackingList.GetNumNodes() ) + { + qtss_printf( "\n" ); + qtss_printf( "Current Playlist Broadcasts\n" ); + qtss_printf( " ID Description file; Process ID\n" ); + qtss_printf( "----------------------------------\n" ); + + // display the elements in the list + mTrackingList.ForEach( ShowElement, &showIndex ); + } + else + { qtss_printf( "\n" ); + qtss_printf( "- PlaylistBroadcaster: No Broadcasts running.\n" ); + } + +} + + +bool BCasterTracker::IsProcessRunning( pid_t pid ) +{ + bool isRunning=false; + +/* + +// Generic unix code + + char procPath[256]; + qtss_sprintf( procPath, "ps -p%li | grep %li > %s ",(SInt32)pid,(SInt32)pid,gTrackerFileTempDataPath); + + int result = system(procPath); + if (0 == result) + { isRunning = true; + } + +*/ + + // a no-grep version to find the pid + + char pidStr[32]; + qtss_sprintf( pidStr, "%li",(SInt32)pid); + + char procPath[64] = "ps -p"; + ::strcat( procPath, pidStr); + + FILE *inPipe = ::popen(procPath, "r"); + if (NULL == inPipe) + return false; + + char inBuff[256] = ""; + while (!isRunning && ::fgets(inBuff, sizeof(inBuff), inPipe) != 0) + { if (::strstr(inBuff,pidStr) != NULL) + { isRunning = true; + break; + } + } + + (void) ::pclose(inPipe); + + + return isRunning; + +} + +bool IsProcessID( PLDoubleLinkedListNode*ten, void* userData) +{ + + /* + used by ForEachUntil to find a TrackingElement with a given Process ID + userData is a pointer to the process id we want find in our list. + + */ + + pid_t pid; + bool isProcID = false; + + pid = *(pid_t*)userData; + + if ( pid == ten->fElement->mPID ) + isProcID = true; + + return isProcID; + +} + + +int BCasterTracker::RemoveByProcessID( pid_t pid ) +{ + int error = -1; + + // remove the element with the given process ID + // from the tracking list. + + // called by the app when it is going away. + // DO NOT kill the pid being passed in. + + PLDoubleLinkedListNode *te; + + te = mTrackingList.ForEachUntil( IsProcessID, &pid ); + + // remove that element + if ( te ) + { + mTrackingList.RemoveNode(te); + error = 0; + } + + return error; + +} + +static bool IsElementID( PLDoubleLinkedListNode */*ten*/, void* userData) +{ + /* + used by ForEachUntil to find a TrackingElement with a given index number + userData is a pointer to the counter, initialized to the index we want + to find. + + we decrement it until is reaches zero. + + */ + + UInt32* showIndex = (UInt32*)userData; + bool isItemId = false; + + // when the counter reduces to zero, return true. + + if ( *showIndex == 0 ) + isItemId = true; + else + *(int*)userData = *showIndex - 1; + + return isItemId; + +} + +int BCasterTracker::Remove( UInt32 itemID ) +{ + + int error = -1; + UInt32 itemIDIndex = itemID; + + // KILL the process that is associated with the item + // id in our list. + + // remove the element with the given index in the list + // from the tracking list. + + + // itemID is zero based + // set the index to the item number we want to find. Use ForEachUntil with IsElementID + // to count down through the elements until we get to the Nth element. + + PLDoubleLinkedListNode *te; + + + te = mTrackingList.ForEachUntil( IsElementID, &itemIDIndex ); + + + // remove that element, and kill the process + if ( te ) + { + if ( ::kill( te->fElement->mPID, SIGTERM ) == 0 ) + { + error = 0; + } + else + { + + error = OSThread::GetErrno(); + + if ( error != ESRCH ) // no such process + { + // we probably cannot kill this process because it is not ours + + + } + else + { // this process already died, or was killed by other means. + error = 0; + } + + } + + if ( !error ) + { + mTrackingList.RemoveNode(te); + } + + } + + return error; +} + + +int BCasterTracker::Add( pid_t pid, const char* bcastList ) +{ + /* + add an entry to our tracking list + */ + + int addErr = -1; + + + TrackingElement* te = new TrackingElement( pid, bcastList ); + + Assert( te ); + + if ( te ) + { + PLDoubleLinkedListNode* ten = new PLDoubleLinkedListNode( te ); + + Assert( ten ); + + if ( ten ) + { mTrackingList.AddNodeToTail( ten ); + addErr = 0; + } + else + delete te; + } + + return addErr; + +} + + + +BCasterTracker::~BCasterTracker() +{ + // truncate the file to the desired size and close. + + if ( mTrackerFile != NULL ) + { + int err = OSThread::GetErrno(); + + if ( ::fseek( mTrackerFile, mEofPos, SEEK_SET ) < 0 ) + qtss_printf( "fseek at close eof(%li), err(%li), fileno(%li)\n", mEofPos, (SInt32)err, (SInt32)fileno(mTrackerFile) ); + + if ( ::fflush( mTrackerFile ) ) + qtss_printf( "fflush at close eof(%li), err(%li), fileno(%li)\n", mEofPos, (SInt32)err, (SInt32)fileno(mTrackerFile) ); + + if ( ::ftruncate( fileno(mTrackerFile), (off_t)mEofPos ) ) + { + qtss_printf( "ftruncate at close eof(%li), err(%li), fileno(%li)\n", mEofPos, (SInt32)err, (SInt32)fileno(mTrackerFile) ); + } + + (void)::fclose( mTrackerFile ); + } +} + +static bool SaveElement( PLDoubleLinkedListNode* ten, void* userData) +{ + FILE* fp = (FILE*) userData; + char buff[512]; + + /* + used by ForEachUntil to find a SaveElement each element to the tracking file. + userData is a pointer to the FILE of our open tracking file. + + */ + + qtss_sprintf( buff, "%li \"%s\"\n", (SInt32)ten->fElement->mPID, ten->fElement->mName ); + + // linux version of fputs returns <0 for err, or num bytes written + // mac os X version of fputs returns <0 for err, or 0 for no err + if (::fputs( buff, fp ) < 0 ) + return true; + + return false; + +} + +int BCasterTracker::Save() +{ + /* + save each record in the the tracker to disk. + + return 0 on success, < 0 otherwise. + */ + int error = -1; + + mEofPos = 0; + + if ( mTrackerFile != NULL ) + { + + if ( !::fseek( mTrackerFile, 0, SEEK_SET ) ) + { + if ( !mTrackingList.ForEachUntil( SaveElement, mTrackerFile ) ) + { + mEofPos = ::ftell( mTrackerFile ); + + error = 0; + } + } + + } + + return error; + +} + +bool BCasterTracker::IsOpen() +{ + // return false if the file is not open, + // true if the file is open. + + if ( mTrackerFile == NULL ) + return false; + else + return true; + + +} + + +BCasterTracker::BCasterTracker( const char* name ) +{ + + mEofPos = 0; + mTrackerFile = NULL; + time_t calendarTime = 0; + + calendarTime = ::time(NULL) + 10; + + // wait a SInt32 time for access to the file. + // 2 possible loops one to try to open ( and possible create ) the file + // the second to obtain an exclusive lock on the file. + + // the app should probably fail if this cannot be done within the alloted time + //qtss_printf("time=%"_S32BITARG_"\n",calendarTime); + + + while ( mTrackerFile == NULL && calendarTime > ::time(NULL) ) + { mTrackerFile = ::fopen( name, "r+" ); + if ( !mTrackerFile ) + { // try and create + mTrackerFile = ::fopen( name, "a+" ); + + if ( mTrackerFile ) + { + // let "everyone" read and write this file so that we can track + // all the broadcasters no matter which user starts them + (void)::chmod( name, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); + + (void)::fclose( mTrackerFile ); + } + + mTrackerFile = NULL; + } + ::sleep(1); + + } + ::strcpy(gTrackerFileTempDataPath,name); + ::strcat( gTrackerFileTempDataPath, "_tmp" ); + FILE * tempFile = NULL; + calendarTime = ::time(NULL) + 10; + while ( tempFile == NULL && calendarTime > ::time(NULL) ) + { tempFile = ::fopen( gTrackerFileTempDataPath, "r+" ); + if ( !tempFile ) + { // try and create + tempFile = ::fopen( gTrackerFileTempDataPath, "a+" ); + if ( tempFile ) + { + // let "everyone" read and write this file so that we can track + // all the broadcasters no matter which user starts them + (void)::chmod( gTrackerFileTempDataPath, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); + } + } + + } + if (tempFile) + (void)::fclose( tempFile ); + + if ( mTrackerFile ) + { + bool haveLock = false; + + while ( !haveLock && calendarTime > ::time(NULL) ) + { + struct flock lock_params; + + lock_params.l_start = 0; /* starting offset */ + lock_params.l_len = 0; /* len = 0 means until end of file */ + lock_params.l_pid = getpid(); /* lock owner */ + lock_params.l_type = F_WRLCK; /* lock type: read/write, etc. */ + lock_params.l_whence = 0; /* type of l_start */ + + if ( !::fcntl( fileno(mTrackerFile), F_SETLK, &lock_params ) ) + haveLock = true; + + } + + // if can't lock it, close it. + if ( !haveLock ) + { (void)::fclose( mTrackerFile ); + mTrackerFile = NULL; + } + + } + + if ( mTrackerFile ) + { + SInt32 lineBuffSize = kTrackerLineBuffSize; + SInt32 wordBuffSize = kTrackerLineBuffSize; + char lineBuff[kTrackerLineBuffSize]; + char wordBuff[kTrackerLineBuffSize]; + + char *next; + UInt32 pid; + int error = 0; + + error = ::fseek( mTrackerFile, 0, SEEK_SET ); + Assert(error == 0); + do + { + // get a line ( fgets adds \n+ 0x00 ) + + if ( ::fgets( lineBuff, lineBuffSize, mTrackerFile ) == NULL ) + break; + + // trim the leading whitespace + next = ::TrimLeft( lineBuff ); + + if (*next) + { + // grab the pid + next = ::GetWord( wordBuff, next, wordBuffSize ); + + Assert( *wordBuff ); + + pid = ::atoi( wordBuff ); + + if (*next) + next = ::TrimLeft( next ); + // grab the broadcast list string + + if (*next) + { + next = ::TrimLeft( next ); + + if (*next == '"' ) + next = ::GetQuotedWord( wordBuff, next, wordBuffSize ); + else + next = ::GetWord( wordBuff, next, wordBuffSize ); + + if ( this->IsProcessRunning( pid ) ) + { + if (*wordBuff) + { error = this->Add( pid, wordBuff ); + + } + } + } + + } + + } while ( feof( mTrackerFile ) == 0 && error == 0 ); + + + mEofPos = ::ftell( mTrackerFile ); + } + + // dtor closes file... + +} diff --git a/PlaylistBroadcaster.tproj/BCasterTracker.h b/PlaylistBroadcaster.tproj/BCasterTracker.h new file mode 100644 index 0000000..7a2cc3c --- /dev/null +++ b/PlaylistBroadcaster.tproj/BCasterTracker.h @@ -0,0 +1,61 @@ + +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 __BCASTERTRACKER__ +#define __BCASTERTRACKER__ + +#include +#include "TrackingElement.h" +#include "PLDoubleLinkedList.h" + +class BCasterTracker +{ + public: + BCasterTracker( const char* fName ); + virtual ~BCasterTracker(); + + int RemoveByProcessID( pid_t pid ); + int Remove( UInt32 itemID ); + int Add( pid_t pid, const char* bcastList ); + int Save(); + void Show(); + static bool IsProcessRunning( pid_t pid ); + bool IsOpen(); + + protected: + + enum { kTrackerLineBuffSize = 512 }; + + FILE* mTrackerFile; + SInt32 mEofPos; + + PLDoubleLinkedList mTrackingList; + +}; + +void TestBCasterTracker(int x); + +#endif diff --git a/PlaylistBroadcaster.tproj/BroadcastLog.cpp b/PlaylistBroadcaster.tproj/BroadcastLog.cpp new file mode 100644 index 0000000..5c7c746 --- /dev/null +++ b/PlaylistBroadcaster.tproj/BroadcastLog.cpp @@ -0,0 +1,302 @@ + +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "BroadcastLog.h" + +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 copyright comment author artist album duration result\n"; + + +BroadcastLog::BroadcastLog( PLBroadcastDef* broadcastParms, StrPtrLen* defaultPathPtr ) + : QTSSRollingLog() +{ + *mDirPath = 0; + *mLogFileName = 0; + mWantsLogging = false; + + if (broadcastParms->mLogging) + { + if (!::strcmp( broadcastParms->mLogging, "enabled" ) ) + { + mWantsLogging = true; + + ::strcpy( mDirPath, broadcastParms->mLogFile ); + 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 + memcpy(mDirPath,defaultPathPtr->Ptr,defaultPathPtr->Len); + mDirPath[defaultPathPtr->Len] = 0; + + ::strcpy( mLogFileName, broadcastParms->mLogFile ); + } + + } + } + + this->SetLoggingEnabled(mWantsLogging); +} + +time_t BroadcastLog::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, "PlaylistBroadcaster" , kVersionString, + theDateBuffer, sLogTimeInGMT ? "GMT" : "local time"); + this->WriteToLog(tempBuffer, !kAllowLogToRoll); + } + + return calendarTime; +} + + +void BroadcastLog::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 BroadcastLog::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 BroadcastLog::LogMediaData( const char* path, const char* title, const char* copyright, + const char* comment, const char* author, 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 != path) + && ( (strlen(path) + strlen(dateBuff) ) < 800) + ) + { + + qtss_sprintf(strBuff,"%s '%s'",dateBuff, path); + + if ( title || title[0] != 0) + { if ( (strlen(strBuff) + strlen(title) ) < 1000 ) + { + ::strcat(strBuff," '"); + ::strcat(strBuff, title); + ::strcat(strBuff,"'"); + } + } + else + { + ::strcat(strBuff," -"); + } + + if ( copyright || copyright[0] != 0) + { if ( (strlen(strBuff) + strlen(copyright) ) < 1000 ) + { + ::strcat(strBuff," '"); + ::strcat(strBuff, copyright); + ::strcat(strBuff,"'"); + } + } + else + { + ::strcat(strBuff," -"); + } + + if ( comment || comment[0] != 0) + { if ( (strlen(strBuff) + strlen(comment) ) < 1000 ) + { + ::strcat(strBuff," '"); + ::strcat(strBuff, comment); + ::strcat(strBuff,"'"); + } + } + else + { + ::strcat(strBuff," -"); + } + + if ( author || author[0] != 0) + { if ( (strlen(strBuff) + strlen(author) ) < 1000 ) + { + ::strcat(strBuff," '"); + ::strcat(strBuff, author); + ::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/PlaylistBroadcaster.tproj/BroadcastLog.h b/PlaylistBroadcaster.tproj/BroadcastLog.h new file mode 100644 index 0000000..ca86334 --- /dev/null +++ b/PlaylistBroadcaster.tproj/BroadcastLog.h @@ -0,0 +1,90 @@ + +#ifndef __BroadcastLog__ +#define __BroadcastLog__ + +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "QTSSRollingLog.h" +#include "PLBroadcastDef.h" +#include "StrPtrLen.h" +#include + + +class BroadcastLog : public QTSSRollingLog +{ + enum { eLogMaxBytes = 0, eLogMaxDays = 0 }; + + public: + BroadcastLog( PLBroadcastDef* broadcastParms, StrPtrLen* defaultPathPtr); + virtual ~BroadcastLog() {} + + 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* path, const char* title, const char* copyright, + const char* comment, const char* author, 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/PlaylistBroadcaster.tproj/BroadcasterSession.cpp b/PlaylistBroadcaster.tproj/BroadcasterSession.cpp new file mode 100755 index 0000000..97cba7b --- /dev/null +++ b/PlaylistBroadcaster.tproj/BroadcasterSession.cpp @@ -0,0 +1,1104 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: BroadcasterSession.cpp + + Contains: . + + + +*/ + +#include "BroadcasterSession.h" +#include "OSMemory.h" +#include "StrPtrLen.h" +#include "OSMutex.h" +#include "SDPUtils.h" +#include +#include "SafeStdLib.h" +#define BROADCAST_SESSION_DEBUG 0 + +static const SInt64 kMaxWaitTimeInMsec = 1000; +static const SInt64 kIdleTimeoutInMsec = 60 * 1000; // Time out at 60 seconds +static const SInt16 kSanitySeqNumDifference = 300; + +UInt32 BroadcasterSession::sActiveConnections = 0; +UInt32 BroadcasterSession::sBroadcastingConnections = 0; +UInt32 BroadcasterSession::sTotalConnectionAttempts = 0; + + +char *BroadcasterSession::fPacket = NULL; + +BroadcasterSession::BroadcasterSession( UInt32 inAddr, UInt16 inPort, char* inURL, + BroadcasterType inClientType, + UInt32 inDurationInSec, UInt32 inStartPlayTimeInSec, + UInt32 inRTCPIntervalInSec, UInt32 inOptionsIntervalInSec, + UInt32 inHTTPCookie, Bool16 inAppendJunkData, UInt32 inReadInterval, + UInt32 inSockRcvBufSize, + StrPtrLen *sdpSPLPtr, + char *namePtr, + char *passwordPtr, + Bool16 deepDebug, + Bool16 burst) +: fSocket(NULL), + fRTSPClient(NULL), + fTimeoutTask(NULL, kIdleTimeoutInMsec), + + fDurationInSec(inDurationInSec), + fStartPlayTimeInSec(inStartPlayTimeInSec), + fRTCPIntervalInSec(inRTCPIntervalInSec), + fOptionsIntervalInSec(inOptionsIntervalInSec), + + fState(kSendingAnnounce), + fDeathState(kSendingAnnounce), + fDeathReason(kDiedNormally), + fNumSetups(0), + fUDPSocketArray(NULL), + + fPlayTime(0), + fTotalPlayTime(0), + fLastRTCPTime(0), + fTeardownImmediately(false), + fAppendJunk(inAppendJunkData), + fReadInterval(inReadInterval), + fSockRcvBufSize(inSockRcvBufSize), + fBurst(burst), + fBurstTime(10), + fStats(NULL), + fPacketLen(0), + fChannel(0) +// fPacket(NULL) + +{ + fTimeoutTask.SetTask(this); + StrPtrLen theURL(inURL); + fSDPParser.Parse(sdpSPLPtr->Ptr, sdpSPLPtr->Len); + if (fBurst && deepDebug) + printf("Burst Mode enabled: broadcast will be delayed for %"_U32BITARG_" seconds before starting\n", fBurstTime); + +#if BROADCAST_SESSION_DEBUG + + qtss_printf("Connecting to: %s, port %d\n", inURL, inPort); + +#endif + // + // Construct the appropriate ClientSocket type depending on what type of client we are supposed to be + switch (inClientType) + { + case kRTSPUDPBroadcasterType: + { + fControlType = kRawRTSPControlType; + fTransportType = kUDPTransportType; + fSocket = NEW TCPClientSocket(Socket::kNonBlockingSocketType); + break; + } + case kRTSPTCPBroadcasterType: + { + fControlType = kRawRTSPControlType; + fTransportType = kTCPTransportType; + fSocket = NEW TCPClientSocket(Socket::kNonBlockingSocketType); + break; + } + case kRTSPHTTPBroadcasterType: + { + fControlType = kRTSPHTTPControlType; + fTransportType = kTCPTransportType; + fSocket = NEW HTTPClientSocket(theURL, inHTTPCookie, Socket::kNonBlockingSocketType); + break; + } + case kRTSPHTTPDropPostBroadcasterType: + { + fControlType = kRTSPHTTPDropPostControlType; + fTransportType = kTCPTransportType; + fSocket = NEW HTTPClientSocket(theURL, inHTTPCookie, Socket::kNonBlockingSocketType); + break; + } + case kRTSPReliableUDPBroadcasterType: + { + Assert(0); + break; + } + default: + { + qtss_printf("BroadcasterSession: Attempt to create unsupported client type.\n"); + ::exit(-1); + } + } + + fSocket->Set(inAddr, inPort); + fSocket->GetSocket()->SetTask(this); + + int sndBufSize = 32 * 1024; + int rcvBufSize=1024; + ((TCPClientSocket*)fSocket)->SetOptions(sndBufSize,rcvBufSize); + // + // Construct the client object using this socket. + Bool16 verbose = deepDebug; + fRTSPClient = NEW RTSPClient(fSocket, verbose); + fRTSPClient->Set(theURL); + fRTSPClient->SetTransportMode(RTSPClient::kPushMode); + fRTSPClient->SetName(namePtr); + fRTSPClient->SetPassword(passwordPtr); + + // + // Start the connection process going + this->Signal(Task::kStartEvent); +} + +BroadcasterSession::~BroadcasterSession() +{ +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadcasterSession::~BroadcasterSession() \n"); +#endif + + if (fUDPSocketArray != NULL) + { + for (UInt32 x = 0; x < fSDPParser.GetNumStreams() * 2; x++) + delete fUDPSocketArray[x]; + } + + delete [] fUDPSocketArray; + delete fRTSPClient; + delete fSocket; +} + +char* BroadcasterSession::GetNextPacket(UInt32 *packetLen, UInt8 *channel) +{ + char* thePacketData = NULL; + *packetLen = 0; + *channel = 0; + //qtss_printf("BroadcasterSession::GetNextPacket Q len=%"_U32BITARG_"\n",fPacketQueue.GetLength()); + OSQueueElem *thePacketQElemPtr = fPacketQueue.GetHead(); + if(thePacketQElemPtr != NULL) + { + BroadcasterSession::RTPPacket* thePacket = (BroadcasterSession::RTPPacket*) thePacketQElemPtr->GetEnclosingObject(); + fPacketQueue.Remove(thePacketQElemPtr); + + Assert(thePacket != NULL); + Assert(thePacket->fData != NULL); + Assert(thePacket->fLen > 0); + + if (NULL == thePacket || NULL == thePacket->fData || 0 == thePacket->fLen) + return NULL; + + thePacketData = thePacket->fData; + *packetLen = thePacket->fLen; + *channel = thePacket->fChannel; + + delete thePacket; + } + + Assert(*packetLen <= RTSPClient::kReqBufSize); + + return thePacketData; +} + +OS_Error BroadcasterSession::SendPacket(char* data, UInt32 len,UInt8 channel) +{ + OS_Error theErr = 0; + if (fState != kBroadcasting) + { + switch (fState) + { case kSendingAnnounce: + theErr = kConnectionFailed; + break; + + case kSendingReceive: + case kSendingSetup: + theErr = kRequestFailed; + break; + + default: + theErr = kDiedWhileBroadcasting; + } + return theErr; + } + + BroadcasterSession::RTPPacket* newPacket = NEW BroadcasterSession::RTPPacket; + if (newPacket == NULL) + return kMemoryError; + + if (fBurst) + { + char *theSendData = NEW char[len + 12]; + memcpy(theSendData,data,len); + static char* tag="aktt"; + memcpy(&theSendData[len],tag,4); + SInt64 currentTime= OS::HostToNetworkSInt64(OS::Milliseconds()); + memcpy(&theSendData[len+4],¤tTime,8); + + newPacket->fCount = ++fPacketCount; + newPacket->SetEnclosingObject(newPacket); + newPacket->SetPacketData(theSendData,len +12,channel); + } + + else + { + char *theSendData = NEW char[len]; + memcpy(theSendData,data,len); + newPacket->fCount = ++fPacketCount; + newPacket->SetEnclosingObject(newPacket); + newPacket->SetPacketData(theSendData,len,channel); + } + + fPacketQueue.EnQueue(newPacket->GetQElement()); + return 0; +} + +OS_Error BroadcasterSession::SendWaitingPackets() +{ + static SInt64 sFirstTime = 0; + + if (fBurst) + { + if (sFirstTime == 0) + sFirstTime = OS::Milliseconds() + (fBurstTime * 1000); + + if (sFirstTime > OS::Milliseconds()) + { + // qtss_printf("BroadcasterSession::GetNextPacket NOT sending packets Q len=%"_U32BITARG_"\n",fPacketQueue.GetLength()); + return QTSS_NoErr; + } + } + + OSMutexLocker locker(this->GetMutex()); + OS_Error theErr = OS_NoErr; + + if (fPacket == NULL) + { fPacket = this->GetNextPacket(&fPacketLen, &fChannel); + } + + while (fPacket != NULL) + { Bool16 getNext = false; + //qtss_printf("BroadcasterSession::Run- Broadcasting SendInterleavedWrite \n"); + Assert(fPacketLen <= RTSPClient::kReqBufSize); + OSMutexLocker locker(fRTSPClient->GetMutex()); + theErr = fRTSPClient->SendInterleavedWrite(fChannel, (UInt16) fPacketLen, fPacket, &getNext); + if (getNext) + { delete [] fPacket; + fPacket = this->GetNextPacket(&fPacketLen, &fChannel); + } + + if (theErr != OS_NoErr) + { + if (theErr == EAGAIN || theErr == EINPROGRESS) + { // keep the packet around and try again + //qtss_printf("BroadcasterSession::SendWaitingPackets- Broadcasting SendInterleavedWrite err=%"_S32BITARG_" Request Write Event\n",theErr); + break; + } + // some bad error bail. + delete [] fPacket; + fPacket = NULL; + + break; + } + + // we sent the packet and may or may not have another packet to send + + fTimeoutTask.RefreshTimeout(); + //qtss_printf("send channel=%u len=%"_U32BITARG_"\n",(UInt16)fChannel,fPacketLen); + } + + return theErr; +} + +SInt64 BroadcasterSession::Run() +{ + + EventFlags theEvents = this->GetEvents(); + + if (theEvents & Task::kStartEvent) + { + SDPContainer theSDP; + if ( !theSDP.SetSDPBuffer(this->GetSDPInfo()->GetSDPData()) ) + { + fDeathReason = kBadSDP; + fDeathState = fState; + fState = kDone; + return 0; + } + sActiveConnections++; + sTotalConnectionAttempts++; + Assert(theEvents == Task::kStartEvent); + } + + // + if (theEvents & Task::kTimeoutEvent) + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("Session timing out.\n"); +#endif +#if __FreeBSD__ || __MacOSX__ + if (fTransportType != kTCPTransportType) + { + fTimeoutTask.RefreshTimeout(); + fSocket->GetSocket()->SetTask(this); + fSocket->GetSocket()->RequestEvent(fSocket->GetEventMask()); + return 10000; + } +#else + if (fTransportType != kTCPTransportType) + { fTimeoutTask.RefreshTimeout(); + return 0; + } +#endif + + fDeathReason = kSessionTimedout; + fTeardownImmediately = true; + fState = kSendingTeardown; + } + + // + // If we've been told to TEARDOWN, do so. + if (theEvents & BroadcasterSession::kTeardownEvent && fState != kDone) + { + +#if BROADCAST_SESSION_DEBUG + qtss_printf("Session tearing down immediately.\n"); +#endif + fTeardownImmediately = true; + fState = kSendingTeardown; + } + + // We have been told to delete ourselves. Do so... NOW!!!!!!!!!!!!!!! + if (theEvents & Task::kKillEvent || fTeardownImmediately) + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("Session killed.\n"); +#endif + if (fState != kSendingTeardown ) + { + + sActiveConnections--; + // return -1; + fTeardownImmediately = true; + fState = kSendingTeardown; + } + } + + // 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 kSendingAnnounce: + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadcasterSession::kSendingAnnounce\n"); +#endif + + char *theSDP = (fSDPParser.GetSDPData())->Ptr; + theErr = fRTSPClient->SendAnnounce(theSDP); + +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadcasterSession::Run Sending ANNOUNCE. Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", theErr, fRTSPClient->GetStatus()); +#endif + if (theErr == OS_NoErr) + { + // Check that the ANNOUNCE response is a 200 OK. If not, bail + if (fRTSPClient->GetStatus() != 200) + { + theErr = ENOTCONN; // Exit the state machine + break; + } + else + { + Assert(fControlType != kReliableUDPTransportType); + + // + // + // We have valid SDP. If this is a UDP connection, construct a UDP + // socket array to act as incoming sockets. + if ((fControlType == kUDPTransportType) && (fTransportType != kTCPTransportType)) + this->SetupUDPSockets(); + // + // Setup client stats + fStats = NEW TrackStats[fSDPParser.GetNumStreams()]; + ::memset(fStats, 0, sizeof(TrackStats) * fSDPParser.GetNumStreams()); + } + + fState = kSendingSetup; + } +#if BROADCAST_SESSION_DEBUG + else + { + if ( (fRTSPClient->GetStatus() == 0) && ( (theErr == EAGAIN) || (theErr== EINPROGRESS)) ) + { + qtss_printf("BroadcasterSession::Run Sending ANNOUNCE. Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", theErr, fRTSPClient->GetStatus()); + } + } +#endif + break; + } + case kSendingSetup: + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadcasterSession::kSendingSetup\n"); +#endif + // The SETUP request is different depending on whether we are interleaving or not + if (fTransportType == kUDPTransportType) + { + theErr = fRTSPClient->SendUDPSetup(fSDPParser.GetStreamInfo(fNumSetups)->fTrackID, + fUDPSocketArray[fNumSetups*2]->GetLocalPort()); + } + else if (fTransportType == kTCPTransportType) + { + theErr = fRTSPClient->SendTCPSetup(fSDPParser.GetStreamInfo(fNumSetups)->fTrackID,fNumSetups * 2, (fNumSetups * 2) +1); +#if BROADCAST_SESSION_DEBUG + qtss_printf("Sending SETUP #%"_U32BITARG_". Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", fNumSetups, theErr, fRTSPClient->GetStatus()); + if (theErr == EAGAIN || theErr == EINPROGRESS) + { + if (theErr == EAGAIN) + qtss_printf("BroadcasterSession::kSendingSetup EAGAIN\n"); + if (theErr == EINPROGRESS) + qtss_printf("BroadcasterSession::kSendingSetup EINPROGRESS\n"); + + // fSocket->GetSocket()->RequestEvent(EV_RE | EV_WR); + // return 0; + } +#endif + + } + + // 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 (fRTSPClient->GetStatus() != 200) + { + theErr = ENOTCONN; // Exit the state machine + break; + } + else + { + // Record the server port for RTCPs. + + fStats[fNumSetups].fDestRTPPort = fRTSPClient->GetServerPort(); + fStats[fNumSetups].fDestRTCPPort = fRTSPClient->GetServerPort() + 1; + fNumSetups++; + if (fNumSetups == fSDPParser.GetNumStreams()) + fState = kSendingReceive; + } + } + break; + } + case kSendingReceive: + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadcasterSession::kSendingReceive\n"); +#endif + + theErr = fRTSPClient->SendReceive(fStartPlayTimeInSec); + +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadcasterSession::fRTSPClient->SendReceive. Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", theErr, fRTSPClient->GetStatus()); + if (theErr == EAGAIN) + qtss_printf("BroadcasterSession::SendReceive EAGAIN\n"); + if (theErr == EINPROGRESS) + qtss_printf("BroadcasterSession::SendReceive EINPROGRESS\n"); +#endif + // 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 send the data until it's + // time to send the TEARDOWN + if (theErr == OS_NoErr) + { + if (fRTSPClient->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].fSSRC = fRTSPClient->GetSSRCByTrack(fSDPParser.GetStreamInfo(ssrcCount)->fTrackID); + if (fStats[ssrcCount].fSSRC != 0) + fStats[ssrcCount].fIsSSRCValid = true; + } + + fState = kBroadcasting; + sBroadcastingConnections++; + + // + // Start our duration timer. Use this to figure out when to send a teardown + fPlayTime = fLastRTCPTime = OS::Milliseconds(); + } + break; + } +#if __FreeBSD__ || __MacOSX__ + case kBroadcasting: + { +#if BROADCAST_SESSION_DEBUG + //qtss_printf("BroadcasterSession::kBroadcasting\n"); +#endif + theErr = OS_NoErr; // Ignore flow control errors here. + static char buffer[256]; + UInt32 outRecvLen = 0; + theErr = fRTSPClient->GetSocket()->Read(buffer, 256, &outRecvLen); + + if (fTransportType == kTCPTransportType) + { theErr = SendWaitingPackets(); + + if (theErr == EAGAIN || theErr == EINPROGRESS || theErr == ETIMEDOUT) + { fSocket->GetSocket()->SetTask(this); + fSocket->GetSocket()->RequestEvent(EV_WR); + return 0; // important must be 0 + } + + } + else if (theErr == EAGAIN || theErr == EINPROGRESS || theErr == ETIMEDOUT) + { fSocket->GetSocket()->SetTask(this); + fSocket->GetSocket()->RequestEvent(EV_RE); + return 10000; + } + + if (theErr != OS_NoErr) + { + // we've encountered some fatal error, bail. + //qtss_printf("kBroadcasting FATAL ERROR err=%"_S32BITARG_"\n",theErr); + sBroadcastingConnections--; + break; + } + + // no Err and nothing left to do so got to sleep for awhile. + return fReadInterval; + } + +#else + case kBroadcasting: + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadcasterSession::kBroadcasting\n"); +#endif + theErr = OS_NoErr; // Ignore flow control errors here. + + if (fTransportType == kTCPTransportType) + theErr = SendWaitingPackets(); + else + { static char buffer[256]; + UInt32 outRecvLen = 0; + theErr = fRTSPClient->GetSocket()->Read(buffer, 256, &outRecvLen); + if (theErr == EAGAIN || theErr == EINPROGRESS) + { + fSocket->GetSocket()->SetTask(this); + fSocket->GetSocket()->RequestEvent(EV_RE); + return 0; + } + } + + if (theErr == EAGAIN || theErr == EINPROGRESS) + { + fSocket->GetSocket()->SetTask(this); + fSocket->GetSocket()->RequestEvent(EV_WR); + return 0; + } + + if (theErr != OS_NoErr) + { + // we've encountered some fatal error, bail. + //qtss_printf("FATAL ERROR err=%"_S32BITARG_"\n",theErr); + sBroadcastingConnections--; + break; + } + + // no Err and nothing left to do so got to sleep for awhile. + return fReadInterval; + } + +#endif + + case kSendingTeardown: + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadcasterSession::kSendingTeardown\n"); +#endif + UInt16 maxTries = 10; + do + { + theErr = fRTSPClient->SendTeardown(); + OSThread::Sleep((unsigned int) 100); + maxTries --; + } + while (theErr == EAGAIN && maxTries > 0); + + theErr = 2; + + sActiveConnections--; + +#if BROADCAST_SESSION_DEBUG + qtss_printf("Sending TEARDOWN. Result = %"_U32BITARG_". Response code = %"_U32BITARG_"\n", theErr, fRTSPClient->GetStatus()); +#endif + // Once the TEARDOWN is complete, we are done, so mark ourselves as dead, and wait + // for the owner of this object to delete us + fState = kDone; + //exit (0); + break; + } + } + } + + if ((theErr == EINPROGRESS) || (theErr == EAGAIN)) + { +#if BROADCAST_SESSION_DEBUG + if (theErr == EAGAIN) + qtss_printf("BroadcasterSession::EAGAIN\n"); + if (theErr == EINPROGRESS) + qtss_printf("BroadcasterSession::EINPROGRESS\n"); +#endif + // + // Request an async event + fSocket->GetSocket()->SetTask(this); + fSocket->GetSocket()->RequestEvent(fSocket->GetEventMask() ); + return 100; // try again + } + else if (theErr != OS_NoErr) + { + // +#if BROADCAST_SESSION_DEBUG + qtss_printf("BroadCasterSession::RUN FATAL ERROR err=%"_S32BITARG_"\n",theErr); +#endif + // We encountered some fatal error with the socket. Record this as a connection failure + if (fState == kSendingTeardown) + fDeathReason = kTeardownFailed; + else if (fState == kBroadcasting) + fDeathReason = kConnectionFailed; + else if (fRTSPClient->GetStatus() >= 300) + fDeathReason = kRequestFailed; + else if ((fState == kSendingAnnounce) && (theErr == OS_NotEnoughSpace)) + fDeathReason = kBadSDP; + else + fDeathReason = kConnectionFailed; + + fDeathState = fState; + fState = kDone; + } + +#if BROADCAST_SESSION_DEBUG + if (fState == kDone) + qtss_printf("Client connection complete. Death reason = %"_U32BITARG_" RTSPClientStatus=%"_S32BITARG_"\n", fDeathReason,fRTSPClient->GetStatus()); +#endif + + return 0; +} + + + +void BroadcasterSession::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("BroadcasterSession: 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("BroadcasterSession: Failed to bind a UDP socket.\n"); + ::exit(-1); + } + } + } +#if BROADCAST_SESSION_DEBUG + qtss_printf("Opened UDP sockets for %"_U32BITARG_" streams\n", fSDPParser.GetNumStreams()); +#endif +} + +OS_Error BroadcasterSession::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 = fRTSPClient->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)) + { + + theUDPSockIndex++; + if (theUDPSockIndex == fSDPParser.GetNumStreams() * 2) + break; + continue; + } + + theTrackID = fSDPParser.GetStreamInfo(theUDPSockIndex >> 1)->fTrackID; + isRTCP = (Bool16) (theUDPSockIndex & 1); + thePacket = &thePacketBuf[0]; + } + + // + // We have a valid packet. Invoke the packet handler function + this->ProcessMediaPacket(thePacket, theLength, theTrackID, isRTCP); + } + return theErr; +} + +void BroadcasterSession::ProcessMediaPacket( char* inPacket, UInt32 inLength, + UInt32 inTrackID, Bool16 isRTCP) +{ + Assert(inLength > 4); + + // Currently we do nothing with RTCPs. + if (isRTCP) + return; + + UInt16* theSeqNumP = (UInt16*)inPacket; + UInt16 theSeqNum = ntohs(theSeqNumP[1]); + +// UInt32* theSsrcP = (UInt32*)inPacket; +// UInt32 theSSRC = ntohl(theSsrcP[2]); + + for (UInt32 x = 0; x < fSDPParser.GetNumStreams(); x++) + { + if (fSDPParser.GetStreamInfo(x)->fTrackID == inTrackID) + { + // Check if this packet is even for our stream + //if (!fStats[x].fIsSSRCValid) + // fStats[x].fSSRC = theSSRC; // If we don't know SSRC yet, just use first one we get + //if (theSSRC != fStats[x].fSSRC) + // return; + + fStats[x].fNumPacketsReceived++; + fStats[x].fNumBytesReceived += inLength; + + // Check if this packet is out of order + if (fStats[x].fHighestSeqNumValid) + { + SInt16 theValidationDifference = theSeqNum - fStats[x].fWrapSeqNum; + if (theValidationDifference < 0) + theValidationDifference -= 2 * theValidationDifference; // take the absolute value + if (theValidationDifference > kSanitySeqNumDifference) + { + // + // If this sequence number is really far out of range, then just toss + // the packet and increment our count of crazy packets + fStats[x].fNumThrownAwayPackets++; + return; + } + + SInt16 theSeqNumDifference = theSeqNum - fStats[x].fHighestSeqNum; + + if (theSeqNumDifference > 0) + { + fStats[x].fNumOutOfOrderPackets += theSeqNumDifference - 1; + fStats[x].fHighestSeqNum = theSeqNum; + } + } + else + { + fStats[x].fHighestSeqNumValid = true; + fStats[x].fWrapSeqNum = fStats[x].fHighestSeqNum = theSeqNum; + fStats[x].fLastAckedSeqNum = theSeqNum - 1; + } + + UInt32 debugblah = 0; + + // Put this sequence number into the map to track packet loss + while ((theSeqNum - fStats[x].fWrapSeqNum) > TrackStats::kSeqNumMapSize) + { + debugblah++; + Assert(debugblah < 10); + + // We've cycled through the entire map. Calculate packet + // loss on the lowest 50 indexes in the map (don't get too + // close to where we are lest we mistake out of order packets + // as packet loss) + UInt32 halfSeqNumMap = TrackStats::kSeqNumMapSize / 2; + UInt32 curIndex = (fStats[x].fWrapSeqNum + 1) % TrackStats::kSeqNumMapSize; + UInt32 numPackets = 0; + + for (UInt32 y = 0; y < halfSeqNumMap; y++, curIndex++) + { + if (curIndex == TrackStats::kSeqNumMapSize) + curIndex = 0; + + if (fStats[x].fSequenceNumberMap[curIndex] > 0) + numPackets++; + fStats[x].fSequenceNumberMap[curIndex] = 0; + } + + // We've figured out how many lost packets there are in the lower + // half of the map. Increment our counters. + fStats[x].fNumOutOfOrderPackets -= halfSeqNumMap - numPackets; + fStats[x].fNumLostPackets += halfSeqNumMap - numPackets; + fStats[x].fWrapSeqNum += (UInt16) halfSeqNumMap; + +#if BROADCAST_SESSION_DEBUG + qtss_printf("Got %"_U32BITARG_" packets for trackID %"_U32BITARG_". %"_U32BITARG_" packets lost, %"_U32BITARG_" packets out of order\n", fStats[x].fNumPacketsReceived, inTrackID, fStats[x].fNumLostPackets, fStats[x].fNumOutOfOrderPackets); +#endif + } + + // + // Track duplicate packets + if (fStats[x].fSequenceNumberMap[theSeqNum % 100]) + fStats[x].fNumDuplicates++; + + fStats[x].fSequenceNumberMap[theSeqNum % 100] = 1; + theSeqNum = 0; + } + } + Assert(theSeqNum == 0); // We should always find a track with this track ID +} + +void BroadcasterSession::AckPackets(UInt32 inTrackIndex, UInt16 inCurSeqNum, Bool16 inCurSeqNumValid) +{ +#if 0 + char theRRBuffer[256]; + UInt32 *theWriterStart = (UInt32*)theRRBuffer; + UInt32 *theWriter = (UInt32*)theRRBuffer; + + // APP PACKET - QoS info + *(theWriter++) = htonl(0x80CC0000); + //*(ia++) = htonl(trk[i].TrackSSRC); + *(theWriter++) = htonl(0); + *(theWriter++) = htonl('ack '); + *(theWriter++) = htonl(0); + + SInt16 theSeqNumDifference = inCurSeqNum - fStats[inTrackIndex].fHighestSeqNum; + + if (!inCurSeqNumValid) + { + theSeqNumDifference = 1; + inCurSeqNum = fStats[inTrackIndex].fHighestSeqNum; + } +#if BROADCAST_SESSION_DEBUG + qtss_printf("Highest seq num: %d\n", inCurSeqNum); +#endif + + if (theSeqNumDifference > 0) + { + *(theWriter++) = htonl(fStats[inTrackIndex].fLastAckedSeqNum + 1); +#if BROADCAST_SESSION_DEBUG + qtss_printf("TrackID: %d Acking: %d\n", fSDPParser.GetStreamInfo(inTrackIndex)->fTrackID, fStats[inTrackIndex].fLastAckedSeqNum + 1); +#endif + + UInt16 maskPosition = fStats[inTrackIndex].fLastAckedSeqNum + 2; + SInt16 numPacketsInMask = inCurSeqNum - (fStats[inTrackIndex].fLastAckedSeqNum + 2); + +#if BROADCAST_SESSION_DEBUG + qtss_printf("NumPacketsInMask: %d\n", numPacketsInMask); +#endif + for (SInt32 y = 0; y < numPacketsInMask; y+=32) + { + UInt32 mask = 0; + for (UInt32 x = 0; x < 32; x++) + { + SInt16 offsetFromHighest = fStats[inTrackIndex].fHighestSeqNum - maskPosition; + mask <<= 1; + + if (offsetFromHighest >= 0) + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("TrackID: %d Acking in mask: %d\n", fSDPParser.GetStreamInfo(inTrackIndex)->fTrackID, maskPosition); +#endif + mask |= 1; + } + else if (maskPosition == inCurSeqNum) + { +#if BROADCAST_SESSION_DEBUG + qtss_printf("TrackID: %d Acking in mask: %d\n", fSDPParser.GetStreamInfo(inTrackIndex)->fTrackID, inCurSeqNum); +#endif + mask |= 1; + } + + maskPosition++; + } + + // We have 1 completed mask. Add it to the packet + *(theWriter++) = htonl(mask); + } + fStats[inTrackIndex].fLastAckedSeqNum = inCurSeqNum; + } + else + { + // Just ack cur seq num, this is an out of order packet + *(theWriter++) = htonl(inCurSeqNum); + } + + // + // Set the packet length + UInt16* lenP = (UInt16*)theRRBuffer; + lenP[1] = htons((theWriter - theWriterStart) - 1); //length in octets - 1 + + // Send the packet + Assert(fStats[inTrackIndex].fDestRTCPPort != 0); + fUDPSocketArray[(inTrackIndex*2)+1]->SendTo(fSocket->GetHostAddr(), fStats[inTrackIndex].fDestRTCPPort, theRRBuffer, + (theWriter - theWriterStart) * sizeof(UInt32)); + + // + // Update the stats for this track + fStats[inTrackIndex].fNumAcks++; +#endif +} + + +void BroadcasterSession::SendReceiverReport() +{ +#if 0 + if (fUDPSocketArray == NULL) + return; + + // + // build the RTCP receiver report. + char theRRBuffer[256]; + UInt32 *theWriterStart = (UInt32*)theRRBuffer; + UInt32 *theWriter = (UInt32*)theRRBuffer; + + // RECEIVER REPORT + *(theWriter++) = htonl(0x81c90007); // 1 src RR packet + //*(theWriter++) = htonl(trk[i].rcvrSSRC); + *(theWriter++) = htonl(0); + //*(theWriter++) = htonl(trk[i].TrackSSRC); + *(theWriter++) = htonl(0); + //if (trk[i].rtp_num_received > 0) + // t = ((float)trk[i].rtp_num_lost / (float)trk[i].rtp_num_received) * 256; + //else + // t = 0.0; + //temp = t; + //temp = (temp & 0xff) << 24; + //temp |= (trk[i].rtp_num_received & 0x00ffffff); + *(theWriter++) = htonl(0); + //temp = (trk[i].seq_num_cycles & 0xffff0000) | (trk[i].last_seq_num & 0x0000ffff); + //*(ia++) = toBigEndian_ulong(temp); + *(theWriter++) = htonl(0); + *(theWriter++) = 0; // don't do jitter yet. + *(theWriter++) = 0; // don't do last SR timestamp + *(theWriter++) = 0; // don't do delay since last SR + + // APP PACKET - QoS info + *(theWriter++) = htonl(0x80CC000C); + //*(ia++) = htonl(trk[i].TrackSSRC); + *(theWriter++) = htonl(0); +// this QTSS changes after beta to 'qtss' + *(theWriter++) = htonl('QTSS'); + //*(ia++) = toBigEndian_ulong(trk[i].rcvrSSRC); + *(theWriter++) = htonl(0); + *(theWriter++) = htonl(8); // eight 4-byte quants below +#define RR 0x72720004 +#define PR 0x70720004 +#define PD 0x70640002 +#define PL 0x706C0004 + *(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(PD); + *(theWriter++) = htonl(0); // should be a short, but we need to pad to a long for the entire RTCP app packet + +#if BROADCAST_SESSION_DEBUG + qtss_printf("Sending receiver reports.\n"); +#endif + // Send the packet + for (UInt32 x = 0; x < fSDPParser.GetNumStreams(); x++) + { + Assert(fStats[x].fDestRTCPPort != 0); + fUDPSocketArray[(x*2)+1]->SendTo(fSocket->GetHostAddr(), fStats[x].fDestRTCPPort, theRRBuffer, + (theWriter - theWriterStart) * sizeof(UInt32)); + } + +#endif +} diff --git a/PlaylistBroadcaster.tproj/BroadcasterSession.h b/PlaylistBroadcaster.tproj/BroadcasterSession.h new file mode 100755 index 0000000..ca8aa79 --- /dev/null +++ b/PlaylistBroadcaster.tproj/BroadcasterSession.h @@ -0,0 +1,288 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: BroadcasterSession.h + + +*/ + +#ifndef __BROADCASTER_SESSION__ +#define __BROADCASTER_SESSION__ + + +#include "Task.h" + +#include "TimeoutTask.h" + + +#include "RTSPClient.h" +#include "ClientSocket.h" +#include "SDPSourceInfo.h" +#include "UDPSocket.h" +#include "StrPtrLen.h" +#include "OSMutex.h" + +class BroadcasterSession : public Task +{ + public: + + enum + { + kRTSPUDPBroadcasterType = 0, + kRTSPTCPBroadcasterType = 1, + kRTSPHTTPBroadcasterType = 2, + kRTSPHTTPDropPostBroadcasterType = 3, + kRTSPReliableUDPBroadcasterType = 4 + }; + typedef UInt32 BroadcasterType; + + BroadcasterSession( UInt32 inAddr, UInt16 inPort, char* inURL, + BroadcasterType inBroadcasterType, + UInt32 inDurationInSec, UInt32 inStartPlayTimeInSec, + UInt32 inRTCPIntervalInSec, UInt32 inOptionsIntervalInSec, + UInt32 inHTTPCookie, Bool16 inAppendJunkData, UInt32 inReadInterval, + UInt32 inSockRcvBufSize, + StrPtrLen *sdpSPLPtr, + char *namePtr, + char *passwordPtr, + Bool16 deepDebug, + Bool16 burst); + virtual ~BroadcasterSession(); + + // + // 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 + { + kSendingAnnounce = 0, + kSendingSetup = 1, + kSendingReceive = 2, + kBroadcasting = 3, + kSendingTeardown = 4, + kDone = 5 + }; + // + // 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. + kDiedWhileBroadcasting = 6, // Connection was forceably closed while Broadcasting the movie + kMemoryError = 7 + }; + + // + // 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* GetBroadcaster() { return fRTSPClient; } + ClientSocket* GetSocket() { return fSocket; } + SDPSourceInfo* GetSDPInfo() { return &fSDPParser; } + UInt32 GetState() { return fState; } + UInt32 GetDeathState() { return fDeathState; } + + // When this object is in the kDone state, this will tell you why the session died. + UInt32 GetReasonForDying() { return fDeathReason; } + UInt32 GetRequestStatus() { return fRTSPClient->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 GetNumPacketsLost(UInt32 inTrackIndex) + { return fStats[inTrackIndex].fNumLostPackets; } + UInt32 GetNumPacketsOutOfOrder(UInt32 inTrackIndex) + { return fStats[inTrackIndex].fNumOutOfOrderPackets; } + UInt32 GetNumDuplicates(UInt32 inTrackIndex) + { return fStats[inTrackIndex].fNumDuplicates; } + UInt32 GetNumAcks(UInt32 inTrackIndex) + { return fStats[inTrackIndex].fNumAcks; } + UInt32 GetNumStreams() + { return fSDPParser.GetNumStreams();} + UInt32 GetStreamDestPort(UInt32 inIndex) + { return fStats[inIndex].fDestRTPPort;} + + void TearDownNow() {fTeardownImmediately = true; this->Signal(Task::kKillEvent);} + // + // Global stats + static UInt32 GetActiveConnections() { return sActiveConnections; } + static UInt32 GetBroadcastingConnections() { return sBroadcastingConnections; } + static UInt32 GetConnectionAttempts() { return sTotalConnectionAttempts; } + char* GetNextPacket(UInt32 *packetLen, UInt8 *channel); + OS_Error SendPacket(char* data, UInt32 len,UInt8 channel); + OS_Error SendWaitingPackets(); + + enum + { + kUDPTransportType = 0, + kReliableUDPTransportType = 1, + kTCPTransportType = 2 + }; + typedef UInt32 TransportType; + + TransportType GetTransportType() { return fTransportType; } + + UInt32 GetPacketQLen() { return fPacketQueue.GetLength(); } + OSMutex* GetMutex() { return &fMutex; } + private: + + enum + { + kRawRTSPControlType = 0, + kRTSPHTTPControlType = 1, + kRTSPHTTPDropPostControlType= 2 + }; + typedef UInt32 ControlType; + + + ClientSocket* fSocket; // Connection object + RTSPClient* fRTSPClient; // 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 fRTCPIntervalInSec; + UInt32 fOptionsIntervalInSec; + + UInt32 fState; // For managing the state machine + UInt32 fDeathState; // state at time of death + UInt32 fDeathReason; + UInt32 fNumSetups; + UDPSocket** fUDPSocketArray; + + SInt64 fPlayTime; + SInt64 fTotalPlayTime; + SInt64 fLastRTCPTime; + + Bool16 fTeardownImmediately; + Bool16 fAppendJunk; + UInt32 fReadInterval; + UInt32 fSockRcvBufSize; + Bool16 fBurst; + UInt32 fBurstTime; + // + // Broadcaster stats + struct TrackStats + { + enum + { + kSeqNumMapSize = 100, + kHalfSeqNumMap = 50 + }; + + UInt16 fDestRTPPort; + UInt16 fDestRTCPPort; + UInt32 fNumPacketsReceived; + UInt32 fNumBytesReceived; + UInt32 fNumLostPackets; + UInt32 fNumOutOfOrderPackets; + UInt32 fNumThrownAwayPackets; + UInt8 fSequenceNumberMap[kSeqNumMapSize]; + UInt16 fWrapSeqNum; + UInt16 fLastSeqNum; + UInt32 fSSRC; + Bool16 fIsSSRCValid; + + UInt16 fHighestSeqNum; + UInt16 fLastAckedSeqNum; + Bool16 fHighestSeqNumValid; + + UInt32 fNumAcks; + UInt32 fNumDuplicates; + + }; + TrackStats* fStats; + + static char* fPacket; + // + // Global stats + static UInt32 sActiveConnections; + static UInt32 sBroadcastingConnections; + static UInt32 sTotalConnectionAttempts; + + // + // Helper functions for Run() + void SetupUDPSockets(); + void ProcessMediaPacket(char* inPacket, UInt32 inLength, UInt32 inTrackID, Bool16 isRTCP); + OS_Error ReadMediaData(); + void SendReceiverReport(); + void AckPackets(UInt32 inTrackIndex, UInt16 inCurSeqNum, Bool16 inCurSeqNumValid); + + OSMutex fMutex;//this data structure is shared! + + struct RTPPacket + { + RTPPacket() : fQueueElem(NULL), fData(NULL), fLen(0), fChannel(0),fCount(0){} + ~RTPPacket() { fData = NULL; fLen = 0; fChannel = 0; } + void SetEnclosingObject(void *obj) {fQueueElem.SetEnclosingObject(obj);} + void SetPacketData(char* data, UInt32 len,UInt8 channel) { fData = data; fLen = len; fChannel = channel; } + OSQueueElem *GetQElement() {return &fQueueElem;} + OSQueueElem fQueueElem; + char* fData; + UInt32 fLen; + UInt8 fChannel; + UInt64 fCount; + }; + UInt64 fPacketCount; + OSQueue fPacketQueue; + + UInt32 fPacketLen; + UInt8 fChannel; +// char* fPacket; + + +}; + +#endif //__BROADCASTER_SESSION__ diff --git a/PlaylistBroadcaster.tproj/GetLocalIPAddressString.c b/PlaylistBroadcaster.tproj/GetLocalIPAddressString.c new file mode 100644 index 0000000..f161b66 --- /dev/null +++ b/PlaylistBroadcaster.tproj/GetLocalIPAddressString.c @@ -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@ + * + */ +#include "GetLocalIPAddressString.h" + + +#ifdef __MACOS__ + #include "BogusDefs.h" +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + + +#endif + +#include +#include +#include "SafeStdLib.h" +#include +#include + +#if __solaris__ || __sgi__ + #include + #include +#endif + + +short GetLocalIPAddressString(char *returnStr, short maxSize) +{ + short result = 0; + int err = -1; + + char defaultAddress[] = "255.255.255.255"; + char* addr = defaultAddress; + do + { + + //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; + + char* ifReqIter = NULL; + struct ifconf ifc; + struct ifreq* ifr; + char buffer[kMaxAddrBufferSize]; + + int tempSocket = socket(AF_INET, SOCK_DGRAM, 0); + if (tempSocket == -1) break; + + ifc.ifc_len = kMaxAddrBufferSize; + ifc.ifc_buf = buffer; + +#if (__linux__ || __MacOSX__ || __MACOS__ || __linuxppc__ || __solaris__ || __sgi__) + err = ioctl(tempSocket, SIOCGIFCONF, (char*)&ifc); +#elif __FreeBSD__ + err = ioctl(tempSocket, OSIOCGIFCONF, (char*)&ifc); +#else + #error +#endif + if (err == -1) break; + + +#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; + +// int numIPAddrs = 0; + + for (ifReqIter = buffer; ifReqIter < (buffer + ifc.ifc_len);) + { + ifr = (struct ifreq*)ifReqIter; +#if __MacOSX__ + ifReqIter += sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len; + if (ifr->ifr_addr.sa_len == 0) + { + switch (ifr->ifr_addr.sa_family) + { + case AF_INET: + ifReqIter += sizeof(struct sockaddr_in); + break; + default: + ifReqIter += sizeof(struct sockaddr); + } + } +#else + ifReqIter += sizeof(ifr->ifr_name) + 0; + switch (ifr->ifr_addr.sa_family) + { + case AF_INET: + ifReqIter += sizeof(struct sockaddr_in); + break; + default: + ifReqIter += sizeof(struct sockaddr); + } +#endif + + //Only count interfaces in the AF_INET family. + //And don't count localhost, loopback interfaces + if ((ifr->ifr_addr.sa_family == AF_INET) && (strncmp(ifr->ifr_name, "lo", 2) != 0)) + { + struct sockaddr_in* addrPtr = (struct sockaddr_in*)&ifr->ifr_addr; + addr = inet_ntoa(addrPtr->sin_addr); + //qtss_printf("found local address: %s\n", addr); + err = 0; + break; + } + } + + } while (0); + + + result = strlen(addr); + + if (maxSize < result) + { err = -1; + } + else + strcpy(returnStr, addr); + + return err; +} diff --git a/PlaylistBroadcaster.tproj/GetLocalIPAddressString.h b/PlaylistBroadcaster.tproj/GetLocalIPAddressString.h new file mode 100644 index 0000000..7d86711 --- /dev/null +++ b/PlaylistBroadcaster.tproj/GetLocalIPAddressString.h @@ -0,0 +1,44 @@ + +#ifndef __getlocalipaddressstring__ +#define __getlocalipaddressstring__ + +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 + + + short GetLocalIPAddressString(char *returnStr, short maxSize); + + #ifdef __cplusplus + } + #endif + + +#endif diff --git a/PlaylistBroadcaster.tproj/NoRepeat.cpp b/PlaylistBroadcaster.tproj/NoRepeat.cpp new file mode 100644 index 0000000..d970167 --- /dev/null +++ b/PlaylistBroadcaster.tproj/NoRepeat.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@ + * + */ + + +#include +#include "SafeStdLib.h" +#include + + +#include "NoRepeat.h" + + + +NoRepeat::NoRepeat ( UInt32 numNoRepeats ) + : PLDoubleLinkedList() +{ + mMaxElements = numNoRepeats; +} + +NoRepeat::~NoRepeat() +{ + // we have nothing to do, just let the PLDoubleLinkedList clear itself +} + +bool NoRepeat::CompareNameToElement( PLDoubleLinkedListNode* node, void* name ) +{ + if ( !::strcmp( node->fElement->mElementName, (const char*)name ) ) + return true; + + return false; +} + + +bool NoRepeat::IsInList( char* name ) +{ + PLDoubleLinkedListNode* whichElement; + + whichElement = ForEachUntil( CompareNameToElement, (void*)name ); + + if ( whichElement ) + return true; + + return false; + +} + + +PLDoubleLinkedListNode* NoRepeat::AddToList( PLDoubleLinkedListNode* node ) +{ + AddNode(node); + PLDoubleLinkedListNode* oldTail = NULL; + + if ( fNumNodes > mMaxElements ) + { oldTail = fTail; + this->RemoveNode( fTail ); + } + + return oldTail; +} + +bool NoRepeat::AddToList( char* name ) +{ + Assert( false ); + + bool addedSuccesfully = false; + + if ( !this->IsInList( name ) ) + { + SimplePlayListElement* element; + PLDoubleLinkedListNode* node = NULL; + + element = new SimplePlayListElement(name); + + Assert( element ); + + if ( element ) + node = new PLDoubleLinkedListNode(element); + + Assert( node ); + + if ( node ) + { + PLDoubleLinkedListNode* deadNode; + + deadNode = this->AddToList(node); + + delete deadNode; + addedSuccesfully = true; + + } + + } + + return addedSuccesfully; +} + diff --git a/PlaylistBroadcaster.tproj/NoRepeat.h b/PlaylistBroadcaster.tproj/NoRepeat.h new file mode 100644 index 0000000..6890635 --- /dev/null +++ b/PlaylistBroadcaster.tproj/NoRepeat.h @@ -0,0 +1,52 @@ +#ifndef __no_repeat__ +#define __no_repeat__ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "PLDoubleLinkedList.h" +#include "SimplePlayListElement.h" + +#include "OSHeaders.h" + + +class NoRepeat : public PLDoubleLinkedList { + + public: + NoRepeat( UInt32 numNoRepeats ); + virtual ~NoRepeat(); + + bool IsInList( char* name ); // return true if name is in list, false if not + bool AddToList( char* name );// return true if could be added to list, no dupes allowd + PLDoubleLinkedListNode* AddToList( PLDoubleLinkedListNode* node ); + + protected: + static bool CompareNameToElement( PLDoubleLinkedListNode*node, void *name ); + UInt32 mMaxElements; + +}; + +#endif diff --git a/PlaylistBroadcaster.tproj/PLBroadcastDef.cpp b/PlaylistBroadcaster.tproj/PLBroadcastDef.cpp new file mode 100644 index 0000000..88df2ac --- /dev/null +++ b/PlaylistBroadcaster.tproj/PLBroadcastDef.cpp @@ -0,0 +1,630 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + + 8.2.99 - rt updated ShowSettings() to display user names for fields instead of C++ member names. +*/ + +#include "PLBroadcastDef.h" +#include "MyAssert.h" +#include "SocketUtils.h" + +#include "ConfParser.h" +#include + +#include +#include +#include "SafeStdLib.h" +#ifndef __Win32__ +#include +#endif + +#include "BroadcasterSession.h" + +Bool16 PLBroadcastDef::ConfigSetter( const char* paramName, const char* paramValue[], void* userData ) +{ + // return true if set fails + + + PLBroadcastDef* broadcastParms = (PLBroadcastDef*)userData; + + if (!::strcmp( "destination_ip_address", paramName) ) + { + broadcastParms->SetValue( &broadcastParms->mOrigDestAddress, paramValue[0] ); + if (broadcastParms->mIgnoreFileIP) + return false; + else + return broadcastParms->SetValue( &broadcastParms->mDestAddress, paramValue[0] ); + } + else if (!::strcmp( "destination_base_port", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mBasePort, paramValue[0] ); + + } + else if (!::strcmp( "max_upcoming_list_size", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mMaxUpcomingMovieListSize, paramValue[0] ); + + } + else if (!::strcmp( "play_mode", paramName) ) + { + + if ( ::strcmp( "sequential", paramValue[0]) + && ::strcmp( "sequential_looped", paramValue[0]) + && ::strcmp( "weighted_random", paramValue[0]) + ) + return true; + + return broadcastParms->SetValue( &broadcastParms->mPlayMode, paramValue[0] ); + + } + /* + rt- rremoved for buld 12 + else if (!::strcmp( "limit_play", paramName) ) + { + if ( ::strcmp( "enabled", paramValue[0]) && ::strcmp( "disabled", paramValue[0]) ) + return true; + + return broadcastParms->SetValue( &broadcastParms->mLimitPlay, paramValue[0] ); + + } + */ + // changed at bulid 12 else if (!::strcmp( "repeats_queue_size", paramName) ) + else if (!::strcmp( "recent_movies_list_size", paramName) ) + { + if ( ::atoi( paramValue[0] ) < 0 ) + return true; + + broadcastParms->mLimitPlayQueueLength = atoi(paramValue[0]); + + return false; + + } + else if (!::strcmp( "playlist_file", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mPlayListFile, paramValue[0] ); + + } + else if (!::strcmp( "sdp_file", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mSDPFile, paramValue[0] ); + + } + else if (!::strcmp( "destination_sdp_file", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mDestSDPFile, paramValue[0] ); + + } + else if (!::strcmp( "logging", paramName) ) + { + if ( ::strcmp( "enabled", paramValue[0]) && ::strcmp( "disabled", paramValue[0]) ) + return true; + + return broadcastParms->SetValue( &broadcastParms->mLogging, paramValue[0] ); + + } + else if (!::strcmp( "log_file", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mLogFile, paramValue[0] ); + + } + else if (!::strcmp( "sdp_reference_movie", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mSDPReferenceMovie, paramValue[0] ); + + } + else if (!::strcmp( "show_current", paramName) ) + { + if ( ::strcmp( "enabled", paramValue[0]) && ::strcmp( "disabled", paramValue[0]) ) + return true; + + return broadcastParms->SetValue( &broadcastParms->mShowCurrent, paramValue[0] ); + + } + else if (!::strcmp( "show_upcoming", paramName) ) + { + if ( ::strcmp( "enabled", paramValue[0]) && ::strcmp( "disabled", paramValue[0]) ) + return true; + + return broadcastParms->SetValue( &broadcastParms->mShowUpcoming, paramValue[0] ); + + } + else if (!::strcmp( "broadcast_start_time", paramName) ) + { + + const char* theValue = paramValue[0]; + if ('*' == theValue[0]) + { + UInt32 startTime = time(NULL) + 2208988800LU + (time_t) ::strtol(&theValue[1], NULL, 10); + char startTimeStr[20] = ""; + qtss_sprintf(startTimeStr,"%"_U32BITARG_"", startTime); // current time + return broadcastParms->SetValue( &broadcastParms->mStartTime, startTimeStr ); + } + + return broadcastParms->SetValue( &broadcastParms->mStartTime, paramValue[0] ); + } + else if (!::strcmp( "broadcast_end_time", paramName) ) + { + UInt32 endTime = 0; + const char* theValue = paramValue[0]; + if ('*' == theValue[0]) + endTime = time(NULL) + 2208988800LU + (SInt32) ::strtol(&theValue[1], NULL, 10); + else + endTime = ::strtoul(theValue, NULL, 10); + + if ( (endTime > 0) && endTime < 2208988800LU) // not a valid time + return true; + + char endTimeStr[20] = ""; + qtss_sprintf(endTimeStr,"%"_U32BITARG_"", endTime); // current time + offset time + return broadcastParms->SetValue( &broadcastParms->mEndTime, endTimeStr ); + + } + else if (!::strcmp( "broadcast_SDP_is_dynamic", paramName) ) + { + if ( ::strcmp( "enabled", paramValue[0]) && ::strcmp( "disabled", paramValue[0]) ) + return true; + + return broadcastParms->SetValue( &broadcastParms->mIsDynamic, paramValue[0] ); + } + else if (!::strcmp( "broadcaster_name", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mName, paramValue[0] ); + + } + else if (!::strcmp( "broadcaster_password", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mPassword, paramValue[0] ); + + } + else if (!::strcmp( "multicast_ttl", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mTTL, paramValue[0] ); + + } + else if (!::strcmp( "rtsp_port", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mRTSPPort, paramValue[0] ); + + } + else if (!::strcmp( "pid_file", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mPIDFile, paramValue[0] ); + + } + else if (!::strcmp( "client_buffer_delay", paramName) ) + { + return broadcastParms->SetValue( &broadcastParms->mClientBufferDelay, paramValue[0] ); + + } + 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 PLBroadcastDef::SetValue( char** dest, const char* value) +{ + Bool16 didFail = false; + + // if same param occurs more than once in file, delete + // initial occurance and override with second. + if ( *dest ) + delete [] *dest; + + *dest = new char[ strlen(value) + 1 ]; + Assert( *dest ); + + if ( *dest ) + ::strcpy( *dest, value ); + else + didFail = true; + + return didFail; +} + +Bool16 PLBroadcastDef::SetDefaults( const char* setupFileName ) +{ + Bool16 didFail = false; + + if (mDestAddress != NULL) + mIgnoreFileIP = true; + + if ( !didFail && !mIgnoreFileIP) + didFail = this->SetValue( &mDestAddress, SocketUtils::GetIPAddrStr(0)->Ptr ); + + if ( !didFail ) + didFail = this->SetValue( &mBasePort, "5004" ); + + if ( !didFail ) + didFail = this->SetValue( &mPlayMode, "sequential_looped" ); + + if ( !didFail ) + didFail = this->SetValue( &mMaxUpcomingMovieListSize, "7" ); + + if ( !didFail ) + didFail = this->SetValue( &mLogging, "enabled" ); + + if ( !didFail ) + didFail = this->SetValue( &mRTSPPort, "554" ); + + char nameBuff[kBufferLen]; + int maxNameLen = kMaxBufferStringLen; //maxNameLen = 492 + nameBuff[ sizeof(nameBuff) -1] = 0; //term buffer + ::strncpy( nameBuff, "broadcast" , maxNameLen); + + if (setupFileName) + ::strncpy( nameBuff, setupFileName , maxNameLen); + + nameBuff[maxNameLen] = '\0'; //zero term the name + + int baseLen = ::strlen(nameBuff); //maxNameLen max + +//add .log to the base name of the description file with the .ext stripped + + char *ext = NULL; + ext = ::strrchr( nameBuff, '.' ); + if ( ext ) + { + *ext = 0; + baseLen = ::strlen(nameBuff); + } + nameBuff[baseLen] = 0; + + + ::strncat( nameBuff, ".log",sizeof(nameBuff) - strlen(nameBuff) - 1 ); + if ( !didFail ) + didFail = this->SetValue( &mLogFile, nameBuff ); + + nameBuff[baseLen] = 0; + ::strncat( nameBuff, ".ply" ,sizeof(nameBuff) - strlen(nameBuff) - 1); + if ( !didFail ) + didFail = this->SetValue( &mPlayListFile, nameBuff ); + + + nameBuff[baseLen] = 0; + ::strncat( nameBuff, ".sdp" ,sizeof(nameBuff) - strlen(nameBuff) - 1 ); + if ( !didFail ) + didFail = this->SetValue( &mSDPFile, nameBuff ); + + if ( !didFail ) + didFail = this->SetValue( &mDestSDPFile, "no_name" ); + + +/* current, upcoming, and replacelist created by emil@popwire.com */ + nameBuff[baseLen] = 0; + ::strncat( nameBuff, ".current" ,sizeof(nameBuff) - strlen(nameBuff) - 1 ); + if ( !didFail ) + didFail = this->SetValue( &mCurrentFile, nameBuff ); + + nameBuff[baseLen] = 0; + ::strncat( nameBuff, ".upcoming" ,sizeof(nameBuff) - strlen(nameBuff) - 1); + if ( !didFail ) + didFail = this->SetValue( &mUpcomingFile, nameBuff ); + + nameBuff[baseLen] = 0; + ::strncat( nameBuff, ".replacelist" ,sizeof(nameBuff) - strlen(nameBuff) - 1 ); + if ( !didFail ) + didFail = this->SetValue( &mReplaceFile, nameBuff ); + + nameBuff[baseLen] = 0; + ::strncat( nameBuff, ".stoplist" ,sizeof(nameBuff) - strlen(nameBuff) - 1); + if ( !didFail ) + didFail = this->SetValue( &mStopFile, nameBuff ); + + nameBuff[baseLen] = 0; + ::strncat( nameBuff, ".insertlist" ,sizeof(nameBuff) - strlen(nameBuff) - 1 ); + if ( !didFail ) + didFail = this->SetValue( &mInsertFile, nameBuff ); + + if ( !didFail ) + didFail = this->SetValue( &mShowCurrent, "enabled" ); + + if ( !didFail ) + didFail = this->SetValue( &mShowUpcoming, "enabled" ); + + if ( !didFail ) + didFail = this->SetValue( &mStartTime, "0" ); + + if ( !didFail ) + didFail = this->SetValue( &mEndTime, "0" ); + + if ( !didFail ) + didFail = this->SetValue( &mIsDynamic, "disabled" ); + + if ( !didFail ) + didFail = this->SetValue( &mName, "" ); + + if ( !didFail ) + didFail = this->SetValue( &mPassword, "" ); + + if ( !didFail ) + didFail = this->SetValue( &mTTL, "1" ); + + if ( !didFail ) + didFail = this->SetValue( &mClientBufferDelay, "0" ); + + //see if there is a defaults File. + //if there is one load it and over-ride the other defaults + if (NULL != setupFileName) + { + int len = ::strlen(setupFileName) + 10; + char *defaultFileName = new char[len]; + qtss_snprintf(defaultFileName, len, "%s%s", setupFileName, ".def"); + (void) ::ParseConfigFile( false, defaultFileName, ConfigSetter, this ); //ignore if no defaults file + delete [] defaultFileName; + } + +/* ***************************************************** */ + return didFail; +} + + +PLBroadcastDef::PLBroadcastDef( const char* setupFileName, char *destinationIP, Bool16 debug ) + : mDestAddress(destinationIP) + , mOrigDestAddress(NULL) + , mBasePort(NULL) + , mPlayMode(NULL) + // removed at build 12 , mLimitPlay(NULL) + , mLimitPlayQueueLength(0) + , mPlayListFile(NULL) + , mSDPFile(NULL) + , mLogging(NULL) + , mLogFile(NULL) + , mSDPReferenceMovie( NULL ) + , mCurrentFile( NULL ) + , mUpcomingFile( NULL ) + , mReplaceFile( NULL ) + , mStopFile( NULL ) + , mInsertFile( NULL ) + , mShowCurrent( NULL ) + , mShowUpcoming( NULL ) + , mTheSession( NULL ) + , mIgnoreFileIP(false) + , mMaxUpcomingMovieListSize(NULL) + , mDestSDPFile(NULL) + , mStartTime(NULL) + , mEndTime(NULL) + , mIsDynamic(NULL) + , mName( NULL) + , mPassword( NULL) + , mTTL(NULL) + , mRTSPPort(NULL) + , mPIDFile(NULL) + , mClientBufferDelay(NULL) + , mParamsAreValid(false) + , mInvalidParamFlags(kInvalidNone) + +{ + + if (!setupFileName && !destinationIP) + { this->SetDefaults( NULL ); + qtss_printf( "default settings\n" ); + this->ShowSettings(); + return; + } + + fDebug = debug; + if (destinationIP != NULL) //we were given an IP to use + mIgnoreFileIP = true; + + Assert( setupFileName ); + + if (setupFileName ) + { + int err = -1; + + if ( !this->SetDefaults( setupFileName ) ) + { err = ::ParseConfigFile( false, setupFileName, ConfigSetter, this ); + } + + + if ( !err ) + { mParamsAreValid = true; + } + + ValidateSettings(); + } +} + +void PLBroadcastDef::ValidateSettings() +{ + + // For now it just validates the destination ip address + UInt32 inAddr = 0; + inAddr = SocketUtils::ConvertStringToAddr(mDestAddress); + if(inAddr == INADDR_NONE) + { + struct hostent* theHostent = ::gethostbyname(mDestAddress); + if (theHostent != NULL) + { + inAddr = ntohl(*(UInt32*)(theHostent->h_addr_list[0])); + + struct in_addr inAddrStruct; + char buffer[50]; + StrPtrLen temp(buffer); + inAddrStruct.s_addr = *(UInt32*)(theHostent->h_addr_list[0]); + SocketUtils::ConvertAddrToString(inAddrStruct, &temp); + SetValue( &mDestAddress, buffer ); + } + } + if(inAddr == INADDR_NONE) + mInvalidParamFlags |= kInvalidDestAddress; + + // If mInvalidParamFlags is set, set mParamsAreValid to false + if ( mInvalidParamFlags | kInvalidNone ) + mParamsAreValid = false; +} + +void PLBroadcastDef::ShowErrorParams() +{ + if( mInvalidParamFlags & kInvalidDestAddress ) + qtss_printf( "destination_ip_address \"%s\" is Invalid\n", mOrigDestAddress ); +} + + +void PLBroadcastDef::ShowSettings() +{ + + + qtss_printf( "\n" ); + qtss_printf( "Description File Settings\n" ); + qtss_printf( "----------------------------\n" ); + + qtss_printf( "destination_ip_address %s\n", mOrigDestAddress ); + qtss_printf( "destination_sdp_file %s\n", mDestSDPFile ); + qtss_printf( "destination_base_port %s\n", mBasePort ); + qtss_printf( "play_mode %s\n", mPlayMode ); + qtss_printf( "recent_movies_list_size %d\n", mLimitPlayQueueLength ); + qtss_printf( "playlist_file %s\n", mPlayListFile ); + qtss_printf( "logging %s\n", mLogging ); + qtss_printf( "log_file %s\n", mLogFile ); + if (mSDPReferenceMovie != NULL) + qtss_printf( "sdp_reference_movie %s\n", mSDPReferenceMovie ); + qtss_printf( "sdp_file %s\n", mSDPFile ); + qtss_printf( "max_upcoming_list_size %s\n", mMaxUpcomingMovieListSize ); + qtss_printf( "show_current %s\n", mShowCurrent ); + qtss_printf( "show_upcoming %s\n", mShowUpcoming ); + qtss_printf( "broadcaster_name \"%s\"\n", mName); + qtss_printf( "broadcaster_password \"XXXXX\"\n"); + qtss_printf( "multicast_ttl %s\n",mTTL); + qtss_printf( "rtsp_port %s\n",mRTSPPort); + + Float32 bufferDelay = 0.0; + ::sscanf(mClientBufferDelay, "%f", &bufferDelay); + if (bufferDelay != 0.0) + qtss_printf( "client_buffer_delay %.2f\n",bufferDelay); + else + qtss_printf( "client_buffer_delay default\n"); + + if (mPIDFile != NULL) + qtss_printf( "pid_file %s\n",mPIDFile); + + qtss_printf( "broadcast_SDP_is_dynamic %s\n", mIsDynamic ); + + UInt32 startTime = (UInt32) ::strtoul(mStartTime, NULL, 10); + if ( startTime > 2208988800LU) + { + qtss_printf( "broadcast_start_time %s (NTP seconds)\n",mStartTime); + + startTime -= 2208988800LU; //1970 - 1900 secs + qtss_printf( "-->broadcast_start_time = %"_U32BITARG_" (unix seconds)\n",startTime); + + time_t tmpTime; + tmpTime = (time_t) startTime; + struct tm timeResult; + struct tm *localTM = qtss_localtime(&tmpTime, &timeResult); + char timeBuffer[kTimeStrSize]; + char *theTime = qtss_asctime(localTM,timeBuffer, sizeof(timeBuffer)); + if (theTime[0] != 0) + theTime[::strlen(theTime) -1] = 0; + qtss_printf( "-->broadcast_start_time = %s (local time)\n",theTime); + + tmpTime = (time_t) startTime; + struct tm *gmTM = qtss_gmtime(&tmpTime, &timeResult); + theTime = qtss_asctime(gmTM, timeBuffer, sizeof(timeBuffer)); + if (theTime[0] != 0) + theTime[::strlen(theTime) -1] = 0; + qtss_printf( "-->broadcast_start_time = %s (UTC/GM time)\n",theTime); + } + else if (0 == startTime) + qtss_printf( "broadcast_start_time 0 (allow all)\n"); + else + qtss_printf( "broadcast_start_time %s (NTPseconds allow all)\n", mStartTime); + + UInt32 endTime = strtoul(mEndTime, NULL, 10); + if (endTime > 2208988800LU) + { + qtss_printf( "broadcast_end_time %s (NTP seconds)\n",mEndTime); + + endTime -= 2208988800LU;//convert to 1970 secs + qtss_printf( "-->broadcast_end_time = %"_U32BITARG_" (unix seconds)\n",endTime); + + time_t tmpTime = (time_t) endTime; + struct tm timeResult; + struct tm *localTM = qtss_localtime(&tmpTime, &timeResult); + char timeBuffer[kTimeStrSize]; + char *theTime = qtss_asctime(localTM,timeBuffer, sizeof(timeBuffer)); + if (theTime[0] != 0) + theTime[::strlen(theTime) -1] = 0; + qtss_printf( "-->broadcast_end_time = %s (local time)\n",theTime); + + tmpTime = (time_t) endTime; + struct tm *gmTM = qtss_gmtime(&tmpTime, &timeResult); + theTime = qtss_asctime(gmTM, timeBuffer, sizeof(timeBuffer)); + if (theTime[0] != 0) + theTime[::strlen(theTime) -1] = 0; + qtss_printf( "-->broadcast_end_time = %s (UTC/GM time)\n",theTime); + } + else if (0 == endTime) + qtss_printf( "broadcast_end_time 0 (unbounded)\n"); + else + qtss_printf( "broadcast_end_time 1900 + %s seconds (looks invalid)\n", mEndTime); + + qtss_printf( "max_err_file_k_size %"_U32BITARG_"\n", qtss_getmaxprintfcharsinK()); + + + qtss_printf( "============================\n" ); + +} + + +PLBroadcastDef::~PLBroadcastDef() +{ + delete [] mDestAddress; + delete [] mOrigDestAddress; + delete [] mBasePort; + delete [] mPlayMode; + // removed at build 12 delete [] mLimitPlay; + delete [] mPlayListFile; + delete [] mSDPFile; + delete [] mLogging; + delete [] mLogFile; + if (mSDPReferenceMovie != NULL) + delete [] mSDPReferenceMovie; + delete [] mMaxUpcomingMovieListSize; + delete [] mTTL; + delete [] mName; + delete [] mShowUpcoming; + delete [] mShowCurrent; + delete [] mPassword; + delete [] mIsDynamic; + delete [] mStartTime; + delete [] mEndTime; + +} diff --git a/PlaylistBroadcaster.tproj/PLBroadcastDef.h b/PlaylistBroadcaster.tproj/PLBroadcastDef.h new file mode 100644 index 0000000..48da003 --- /dev/null +++ b/PlaylistBroadcaster.tproj/PLBroadcastDef.h @@ -0,0 +1,129 @@ + +#ifndef __PLBroadcastDef__ +#define __PLBroadcastDef__ + + +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + + + +/* + +#example (1) A FULL 1.0 DECLARED FILE +#Lines beginning with "#" characters are comments +#The order of the following entries is unimportant +#Quotes are optional for values + +destination_ip_address 225.225.225.225 +destination_base_port 5004 +play_mode [sequential, sequential_looped, weighted] +limit_play enabled +limit_seq_length 10 +play_list_file /path/file +sdp_file /path/file +log_file /path/file + +*/ + +#include "OSHeaders.h" +#include "BroadcasterSession.h" + +class PLBroadcastDef { + + public: + PLBroadcastDef( const char* setupFileName, char *destinationIP, Bool16 debug ); + virtual ~PLBroadcastDef(); + + Bool16 ParamsAreValid() { return mParamsAreValid; } + + void ValidateSettings(); + void ShowErrorParams(); + + void ShowSettings(); + + static Bool16 ConfigSetter( const char* paramName, const char* paramValue[], void * userData ); + + // * == default value, required input + char* mDestAddress; // set by PLB to be resolved address + char* mOrigDestAddress; // [0.0.0.0 | domain name?] *127.0.0.1 ( self ) + char* mBasePort; // [ 0...32k?] *5004 + + + char* mPlayMode; // [sequential | *sequential_looped | weighted] + // removed at build 12 char* mLimitPlay; // [*enabled | disabled] + int mLimitPlayQueueLength; // [ 0...32k?] *20 + //char* mLimitPlayQueueLength; // [ 0...32k?] *20 + char* mPlayListFile; // [os file path] *.ply + char* mSDPFile; // [os file path] + char* mLogging; // [*enabled | disabled] + char* mLogFile; // [os file path] *.log + char* mSDPReferenceMovie; // [os file path] + char* mCurrentFile; // [os file path] *.current + char* mUpcomingFile; // [os file path] *.upcoming + char* mReplaceFile; // [os file path] *.replacelist + char* mStopFile; // [os file path] *.stoplist + char* mInsertFile; // [os file path] *.insertlist + char* mShowCurrent; // [*enabled | disabled] + char* mShowUpcoming; // [*enabled | disabled] + + BroadcasterSession *mTheSession;// a broadcaster RTSP/RTP session with the server. + + bool mIgnoreFileIP; + char* mMaxUpcomingMovieListSize; // [ 2^31] *7 + char* mDestSDPFile; // [movies folder relative file path] + + char* mStartTime; // NTP start time + char* mEndTime; // NTP end time + char* mIsDynamic; // true + + char* mName; // Authentication name + char* mPassword; // Authentication password + + char * mTTL; // TTL for multicast [1..15] *1 + + char * mRTSPPort; + + char * mPIDFile; + + char * mClientBufferDelay; // sdp option a=x-bufferdelay: float + + enum { + kInvalidNone = 0x00000000, + kInvalidDestAddress = 0x00000001, + kBufferLen = 512, + kExtensionLen = 20, + kMaxBufferStringLen = kBufferLen - kExtensionLen + }; + + protected: + Bool16 mParamsAreValid; + UInt32 mInvalidParamFlags; + Bool16 SetValue( char** dest, const char* value); + Bool16 SetDefaults( const char* setupFileName ); + Bool16 fDebug; +}; + +#endif diff --git a/PlaylistBroadcaster.tproj/PickerFromFile.cpp b/PlaylistBroadcaster.tproj/PickerFromFile.cpp new file mode 100644 index 0000000..f85915c --- /dev/null +++ b/PlaylistBroadcaster.tproj/PickerFromFile.cpp @@ -0,0 +1,483 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "PickerFromFile.h" + + +#include +#include "GetWord.h" +#include "Trim.h" +#include "MyAssert.h" + +#include +#ifndef __Win32__ + #include +#endif + +#ifdef __hpux__ + #include +#endif + +#define kMaxPickerPath 512 + +#include + +static bool CompareNameToElement( PLDoubleLinkedListNode* node, void* name ); +static void DisplayPickerErr( int pickErr, const char *message, const char*fname, int lineCount, const char*lineBuff ); + +static bool CompareNameToElement( PLDoubleLinkedListNode* node, void* name ) +{ + if ( !::strcmp( node->fElement->mPathName, (const char*)name ) ) + return true; + + return false; +} + +static bool IsDir(char* path); +static bool IsDir(char* path) +{ + struct stat data; + + int err = stat(path, &data); + + if (err == -1) + return false; + + return ((data.st_mode & S_IFDIR) == S_IFDIR ); +} + +static void DisplayPickerErr( int pickErr, const char *message, const char*fname, int lineCount, const char*lineBuff ) +{ + char *errMessage; + + qtss_printf( "- %s:\n", message ); + + if ( lineCount ) + qtss_printf( " Playlist: %s, line# %i\n", fname, lineCount ); + else + qtss_printf( " Playlist: %s\n", fname ); + + if ( lineBuff ) + qtss_printf( " Playlist text: %s", lineBuff ); // lineBuff already includes a \n + + switch ( pickErr ) + { + case kPickerPopulateLoopDetected: + errMessage = "Include would create a loop.\n"; + break; + + case kPickerPopulateBadFormat: + errMessage = "Playlist file is missing *PLAY-LIST* identifier.\n"; + break; + + case kPickerPopulateFileError: + errMessage = "Playlist file could not be opened.\n"; + break; + + case kPickerPopulateNoMem: + default: + errMessage = "Internal error occurred.\n"; + break; + } + + qtss_printf( " Reason: %s\n", errMessage); +} + +bool PathIsAbsolute(char *pathPtr) +{ + bool result = false; +#ifdef __Win32__ + if ( (pathPtr[1] == ':') && ( pathPtr[2] == kPathDelimiterChar ) ) + result = true; +#else + if ( *pathPtr == kPathDelimiterChar ) + result = true; +#endif + return result; +} + + +int PopulatePickerFromFile( PlaylistPicker* picker, char* fname, const char* basePath, LoopDetectionList *ldList ) +{ + Assert( picker ); + Assert( fname ); + + FILE* weightings = NULL; + LoopDetectionListElement* ldElement = NULL; + LoopDetectionNode* ldNode = NULL; + int lineCount = 0; + int pickErr = kPickerPopulateNoErr; + char path[kMaxPickerPath]; + + + +#if kPartialPathBeginsWithDelimiter + if (PathIsAbsolute(fname)) + { + if ( *basePath ) + fname++; +#else + if ( !PathIsAbsolute(fname) ) + { +#endif + // it's a partial path, expand it to include all + // previously traversed paths + ::strncpy( path, basePath, kMaxPickerPath-1 ); + ::strncat( path, fname, kMaxPickerPath-1 ); + + } + else + { + // it's an absolute reference. use the path + // part of this for the new basePath + ::strncpy( path, fname, kMaxPickerPath-1 ); + + } + + // path is now either an absolute or working directory + // referenced partial path to the playlist file. + int len = strlen(path); + char lastChar = path[len-1]; + if (lastChar == '\n' || lastChar == '\r' || lastChar == ' ') + path[len-1] = '\0'; + + // ldList is passed as NULL by the initial caller. recursive calls + // pass along the ldList we create hre + if ( ldList == NULL ) + ldList = new LoopDetectionList; + + Assert( ldList ); + + if ( !ldList ) + pickErr = kPickerPopulateNoMem; + + + if ( !pickErr ) + { + if ( ldList->ForEachUntil( CompareNameToElement, path ) ) + { + // we're already in the include chain, this is a loop + // print a warning (error?) and continue past the loop. + //qtss_printf("- Playlists include loop at file: %s\n", path ); + pickErr = kPickerPopulateLoopDetected; + } + } + + + + if ( !pickErr ) + { + ldElement = new LoopDetectionListElement( path ); + + Assert( ldElement ); + + if ( ldElement ) + { ldNode = new LoopDetectionNode( ldElement ); + Assert( ldNode ); + if ( !ldNode ) + pickErr = kPickerPopulateNoMem; + } + else + pickErr = kPickerPopulateNoMem; + } + + if (::IsDir(path)) + return ::PopulatePickerFromDir(picker, path); + + if ( !pickErr ) + { + weightings = ::fopen( path, "r" ); + + if (!weightings) + { + qtss_printf("- Playlist picker failed opening list file %s\n", path); + pickErr = kPickerPopulateFileError; + } + } + + if ( !pickErr ) + { + SInt32 lineBuffSize = (kMaxPickerPath *2) - 1; + SInt32 wordBuffSize = kMaxPickerPath - 1; + + char lineBuff[kMaxPickerPath * 2]; + char wordBuff[kMaxPickerPath]; + char* next; + char* pathEnd; + char* thisLine; + + // add ourselves to the list + ldList->AddNode( ldNode ); + + // trim off the file name to get just the path part + pathEnd = ::strrchr( path, kPathDelimiterChar ); + + if ( pathEnd ) + { + pathEnd++; + *pathEnd = 0; + } + else + *path = 0; + + thisLine = lineBuff; + + if ( ::fgets( lineBuff, lineBuffSize, weightings ) != NULL ) + { + lineCount++; + + thisLine = ::TrimLeft( lineBuff ); + + if ( 0 != ::strncmp(thisLine,"*PLAY-LIST*",11) ) + { + //qtss_printf("- Playlist file missing *PLAY-LIST* identifier as first line:\n"); + //qtss_printf(" %s%s\n", basePath, fname); + pickErr = kPickerPopulateBadFormat; + } + } + + + if ( !pickErr ) + { + do + { + next = lineBuff; + + if ( ::fgets( lineBuff, lineBuffSize, weightings ) == NULL ) + break; + +// qtss_printf("line = %s\n", lineBuff); + lineCount++; + + next = ::TrimLeft( lineBuff ); + + if ( *next == '#' ) + { + // it's a comment - just toss + + //if ( *next ) + // qtss_printf( "comment: %s" , &lineBuff[1] ); + + } + else if (*next == '+') // a list + { + next = ::TrimLeft( next+1 ); // skip past + include + + if ( *next == '"' ) // get the name from the next part of the buff + next = ::GetQuotedWord( wordBuff, next, wordBuffSize ); + else + next = ::GetWord( wordBuff, next, wordBuffSize ); + + + + // recusively populate from the include file. + pickErr = PopulatePickerFromFile( picker, wordBuff, path, ldList ); + + if ( pickErr ) + { + DisplayPickerErr( pickErr, "Playlist Include failed", fname, lineCount, lineBuff ); + pickErr = kPickerPopulateNoErr; + } + } + else if ( *next ) + { + char numBuff[32]; + char expandedFileName[kMaxPickerPath]; + int weight = 10; // default weight is 10 + + // get the movie file name + if ( *next == '"' ) + next = ::GetQuotedWord( wordBuff, next, wordBuffSize ); + else + next = ::GetWord( wordBuff, next, wordBuffSize ); + + if (*wordBuff) + { + #if kPartialPathBeginsWithDelimiter + if ( PathIsAbsolute(wordBuff) ) + { + char *wordStart = wordBuff; + if ( *path ) + wordStart++; + // full or partial path to the movie + ::strcpy( expandedFileName, path ); + ::strcat( expandedFileName, wordStart ); + } + #else + if ( !PathIsAbsolute(wordBuff) ) + { + // it's a partial path.. + + // cat the path and fname to form the + // full or partial path to the movie + ::strcpy( expandedFileName, path ); + ::strcat( expandedFileName, wordBuff ); + } + #endif + else + { // it's an absolute path.. + ::strcpy( expandedFileName, wordBuff ); + } + + // then get the weighting ( if supplied ) + next = ::GetWord( numBuff, next, 32 ); + + if ( *numBuff ) + weight = ::atoi(numBuff); + + // qtss_printf("expanded file name = %s\n", expandedFileName); + if (::IsDir(expandedFileName)) + pickErr = ::PopulatePickerFromDir(picker, expandedFileName, weight); + else if ( !picker->AddToList( expandedFileName, weight ) ) + pickErr = kPickerPopulateNoMem; + } + } + + } while ( feof( weightings ) == 0 && pickErr == kPickerPopulateNoErr ); + } + + // remove ourselves from the list + ldList->RemoveNode( ldNode ); + } + + // only report unreported errors. + if ( ldList && ldList->GetNumNodes() == 0 && pickErr ) + ::DisplayPickerErr( pickErr, "Playlist error", fname, lineCount, NULL ); + + + if ( ldNode ) + delete ldNode; // node deletes element + else if ( ldElement ) + delete ldElement; + + + if ( weightings ) + (void)::fclose( weightings ); + + if ( ldList && ldList->GetNumNodes() == 0 ) + { + // all done now! + delete ldList; + ldList = NULL; + + } + + return pickErr; +} + +int PopulatePickerFromDir( PlaylistPicker* picker, char* dirPath, int weight ) +{ + static char expandedFileName[kMaxPickerPath]; // static so we don't build up the stack frame on recursion + int pickErr = 0; + if (dirPath != NULL) + strcpy(expandedFileName, dirPath); + +#ifdef __Win32__ + WIN32_FIND_DATA findData; + HANDLE findResultHandle; + Bool16 keepSearching = true; + int len = strlen(expandedFileName); + if (expandedFileName[len - 1] != kPathDelimiterChar) + { + expandedFileName[len] = kPathDelimiterChar; + expandedFileName[len+1] = 0; + len++; + } + strcat(expandedFileName, "*"); + + findResultHandle = ::FindFirstFile( expandedFileName, &findData); + if ( NULL == findResultHandle || INVALID_HANDLE_VALUE == findResultHandle ) + { + //qtss_printf( "FindFirstFile( \"%s\" ): gle = %"_U32BITARG_"\n", searchPath, GetLastError() ); + return 0; + } + + while ( (pickErr == 0) && keepSearching ) + { + expandedFileName[len] = 0; // retruncate name + if (findData.cFileName[0] != '.') // ignore anything beginning with a "." + { + strcat(expandedFileName, findData.cFileName); + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + pickErr = PopulatePickerFromDir(picker, NULL, weight); + else if ( !picker->AddToList( expandedFileName, weight ) ) + pickErr = kPickerPopulateNoMem; + } + + keepSearching = FindNextFile( findResultHandle, &findData ); + } + +#else + DIR* dir; + struct dirent* entry; + int len = strlen(expandedFileName); + + if (expandedFileName[len - 1] != kPathDelimiterChar) + { + expandedFileName[len] = kPathDelimiterChar; + expandedFileName[len+1] = 0; + len++; + } + + dir = opendir(expandedFileName); + if (dir == NULL) + return kPickerPopulateFileError; + + do { + entry = readdir(dir); + if (entry == NULL) break; + + if (entry->d_name[0] == '.') // ignore anything beginning with a "." + continue; + + if (len + strlen(entry->d_name) < kMaxPickerPath) + { + strcat(expandedFileName, entry->d_name); + +#if __solaris__ || __sgi__ || __osf__ || __hpux__ + if (::IsDir(expandedFileName)) +#else + if ((entry->d_type & DT_DIR) != 0) +#endif + pickErr = PopulatePickerFromDir(picker, NULL, weight); + else if ( !picker->AddToList( expandedFileName, weight ) ) + pickErr = kPickerPopulateNoMem; + } + expandedFileName[len] = 0; // retruncate name + } while (pickErr == 0); + + //close the directory back up + (void)::closedir(dir); + +#endif + return pickErr; +} + + + diff --git a/PlaylistBroadcaster.tproj/PickerFromFile.h b/PlaylistBroadcaster.tproj/PickerFromFile.h new file mode 100644 index 0000000..5ba148a --- /dev/null +++ b/PlaylistBroadcaster.tproj/PickerFromFile.h @@ -0,0 +1,77 @@ +#ifndef __picker_from_file__ +#define __picker_from_file__ + +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "PlaylistPicker.h" +#include "PLDoubleLinkedList.h" +#include + +class LoopDetectionListElement { + + public: + LoopDetectionListElement( const char * name ) + { + mPathName = new char[ strlen(name) + 1 ]; + + Assert( mPathName ); + if( mPathName ) + ::strcpy( mPathName, name ); + + } + + virtual ~LoopDetectionListElement() + { + if ( mPathName ) + delete [] mPathName; + } + + char *mPathName; + +}; + + +typedef PLDoubleLinkedList LoopDetectionList; +typedef PLDoubleLinkedListNode LoopDetectionNode; + +enum PickerPopulationErrors { + + kPickerPopulateLoopDetected = 1000 + , kPickerPopulateBadFormat + , kPickerPopulateFileError + , kPickerPopulateNoMem + + , kPickerPopulateNoErr = 0 + +}; + +int PopulatePickerFromFile( PlaylistPicker* picker, char* fname, const char* basePath, LoopDetectionList *ldList ); +int PopulatePickerFromDir( PlaylistPicker* picker, char* dirPath, int weight = 10 ); + + +#endif diff --git a/PlaylistBroadcaster.tproj/PlaylistBroadcaster.cpp b/PlaylistBroadcaster.tproj/PlaylistBroadcaster.cpp new file mode 100644 index 0000000..14ea478 --- /dev/null +++ b/PlaylistBroadcaster.tproj/PlaylistBroadcaster.cpp @@ -0,0 +1,2235 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + 8.11.99 rt - changed references to "PlaylistBroadcaster Setup" to Broadcast Description File + + 8.4.99 rt - changed references to "PlaylistBroadcaster Description" to Broadcast Setup File + - addded error messages + - prefilght config file access + - require log file creation + + + 7.27.99 rt - removed license from about display + - updated credit names + - fixed mapping of --stop to 's' from 'l' + - added about to help + + + 8.2.99 rt - changed reference to "channel setup" to "PlaylistBroadcaster Description" + - changed &d's in qtss_printf's to %d + +*/ + + + +#include +#include +#include "SafeStdLib.h" +#include +#ifndef __MacOSX__ +#include "getopt.h" +#endif + +#ifndef __Win32__ + #if defined (__solaris__) || defined (__osf__) || defined (__sgi__) || defined (__hpux__) + #include "daemon.h" + #else + #ifndef __FreeBSD__ + #include + #endif + #endif +#endif + +#ifndef kVersionString +#include "../revision.h" +#endif + +#include +#include "QTRTPFile.h" + +#include "OSHeaders.h" +#include "OS.h" +#include "OSMemory.h" +#include "SocketUtils.h" +#include "Socket.h" +#include "Task.h" +#include "TimeoutTask.h" +#include "BroadcasterSession.h" +#include "OSArrayObjectDeleter.h" +#include "PickerFromFile.h" +#include "PLBroadcastDef.h" +#include "BroadcastLog.h" +#include "StringTranslator.h" + +#ifndef __Win32__ + #include "BCasterTracker.h" +#endif + +#ifdef __Win32__ + #include "getopt.h" +#endif + +// must now inlcude this from the project level using the -i switch in the compiler +#ifndef __MacOSX__ + #include "../revision.h" +#endif + +#ifdef __MacOSX__ + #include +#endif + +#include "playlist_SDPGen.h" +#include "playlist_broadcaster.h" + + +#include "MyAssert.h" + +/* + local functions +*/ + +static void usage(); +static void version(); +static void help(); +static PlaylistPicker* MakePickerFromConfig( PLBroadcastDef* broadcastParms ); +static void SignalEventHandler( int signalID ); +static int EvalBroadcasterErr(int err); +static const char* GetMovieFileErrString( int err ); + +static void RegisterEventHandlers(); +static void ShowPlaylistStatus(); +static void StopABroadcast( const char* arg ); +static bool DoSDPGen( PLBroadcastDef *broadcastParms, bool preflight, bool overWriteSDP, bool generateNewSDP, int* numErrorsPtr, char* refMovie); + +static bool AddOurPIDToTracker( const char* bcastSetupFilePath ); + +static void Cleanup(); + +/* -- preflighting --*/ + +static int MyAccess( const char* path, int mode ); +static int PreflightSDPFileAccess( const char * sdpPath ); +static int PreflightDestinationAddress( const char *destAddress ); +static int PreflightBasePort( const char *basePort ); +static int PreflightReferenceMovieAccess( const char *refMoviePath ); +static bool PreflightTrackerFileAccess( int mode ); +static bool PreFlightSetupFile( const char * bcastSetupFilePath ); +static int PreflightClientBufferDelay( const char * delay, Float32 *ioDelay); +static void ShowSetupParams(PLBroadcastDef* broadcastParms, const char *file); +static int PreflightDestSDPFileAccess( const char * sdpPath ); +static void PreFlightOrBroadcast( const char *bcastSetupFilePath, bool preflight, bool daemonize, bool showMovieList, bool writeCurrentMovieFile, char *destinationIP,bool writeNewSDP, const char* errorFilePath); + +/* changed by emil@popwire.com */ +static bool FileCreateAndCheckAccess(char *theFileName); +static void PrintPlaylistElement(PLDoubleLinkedListNode *node,void *file); +static void ShowPlaylistElements(PlaylistPicker *picker,FILE *file); +static void RemoveFiles(PLBroadcastDef* broadcastParms); +/* ***************************************************** */ + +/* + local variables +*/ +static char* sgProgramName; // the actual program name as input at commmand line +static char* sgTrackerDirPath = "/var/run"; +static char* sgTrackerFilePath = "/var/run/broadcastlist"; +static BroadcastLog* sgLogger = NULL; +static bool sgTrackingSucceeded = false; +static BroadcasterSession* sBroadcasterSession = NULL; +static StrPtrLen sSetupDirPath; +static PlaylistPicker* sTempPicker = NULL; +static SInt32 sElementCount = 0; +static SInt32 sMaxUpcomingListSize = 5; +static PLBroadcastDef* sBroadcastParms = NULL; + +enum {maxSDPbuffSize = 10000}; +static char sSDPBuffer[maxSDPbuffSize] = {0}; +static bool sQuitImmediate = false; +static bool sAnnounceBroadcast = false; +static bool sPushOverTCP = false; +static int sRTSPClientError = 0; +static bool sErrorEvaluted = false; +static bool sDeepDebug = false; + +static int sNumWarnings = 0; +static int sNumErrors = 0; +static bool sPreflight = false; +static bool sCleanupDone = false; +static bool sBurst = false; + +//+ main is getting too big.. need to clean up and separate the command actions +// into a separate file. + + +int main(int argc, char *argv[]) { + + char *bcastSetupFilePath = NULL; + bool daemonize = true; + int anOption = 0; + bool preflight = false; + bool showMovieList = false; + bool writeCurrentMovieFile = false; + int displayedOptions = 0; // count number of command line options we displayed + bool needsTracker = false; // set to true when PLB needs tracker access + bool needsLogfile = false; // set to true when PLB needs tracker access + char* destinationIP = NULL; + bool writeNewSDP = false; + char* errorlog = NULL; + extern char* optarg; + extern int optind; + +#ifdef __Win32__ + // + // Start Win32 DLLs + WORD wsVersion = MAKEWORD(1, 1); + WSADATA wsData; + (void)::WSAStartup(wsVersion, &wsData); +#endif + + + QTRTPFile::Initialize(); // do only once + 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 PlaylistBroadcaster\n"); + ::exit(0); + } + +#ifdef __MacOSX__ + (void) ::umask(S_IWOTH); // make sure files are opened with default of owner -rw-rw-r- +#endif + + + sgProgramName = argv[0]; +#ifdef __Win32__ + while ((anOption = getopt(argc, argv, "vhdcpbDtai:fe:" )) != EOF) +#else + while ((anOption = getopt(argc, argv, "vhdcpbDls:tai:fe:" )) != EOF) +#endif + { + + switch(anOption) + { + case 'b': + sBurst = true; + break; + + case 'v': + ::version(); + ::usage(); + return 0; + + case 'h': + ::help(); + displayedOptions++; + break; + + case 'd': + daemonize = false; + break; + + case 'c': + writeCurrentMovieFile = true; + break; + +/* + case 'm': + showMovieList = true; + daemonize = false; + break; +*/ + + case 'p': + preflight = true; + daemonize = false; + needsTracker = true; + needsLogfile = true; + break; + + case 'D': + sDeepDebug = true; + daemonize = false; + break; + +#ifndef __Win32__ + + + case 'l': + // show playlist broadcast status + if (!PreflightTrackerFileAccess( R_OK )) // check for read access. + ::exit(-1); + ShowPlaylistStatus(); // <- exits() on failure + displayedOptions++; + break; + + case 's': + // stop a playlist broadcast + // is there one to kill? + if (!PreflightTrackerFileAccess( R_OK | W_OK )) // check for read access. + ::exit(-1); + StopABroadcast( optarg ); // <- exits() on failure + displayedOptions++; + break; +#endif + + case 't': + sAnnounceBroadcast = true; + sPushOverTCP = true; + break; + + case 'a': + sAnnounceBroadcast = true; + break; + + case 'i': + destinationIP = (char*)malloc(strlen(optarg)+1); + strcpy(destinationIP, optarg); + qtss_printf("destinationIP =%s\n",destinationIP); + break; + + case 'f': + writeNewSDP = true; + break; + + case 'e': + errorlog = (char*)malloc(strlen(optarg)+1); + strcpy(errorlog, optarg); + break; + + default: + ::usage(); + ::exit(-1); + } + } + + if (argv[optind] != NULL) + { + bcastSetupFilePath = (char*)malloc(strlen(argv[optind])+1); + strcpy(bcastSetupFilePath, argv[optind]); + } + + sPreflight = preflight; + + if (errorlog != NULL) + { + if (preflight) + freopen(errorlog, "w", stdout); + else + freopen(errorlog, "a", stdout); + ::setvbuf(stdout, (char *)NULL, _IONBF, 0); + } + + // preflight needs a description file + if ( preflight && !bcastSetupFilePath ) + { qtss_printf("- PlaylistBroadcaster: Error. \"Preflight\" requires a broadcast description file.\n" ); + ::usage(); + ::exit(-1); + } + + // don't complain about lack of description File if we were asked to display some options. + if ( displayedOptions > 0 && !bcastSetupFilePath ) + ::exit(0); // <-- we displayed option info, but have no description file to broadcast + + PreFlightOrBroadcast( bcastSetupFilePath, preflight, daemonize, showMovieList, writeCurrentMovieFile,destinationIP,writeNewSDP, errorlog ); // <- exits() on failure -- NOT ANYMORE + + return 0; + +} + +BroadcasterSession *StartBroadcastRTSPSession(PLBroadcastDef *broadcastParms) +{ + TaskThreadPool::AddThreads(1); + TimeoutTask::Initialize(); + Socket::StartThread(); + + UInt32 inAddr = SocketUtils::ConvertStringToAddr(broadcastParms->mDestAddress); + + UInt16 inPort = atoi(broadcastParms->mRTSPPort); + char * theURL = new char[512]; + StringTranslator::EncodeURL(broadcastParms->mDestSDPFile, strlen(broadcastParms->mDestSDPFile) + 1, theURL, 512); + BroadcasterSession::BroadcasterType inBroadcasterType; + + if (sPushOverTCP) + inBroadcasterType = BroadcasterSession::kRTSPTCPBroadcasterType; + else + inBroadcasterType = BroadcasterSession::kRTSPUDPBroadcasterType; + + UInt32 inDurationInSec = 0; + UInt32 inStartPlayTimeInSec = 0; + UInt32 inRTCPIntervalInSec = 5; + UInt32 inOptionsIntervalInSec = 0; + UInt32 inHTTPCookie = 1; + Bool16 inAppendJunkData = false; + UInt32 inReadInterval = 50; + UInt32 inSockRcvBufSize = 32768; + StrPtrLen sdpSPL(sSDPBuffer,maxSDPbuffSize); + sRTSPClientError = QTFileBroadcaster::eNetworkConnectionError; + sBroadcasterSession = NEW BroadcasterSession(inAddr, + inPort, + theURL, + inBroadcasterType, // Client type + inDurationInSec, // Movie length + inStartPlayTimeInSec, // Movie start time + inRTCPIntervalInSec, // RTCP interval + inOptionsIntervalInSec, // Options interval + inHTTPCookie, // HTTP cookie + inAppendJunkData, + inReadInterval, // Interval between data reads + inSockRcvBufSize, + &sdpSPL, + broadcastParms->mName, + broadcastParms->mPassword, + sDeepDebug, + sBurst); + + return sBroadcasterSession; +} + +Bool16 AnnounceBroadcast(PLBroadcastDef *broadcastParms,QTFileBroadcaster *fileBroadcasterPtr) +{ // return true if no Announce required or broadcast is ok. + + if (!sAnnounceBroadcast) return true; + + // if the address is a multicast address then we can't announce the broadcast. + + if(SocketUtils::IsMulticastIPAddr(ntohl(inet_addr(broadcastParms->mDestAddress)))) { + sAnnounceBroadcast = false; + return true; + } + +#if !MACOSXEVENTQUEUE + ::select_startevents();//initialize the select() implementation of the event queue +#endif + + if (SocketUtils::GetNumIPAddrs() == 0) + { + qtss_printf("IP must be enabled to run PlaylistBroadcaster\n"); + //::exit(0); // why exit here? If we return false here, the calling function will take care of the error + return false; + } + + broadcastParms->mTheSession = sBroadcasterSession = StartBroadcastRTSPSession(broadcastParms); + while (!sBroadcasterSession->IsDone() && BroadcasterSession::kBroadcasting != sBroadcasterSession->GetState()) + { OSThread::Sleep(100); + } + sRTSPClientError = 0; + int broadcastErr = sBroadcasterSession->GetRequestStatus(); + + Bool16 isOK = false; + if (broadcastErr != 200) do + { + if (412 == broadcastErr) + { + ::EvalBroadcasterErr(broadcastErr); + break; + } + + if (200 != broadcastErr) + { //qtss_printf("broadcastErr = %"_S32BITARG_" sBroadcasterSession->GetDeathState()=%"_S32BITARG_" sBroadcasterSession->GetReasonForDying()=%"_S32BITARG_"\n",broadcastErr,sBroadcasterSession->GetDeathState(),sBroadcasterSession->GetReasonForDying()); + if (sBroadcasterSession->GetDeathState() == BroadcasterSession::kSendingAnnounce && sBroadcasterSession->GetReasonForDying() == BroadcasterSession::kConnectionFailed) + ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionError); + else if (sBroadcasterSession->GetDeathState() == BroadcasterSession::kSendingAnnounce && sBroadcasterSession->GetReasonForDying() == BroadcasterSession::kBadSDP) + ::EvalBroadcasterErr(QTFileBroadcaster::eSDPFileInvalid); + else if (401 == broadcastErr) + ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkAuthorization); + else if ((500 == broadcastErr) || (400 == broadcastErr) ) + ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkNotSupported); + else + ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkRequestError); + + break; + } + + if (sBroadcasterSession != NULL && BroadcasterSession::kDiedNormally != sBroadcasterSession->GetReasonForDying()) + { ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkRequestError); + break; + } + + isOK = true; + + } while (false); + else + isOK = true; + + if (isOK) + { + broadcastErr = fileBroadcasterPtr->SetUp(broadcastParms, &sQuitImmediate); + if ( broadcastErr ) + { ::EvalBroadcasterErr(broadcastErr); + qtss_printf( "- Broadcaster setup failed.\n" ); + isOK = false; + if (sBroadcasterSession != NULL && !sBroadcasterSession->IsDone()) + sBroadcasterSession->TearDownNow(); + } + } + + return isOK; +} + +char* GetBroadcastDirPath(const char * setupFilePath) +{ + int len = 2; + + if (setupFilePath != NULL) + len = ::strlen(setupFilePath); + + char *setupDirPath = new char [ len + 1 ]; + + if ( setupDirPath ) + { + strcpy( setupDirPath, setupFilePath ); + char* endOfDirPath = strrchr( setupDirPath, kPathDelimiterChar ); + + if ( endOfDirPath ) + { + endOfDirPath++; + *endOfDirPath= 0; + + int chDirErr = ::chdir( setupDirPath ); + if ( chDirErr ) + chDirErr = errno; + + //qtss_printf("- PLB DEBUG MSG: setupDirPath==%s\n chdir err: %i\n", setupDirPath, chDirErr); + } + else + { + setupDirPath[0] = '.'; + setupDirPath[1] = kPathDelimiterChar; + setupDirPath[2] = 0; + } + + } + + //qtss_printf("GetBroadcastDirPath setupDirPath = %s\n",setupDirPath); + return setupDirPath; +} + +void CreateCurrentAndUpcomingFiles(PLBroadcastDef* broadcastParms) +{ + if (!::strcmp(broadcastParms->mShowCurrent, "enabled")) + { if(FileCreateAndCheckAccess(broadcastParms->mCurrentFile)) + { /* error */ + sgLogger->LogInfo( "PlaylistBroadcaster Error: Failed to create current broadcast file" ); + } + } + + + if (!::strcmp(broadcastParms->mShowUpcoming, "enabled")) + { if(FileCreateAndCheckAccess(broadcastParms->mUpcomingFile)) + { /* error */ + sgLogger->LogInfo( "PlaylistBroadcaster Error: Failed to create upcoming broadcast file" ); + } + } + +} + +void UpdatePlaylistFiles(PLBroadcastDef* broadcastParms, PlaylistPicker *picker,PlaylistPicker *insertPicker) +{ + if ( (NULL == broadcastParms) + || (NULL == picker) + || (NULL == insertPicker) + ) return; + + if(!access(broadcastParms->mStopFile, R_OK)) + { + picker->CleanList(); + PopulatePickerFromFile(picker, broadcastParms->mStopFile, "", NULL); + + sTempPicker->CleanList(); + + remove(broadcastParms->mStopFile); + picker->mStopFlag = true; + } + + /* if .replacelist file exists - replace current playlist */ + if(!access(broadcastParms->mReplaceFile, R_OK)) + { + picker->CleanList(); + PopulatePickerFromFile(picker, broadcastParms->mReplaceFile, "", NULL); + + sTempPicker->CleanList(); + + remove(broadcastParms->mReplaceFile); + picker->mStopFlag = false; + } + + /* if .insertlist file exists - insert into current playlist */ + if(!access(broadcastParms->mInsertFile, R_OK)) + { + insertPicker->CleanList(); + sTempPicker->CleanList(); + + PopulatePickerFromFile(insertPicker, broadcastParms->mInsertFile, "", NULL); + remove(broadcastParms->mInsertFile); + picker->mStopFlag = false; + } + + + // write upcoming playlist to .upcoming file + if (!::strcmp(broadcastParms->mShowUpcoming, "enabled")) + { + FILE *upcomingFile = fopen(broadcastParms->mUpcomingFile, "w"); + if(upcomingFile) + { + sElementCount = 0; + + if (!::strcmp(broadcastParms->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(broadcastParms->mPlayMode, "sequential") + ) + { picker->CleanList(); + PopulatePickerFromFile(picker,broadcastParms->mPlayListFile,"",NULL); + ShowPlaylistElements(picker,upcomingFile); + sTempPicker->CleanList(); + PopulatePickerFromFile(sTempPicker,broadcastParms->mPlayListFile,"",NULL); + } + + if ( sElementCount <= sMaxUpcomingListSize + && 0 == ::strcmp(broadcastParms->mPlayMode, "sequential_looped") + ) + { if (sTempPicker->GetNumMovies() == 0) + { sTempPicker->CleanList(); + PopulatePickerFromFile(sTempPicker,broadcastParms->mPlayListFile,"",NULL); + } + //sElementCount can be zero if the playlist contains no paths to valid files + while ( (sElementCount != 0) && sElementCount <= sMaxUpcomingListSize ) + ShowPlaylistElements(sTempPicker,upcomingFile); + } + } + fclose(upcomingFile); + } + } + else + { + if ( picker->GetNumMovies() == 0 + && !picker->mStopFlag + && ::strcmp(broadcastParms->mPlayMode, "sequential") + ) + { picker->CleanList(); + PopulatePickerFromFile(picker,broadcastParms->mPlayListFile,"",NULL); + } + } + +} + + +void UpdateCurrentFile(PLBroadcastDef* broadcastParms, char *thePick) +{ + if ( (NULL == broadcastParms) || (NULL == thePick) ) + return; + + // save currently playing song to .current file + if (!::strcmp(broadcastParms->mShowCurrent, "enabled")) + { FILE *currentFile = fopen(broadcastParms->mCurrentFile, "w"); + if(currentFile) + { + if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0]) + qtss_fprintf(currentFile,"u=%s\n",thePick); + else + qtss_fprintf(currentFile,"u=%s%s\n", sSetupDirPath.Ptr,thePick); + + fclose(currentFile); + } + } + + + +} + + +static void PreFlightOrBroadcast( const char *bcastSetupFilePath, bool preflight, bool daemonize, bool showMovieList, bool writeCurrentMovieFile, char *destinationIP,bool writeNewSDP, const char* errorFilePath) +{ + PLBroadcastDef* broadcastParms = NULL; + PlaylistPicker* picker = NULL; + PlaylistPicker* insertPicker = NULL; + + QTFileBroadcaster fileBroadcaster; + int broadcastErr = 0; + SInt32 moviePlayCount; + char* thePick = NULL; + int numMovieErrors; + bool didAtLeastOneMoviePlay = false; + bool sdpFileCreated = false; + char *allocatedIPStr = NULL; + bool generateNewSDP = false; + int numErrorsBeforeSDPGen = 0; + char theUserAgentStr[128]; + char* thePLBStr = "PlaylistBroadcaster"; + + RegisterEventHandlers(); + + if ( !PreFlightSetupFile( bcastSetupFilePath ) ) // returns true on success and false on failure + { + sNumErrors++; + goto bail; + } + + if (destinationIP != NULL) + { + allocatedIPStr = new char[strlen(destinationIP)+1]; + strcpy(allocatedIPStr,destinationIP); + generateNewSDP = true; + } + + broadcastParms = new PLBroadcastDef( bcastSetupFilePath, allocatedIPStr, sDeepDebug); + sBroadcastParms = broadcastParms; + + if( !broadcastParms ) + { + qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" ); + sNumErrors++; + goto bail; + } + + + if ( !broadcastParms->ParamsAreValid() ) + { + qtss_printf("- PlaylistBroadcaster: Error reading the broadcast description file \"%s\". (bad format or missing file)\n", bcastSetupFilePath ); + broadcastParms->ShowErrorParams(); + sNumErrors++; + goto bail; + } + + + if ( preflight || sDeepDebug) + ShowSetupParams(broadcastParms, bcastSetupFilePath); + + if (NULL == GetBroadcastDirPath(bcastSetupFilePath)) + { + qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" ); + sNumErrors++; + goto bail; + } + sSetupDirPath.Set(GetBroadcastDirPath(bcastSetupFilePath), strlen(GetBroadcastDirPath(bcastSetupFilePath))); + + if (preflight) + { + picker = new PlaylistPicker(false); // make sequential picker, no looping + } + else + { + picker = MakePickerFromConfig( broadcastParms ); // make picker according to parms + sTempPicker = new PlaylistPicker(false); + insertPicker = new PlaylistPicker(false); + insertPicker->mRemoveFlag = true; + } + + if ( !picker ) + { + qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" ); + sNumErrors++; + goto bail; + } + + if ( preflight || sDeepDebug) + qtss_printf("\n"); + + Assert( broadcastParms->mPlayListFile ); + if ( broadcastParms->mPlayListFile ) + { + char* fileName; + + fileName = broadcastParms->mPlayListFile; + // initial call uses empty string for path, NULL for loop detection list + (void)PopulatePickerFromFile( picker, fileName, "", NULL ); + + // ignore errors, if we have movies in the list, play them + } + + if ( preflight ) + { + if ( picker->mNumToPickFrom == 1 ) + qtss_printf( "\nThere is (%li) movie in the Playlist.\n\n", (SInt32) picker->mNumToPickFrom ); + else + qtss_printf( "\nThere are (%li) movies in the Playlist.\n\n", (SInt32) picker->mNumToPickFrom ); + } + + if ( !picker->mNumToPickFrom ) + { + qtss_printf( "- PlaylistBroadcaster setup failed: There are no movies to play.\n" ); + sNumErrors++; + goto bail; + } + + + // check that we have enough movies to cover the recent movies list. + if ( preflight && broadcastParms->ParamsAreValid() ) + { + if ( !strcmp( broadcastParms->mPlayMode, "weighted_random" ) ) // this implies "random" play + { + if ( broadcastParms->mLimitPlayQueueLength >= picker->mNumToPickFrom ) + { + broadcastParms->mLimitPlayQueueLength = 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" ); + } + } + } + + // create the log file + sgLogger = new BroadcastLog( broadcastParms, &sSetupDirPath ); + + + Assert( sgLogger != NULL ); + + if( sgLogger == NULL ) + { + qtss_printf("- PlaylistBroadcaster: Error. Out of memory.\n" ); + sNumErrors++; + goto bail; + } + + numErrorsBeforeSDPGen = sNumErrors; + sdpFileCreated = DoSDPGen( broadcastParms, preflight, writeNewSDP,generateNewSDP, &sNumErrors, picker->GetFirstFile()); + if( sNumErrors > numErrorsBeforeSDPGen ) + goto bail; + + if (!sAnnounceBroadcast) + broadcastErr = fileBroadcaster.SetUp(broadcastParms, &sQuitImmediate); + + if ( broadcastErr ) + { + ::EvalBroadcasterErr(broadcastErr); + qtss_printf( "- Broadcaster setup failed.\n" ); + sNumErrors++; + goto bail; + } + + if (preflight) + { + fileBroadcaster.fPlay = false; + } + if ( !PreflightTrackerFileAccess( R_OK | W_OK ) ) + { + sNumErrors++; + goto bail; + } + + //Unless the Debug command line option is set, daemonize the process at this point + if (daemonize) + { +#ifndef __Win32__ + qtss_printf("- PlaylistBroadcaster: Started in background.\n"); + + // keep the same working directory.. + if (::daemon( 1, 0 ) != 0) + { + qtss_printf("- PlaylistBroadcaster: System error (%i).\n", errno); + goto bail; + } + +#endif //__Win32__ + } + + // If daemonize, then reopen stdout to the error file + if (daemonize && (errorFilePath != NULL)) + { + freopen(errorFilePath, "a", stdout); + ::setvbuf(stdout, (char *)NULL, _IONBF, 0); + } + + +#ifndef __Win32__ + qtss_snprintf(theUserAgentStr, ::strlen(thePLBStr) + 1 + ::strlen(kVersionString) + 1, "%s/%s", thePLBStr, kVersionString); +#else + _snprintf(theUserAgentStr, ::strlen(thePLBStr) + 1 + ::strlen(kVersionString) + 1, "%s/%s", thePLBStr, kVersionString); +#endif + RTSPClient::SetUserAgentStr(theUserAgentStr); + + if (!preflight && !AnnounceBroadcast(broadcastParms,&fileBroadcaster)) + { + sNumErrors++; + goto bail; + } + + // ^ 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 (( sgLogger != NULL ) && ( sgLogger->WantsLogging() )) + sgLogger->EnableLog( false ); // don't append ".log" to name for PLB + + if ( sgLogger->WantsLogging() && !sgLogger->IsLogEnabled() ) + { + if ( sgLogger->LogDirName() && *sgLogger->LogDirName() ) + qtss_printf("- PlaylistBroadcaster: The log file failed to open.\n ( path: %s/%s )\n Exiting.\n", sgLogger->LogDirName(), sgLogger->LogFileName() ); + else + qtss_printf("- PlaylistBroadcaster: The log file failed to open.\n ( path: %s )\n Exiting.\n", sgLogger->LogFileName() ); + + sNumErrors++; + goto bail; + } + + + if (broadcastParms->mPIDFile != NULL) + { + if(!FileCreateAndCheckAccess(broadcastParms->mPIDFile)) + { + FILE *pidFile = fopen(broadcastParms->mPIDFile, "w"); + if(pidFile) + { + qtss_fprintf(pidFile,"%d\n",getpid()); + fclose(pidFile); + } + } + } + else if ( !AddOurPIDToTracker( bcastSetupFilePath ) ) // <-- doesn't exit on failure anymore - returns false if failed + { + // writes to the broadcast list file only if the pid_file config param doesn't exist + sNumErrors++; + goto bail; + } + + if ( !preflight ) + sgLogger->LogInfo( "PlaylistBroadcaster started." ); + else + sgLogger->LogInfo( "PlaylistBroadcaster preflight started." ); + + //+ make the RTP movie broadcaster + + if ( !preflight ) + { + qtss_printf( "\n" ); + qtss_printf( "[pick#] movie path\n" ); + qtss_printf( "----------------------------\n" ); + } + else + { + qtss_printf( "\n" ); + qtss_printf( "Problems found\n" ); + qtss_printf( "--------------\n" ); + } + + if(!preflight) + CreateCurrentAndUpcomingFiles(broadcastParms); + + moviePlayCount = 0; + numMovieErrors = 0; + didAtLeastOneMoviePlay = false; + sMaxUpcomingListSize = ::atoi( broadcastParms->mMaxUpcomingMovieListSize ); + + while (true) + { + + if (!showMovieList && !preflight) + { + UpdatePlaylistFiles(broadcastParms, 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] ", moviePlayCount ); + { + if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0]) + qtss_printf("%s picked\n", thePick); + else + qtss_printf("%s%s picked\n", sSetupDirPath.Ptr,thePick); + } + + } + + + if ( !showMovieList ) + { + int playError; + + if(!preflight) + { UpdateCurrentFile(broadcastParms, thePick); + + /* if playlist is about to run out repopulate it */ + if ( !::strcmp(broadcastParms->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.PlayMovie( thePick, broadcastParms->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"); + sgLogger->LogInfo( "Quitting: Playlist contains no valid files.\n" ); + goto bail; + } + else + { + didAtLeastOneMoviePlay = false; + } + } + + // log the result of broacasting the picked movie + sgLogger->LogMediaData( thePick, + fileBroadcaster.fCurrentMovieName, + fileBroadcaster.fCurrentMovieCopyright, + fileBroadcaster.fCurrentMovieComment, + fileBroadcaster.fCurrentMovieAuthor, + fileBroadcaster.fCurrentMovieArtist, + fileBroadcaster.fCurrentMovieAlbum, + (UInt32) ((endTime - startTime)/1000L), + playError); + } + else + { + playError = fileBroadcaster.PlayMovie( thePick, NULL ); + } + + if (sQuitImmediate) + { + break; + } + + if (sBroadcasterSession != NULL && sBroadcasterSession->GetReasonForDying() != BroadcasterSession::kDiedNormally) + { + playError = ::EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionFailed); + sNumErrors++; + goto bail; + } + + if ( playError == 0 && sAnnounceBroadcast) + { + int theErr = sBroadcasterSession->GetRequestStatus(); + if (200 != theErr) + { + if (401 == theErr) + playError = QTFileBroadcaster::eNetworkAuthorization; + else if (500 == theErr) + playError = QTFileBroadcaster::eNetworkNotSupported; + else + playError = QTFileBroadcaster::eNetworkConnectionError; + + sNumErrors++; + goto bail; + } + } + + if (playError) + { + playError = ::EvalBroadcasterErr(playError); + + if (playError == QTFileBroadcaster::eNetworkConnectionError) + goto bail; + + qtss_printf(" (file: %s err: %d %s)\n", thePick, playError,GetMovieFileErrString( playError ) ); + sNumWarnings++; + numMovieErrors++; + } + else + { + int tracks = fileBroadcaster.GetMovieTrackCount() ; + int mtracks = fileBroadcaster.GetMappedMovieTrackCount(); + + if (tracks != mtracks) + { + sNumWarnings++; + numMovieErrors++; + qtss_printf("- PlaylistBroadcaster: Warning, movie tracks do not match the SDP file.\n" ); + qtss_printf(" Movie: %s .\n", thePick ); + qtss_printf(" %i of %i hinted tracks will not broadcast.\n", tracks- mtracks, tracks ); + } + } + + if ( !preflight && (playError != 0) ) + sgLogger->LogMediaError( thePick, GetMovieFileErrString( playError ),NULL ); + } + + + delete [] thePick; + thePick = NULL; + + } //while (true) + + remove(broadcastParms->mCurrentFile); + remove(broadcastParms->mUpcomingFile); + + if ( preflight ) + { + + + char str[256]; + qtss_printf( " - " ); + if (numMovieErrors == 1) + strcpy(str, "PlaylistBroadcaster found one problem movie file."); + else + qtss_sprintf( str, "PlaylistBroadcaster found %d problem movie files." , numMovieErrors ); + qtss_printf( "%s\n", str ); + if (sgLogger != NULL) + sgLogger->LogInfo( str ); + + if (numMovieErrors == moviePlayCount) + { + qtss_printf("There are no valid movies to play\n"); + sNumErrors++; + } + } + + + if (NULL != sBroadcasterSession && !sBroadcasterSession->IsDone() ) + { sErrorEvaluted = true; + } + +bail: + + Cleanup(); + + delete picker; + + sBroadcastParms = NULL; + delete broadcastParms; + + if ( !preflight ) + { + if (sgLogger != NULL) + { if (!sQuitImmediate) + sgLogger->LogInfo( "PlaylistBroadcaster finished." ); + else + sgLogger->LogInfo( "PlaylistBroadcaster stopped." ); + } + + if (!sQuitImmediate) // test sQuitImmediate again + qtss_printf( "\nPlaylistBroadcaster broadcast finished.\n" ); + else + qtss_printf( "\nPlaylistBroadcaster broadcast stopped.\n" ); + } + else + { + if (sgLogger != NULL) + { if (!sQuitImmediate) + sgLogger->LogInfo( "PlaylistBroadcaster preflight finished." ); + else + sgLogger->LogInfo( "PlaylistBroadcaster preflight stopped." ); + } + + if (!sQuitImmediate) // test sQuitImmediate again + qtss_printf( "\nPlaylistBroadcaster preflight finished.\n" ); + else + qtss_printf( "\nPlaylistBroadcaster preflight stopped.\n" ); + + } + sgLogger = NULL; // protect the interrupt handler and just let it die don't delete because it is a task thread + +#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 //__Win32__ + if (sBroadcasterSession != NULL && !sBroadcasterSession->IsDone()) + { + //qtss_printf("QUIT now sBroadcasterSession->TearDownNow();\n"); + sBroadcasterSession->TearDownNow(); + int count=0; + while (count++ < 30 && !sBroadcasterSession->IsDone() ) + { sBroadcasterSession->Run(); + OSThread::Sleep(100); + } + sBroadcasterSession = NULL; + } + +} + +static void Cleanup() +{ + if (sCleanupDone == true) + return; + + sCleanupDone = true; + + if (sPreflight) + { + qtss_printf("Warnings: %d\n", sNumWarnings); + qtss_printf("Errors: %d\n", sNumErrors); + } + else + { + qtss_printf("Broadcast Warnings: %d\n", sNumWarnings); + qtss_printf("Broadcast Errors: %d\n", sNumErrors); + } + + RemoveFiles(sBroadcastParms); + +} + +static 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("PlaylistBroadcaster/%s Built on: %s, %s\n", kVersionString, __DATE__, __TIME__ ); + +} + +static void usage() +{ + /* + print PlaylistBroadcaster usage string + + */ +#ifndef __Win32__ + qtss_printf("usage: PlaylistBroadcaster [-v] [-h] [-p] [-c] [-a] [-t] [-i destAddress] [-e filename] [-f] [-d] [-l] [-s broadcastNum] filename\n" ); +#else + qtss_printf("usage: PlaylistBroadcaster [-v] [-h] [-p] [-c] [-a] [-t] [-i destAddress] [-e filename] [-f] filename\n" ); +#endif + + qtss_printf(" -v: Display version\n" ); + qtss_printf(" -h: Display help\n" ); + qtss_printf(" -p: Verify a broadcast description file and movie list.\n" ); + qtss_printf(" -c: Show the current movie in the log file.\n" ); + qtss_printf(" -a: Announce the broadcast to the server.\n" ); + qtss_printf(" -t: Send the broadcast over TCP to the server.\n" ); + qtss_printf(" -i: Specify the destination ip address. Over-rides config file value.\n" ); + qtss_printf(" -e: Log errors to filename.\n" ); + qtss_printf(" -f: Force a new SDP file to be created even if one already exists.\n" ); +#ifndef __Win32__ + qtss_printf(" -d: Run attached to the terminal.\n" ); + qtss_printf(" -l: List running currently broadcasts.\n" ); + qtss_printf(" -s: Stop a running broadcast.\n" ); +#endif + qtss_printf(" filename: Broadcast description filename.\n" ); + + +} + + + +static void help() +{ + /* + print PlaylistBroadcaster help info + + */ + ::version(); + + ::usage(); + + + qtss_printf("\n\nSample broadcast description file: "); + PLBroadcastDef(NULL,NULL,false); + +} + + + +static PlaylistPicker* MakePickerFromConfig( PLBroadcastDef* broadcastParms ) +{ + // construct a PlaylistPicker object using options set from a PLBroadcastDef + + PlaylistPicker *picker = NULL; + + if ( broadcastParms && broadcastParms->ParamsAreValid() ) + { + if ( broadcastParms->mPlayMode ) + { + if ( !::strcmp( broadcastParms->mPlayMode, "weighted_random" ) ) + { + int noPlayQueueLen = 0; + + noPlayQueueLen = broadcastParms->mLimitPlayQueueLength; + + picker = new PlaylistPicker( 10, noPlayQueueLen ); + + } + else if ( !::strcmp( broadcastParms->mPlayMode, "sequential_looped" ) ) + { + picker = new PlaylistPicker(true); + picker->mRemoveFlag = true; + } + else if ( !::strcmp( broadcastParms->mPlayMode, "sequential" ) ) + { + picker = new PlaylistPicker(false); + picker->mRemoveFlag = true; + } + + } + } + + return picker; +} + +static const char* GetMovieFileErrString( int err ) +{ + static char buff[80]; + + switch ( err ) + { + case 0: + break; + + case QTFileBroadcaster::eMovieFileNotFound: + return "Movie file not found."; + + case QTFileBroadcaster::eMovieFileNoHintedTracks: + return "Movie file has no hinted tracks."; + + case QTFileBroadcaster::eMovieFileNoSDPMatches : + return "Movie file does not match SDP."; + + case QTFileBroadcaster::eMovieFileInvalid: + return "Movie file is invalid."; + + case QTFileBroadcaster::eMovieFileInvalidName: + return "Movie file name is missing or too long."; + + default: + qtss_sprintf( buff, "Movie set up error %d occured.",err ); + return buff; + } + + return NULL; + +} + +static int sErr = 0; +static int EvalBroadcasterErr(int err) +{ + int returnErr = err; + if (sErr != 0) + return sErr; + + switch (err) + { case 412: + { qtss_printf("- Server Session Failed: The request was denied."); + qtss_printf("\n"); + break; + } + case QTFileBroadcaster::eNoAvailableSockets : + case QTFileBroadcaster::eSDPFileNotFound : + case QTFileBroadcaster::eSDPDestAddrInvalid : + case QTFileBroadcaster::eSDPFileInvalid : + case QTFileBroadcaster::eSDPFileNoMedia : + case QTFileBroadcaster::eSDPFileNoPorts : + case QTFileBroadcaster::eSDPFileInvalidPort : + { qtss_printf("- SDP set up failed: "); + switch( err ) + { + case QTFileBroadcaster::eNoAvailableSockets : qtss_printf("System error. No sockets are available to broadcast from."); + break; + case QTFileBroadcaster::eSDPFileNotFound : qtss_printf("The SDP file is missing."); + break; + case QTFileBroadcaster::eSDPDestAddrInvalid : qtss_printf("The SDP file server address is invalid."); + break; + case QTFileBroadcaster::eSDPFileInvalid : qtss_printf("The SDP file is invalid."); + break; + case QTFileBroadcaster::eSDPFileNoMedia : qtss_printf("The SDP file is missing media (m=) references."); + break; + case QTFileBroadcaster::eSDPFileNoPorts : qtss_printf("The SDP file is missing server port information."); + break; + case QTFileBroadcaster::eSDPFileInvalidPort : qtss_printf("The SDP file contains an invalid port. Valid range is 5004 - 65530."); + break; + + default: qtss_printf("SDP set up error %d occured.",err); + break; + + }; + qtss_printf("\n"); + } + break; + + case QTFileBroadcaster::eSDPFileInvalidTTL: + case QTFileBroadcaster::eDescriptionInvalidDestPort: + case QTFileBroadcaster::eSDPFileInvalidName: + case QTFileBroadcaster::eNetworkSDPFileNameInvalidBadPath: + case QTFileBroadcaster::eNetworkSDPFileNameInvalidMissing: + { qtss_printf("- Description set up failed: "); + switch( err ) + { + + case QTFileBroadcaster::eSDPFileInvalidTTL : qtss_printf("The multicast_ttl value is incorrect. Valid range is 1 to 255."); + break; + case QTFileBroadcaster::eDescriptionInvalidDestPort : qtss_printf("The destination_base_port value is incorrect. Valid range is 5004 - 65530."); + break; + case QTFileBroadcaster::eSDPFileInvalidName : qtss_printf("The sdp_file name is missing or too long."); + break; + case QTFileBroadcaster::eNetworkSDPFileNameInvalidBadPath: qtss_printf("The specified destination_sdp_file must a be relative file path in the movies directory."); + break; + case QTFileBroadcaster::eNetworkSDPFileNameInvalidMissing: qtss_printf("The specified destination_sdp_file name is missing."); + break; + + default: qtss_printf("Description set up error %d occured.",err); + break; + } + qtss_printf("\n"); + } + break; + + + + case QTFileBroadcaster::eMovieFileNotFound : + case QTFileBroadcaster::eMovieFileNoHintedTracks : + case QTFileBroadcaster::eMovieFileNoSDPMatches : + case QTFileBroadcaster::eMovieFileInvalid : + case QTFileBroadcaster::eMovieFileInvalidName : + { qtss_printf("- Movie set up failed: "); + switch( err ) + { + case QTFileBroadcaster::eMovieFileNotFound : qtss_printf("Movie file not found."); + break; + case QTFileBroadcaster::eMovieFileNoHintedTracks : qtss_printf("Movie file has no hinted tracks."); + break; + case QTFileBroadcaster::eMovieFileNoSDPMatches : qtss_printf("Movie file does not match SDP."); + break; + case QTFileBroadcaster::eMovieFileInvalid : qtss_printf("Movie file is invalid."); + break; + case QTFileBroadcaster::eMovieFileInvalidName : qtss_printf("Movie file name is missing or too long."); + break; + + default: qtss_printf("Movie set up error %d occured.",err); + break; + + }; + qtss_printf("\n"); + } + break; + + case QTFileBroadcaster::eMem: + case QTFileBroadcaster::eInternalError: + { qtss_printf("- Internal Error: "); + switch( err ) + { + case QTFileBroadcaster::eMem : qtss_printf("Memory error."); + break; + + case QTFileBroadcaster::eInternalError : qtss_printf("Internal error."); + break; + + default: qtss_printf("internal error %d occured.",err); + break; + } + qtss_printf("\n"); + } + break; + + case QTFileBroadcaster::eFailedBind: + case QTFileBroadcaster::eNetworkConnectionError: + case QTFileBroadcaster::eNetworkRequestError: + case QTFileBroadcaster::eNetworkConnectionStopped: + case QTFileBroadcaster::eNetworkAuthorization: + case QTFileBroadcaster::eNetworkNotSupported: + case QTFileBroadcaster::eNetworkConnectionFailed: + { sErrorEvaluted = true; + qtss_printf("- Network Connection: "); + switch( err ) + { + case QTFileBroadcaster::eFailedBind : qtss_printf("A Socket failed trying to open and bind to a local port.\n."); + break; + + case QTFileBroadcaster::eNetworkConnectionError : qtss_printf("Failed to connect."); + break; + + case QTFileBroadcaster::eNetworkRequestError : qtss_printf("Server returned error."); + break; + + case QTFileBroadcaster::eNetworkConnectionStopped: qtss_printf("Connection stopped."); + break; + + case QTFileBroadcaster::eNetworkAuthorization: qtss_printf("Authorization failed."); + break; + + case QTFileBroadcaster::eNetworkNotSupported: qtss_printf("Connection not supported by server."); + break; + + case QTFileBroadcaster::eNetworkConnectionFailed : qtss_printf("Disconnected."); + break; + + default: qtss_printf("internal error %d occured.",err); + break; + } + qtss_printf("\n"); + returnErr = QTFileBroadcaster::eNetworkConnectionError; + } + break; + + default: + + break; + } + + sErr = returnErr; + + return returnErr; +} + + +static int MyAccess( const char* path, int mode ) +{ + int error = 0; + +#ifndef __Win32__ + if ( access( path, mode ) ) + error = errno; + else + error = 0; +#endif + + return error; +} + +static int PreflightClientBufferDelay( const char * delay, Float32 *ioDelay) +{ + + int numPreflightErrors = 0; + + if ( NULL == delay|| 0 == delay[0] ) + numPreflightErrors++; + else + { Float32 delayValue = 0.0; + ::sscanf(delay, "%f", &delayValue); + if (delayValue < 0.0) + numPreflightErrors++; + if (ioDelay != NULL) + *ioDelay = delayValue; + } + + if (numPreflightErrors > 0) + qtss_printf("- PlaylistBroadcaster: The client_buffer_delay parameter is invalid.\n" ); + + return numPreflightErrors; +} + +static int PreflightDestSDPFileAccess( const char * sdpPath ) +{ + int numPreflightErrors = 0; + + if ( NULL == sdpPath || 0==sdpPath[0] || 0 == ::strcmp(sdpPath, "no_name") ) + { qtss_printf("- PlaylistBroadcaster: The destination_sdp_file parameter is missing from the Broadcaster description file.\n" ); + numPreflightErrors++; + } + + return numPreflightErrors; + +} + +static int PreflightSDPFileAccess( const char * sdpPath ) +{ + int numPreflightErrors = 0; + + Assert( sdpPath ); + + if ( !sdpPath ) + { qtss_printf("- PlaylistBroadcaster: The sdp_file parameter is missing from the Broadcaster description file.\n" ); + numPreflightErrors++; + } + else + { int accessError; + + accessError = MyAccess( sdpPath, R_OK | W_OK ); + + switch( accessError ) + { + + case 0: + case ENOENT: // if its not there, we'll create it + break; + + case EACCES: + qtss_printf("- PlaylistBroadcaster: Permission to access the SDP File was denied.\n Read/Write access is required.\n (path: %s, errno: %i).\n", sdpPath, accessError); + numPreflightErrors++; + break; + + default: + qtss_printf("- PlaylistBroadcaster: Unable to access the SDP File.\n (path: %s, errno: %i).\n", sdpPath, accessError); + numPreflightErrors++; + break; + + } + + } + + return numPreflightErrors; + +} + +static int PreflightDestinationAddress( const char *destAddress ) +{ + int numPreflightErrors = 0; + + + if ( !destAddress || (SocketUtils::ConvertStringToAddr(destAddress) == INADDR_NONE) ) + { qtss_printf("- PlaylistBroadcaster: Error, desitination_ip_address parameter missing or incorrect in the Broadcaster description file.\n" ); + numPreflightErrors++; + } + + return numPreflightErrors; + +} + +static int PreflightBasePort( const char *basePort ) +{ + int numPreflightErrors = 0; + + + Assert( basePort ); + if ( basePort == NULL ) + { qtss_printf("- PlaylistBroadcaster: Error, destination_base_port parameter missing from the Broadcaster description file.\n" ); + numPreflightErrors++; + } + else if ( (atoi(basePort) & 1) != 0) + { + qtss_printf("- PlaylistBroadcaster: Warning, the destination_base_port parameter(%s) is an odd port number. It should be even.\n", basePort ); + } + + return numPreflightErrors; + +} + + +static int PreflightReferenceMovieAccess( const char *refMoviePath ) +{ + int numPreflightErrors = 0; + + Assert( refMoviePath ); + if ( !refMoviePath ) + { qtss_printf("- PlaylistBroadcaster: Error, sdp_reference_movie parameter missing from the Broadcaster description file.\n" ); + numPreflightErrors++; + } + else + { int accessError; + + accessError = MyAccess( refMoviePath, R_OK ); + + switch( accessError ) + { + case 0: + break; + + case ENOENT: + qtss_printf("- PlaylistBroadcaster: Error, SDP Reference Movie is missing.\n (path: %s, errno: %i).\n", refMoviePath, accessError); + numPreflightErrors++; + break; + + case EACCES: + qtss_printf("- PlaylistBroadcaster: Permission to access the SDP reference movie was denied.\n Read access required.\n (path: %s, errno: %i).\n", refMoviePath, accessError); + numPreflightErrors++; + break; + + default: + qtss_printf("- PlaylistBroadcaster: Unable to access the SDP reference movie.\n (path: %s, errno: %i).\n", refMoviePath, accessError); + numPreflightErrors++; + break; + + } + + } + + return numPreflightErrors; +} + + +static void ShowPlaylistStatus() +{ +#ifndef __Win32__ + BCasterTracker tracker( sgTrackerFilePath ); + + if ( tracker.IsOpen() ) + { + tracker.Show(); + } + else + { + qtss_printf("- PlaylistBroadcaster: Could not open %s. Reason: access denied.\n", sgTrackerFilePath ); + qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ; + ::exit(-1); + + } +#else + qtss_printf("Showing Playlist status is currently not supported on this platform\n"); +#endif +} + +static void StopABroadcast( const char* arg ) +{ +#ifndef __Win32__ + if ( arg ) + { + + BCasterTracker tracker( sgTrackerFilePath ); + int playlistIDToKill; + + playlistIDToKill = ::atoi( arg ); + playlistIDToKill--; // convert from UI one based to to zero based ID for BCasterTracker + + + if ( tracker.IsOpen() ) + { + bool broadcastIDIsValid; + int error; + + error = tracker.Remove( playlistIDToKill ); + + if ( !error ) // remove the xth item from the list. + { tracker.Save(); + broadcastIDIsValid = true; + } + else + broadcastIDIsValid = false; + + if ( !broadcastIDIsValid ) + { + if ( playlistIDToKill >= 0 ) + { + if ( error == ESRCH || error == -1 ) + qtss_printf( "- PlaylistBroadcaster Broadcast ID (%s) not running.\n", arg ); + else + qtss_printf( "- PlaylistBroadcaster Broadcast ID (%s), permission to stop denied (%i).\n", arg, error ); + + } + else + { qtss_printf( "- Bad argument for stop: (%s).\n", arg ); + ::exit( -1 ); + } + } + else + qtss_printf("PlaylistBroadcaster stopped Broadcast ID: %s.\n", arg); + } + else + { qtss_printf("- PlaylistBroadcaster: Could not open %s. Reason: access denied.\n", sgTrackerFilePath ); + qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ; + ::exit( -1 ); + + } + } + else + { // getopt will catch this problem before we see it. + qtss_printf("- Stop requires a Broadcast ID.\n"); + ::exit( -1 ); + } +#else + qtss_printf("Stopping a broadcast is currently not supported on this platform\n"); +#endif +} + + +static bool DoSDPGen( PLBroadcastDef *broadcastParms, bool preflight, bool overWriteSDP, bool generateNewSDP, int* numErrorsPtr, char* refMovie) +{ + int numSDPSetupErrors = 0; + bool sdpFileCreated = false; + + if (sAnnounceBroadcast) + { numSDPSetupErrors += PreflightDestSDPFileAccess( broadcastParms->mDestSDPFile ); + Assert(broadcastParms->mBasePort != NULL); + Assert(broadcastParms->mBasePort[0]!=0); + broadcastParms->mBasePort[0] = '0';//set to dynamic. ignore any defined. + broadcastParms->mBasePort[1] = 0; + } + + if (broadcastParms->mSDPReferenceMovie != NULL) + refMovie = broadcastParms->mSDPReferenceMovie; + + qtss_printf("Ref Movie = %s\n", refMovie); + qtss_printf("SDP file = %s\n", broadcastParms->mSDPFile); + + numSDPSetupErrors += PreflightSDPFileAccess( broadcastParms->mSDPFile ); + + + numSDPSetupErrors += PreflightReferenceMovieAccess( refMovie ); + + + numSDPSetupErrors += PreflightDestinationAddress( broadcastParms->mDestAddress ); + + + numSDPSetupErrors += PreflightBasePort( broadcastParms->mBasePort ); + + Float32 bufferDelay = 0.0; + numSDPSetupErrors += PreflightClientBufferDelay( broadcastParms->mClientBufferDelay,&bufferDelay ); + + if ( numSDPSetupErrors == 0 ) + { + SDPGen* sdpGen = new SDPGen; + + int sdpResult = -1; + + Assert( sdpGen ); + + if ( sdpGen ) + { + sdpGen->SetClientBufferDelay(bufferDelay); // sdp "a=x-bufferdelay: value" + sdpGen->KeepSDPTracks(false); // set this to keep a=control track ids if we are going to ANNOUNCE the sdp to the server. + sdpGen->AddIndexTracks(true); // set this if KeepTracks is false and to ANNOUNCE the sdp to the server. + sdpResult = sdpGen->Run( refMovie, broadcastParms->mSDPFile, + broadcastParms->mBasePort, broadcastParms->mDestAddress, + sSDPBuffer, maxSDPbuffSize, + overWriteSDP, generateNewSDP, + broadcastParms->mStartTime, + broadcastParms->mEndTime, + broadcastParms->mIsDynamic, + broadcastParms->mTTL + ); + + sdpFileCreated = sdpGen->fSDPFileCreated; + + if ( sdpGen->fSDPFileCreated && !preflight) + qtss_printf( "- PlaylistBroadcaster: Created SDP file.\n (path: %s)\n", broadcastParms->mSDPFile); + } + + + if ( sdpResult ) + { + if (sdpResult == -2) + qtss_printf("- SDP generation failed: Unable to create the SDP File.\n (path: %s, errno: %d).\n", broadcastParms->mSDPFile, sdpResult); + else + qtss_printf( "- SDP generation failed (error: %li).\n", (SInt32)sdpResult ); + + (*numErrorsPtr)++; + return sdpFileCreated; + } + + if ( sdpGen ) + delete sdpGen; + + sdpGen = NULL; + } + else + { qtss_printf( "- PlaylistBroadcaster: Too many SDP set up errors, exiting.\n" ); + *numErrorsPtr += numSDPSetupErrors; + } + + return sdpFileCreated; + +} + +static bool PreflightTrackerFileAccess( int mode ) +{ + +#ifndef __Win32__ + int trackerError; + + int dirlen = strlen(sgTrackerDirPath) + 1; + OSCharArrayDeleter trackDir(new char[dirlen]); + char *trackDirPtr = trackDir.GetObject(); + ::memcpy(trackDirPtr, sgTrackerDirPath, dirlen); + + trackerError= OS::RecursiveMakeDir( trackDirPtr ); + + if ( trackerError == 0 ) + trackerError = MyAccess( sgTrackerFilePath, mode ); + + switch ( trackerError ) + { + case 0: + case ENOENT: + break; + + default: + qtss_printf("- PlaylistBroadcaster: Error opening %s. (errno: %i).\n", sgTrackerFilePath, trackerError); + qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ; + return false; + break; + + } +#endif + return true; +} + +static void ShowSetupParams(PLBroadcastDef* broadcastParms, const char *bcastSetupFilePath) +{ + qtss_printf( "\n" ); + qtss_printf( "PlaylistBroadcaster broadcast description File\n" ); + qtss_printf( "----------------------------------------------\n" ); + qtss_printf( "%s\n", bcastSetupFilePath ); + + broadcastParms->ShowSettings(); + +} + +static bool PreFlightSetupFile( const char * bcastSetupFilePath ) +{ + bool success = true; + + // now complain! + if ( !bcastSetupFilePath ) + { qtss_printf("- PlaylistBroadcaster: A broadcast description file is required.\n" ); + ::usage(); + success = false; + } + else if (::strlen(bcastSetupFilePath) > PLBroadcastDef::kMaxBufferStringLen) + { qtss_printf("- PlaylistBroadcaster: A broadcast description file path cannot be longer than %d in length.\n",PLBroadcastDef::kMaxBufferStringLen ); + ::usage(); + success = false; + } + else + { int accessError; + + accessError = MyAccess(bcastSetupFilePath, R_OK ); + + switch( accessError ) + { + + case 0: + break; + + case ENOENT: + qtss_printf("- PlaylistBroadcaster: The broadcast description file is missing.\n (path: %s, errno: %i).\n", bcastSetupFilePath, accessError); + success = false; + break; + + case EACCES: + qtss_printf("- PlaylistBroadcaster: Permission denied to access the broadcast description file.\n Read access required.\n (path: %s, errno: %i).\n", bcastSetupFilePath, accessError); + success = false; + break; + + default: + qtss_printf("- PlaylistBroadcaster: Unable to access the broadcast description file.\n (path: %s, errno: %i).\n", bcastSetupFilePath, accessError); + success = false; + break; + + } + + } + + return success; +} + +static bool AddOurPIDToTracker( const char* bcastSetupFilePath ) +{ + // add our pid and Broadcast description file File to the tracker +#ifndef __Win32__ + BCasterTracker tracker( sgTrackerFilePath ); + + if ( tracker.IsOpen() ) + { sgTrackingSucceeded = 1; + tracker.Add( getpid(), bcastSetupFilePath ); + tracker.Save(); + } + else + { + qtss_printf("- PlaylistBroadcaster: Could not open %s. Reason: access denied.\n", sgTrackerFilePath ); + qtss_printf("- PlaylistBroadcaster: Change user or change the directory's privileges to access %s.\n",sgTrackerFilePath) ; + return false; + } +#endif + return true; // return true if pid was successfully added to the tracker +} + +#if 0 + +static void ShowPickDistribution( PlaylistPicker *picker ) +{ + qtss_printf( "\n" ); + qtss_printf( "Pick Distribution by Bucket\n" ); + qtss_printf( "---------------------------\n" ); + + UInt32 bucketIndex; + + for ( bucketIndex = 0; bucketIndex < picker->GetNumBuckets(); bucketIndex++ ) + { + qtss_printf( "bucket total for w: %li, (%li)\n", (bucketIndex + 1), (SInt32)picker->mPickCounts[bucketIndex] ); + + } +} + +// archaic debug code + +static void PrintContents( PLDoubleLinkedListNode *node, void * ); +static void ShowPicker(PlaylistPicker *picker); + + +static void PrintContents( PLDoubleLinkedListNode *node, void * ) +{ + qtss_printf( "element name %s\n", node->mElement->mElementName ); +} + +static void ShowPicker(PlaylistPicker *picker) +{ + int x; + + for ( x= 0; x < picker->GetNumBuckets(); x++ ) + { + picker->GetBucket(x)->ForEach( PrintContents, NULL ); + } + +} + +#endif +/* changed by emil@popwire.com (see relaod.txt for info) */ +bool 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); + (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; +} + +static void PrintPlaylistElement(PLDoubleLinkedListNode *node,void *file) +{ + sElementCount ++; + if (sElementCount <= sMaxUpcomingListSize) + { char* thePick = node->fElement->mElementName; + if (sSetupDirPath.EqualIgnoreCase(thePick, sSetupDirPath.Len) || '\\' == thePick[0] || '/' == thePick[0]) + qtss_fprintf((FILE*)file,"%s\n", thePick); + else + qtss_fprintf((FILE*)file,"%s%s\n", sSetupDirPath.Ptr,thePick); + } +} + +static void ShowPlaylistElements(PlaylistPicker *picker,FILE *file) +{ + if (sElementCount > sMaxUpcomingListSize) + return; + + UInt32 x; + for (x= 0;xGetNumBuckets();x++) + { + picker->GetBucket(x)->ForEach(PrintPlaylistElement,file); + } +} +/* ***************************************************** */ + +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(__powerpc__) || defined (__sgi_cc__) || defined(__osf__) || defined(__hpux__) || defined(__linux__) + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = (void(*)(int))&SignalEventHandler; +#elif defined(__sgi__) + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = (void(*)(...))&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) + { // broken pipe probably from a failed RTSP session (the server went down?) + if ( ::sigaction(SIGHUP, &act, NULL) != 0) + { qtss_printf( "- PlaylistBroadcaster: System error (%"_SPOINTERSIZEARG_").\n", (PointerSizedInt)SIG_ERR ); + } + + } + + +#endif + + +} + +/* ======================================================================== + * Signal and error handler. + */ +static void RemoveFiles(PLBroadcastDef* broadcastParms) +{ + if (broadcastParms != NULL) + { + if (broadcastParms->mPIDFile != NULL) + remove(broadcastParms->mPIDFile); + if (broadcastParms->mStopFile != NULL) + remove(broadcastParms->mStopFile); + if (broadcastParms->mReplaceFile != NULL) + remove(broadcastParms->mReplaceFile); + if (broadcastParms->mInsertFile != NULL) + remove(broadcastParms->mInsertFile); + if (broadcastParms->mCurrentFile != NULL) + remove(broadcastParms->mCurrentFile); + if (broadcastParms->mUpcomingFile != NULL) + remove(broadcastParms->mUpcomingFile); + } + +} +/* ======================================================================== + * Signal and error handler. + */ +static void SignalEventHandler( int signalID ) +{ + +#ifdef __Win32__ + if ( (signalID != SIGINT) && (signalID != SIGTERM) ) +#else + if (signalID == SIGPIPE) +#endif + sNumErrors++; + + // evaluate the error + if (sRTSPClientError != 0 && !sErrorEvaluted) + { + EvalBroadcasterErr(sRTSPClientError); + sErrorEvaluted = true; + } + else if (sBroadcasterSession != NULL && !sErrorEvaluted) + { +#ifndef __Win32__ + if ( (signalID == SIGINT) || (signalID == SIGTERM) ) + { EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionStopped); + } + else + { EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionFailed); + } +#else + EvalBroadcasterErr(QTFileBroadcaster::eNetworkConnectionStopped); +#endif + sErrorEvaluted = true; + } + + // do the cleanup - write warning and error messages and remove files + Cleanup(); + +#ifndef __Win32__ + if (sBroadcasterSession != NULL) + { + + if ( (signalID == SIGINT) || (signalID == SIGTERM) ) + sQuitImmediate = true; // just in case we get called again + + return; //we need to let the broadcaster session teardown and clean up + } + + if ( sgTrackingSucceeded ) + { + // give tracker scope so that it really does it's + // thing before "exit" is called. + BCasterTracker tracker( sgTrackerFilePath ); + + tracker.RemoveByProcessID( getpid() ); + tracker.Save(); + } + +#endif + + if (!sQuitImmediate) // do once + { + sQuitImmediate = true; // just in case we get called again + +#ifdef __Win32__ + + if (sBroadcasterSession && !sBroadcasterSession->IsDone()) + { sBroadcasterSession->TearDownNow(); + OSThread::Sleep(1000); + } + if (NULL == sBroadcasterSession) + qtss_printf("\n"); // make sure the message was printed before quitting. + +#endif + return; + + } + ::exit(-1); +} diff --git a/PlaylistBroadcaster.tproj/PlaylistPicker.cpp b/PlaylistBroadcaster.tproj/PlaylistPicker.cpp new file mode 100644 index 0000000..e54ed41 --- /dev/null +++ b/PlaylistBroadcaster.tproj/PlaylistPicker.cpp @@ -0,0 +1,430 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "MyAssert.h" +#include "OS.h" + + +#include "PlaylistPicker.h" + +/* + PlaylistPicker has 3 modes + - sequential looping through the items in the play list(s) + in the order they are entered. + + - above w/ looping + + - weighted picking "randomly" from weighted buckets + + +*/ + + + + +PlaylistPicker::PlaylistPicker( UInt32 numBuckets, UInt32 numNoRepeats ) +{ + // weighted random ctor + + mFirstElement = NULL; + mNumToPickFrom = 0; + mBuckets = numBuckets; + mIsSequentialPicker = false; + mRecentMoviesListSize = numNoRepeats; + +/* changed by emil@popwire.com (see relaod.txt for info) */ + mRemoveFlag = false; + mStopFlag = false; +/* ***************************************************** */ + mLastResult = (UInt32) OS::Milliseconds(); + + mPickCounts = new SInt32[numBuckets]; + UInt32 x; + + for ( x = 0; x < mBuckets; x++ ) + { mPickCounts[x] = 0; + mElementLists[x] = new PLDoubleLinkedList; + Assert( mElementLists[x] ); + + } + + + + mUsedElements = new NoRepeat( numNoRepeats ); + + Assert( mUsedElements ); +} + +PlaylistPicker::PlaylistPicker(bool doLoop) +{ + // sequential ctor + + mFirstElement = NULL; + mIsSequentialLooping = doLoop; + + mIsSequentialPicker = true; + mWhichSequentialBucket = 0; + mRecentMoviesListSize = 0; + + mNumToPickFrom = 0; + mBuckets = 2; // alternating used/remaining pick buckets +/* changed by emil@popwire.com (see relaod.txt for info) */ + mRemoveFlag = false; + mStopFlag = false; + fLastPick = NULL; +/* ***************************************************** */ + + + mPickCounts = new SInt32[mBuckets]; + + + UInt32 bucketIndex; + + for ( bucketIndex = 0; bucketIndex < mBuckets; bucketIndex++ ) + { + mPickCounts[bucketIndex] = 0; + mElementLists[bucketIndex] = new PLDoubleLinkedList; + Assert( mElementLists[bucketIndex] ); + + } + + + mUsedElements = NULL; + +} + +PlaylistPicker::~PlaylistPicker() +{ + UInt32 bucketIndex; + + delete mUsedElements; + + for ( bucketIndex = 0; bucketIndex < mBuckets; bucketIndex++ ) + { + delete mElementLists[bucketIndex] ; + + } + + delete [] mPickCounts; +} + + +UInt32 PlaylistPicker::Random() +{ + UInt32 seed = 1664525L * mLastResult + 1013904223L; //1013904223 is prime .. Knuth D.E. + ::srand( seed ); + + UInt32 result = ::rand(); + + mLastResult = result; + return result; +} + + +char* PlaylistPicker::PickOne() +{ + + char* foundName = NULL; // pointer to name of pick we find, caller deletes. + + + if ( mIsSequentialPicker ) + { + if ( mElementLists[mWhichSequentialBucket]->GetNumNodes() == 0 && mIsSequentialLooping ) + { // ran out of items switch to other list. + if ( mWhichSequentialBucket == 0 ) + mWhichSequentialBucket = 1; + else + mWhichSequentialBucket = 0; + + } + + if ( mElementLists[mWhichSequentialBucket]->GetNumNodes() > 0 ) + { + PLDoubleLinkedListNode* node; + + + node = mElementLists[mWhichSequentialBucket]->GetFirst(); + + Assert( node ); + + int nameLen = ::strlen( node->fElement->mElementName ); + + foundName = new char[ nameLen +1 ]; + + Assert( foundName ); + + if ( foundName ) + { + int usedBucketIndex; + + ::strcpy( foundName, node->fElement->mElementName ); + + // take him out of the bucket since he's now in play + mElementLists[mWhichSequentialBucket]->RemoveNode( node ); + + + if ( mWhichSequentialBucket == 0 ) + usedBucketIndex = 1; + else + usedBucketIndex = 0; + +/* changed by emil@popwire.com (see relaod.txt for info) */ + if(!mRemoveFlag) +/* ***************************************************** */ + mElementLists[usedBucketIndex]->AddNodeToTail( node ); +/* changed by emil@popwire.com (see relaod.txt for info) */ + else + mNumToPickFrom--; +/* ***************************************************** */ + + } + + } + + + } + else + { + SInt32 bucketIndex; + UInt32 minimumBucket = 0; + UInt32 avaiableToPick; + UInt32 theOneToPick; + SInt32 topBucket; + + + // find the highest bucket with some elements. + bucketIndex = this->GetNumBuckets() - 1; + + while ( bucketIndex >= 0 && mElementLists[bucketIndex]->GetNumNodes() == 0 ) + { + bucketIndex--; + } + + + // adjust to 1 based so we can use MOD + topBucket = bucketIndex + 1; + + //qtss_printf( "topBucket %li \n", topBucket ); + + if (topBucket > 0) + minimumBucket = this->Random() % topBucket; // find our minimum bucket + + //qtss_printf( "minimumBucket %li \n", minimumBucket ); + + // pick randomly from the movies in this and higher buckets + // sum the available elements, then pick randomly from them. + + avaiableToPick = 0; + + bucketIndex = minimumBucket; + + while ( bucketIndex < topBucket ) + { + avaiableToPick += mElementLists[bucketIndex]->GetNumNodes(); + + bucketIndex++; + } + + //qtss_printf( "avaiableToPick %li \n", avaiableToPick ); + + // was anyone left?? + + if ( avaiableToPick ) + { + theOneToPick = this->Random() % avaiableToPick; + //qtss_printf( "theOneToPick %li \n", theOneToPick ); + + // now walk through the lists unitl we get to the list + // that contains our pick, then pick that one. + bucketIndex = minimumBucket; + + while ( bucketIndex < topBucket && foundName == NULL ) + { + + //qtss_printf( "theOneToPick %li, index %li numelements %li\n", theOneToPick , bucketIndex, mElementLists[bucketIndex]->GetNumNodes()); + + if ( theOneToPick >= mElementLists[bucketIndex]->GetNumNodes() ) + theOneToPick -= mElementLists[bucketIndex]->GetNumNodes(); + else + { //qtss_printf( "will pick theOneToPick %li, index %li \n", theOneToPick, bucketIndex); + foundName = this->PickFromList( mElementLists[bucketIndex], theOneToPick ); + if ( foundName ) + mPickCounts[bucketIndex]++; + } + + bucketIndex++; + } + + // we messed up if we don't have a name at this point. + Assert( foundName ); + } + } + + fLastPick = foundName; + return foundName; + +} + +void PlaylistPicker::CleanList() +{ + + mFirstElement = NULL; + mNumToPickFrom = 0; + + delete mUsedElements; + mUsedElements = new NoRepeat( mRecentMoviesListSize ); + + delete [] mPickCounts; + mPickCounts = new SInt32[mBuckets]; + + UInt32 x; + for ( x = 0; x < mBuckets; x++ ) + { + mPickCounts[x] = 0; + delete mElementLists[x]; + mElementLists[x] = new PLDoubleLinkedList; + Assert( mElementLists[x] ); + } + +}; + +char* PlaylistPicker::PickFromList( PLDoubleLinkedList* list, UInt32 elementIndex ) +{ + PLDoubleLinkedListNode* plNode; + char* foundName = NULL; + + + plNode = list->GetNthNode( elementIndex ); + + if ( plNode ) + { + int nameLen = ::strlen(plNode->fElement->mElementName ); + + foundName = new char[ nameLen +1 ]; + + Assert( foundName ); + + if ( foundName ) + { + ::strcpy( foundName, plNode->fElement->mElementName ); + + // take him out of the bucket since he's now in play + list->RemoveNode( plNode ); + + mNumToPickFrom--; + + // add him to our used list, and possibly + // get an older one to put back into play + PLDoubleLinkedListNode* recycleNode = mUsedElements->AddToList( plNode ); + + // if we got an old one to recycle, do so. + if ( recycleNode ) + this->AddNode( recycleNode ); + } + } + + return foundName; + +} + +bool PlaylistPicker::AddToList( const char* name, int weight ) +{ + bool addedSuccesfully; + PLDoubleLinkedListNode* node; + SimplePlayListElement* element; + + + node = NULL; + addedSuccesfully = false; + element = new SimplePlayListElement(name); + if (mFirstElement == NULL) + mFirstElement = element->mElementName; + + Assert( element ); + + + if ( element ) + { element->mElementWeight = weight; + node = new PLDoubleLinkedListNode(element); + + Assert( node ); + } + + if ( node ) + addedSuccesfully = AddNode(node); + + + return addedSuccesfully; +} + +bool PlaylistPicker::AddNode( PLDoubleLinkedListNode* node ) +{ + bool addSucceeded = false; + + + Assert( node ); + Assert( node->fElement ); + + + if ( mIsSequentialPicker ) // make picks in sequential order, not weighted random + { + // add all to bucket 0 + mElementLists[0]->AddNodeToTail( node ); + + addSucceeded = true; + mNumToPickFrom++; + + } + else + { + int weight; + + weight = node->fElement->mElementWeight; + + // weights are 1 based, correct to zero based for use as array myIndex + weight--; + + Assert( weight >= 0 ); + + Assert( (UInt32)weight < mBuckets ); + + if ( (UInt32)weight < mBuckets ) + { + // the elements weighting defines the list it is in. + mElementLists[weight]->AddNode( node ); + + addSucceeded = true; + mNumToPickFrom++; + + } + } + + return addSucceeded; + +} diff --git a/PlaylistBroadcaster.tproj/PlaylistPicker.h b/PlaylistBroadcaster.tproj/PlaylistPicker.h new file mode 100644 index 0000000..c760f9e --- /dev/null +++ b/PlaylistBroadcaster.tproj/PlaylistPicker.h @@ -0,0 +1,86 @@ +#ifndef __playlist_picker__ +#define __playlist_picker__ + +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "PLDoubleLinkedList.h" +#include "SimplePlayListElement.h" + +#include "OSHeaders.h" + +#include "NoRepeat.h" + +class PlaylistPicker +{ + + public: + enum { kMaxBuckets = 10 }; + + PlaylistPicker(bool doLoop); + PlaylistPicker( UInt32 numBuckets, UInt32 numNoRepeats ); + virtual ~PlaylistPicker(); + void CleanList(); + bool AddToList( const char *name, int weight ); + bool AddNode( PLDoubleLinkedListNode *node ); + char* PickOne(); + char* LastPick() { return fLastPick; } + UInt32 GetNumBuckets() { return mBuckets; } +/* changed by emil@popwire.com (see relaod.txt for info) */ + UInt32 GetNumMovies() { return mNumToPickFrom; } + + bool mRemoveFlag; + bool mStopFlag; +/* ***************************************************** */ + SInt32* mPickCounts; + SInt32 mNumToPickFrom; + UInt32 mRecentMoviesListSize; + char* fLastPick; + PLDoubleLinkedList* GetBucket( UInt32 myIndex ) { return mElementLists[myIndex]; } + + char* GetFirstFile() { return mFirstElement; } + + protected: + + bool mIsSequentialPicker; // picker picks sequentially? + bool mIsSequentialLooping; // loop over and over? + int mWhichSequentialBucket; // sequential picker picks from list0 or list1? + + UInt32 Random(); + UInt32 mLastResult; + + char* PickFromList( PLDoubleLinkedList* list, UInt32 elementIndex ); + + PLDoubleLinkedList* mElementLists[kMaxBuckets]; + + UInt32 mBuckets; + NoRepeat *mUsedElements; + char* mFirstElement; +}; + + + +#endif diff --git a/PlaylistBroadcaster.tproj/SimplePlayListElement.h b/PlaylistBroadcaster.tproj/SimplePlayListElement.h new file mode 100644 index 0000000..a500d7f --- /dev/null +++ b/PlaylistBroadcaster.tproj/SimplePlayListElement.h @@ -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@ + * + */ +#ifndef __SimplePlayListElement__ +#define __SimplePlayListElement__ + +#include +#include "MyAssert.h" + +class SimplePlayListElement { + + public: + SimplePlayListElement( const char * name ) + { + mElementName = new char[ strlen(name) + 1 ]; + + Assert( mElementName ); + if( mElementName ) + strcpy( mElementName, name ); + + mElementWeight = -1; + + mWasPlayed = false; + + } + + virtual ~SimplePlayListElement() + { + if ( mElementName ) + delete [] mElementName; + } + + int mElementWeight; + bool mWasPlayed; + char *mElementName; + +}; + +#endif diff --git a/PlaylistBroadcaster.tproj/StSmartArrayPointer.h b/PlaylistBroadcaster.tproj/StSmartArrayPointer.h new file mode 100644 index 0000000..55efd20 --- /dev/null +++ b/PlaylistBroadcaster.tproj/StSmartArrayPointer.h @@ -0,0 +1,56 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 __StSmartArrayPointer__ +#define __StSmartArrayPointer__ + +template +class StSmartArrayPointer +{ + public: + StSmartArrayPointer(T* victim) : fT(victim) {} + ~StSmartArrayPointer() { delete [] fT; } + + void SetObject(T* victim) + { + //can't use a normal assert here because "Assert.h" eventually includes this file.... + #ifdef ASSERT + //char s[65]; + if (fT != NULL) qtss_printf ("_Assert: StSmartArrayPointer::SetObject() %s, %d\n", __FILE__, __LINE__); + #endif + + fT = victim; + } + + T* GetObject() { return fT; } + + operator T*() { return fT; } + + private: + + T* fT; +}; + +typedef StSmartArrayPointer OSCharArrayDeleter; +#endif diff --git a/PlaylistBroadcaster.tproj/TrackingElement.h b/PlaylistBroadcaster.tproj/TrackingElement.h new file mode 100644 index 0000000..faa2621 --- /dev/null +++ b/PlaylistBroadcaster.tproj/TrackingElement.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 __tracking_element__ +#define __tracking_element__ + +#include +#include +#include "OSHeaders.h" +#include "MyAssert.h" + +class TrackingElement { + + public: + TrackingElement( pid_t pid, const char * name ) + { + mName = new char[ strlen(name) + 1 ]; + + Assert( mName ); + if( mName ) + strcpy( mName, name ); + + mPID = pid; + + } + + virtual ~TrackingElement() + { + if ( mName ) + delete [] mName; + } + + char *mName; + pid_t mPID; + +}; + +#endif diff --git a/PlaylistBroadcaster.tproj/notes.c b/PlaylistBroadcaster.tproj/notes.c new file mode 100644 index 0000000..7d40345 --- /dev/null +++ b/PlaylistBroadcaster.tproj/notes.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@ + */ +#if 0 + + +todo + + +v12 -- + - changed PlayListBroadcaster to PlaylistBroadcaster + - don't strip ext from Descr filename before adding .log in a default file name. + - play_list_file becomes playlist_file + - repeats_queue_size becomes recent_movies_list_size + - limit_play is not used anymore + + +v11-- + - changed a bunch of error messages + - fixed: In my broadcast description file, I left off any keyword after "logging" + and both the preflight and the regular run of the file caused a Bus error. + - added descriptive messages about setup file errors. + - changed name from /tmp/trackerfile to /tmp/broadcastlist + - chdir to location of Setup file ( means that paths in Setup file are + relative to locaation of setup file, not PlayListBroadcaster + + + +v10 + -- writes message that SDP file was generated in debug or preflight? 2365986 + -- fixed pathname display bug for log file name errors. ... "the.log", or else "./the.log", or else + the full path should have been displayed... + -- added "PlayListBroadcaster preflight finished.\n" at end of prefilght ( already logged ). + + -- err desc is logged, not err # + + Error:# + + as in these samples + + # 1999-08-04 13:36:25 PlayListBroadcaster started + 1999-08-04 13:44:53 /Beamer/projects/Servers/TimeShare/PlayListBroadcaster/pl_files/lists/3movie3.mov Error:12 + 1999-08-04 13:44:55 /Beamer/projects/Servers/TimeShare/PlaylistBroadcaster/pl_files/lists/test1.mov OK + # 1999-08-04 14:57:49 PlaylistBroadcaster finished + + + to + + Error:"errstring" + + where "errstring" is one of the following: + + Movie file not found + Movie file has no hinted tracks + Movie file does not match SDP + Movie file is invalid + Movie file name is missing or too long + +8.6.99 build 9 + - PLB logs preflights, # movie problems + - cleaned up code in PlayListBroadcaster.cpp + - only complains about lack of tracker and logfile access when necessary + - "Setup File Name".los is the default logfile name + - changed display occurances of PlaylistBroadcaster to PlayListBroadcaster + - report permission to stop denied error, not just "not running" + + +8.5.99 build 8 + - chanegd track match error to warning and reworded + - added check for tracker access. + - added Lock to log file + - fixed problems when running in background ( loss of tracking and logging ) + + + +8.4.99 build 7 + - added Error message "- PlayListBroadcaster: Error, unsupported Play Mode (%s)\n" + - uses Setup supplied logfile name, doed not append .log 2364223 + - added error messages about not being able to open the log file + - added error messages about not being able to open the Broadcast setup file. + - added playError messages to log file: + # 1999-08-04 13:36:25 PlayListBroadcaster started + 1999-08-04 13:44:53 /Beamer/projects/Servers/TimeShare/PlayListBroadcaster/pl_files/lists/3movie3.mov Error:12 + 1999-08-04 13:44:55 /Beamer/projects/Servers/TimeShare/PlaylistBroadcaster/pl_files/lists/test1.mov OK + # 1999-08-04 14:57:49 PlaylistBroadcaster finished + + - will not ignore ^C's when there is an initial problem with the tracker file. + +8.2.99 -- build 6 + - gen error messages from broadcaster + - changed to all verbose options + - removed -c option, just list file the name at end of command. + - destination ip now defaults to machine's primary address + - changed BCasterTracker::BCasterTracker to 5 second open timer. + - changed reference to "channel setup" to "PlaylistBroadcaster Description" + - changed &d's in qtss_printf's to %d in Broadcaster error messages + - only print errors when walking the play list + - only show Setup options in Preflight mode + - changed display of setup options to show user names, not C++ names. + - the -list option no longer lists broadcasts that are not running. file is + cleaned up on next "stop" + - multiple options per command line are now supported, exits on first error. + - command line options uses "getopt"'s long form that allows -fullword options + and partial ( down to one char ) options as long as they are unambiguous. + +8.3.99 + - fixed include loops + - better error reporting from building the picklist + - chmod's the "trackerfile" to 666 so that all users can run broadcasts + and track them. + - Shows permission error if that is the reason a "stop" fails. + - fixes problems with fully qualified paths in both Playlist and "play_list_file" + Setup file option + + +7.30.99 - build 5 + - Movie file and SDP parsing now includes "user" error reporting ( not using Assert ). + So the program should continue past recoverable errors and stop only on non recoverable + errors. + + - Playlists no longer require weights, non - weighted files in a random playlist + default to a weight of 10. + + - + symbol introduces an "included" play list + + - play list files MUST have "*PLAY-LIST*" as the first line ( exactly ). + Else they will be treated as an invalid play list file. + + - new "-prefilght" option displays Setup, SDP, and walks the movie list to check + files for correctness. + + - removed debug options from command line options. + + + +7.27.99 - build 4 + - removed license from about display + - updated credit names + - fixed mapping of --stop to 's' from 'l' + - added -a (about) to help + + - builds from libQTRTPFile.a + - added ppc only instructions to make.preamble + + - uses a well known place to keep the tracker file, was kept is same dir + as PlaylistBroadcaster ( now uses /tmp/ ) + + - better error message on "stop" with bad parameters. + - removed Assert on playlist "includes" + - added default destination adddress ( 127.0.0.1 "localhost" ) + - changed "weighted" mode parameter to "weighted_random" as per docs + - changes picking algorithm to provide better weighting + - fixes the "count5.mov" crash + - fixes the "can't play more than 8 movies without losing audio" bug + + + + +7.26.99 -- build 3 Notes and Errata + + not documented: uses "sdp_reference_movie" option (mSDPReferenceMovie) to bulid SDP file. + Add this option to the config file. It's a required field. + + Use the -g file.config option to cause PlaylistBroadcaster to parse and display the + settings in a config file. + + SDP generation displays the raw SDP of the reference file, not the generated + file. + + If you set the config "limit_seq_length" to a value greater than the number of + movies available for play in random mode, the movies will play once and the + broadcast will stop. + + + The executable is incorrectly named BroadcastPlaylist, instead of PlaylistBroacaster. + + The installer read me references v1.0.1, not v1.0.2. + + The broadcast will lose audio after 7 or 8 movies play. You can restore audio + by pausing the client and then restarting. + + + + + + + +#endif \ No newline at end of file diff --git a/PlaylistBroadcaster.tproj/playlist_QTRTPBroadcastFile.cpp b/PlaylistBroadcaster.tproj/playlist_QTRTPBroadcastFile.cpp new file mode 100644 index 0000000..5d6b1a3 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_QTRTPBroadcastFile.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@ + * + */ +// $Id: playlist_QTRTPBroadcastFile.cpp,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTRTPBroadcastFile: +// An interface to QTFile for TimeShare. + + +// ------------------------------------- +// Includes +// +#include +#include +#include "SafeStdLib.h" +#include + +//#include "OSMutex.h" + +//#include "QTFile.h" + +//#include "QTTrack.h" +//#include "QTHintTrack.h" + +#include "playlist_QTRTPBroadcastFile.h" + + + +bool QTRTPBroadcastFile::FindTrackSSRC( UInt32 SSRC) +{ + // General vars + RTPTrackListEntry *ListEntry; + + + // + // Find the specified track. + for( ListEntry = fFirstTrack; ListEntry != NULL; ListEntry = ListEntry->NextTrack ) + { + // Check for matches. + if( ListEntry->SSRC == SSRC ) + { return true; + } + } + + // The search failed. + return false; +} + + diff --git a/PlaylistBroadcaster.tproj/playlist_QTRTPBroadcastFile.h b/PlaylistBroadcaster.tproj/playlist_QTRTPBroadcastFile.h new file mode 100644 index 0000000..7a0ac65 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_QTRTPBroadcastFile.h @@ -0,0 +1,56 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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: playlist_QTRTPBroadcastFile.h,v 1.1 2006/01/05 13:20:36 murata Exp $ +// +// QTRTPFile: +// An interface to QTFile for TimeShare. + +#ifndef QTRTPBroadcastFile_H +#define QTRTPBroadcastFile_H + + +// +// Includes +#include "OSHeaders.h" +#include "QTRTPFile.h" + + +#ifndef __Win32__ + #include +#endif + + + +class QTRTPBroadcastFile : public QTRTPFile { + + +public: + + bool FindTrackSSRC( UInt32 SSRC); + + +}; + +#endif // QTRTPBroadcastFile diff --git a/PlaylistBroadcaster.tproj/playlist_SDPGen.cpp b/PlaylistBroadcaster.tproj/playlist_SDPGen.cpp new file mode 100644 index 0000000..7150ac2 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_SDPGen.cpp @@ -0,0 +1,450 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "playlist_utils.h" +#include "playlist_SDPGen.h" +#include "playlist_broadcaster.h" +#include "QTRTPFile.h" +#include "OS.h" +#include "SocketUtils.h" +#include "SDPUtils.h" +#include "OSArrayObjectDeleter.h" + +short SDPGen::AddToBuff(char *aSDPFile, short currentFilePos, char *chars) +{ + short charLen = strlen(chars); + short newPos = currentFilePos + charLen; + + if (newPos <= eMaxSDPFileSize) + { memcpy(&aSDPFile[currentFilePos],chars,charLen); // only the chars not the \0 + aSDPFile[currentFilePos +charLen] = '\0'; + } + else + { newPos = (-newPos); + } + currentFilePos = newPos; + + return currentFilePos; +} + +UInt32 SDPGen::RandomTime(void) +{ + SInt64 curTime = 0; + curTime = PlayListUtils::Milliseconds(); + + curTime += rand() ; + return (UInt32) curTime; +} + +short SDPGen::GetNoExtensionFileName(char *pathName, char *result, short maxResult) +{ + char *start; + char *end; + char *sdpPath = pathName; + short pathNameLen = strlen(pathName); + short copyLen = 0; + + + do + { + start = strrchr(sdpPath, ePath_Separator); + if(start == NULL ) // no path separator + { start = sdpPath; + copyLen = pathNameLen; + break; + } + + start ++; // move start to one past the separator + end = strrchr(sdpPath, eExtension_Separator); + if (end == NULL) // no extension + { copyLen = strlen(start) + 1; + break; + } + + // both path separator and an extension + short startLen = strlen(start); + short endLen = strlen(end); + copyLen = startLen - endLen; + + } while (false); + + if (copyLen > maxResult) + copyLen = maxResult; + + memcpy(result, start, copyLen); + result[copyLen] = '\0'; + + return copyLen; +} + + + +char *SDPGen::Process( char *sdpFileName, + char * basePort, + char *ipAddress, + char *anSDPBuffer, + char *startTime, + char *endTime, + char *isDynamic, + char *ttl, + int *error + ) +{ + char *resultBuf = NULL; + short currentPos = 0; + short trackID = 1; + *error = -1; + do + { + fSDPFileContentsBuf = new char[eMaxSDPFileSize]; + // SDP required RFC 2327 + // v= (protocol version) + // o=
+ // s= (session name) + // c=IN IP4 (destinatin ip address) + // * see RFC for recommended Network Time Stamp (NTP implementation not required) + + // v= (protocol version) + { char version[] = "v=0\r\n"; + + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, version); + if (currentPos < 0) break; + } + + // o=
+ { char *userName = "QTSS_Play_List"; + UInt32 sessIDAsRandomTime = RandomTime(); + UInt32 versAsRandomTime = RandomTime(); + char ownerLine[255]; + + Assert(SocketUtils::GetIPAddrStr(0) != NULL); + Assert(SocketUtils::GetIPAddrStr(0)->Ptr != NULL); + qtss_sprintf(ownerLine, "o=%s %"_U32BITARG_" %"_U32BITARG_" IN IP4 %s\r\n",userName ,sessIDAsRandomTime,versAsRandomTime,SocketUtils::GetIPAddrStr(0)->Ptr); + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, ownerLine); + if (currentPos < 0) break; + } + + + // s= (session name) + { enum { eMaxSessionName = 64}; + char newSessionName[eMaxSessionName]; + short nameSize = 0; + char sessionLine[255]; + nameSize = GetNoExtensionFileName(sdpFileName, newSessionName, eMaxSessionName); + if (nameSize < 0) break; + + qtss_sprintf(sessionLine, "s=%s\r\n", newSessionName); + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, sessionLine); + if (currentPos < 0) break; + } + + // c=IN IP4 (destinatin ip address) + { + char sdpLine[255]; + if (SocketUtils::IsMulticastIPAddr(ntohl(inet_addr(ipAddress)))) + qtss_sprintf(sdpLine, "c=IN IP4 %s/%s\r\n", ipAddress,ttl); + else + qtss_sprintf(sdpLine, "c=IN IP4 %s\r\n", ipAddress); + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, sdpLine); + if (currentPos < 0) break; + } + + { + char controlLine[255]; + if ( 0 == ::strcmp( "enabled", isDynamic) ) + qtss_sprintf(controlLine, "a=x-broadcastcontrol:RTSP\r\n"); + else + qtss_sprintf(controlLine, "a=x-broadcastcontrol:TIME\r\n"); + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, controlLine); + if (currentPos < 0) break; + } + + + { + char timeLine[255]; + UInt32 startTimeNTPSecs = strtoul(startTime, NULL, 10); + UInt32 endTimeNTPSecs = strtoul(endTime, NULL, 10); + qtss_sprintf(timeLine, "t=%"_U32BITARG_" %"_U32BITARG_"\r\n", startTimeNTPSecs, endTimeNTPSecs); + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, timeLine); + if (currentPos < 0) break; + } + + { + SimpleString resultString; + SimpleString sdpBuffString(anSDPBuffer); + + short numLines = 0; + char *found = NULL; + + enum {eMaxLineLen = 1024}; + char aLine[eMaxLineLen +1]; + ::memset(aLine, 0, sizeof(aLine)); + + int portCount = atoi(basePort); + + + + SimpleParser sdpParser; + while ( sdpParser.GetLine(&sdpBuffString,&resultString) ) + { + numLines ++; + if (resultString.fLen > eMaxLineLen) continue; + + memcpy(aLine,resultString.fTheString,resultString.fLen); + aLine[resultString.fLen] = '\0'; + + int newBuffSize = sdpBuffString.fLen - (resultString.fLen); + char *newBuffPtr = &resultString.fTheString[resultString.fLen]; + + sdpBuffString.SetString(newBuffPtr, newBuffSize); + + char firstChar = aLine[0]; + { // we are setting these so ignore any defined + if (firstChar == 'v') continue;// (protocol version) + if (firstChar == 'o') continue; //(owner/creator and session identifier). + if (firstChar == 's') continue; //(session name) + if (firstChar == 'c') continue; //(connection information - optional if included at session-level) + } + + { // no longer important as a play list broadcast + // there may be others that should go here....... + if (firstChar == 't') continue;// (time the session is active) + if (firstChar == 'r') continue;// (zero or more repeat times) + + // found = strstr(aLine, "a=cliprect"); // turn this off + // if (found != NULL) continue; + + found = strstr(aLine, "a=control:trackID"); // turn this off + if (!fKeepTracks) + { + if (fAddIndexTracks) + { + if (found != NULL) + { char mediaLine[eMaxLineLen]; + qtss_sprintf(mediaLine,"a=control:trackID=%d\r\n",trackID); + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, mediaLine); // copy rest of line starting with the transport protocol + trackID ++; + } + } + if (found != NULL) continue; + } + + + found = strstr(aLine, "a=range"); // turn this off + if (found != NULL) continue; + + // found = strstr(aLine, "a=x"); // turn this off - + // if (found != NULL) continue; + } + + { // handle the media line and put in the port value past the media type + found = strstr(aLine,"m="); //(media name and transport address) + if (found != NULL) + { + char *startToPortVal = strtok(aLine," "); + strtok(NULL," "); // step past the current port value we put it in below + if (found != NULL) + { char mediaLine[eMaxLineLen]; + char *protocol = strtok(NULL,"\r\n"); // the transport protocol + + qtss_sprintf(mediaLine,"%s %d %s\r\n",startToPortVal,portCount,protocol); + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, mediaLine); // copy rest of line starting with the transport protocol + if (portCount != 0) + portCount += 2; // set the next port value ( this port + 1 is the RTCP port for this port so we skip by 2) + continue; + } + } + } + + { // this line looks ok so just get it and make sure it has a carriage return + new line at the end + // also remove any garbage that might be there + // this is a defensive measure because the hinter may have bad line endings like a single \n or \r or + // there might also be chars after the last line delimeter and before a \0 so we get rid of it. + + short lineLen = strlen(aLine); + + // get rid of trailing characters + while (lineLen > 0 && (NULL == strchr("\r\n",aLine[lineLen])) ) + { aLine[lineLen] = '\0'; + lineLen --; + } + + // get rid of any line feeds and carriage returns + while (lineLen > 0 && (NULL != strchr("\r\n",aLine[lineLen])) ) + { aLine[lineLen] = '\0'; + lineLen --; + } + aLine[lineLen + 1] = '\r'; + aLine[lineLen + 2] = '\n'; + aLine[lineLen + 3] = 0; + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, aLine); // copy this line + } + } + + // buffer delay + if (fClientBufferDelay > 0.0) + { + char delayLine[255]; + qtss_sprintf(delayLine, "a=x-bufferdelay:%.2f\r\n", fClientBufferDelay); + currentPos = AddToBuff(fSDPFileContentsBuf, currentPos, delayLine); + if (currentPos < 0) break; + } + } + + + StrPtrLen theSDPStr(fSDPFileContentsBuf); + SDPContainer rawSDPContainer; + if (!rawSDPContainer.SetSDPBuffer( &theSDPStr )) + { delete [] fSDPFileContentsBuf; + fSDPFileContentsBuf = NULL; + return NULL; + } + + SDPLineSorter sortedSDP(&rawSDPContainer); + resultBuf = sortedSDP.GetSortedSDPCopy(); // return a new copy of the sorted SDP + delete [] fSDPFileContentsBuf; // this has to happen after GetSortedSDPCopy + fSDPFileContentsBuf = resultBuf; + + *error = 0; + + } while (false); + + return resultBuf; +} + +int SDPGen::Run( char *movieFilename + , char *sdpFilename + , char *basePort + , char *ipAddress + , char *buff + , short buffSize + , bool overWriteSDP + , bool forceNewSDP + , char *startTime + , char *endTime + , char *isDynamic + , char *ttl + ) +{ + int result = -1; + int fdsdp = -1; + bool sdpExists = false; + + do + { + if (!movieFilename) break; + if (!sdpFilename) break; + + if (buff && buffSize > 0) // set buff to 0 length string + buff[0] = 0; + + fdsdp = open(sdpFilename, O_RDONLY, 0); + if (fdsdp != -1) + sdpExists = true; + + if (sdpExists && !forceNewSDP) + { + if (!overWriteSDP) + { + if (buff && (buffSize > 0)) + { int count = ::read(fdsdp,buff, buffSize -1); + if (count > 0) + buff[count] = 0; + } + + } + + close(fdsdp); + fdsdp = -1; + + if (!overWriteSDP) + { result = 0; + break; // leave nothing to do + } + } + + QTRTPFile::ErrorCode err = fRTPFile.Initialize(movieFilename); + result = QTFileBroadcaster::EvalErrorCode(err); + if( result != QTRTPFile::errNoError ) + break; + + // Get the file + int sdpFileLength=0; + int processedSize=0; + char *theSDPText = fRTPFile.GetSDPFile(&sdpFileLength); + + if( theSDPText == NULL || sdpFileLength <= 0) + { break; + } + + char *processedSDP = NULL; + processedSDP = Process(sdpFilename, basePort, ipAddress, theSDPText,startTime,endTime,isDynamic, ttl, &result); + if (result != 0) break; + + processedSize = strlen(processedSDP); + + if (buff != NULL) + { if (buffSize > processedSize ) + { + buffSize = processedSize; + } + memcpy(buff,processedSDP,buffSize); + buff[buffSize] = 0; + } + + if (!overWriteSDP && sdpExists) + { + break; + } + // Create our SDP file and write out the data +#ifdef __Win32__ + fdsdp = open(sdpFilename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, 0664); +#else + fdsdp = open(sdpFilename, O_CREAT | O_TRUNC | O_WRONLY, 0664); +#endif + if( fdsdp == -1 ) + { + //result = -1; + result = -2; + break; + } + write(fdsdp, processedSDP, processedSize); + result = 0; + + // report that we made a file + fSDPFileCreated = true; + + + } while (false); + + if (fdsdp != -1) + { result = 0; + close(fdsdp); + fdsdp = -1; + } + + return result; + +} + diff --git a/PlaylistBroadcaster.tproj/playlist_SDPGen.h b/PlaylistBroadcaster.tproj/playlist_SDPGen.h new file mode 100644 index 0000000..ffdd239 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_SDPGen.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@ + * + */ +// SimpleParse: + +#ifndef SDPGen_H +#define SDPGen_H + +#include +#include +#include "SafeStdLib.h" +#include +#include + +#ifndef __Win32__ + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#include "playlist_SimpleParse.h" +#include "QTRTPFile.h" + +class SDPGen +{ + enum {eMaxSDPFileSize = 10240}; + enum {ePath_Separator = '/', eExtension_Separator = '.'}; + + private: + char *fSDPFileContentsBuf; + + protected: + QTRTPFile fRTPFile; + bool fKeepTracks; + bool fAddIndexTracks; + short AddToBuff(char *aSDPFile, short currentFilePos, char *chars); + UInt32 RandomTime(void); + short GetNoExtensionFileName(char *pathName, char *result, short maxResult); + char *Process( char *sdpFileName, + char * basePort, + char *ipAddress, + char *anSDPBuffer, + char *startTime, + char *endTime, + char *isDynamic, + char *ttl, + int *error + ); + + public: + SDPGen() : fSDPFileContentsBuf(NULL), fKeepTracks(false),fAddIndexTracks(false), fSDPFileCreated(false),fClientBufferDelay(0.0) { QTRTPFile::Initialize(); }; + ~SDPGen() { if (fSDPFileContentsBuf) delete fSDPFileContentsBuf; }; + void KeepSDPTracks(bool enabled) { fKeepTracks = enabled;} ; + void AddIndexTracks(bool enabled) { fAddIndexTracks = enabled;} ; + void SetClientBufferDelay(Float32 bufferDelay) { fClientBufferDelay = bufferDelay;} ; + int Run( char *movieFilename + , char *sdpFilename + , char *basePort + , char *ipAddress + , char *buff + , short buffSize + , bool overWriteSDP + , bool forceNewSDP + , char *startTime + , char *endTime + , char *isDynamic + , char *ttl + ); + + bool fSDPFileCreated; + Float32 fClientBufferDelay; +}; + +#endif + diff --git a/PlaylistBroadcaster.tproj/playlist_SimpleParse.cpp b/PlaylistBroadcaster.tproj/playlist_SimpleParse.cpp new file mode 100644 index 0000000..b0bdfa4 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_SimpleParse.cpp @@ -0,0 +1,357 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "playlist_SimpleParse.h" + + +SimpleString::SimpleString(char *theString) +{ + fTheString = theString; + if (theString == NULL) + fLen = 0; + else + fLen = strlen(theString); +} + +void SimpleString::Init() +{ + fTheString = NULL; + fLen = 0; + +} + +void SimpleString::SetString(char *theString, SInt32 len) +{ + fTheString = theString; + fLen = len; +} + +SInt32 SimpleString::GetString(char *theString, SInt32 len) +{ + SInt32 copyLen = fLen + 1; + if (len < copyLen ) copyLen = len; + if (copyLen > 0) + { memcpy(theString,fTheString,copyLen); + theString[copyLen -1] = 0; + } + + return copyLen; +} + +SInt32 SimpleString::GetInt() +{ + SInt32 result = 0; + + if (fTheString == NULL|| fLen == 0 || fLen > 32) + return result; + + char* buff = new char[fLen +1]; + memcpy (buff, fTheString,fLen); + buff[fLen] = 0; + + result = strtol(buff, NULL, 0); + delete [] buff; + + return result; +} + +void SimpleString::Print() +{ + char* buff = new char[fLen +1]; + memcpy (buff, fTheString,fLen); + buff[fLen] = 0; + printf("SimpleString( len=%"_S32BITARG_" str=>%s< )\n",fLen,buff); + delete [] buff; +} + + +char SimpleParser::sWordDelimeters[] = "=:/\t \r\n"; +char SimpleParser::sLineDelimeters[] = "\r\n"; + +bool SimpleParser::Compare(SimpleString *str1Ptr, SimpleString *str2Ptr, bool caseSensitive) +{ + bool result = false; + + do + { + if (NULL == str1Ptr) break; + if (NULL == str2Ptr) break; + + if (NULL == str1Ptr->fTheString) break; + if (NULL == str2Ptr->fTheString) break; + + if (str1Ptr->fLen != str2Ptr->fLen) + break; + + int test = 0; + if (caseSensitive) + test = strncmp(str1Ptr->fTheString, str2Ptr->fTheString,str1Ptr->fLen); + else +#if __Win32__ + + test = _strnicmp(str1Ptr->fTheString, str2Ptr->fTheString,str1Ptr->fLen); +#else + + test = strncasecmp(str1Ptr->fTheString, str2Ptr->fTheString,str1Ptr->fLen); + +#endif + + + if (test != 0) break; + + result = true; + } while (false); + + + return result; +} + +bool SimpleParser::FindString( SimpleString *sourcePtr, SimpleString *findPtr, SimpleString *resultStringPtr) +{ + bool result = false; + + do + { + if (NULL == sourcePtr) break; + if (NULL == findPtr) break; + + if (NULL == sourcePtr->fTheString) break; + if (NULL == findPtr->fTheString) break; + + if (findPtr->fLen > sourcePtr->fLen) + break; + + char *start = strstr(sourcePtr->fTheString, findPtr->fTheString); + if (start == NULL) break; + + if (NULL != resultStringPtr) + { + SInt32 len = (PointerSizedInt) start - (PointerSizedInt) sourcePtr->fTheString; + if (len > sourcePtr->fLen) break; + + resultStringPtr->SetString(start, findPtr->fLen); + } + + result = true; + } while (false); + + return result; +} + +bool SimpleParser::FindNextString( SimpleString *sourcePtr, SimpleString *currentPtr, SimpleString *findPtr, SimpleString *resultStringPtr) +{ + bool result = false; + SInt32 length = 0; + + + do + { + if (NULL == sourcePtr) break; + if (NULL == currentPtr) break; + if (NULL == findPtr) break; + if (NULL == sourcePtr->fTheString) break; + if (NULL == currentPtr->fTheString) break; + + SimpleString tempSource(NULL); + + + length = currentPtr->fTheString - sourcePtr->fTheString; + if (length < 0) break; + if (length > sourcePtr->fLen) break; + + length = sourcePtr->fLen - (length + currentPtr->fLen); // the remaining length to search + tempSource.SetString(¤tPtr->fTheString[currentPtr->fLen], length); // step past the end of current with remaining length + + result = FindString(&tempSource, findPtr, resultStringPtr); + + } while (false); + + return result; + +} + + +bool SimpleParser::GetString( SimpleString *sourcePtr, SimpleString *findPtr, SimpleString *resultStringPtr) +{ + bool result = false; + + result = FindString(sourcePtr, findPtr, resultStringPtr); + + if (result) + { resultStringPtr->fLen = (PointerSizedInt) resultStringPtr->fTheString -(PointerSizedInt) sourcePtr->fTheString; + resultStringPtr->fTheString = sourcePtr->fTheString; + } + + return result; +} + +bool SimpleParser::FindDelimeter( SimpleString *sourcePtr, char *findChars, SimpleString *resultStringPtr) +{ + bool result = false; + + do + { + if (NULL == sourcePtr) break; + if (NULL == findChars) break; + if (NULL == resultStringPtr) break; + + SInt32 charCount = 0; + char* charOffset = sourcePtr->fTheString; + char* foundChar = NULL; + + if ( (NULL == sourcePtr->fTheString) || (0 == sourcePtr->fLen) ) + { //qtss_printf("NULL string in FindDelimeter \n"); + break; + } + + // skip past any delimeters + while ( (*charOffset != 0) && (charCount <= sourcePtr->fLen) ) + { foundChar = strchr(findChars, *charOffset); + if (NULL == foundChar) break; // found non delimeter char + charOffset ++; charCount ++; + } + + char *theChar = charOffset; // start past delimeters + + while ( (*theChar != 0) && (charCount <= sourcePtr->fLen) ) + { + foundChar = strchr(findChars, *theChar); + if (NULL != foundChar) break; // found delimeter + charCount++; + theChar++; + } + if (NULL == foundChar) break; // we didn't find a delimeter; + + + if (NULL != resultStringPtr) + { UInt32 theLen = ((PointerSizedInt) theChar - (PointerSizedInt)charOffset); + resultStringPtr->SetString(charOffset, theLen); // start is charOffset + if (theLen == 0) break; + } + + result = true; + } while (false); + + if (!result) + resultStringPtr->SetString(NULL, 0); // start is charOffset + + return result; +} + + +int SimpleParser::CountDelimeters( SimpleString *sourcePtr, char *delimeters) +{ + short count = 0; + if (sourcePtr && delimeters) + { + SimpleString currentString = *sourcePtr; + currentString.fLen = 0; + + while (GetNextThing(sourcePtr,¤tString, delimeters, ¤tString)) + { count ++; + } + } + return count; +} + + + +bool SimpleParser::GetWord( SimpleString *sourcePtr, SimpleString *resultStringPtr) +{ + bool result = false; + char delimeter[] = " :/"; // space or colon or / + result = FindDelimeter(sourcePtr, delimeter,resultStringPtr); + return result; +} + +bool SimpleParser::GetLine(SimpleString *sourcePtr, SimpleString *resultStringPtr) +{ + bool result = false; + char delimeter[] = "\r\n"; + result = FindDelimeter(sourcePtr, delimeter,resultStringPtr); + return result; +} + +bool SimpleParser::GetNextThing(SimpleString *sourcePtr, SimpleString *currentPtr, char *findChars, SimpleString *resultStringPtr) +{ + bool result = false; + + do + { + if (NULL == sourcePtr) break; + if (NULL == currentPtr) break; + if (NULL == findChars) break; + + if ( (PointerSizedInt) currentPtr->fTheString < (PointerSizedInt) sourcePtr->fTheString) + break; + + if (NULL == sourcePtr->fTheString) + { //qtss_printf("NULL sourcePtr->fTheString in GetNextThing \n"); + break; + } + if (NULL == currentPtr->fTheString) + { //qtss_printf("NULL currentPtr->fTheString in GetNextThing \n"); + break; + } + + PointerSizedInt endSource = (PointerSizedInt) &sourcePtr->fTheString[sourcePtr->fLen]; + PointerSizedInt endCurrent = (PointerSizedInt) ¤tPtr->fTheString[currentPtr->fLen]; + + if (endCurrent > endSource) break; + + SimpleString tempSource(NULL); + + + UInt32 searchLen = endSource - endCurrent; + + tempSource.SetString(¤tPtr->fTheString[currentPtr->fLen], searchLen); // step past the end of current with remaining length + + result = FindDelimeter(&tempSource, findChars,resultStringPtr); + + } while (false); + + return result; +} + +bool SimpleParser::GetNextWord( SimpleString *sourcePtr, SimpleString *currentWord, SimpleString *resultStringPtr) +{ + bool result = false; + char *findChars = sWordDelimeters;// + result = GetNextThing(sourcePtr,currentWord, findChars, resultStringPtr); + return result; +} + +bool SimpleParser::GetNextLine( SimpleString *sourcePtr, SimpleString *currentLine, SimpleString *resultStringPtr) +{ + bool result = false; + char *findChars = sLineDelimeters;//"\r\n"; + result = GetNextThing(sourcePtr,currentLine, findChars, resultStringPtr); + return result; +} diff --git a/PlaylistBroadcaster.tproj/playlist_SimpleParse.h b/PlaylistBroadcaster.tproj/playlist_SimpleParse.h new file mode 100644 index 0000000..b5634ea --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_SimpleParse.h @@ -0,0 +1,96 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 SimpleParser_H +#define SimpleParser_H + + +#include "OSHeaders.h" + + +class SimpleString { + + public: + SInt32 fLen; + char *fTheString; + + SimpleString(char *theString = NULL); + void Init(); + void SetString(char *theString, SInt32 len); + SInt32 GetString(char *theString, SInt32 len); + SInt32 GetInt(); + void Print(); +}; + +class SimpleParser { + + + + protected: + SimpleString fSource; + public: + + SimpleParser() : fSource(NULL) {} ; + SimpleParser(SimpleString *source) : fSource(*source) {} ; + ~SimpleParser() {}; + + static char sWordDelimeters[]; + static char sLineDelimeters[]; + + void SetSource(SimpleString *source) {fSource = *source;}; + + static int CountDelimeters( SimpleString *source, char *delimeters); + + static bool Compare(SimpleString *str1, SimpleString*str2, bool caseSensitive); + + bool Compare(SimpleString *str1Ptr, char *str, bool caseSensitive) + { + if (str == NULL) return false; + SimpleString string(str); + return Compare(str1Ptr, &string, caseSensitive); + } + + static bool FindString( SimpleString *source, SimpleString *find, SimpleString *resultString); + + static bool FindNextString( SimpleString *sourcePtr, SimpleString *currentPtr, SimpleString *findPtr, SimpleString *resultStringPtr); + + static bool GetString( SimpleString *source, SimpleString *find, SimpleString *resultString); + + static bool FindDelimeter( SimpleString *source, char *delimeter, SimpleString *resultString); + + static bool GetLine( SimpleString *sourcePtr, SimpleString *resultStringPtr); + + static bool GetWord( SimpleString *sourcePtr, SimpleString *resultStringPtr); + + static bool GetNextThing( SimpleString *sourcePtr, SimpleString *currentPtr, char *findChars, SimpleString *resultStringPtr); + + static bool GetNextLine( SimpleString *sourcePtr, SimpleString *currentLine, SimpleString *resultStringPtr); + + static bool GetNextWord( SimpleString *sourcePtr, SimpleString *currentWord, SimpleString *resultStringPtr); +}; + + + +#endif diff --git a/PlaylistBroadcaster.tproj/playlist_array.h b/PlaylistBroadcaster.tproj/playlist_array.h new file mode 100644 index 0000000..959e55e --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_array.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@ + * + */ + +#ifndef playlist_array_H +#define playlist_array_H + +// ************************ +// +// TEMPLATE ARRAYLIST +// +// ************************ + +template class ArrayList +{ + + public: + T *fDataArray; + short fSize; + short fCurrent; + + ArrayList() + { fDataArray = NULL; + fSize = 0; + fCurrent = 0; + } + + ~ArrayList() + { delete [] fDataArray; + }; + + T *SetSize(short size) + { if (fDataArray != NULL) delete [] fDataArray; + fDataArray = new T[size]; + if (fDataArray) + { fSize = size; + } + + return fDataArray; + } + + T *Get() + { + T *resultPtr = NULL; + if (fDataArray != NULL) + { + if ( (fCurrent >= 0) && (fCurrent < fSize ) ) + { resultPtr = &fDataArray[fCurrent]; + } + } + return resultPtr; + } + + T *OffsetPos(short offset) + { + T *resultPtr = NULL; + + do + { + if (fDataArray == NULL) break; + short temp = fCurrent + offset; + + if (temp < 0) + { + fCurrent = 0; // peg at begin and return NULL + break; + } + + if (temp < fSize) + { + resultPtr = &fDataArray[temp]; + fCurrent = temp; + break; + } + + fCurrent = fSize -1; // peg at end and return NULL + } while (false); + + return resultPtr; + } + + T *SetPos(short current) + { + T *resultPtr = NULL; + + do + { + if (fDataArray == NULL) break; + + if (current < 0) + { + fCurrent = 0; // peg at begin and return NULL + break; + } + + if (current < fSize) + { + resultPtr = &fDataArray[current]; + fCurrent = current; + break; + } + + fCurrent = fSize -1; // peg at end and return NULL + + } while (false); + + return resultPtr; + } + + short GetPos() { return fCurrent; }; + T *Next() { return OffsetPos(1); }; + T *Begin() { return SetPos(0); }; + short Size() { return fSize;}; +}; + + +#endif + diff --git a/PlaylistBroadcaster.tproj/playlist_broadcaster.cpp b/PlaylistBroadcaster.tproj/playlist_broadcaster.cpp new file mode 100644 index 0000000..eae3d6f --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_broadcaster.cpp @@ -0,0 +1,765 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "playlist_broadcaster.h" +#include "OS.h" +#include "OSThread.h" +#include "BroadcasterSession.h" + +#include + +QTFileBroadcaster::QTFileBroadcaster() +{ + fRTPFilePtr = NULL; + fMovieSDPParser = NULL; + fBasePort = 0; + fDebug = false; + fDeepDebug = false; + fQuitImmediatePtr = NULL; +// transmit time trackers + fLastTransmitTime = 0.0; + + fStreamStartTime = 0; + fMovieStartTime = 0; + fMovieEndTime = 0; + fMovieIntervalTime = 0; + fMovieTimeDiffMilli = 0; + + fMovieStart = false; + fSendTimeOffset = 0.0; + fMovieDuration = 0.0; + fMovieTracks = 0; + fMappedMovieTracks = 0; + fNumMoviesPlayed = 0; + fPlay = true; + fSend = true; + fBroadcastDefPtr = NULL; + // current movie parameters parsed from SDP + ::strcpy(fCurrentMovieName, "-"); + ::strcpy(fCurrentMovieCopyright, "-"); + ::strcpy(fCurrentMovieComment, "-"); + ::strcpy(fCurrentMovieAuthor, "-"); + ::strcpy(fCurrentMovieArtist, "-"); + ::strcpy(fCurrentMovieAlbum, "-"); +} + +QTFileBroadcaster::~QTFileBroadcaster() +{ + LogFileClose(); + if (fRTPFilePtr != NULL) + { delete fRTPFilePtr; + } +} + +int QTFileBroadcaster::SetUp(PLBroadcastDef *broadcastDefPtr, bool *quitImmediatePtr) +{ + int result = -1; + int numStreams = 0; + fQuitImmediatePtr = quitImmediatePtr; + PlayListUtils::Initialize(); + do + { + if (! broadcastDefPtr) + { result = eParam; break; }; + + if (!broadcastDefPtr->mSDPFile || 0 == broadcastDefPtr->mSDPFile[0] ) + { result = eSDPFileInvalidName; break; }; + + int nameLen = strlen(broadcastDefPtr->mSDPFile); + if (nameLen > 255) + { result = eSDPFileInvalidName; break; }; + + if (0 == nameLen) + { result = eSDPFileInvalidName; break; }; + + if (broadcastDefPtr->mTheSession) + { if (!broadcastDefPtr->mDestSDPFile) + { result = eNetworkSDPFileNameInvalidMissing; break; }; + + if (!broadcastDefPtr->mDestSDPFile[0]) + { result = eNetworkSDPFileNameInvalidMissing; break; }; + + if (!::strcmp(broadcastDefPtr->mDestSDPFile, "no_name")) + { result = eNetworkSDPFileNameInvalidMissing; break; }; + +// if ('/' == broadcastDefPtr->mDestSDPFile[0]) +// { result = eNetworkSDPFileNameInvalidBadPath; break; }; + } + + result = fStreamSDPParser.ReadSDP(broadcastDefPtr->mSDPFile); + + if (result != 0) + { if (result < 0) { result = eSDPFileNotFound; break; }; + if (result > 0) { result = eSDPFileInvalid; break; }; + } + + + fBroadcastDefPtr = broadcastDefPtr; + if (broadcastDefPtr->mTheSession == NULL) + { if (!broadcastDefPtr->mBasePort) { result = eSDPFileNoPorts; break; }; + + int portLen = strlen(broadcastDefPtr->mBasePort); + if (0 == portLen) { result = eDescriptionInvalidDestPort; break; }; + if (portLen > 5 ) { result = eDescriptionInvalidDestPort; break; }; + + int basePort = atoi(broadcastDefPtr->mBasePort); + if ( basePort > 65531 ) { result = eDescriptionInvalidDestPort; break; }; + if ( basePort < 5004 ) { result = eDescriptionInvalidDestPort; break; }; + + } + + numStreams = fStreamSDPParser.GetNumTracks(); + if (numStreams == 0) { result = eSDPFileNoMedia; break; }; + + UDPSocketPair *socketArrayPtr = fSocketlist.SetSize(numStreams); + if (socketArrayPtr == NULL) { result = eMem; break; }; + + // Bind SDP file defined ports to active stream ports + { + UInt16 streamIndex = 0; + UInt16 rtpPort = 0; + UInt16 rtcpPort = 0; + TypeMap* mediaTypePtr; + char sdpIPAddress[32]; + SimpleString* ipStringPtr = fStreamSDPParser.GetIPString(); + + if ( (NULL == ipStringPtr) || (ipStringPtr->fLen >= 32) ) + { + result = eSDPFileInvalid; + break; + } + + memcpy(sdpIPAddress,ipStringPtr->fTheString,ipStringPtr->fLen); + sdpIPAddress[ipStringPtr->fLen] = '\0'; + + UDPSocketPair *aSocketPair = fSocketlist.Begin(); + Bool16 setupUDP = true; + while (aSocketPair != NULL) + { + mediaTypePtr = fStreamSDPParser.fSDPMediaList.SetPos(streamIndex); + + if (mediaTypePtr == NULL) + { result = eSDPFileInvalid; + break; + } + + if (broadcastDefPtr->mTheSession != NULL) + { + mediaTypePtr->fPort = broadcastDefPtr->mTheSession->GetStreamDestPort(streamIndex); + //qtss_printf("QTFileBroadcaster::SetUp streamIndex=%u port=%d\n",streamIndex,mediaTypePtr->fPort); + + if (BroadcasterSession::kTCPTransportType == broadcastDefPtr->mTheSession->GetTransportType()) + { aSocketPair->SetRTSPSession(broadcastDefPtr->mTheSession, (UInt8) streamIndex * 2); + setupUDP = false; + } + else + { setupUDP = true; + } + } + + if (setupUDP) + { + SInt16 ttl = (SInt16) atoi(broadcastDefPtr->mTTL); + if ( ( ttl > 255 ) || ( ttl < 1 ) ) + { result = eSDPFileInvalidTTL; break; + }; + + if (mediaTypePtr->fPort == 0) + { + result = eSDPFileInvalidPort; + break; + } + + rtpPort = mediaTypePtr->fPort; + rtcpPort = rtpPort + 1; + + result = aSocketPair->OpenAndBind(rtpPort,rtcpPort,sdpIPAddress); + if (result != 0) + { + result = eFailedBind; + break; + } + + (void) aSocketPair->SetMultiCastOptions(ttl); + + } + + aSocketPair = fSocketlist.Next(); + streamIndex++; + } + + if (result != 0) + break; + } + + + MediaStream *mediaArrayPtr = fMediaStreamList.SetSize(numStreams); + if (mediaArrayPtr == NULL) + { + result = eMem; + break; + } + + for (int i = 0; i < numStreams; i ++) + { UDPSocketPair *socketPairPtr = fSocketlist.SetPos(i); + MediaStream *mediaStreamPtr = fMediaStreamList.SetPos(i); + TypeMap *streamMediaTypePtr = fStreamSDPParser.fSDPMediaList.SetPos(i); + + if (socketPairPtr && mediaStreamPtr && streamMediaTypePtr) + { + mediaStreamPtr->fData.fSocketPair = socketPairPtr; + streamMediaTypePtr->fMediaStreamPtr = mediaStreamPtr; + mediaStreamPtr->fData.fStreamMediaTypePtr = streamMediaTypePtr; + } + else + { + result = eMem; + break; + } + } + + + fMediaStreamList.SetUpStreamSSRCs(); + fStreamStartTime = PlayListUtils::Milliseconds(); + fMediaStreamList.StreamStarted(fStreamStartTime); + result = 0; + LogFileOpen(); + + } while (false); + + return result; +} + + +PayLoad * QTFileBroadcaster::FindPayLoad(short id, ArrayList *PayLoadListPtr) +{ + PayLoad *thePayLoadPtr = PayLoadListPtr->Begin(); + while (thePayLoadPtr) + { + if (thePayLoadPtr->payloadID == id) + break; + + thePayLoadPtr = PayLoadListPtr->Next(); + } + return thePayLoadPtr; +} + + +bool QTFileBroadcaster::CompareRTPMaps(TypeMap *movieMediaTypePtr, TypeMap *streamMediaTypePtr, short moviePayLoadID) +{ + bool found = false; + + do + { + PayLoad *moviePayLoadPtr = FindPayLoad(moviePayLoadID, &movieMediaTypePtr->fPayLoadTypes); + if (!moviePayLoadPtr) break; + + PayLoad *streamPayLoadPtr = streamMediaTypePtr->fPayLoadTypes.Begin(); + while (streamPayLoadPtr) + { + if (moviePayLoadPtr->timeScale == streamPayLoadPtr->timeScale) + { found = SimpleParser::Compare(&moviePayLoadPtr->payLoadString,&streamPayLoadPtr->payLoadString, false ); + } + + if (found) + { moviePayLoadPtr->payloadID = streamPayLoadPtr->payloadID; // map movie ID to match stream id + break; + } + streamPayLoadPtr = streamMediaTypePtr->fPayLoadTypes.Next(); + } + + + } while (false); + return found; +} + +bool QTFileBroadcaster::CompareMediaTypes(TypeMap *movieMediaTypePtr, TypeMap *streamMediaTypePtr) +{ + bool found = false; + + found = SimpleParser::Compare(&movieMediaTypePtr->fTheTypeStr,&streamMediaTypePtr->fTheTypeStr, false); + if (found) + { + found = false; + + short *movieIDPtr = movieMediaTypePtr->fPayLoads.Begin(); + while (movieIDPtr && !found) + { + short *streamIDPtr = streamMediaTypePtr->fPayLoads.Begin(); + while (streamIDPtr && !found) + { + + if (*movieIDPtr >= 96) + found = CompareRTPMaps(movieMediaTypePtr, streamMediaTypePtr, *movieIDPtr); + else + found = (*streamIDPtr == *movieIDPtr) ? true : false; + + streamIDPtr = streamMediaTypePtr->fPayLoads.Next(); + } + + movieIDPtr = movieMediaTypePtr->fPayLoads.Next(); + } + } + + + return found; +} + + +int QTFileBroadcaster::MapMovieToStream() +{ + int result = -1; + bool matches = false; + ArrayList map; + bool *isMappedPtr; + int masterPos = 0; + int mappedTracks = 0; + + map.SetSize(fStreamSDPParser.fSDPMediaList.Size()); + + isMappedPtr = map.Begin(); + + while (isMappedPtr) + { *isMappedPtr = false; + isMappedPtr = map.Next(); + } + + TypeMap *movieMediaTypePtr = fMovieSDPParser->fSDPMediaList.Begin(); + + while (movieMediaTypePtr) + { + TypeMap *streamMediaTypePtr = fStreamSDPParser.fSDPMediaList.Begin(); + + while (streamMediaTypePtr) + { + matches = CompareMediaTypes(movieMediaTypePtr, streamMediaTypePtr); + + if (matches) + { + masterPos = fStreamSDPParser.fSDPMediaList.GetPos(); + isMappedPtr = map.SetPos(masterPos); + if (isMappedPtr == NULL) + break; + + if (false == *isMappedPtr) + { + movieMediaTypePtr->fMediaStreamPtr = streamMediaTypePtr->fMediaStreamPtr; + *isMappedPtr = true; + mappedTracks++; + break; + } + } + streamMediaTypePtr = fStreamSDPParser.fSDPMediaList.Next(); + } + movieMediaTypePtr = fMovieSDPParser->fSDPMediaList.Next(); + } + + result = mappedTracks; + + return result; + +} + + +UInt32 QTFileBroadcaster::GetSDPTracks(QTRTPFile *newRTPFilePtr) +{ + char* sdpBuffer; + int bufferLen = 0; + UInt32 result = 0; + + sdpBuffer = newRTPFilePtr->GetSDPFile(&bufferLen); + fMovieSDPParser->ParseSDP(sdpBuffer); + result = fMovieSDPParser->GetNumTracks(); + + return result; +} + + +int QTFileBroadcaster::AddTrackAndStream(QTRTPFile *newRTPFilePtr) +{ + TypeMap* movieMediaTypePtr = fMovieSDPParser->fSDPMediaList.Begin(); + UInt32 trackID; + char* cookie; + int err = -1; + + while (movieMediaTypePtr) + { + if (movieMediaTypePtr->fMediaStreamPtr != NULL) + { + trackID = movieMediaTypePtr->fTrackID; + if (trackID == 0) break; + + + movieMediaTypePtr->fMediaStreamPtr->fData.fMovieMediaTypePtr = movieMediaTypePtr; + movieMediaTypePtr->fMediaStreamPtr->fData.fRTPFilePtr = newRTPFilePtr; + movieMediaTypePtr->fMediaStreamPtr->fSend = fSend; + + cookie = (char *) movieMediaTypePtr->fMediaStreamPtr; + err = newRTPFilePtr->AddTrack(trackID, false); + if (err != 0) + break; + + newRTPFilePtr->SetTrackCookies(trackID, cookie, 0); + newRTPFilePtr->SetTrackSSRC(trackID, (UInt32) 0); // set later + + err = newRTPFilePtr->Seek(0.0); + if (err != QTRTPFile::errNoError) + break; + + } + + movieMediaTypePtr = fMovieSDPParser->fSDPMediaList.Next(); + } + + return err; +} + +int QTFileBroadcaster::SetUpAMovie(char *theMovieFileName) +{ + int err = -1; + QTRTPFile *newRTPFilePtr = NULL; + do { + fMovieTracks = 0; + fMappedMovieTracks = 0; + + if (fMovieSDPParser != NULL) delete fMovieSDPParser; + if (fRTPFilePtr != NULL) delete fRTPFilePtr; + + fMovieSDPParser = NULL; + fRTPFilePtr = NULL; + + if (!theMovieFileName) { err = eMovieFileInvalidName; break; } + int nameLen = strlen(theMovieFileName); + if (nameLen > 255) { err = eMovieFileInvalidName; break; } + if (0 == nameLen) { err = eMovieFileInvalidName; break; } + + fMovieSDPParser = new SDPFileParser; + if (NULL == fMovieSDPParser) { err = eMem; break;} + + newRTPFilePtr = new QTRTPFile(); + if (NULL == newRTPFilePtr) { err = eMem; break;} + + QTRTPFile::ErrorCode result = newRTPFilePtr->Initialize(theMovieFileName); + err = EvalErrorCode(result); + if (err) break; + + fMovieTracks = GetSDPTracks(newRTPFilePtr); + if (fMovieTracks < 1) { err = eMovieFileNoHintedTracks; break; } + + fMappedMovieTracks = MapMovieToStream(); + if (fMappedMovieTracks < 1) { err = eMovieFileNoSDPMatches; break; } + + err = AddTrackAndStream(newRTPFilePtr); + if (err != 0) { err = eMovieFileInvalid; break; } + + } while (false); + + if (err) + { if (newRTPFilePtr) + delete newRTPFilePtr; + newRTPFilePtr = NULL; + } + + fRTPFilePtr = newRTPFilePtr; + + return err; +} + +void QTFileBroadcaster::MilliSleep(SInt32 sleepTimeMilli) +{ + if (sleepTimeMilli > 0) + { + #if __solaris__ + struct timeval tv; + ::memset(&tv,0,sizeof(tv)); + tv.tv_usec = sleepTimeMilli * 1000; + ::select(0,NULL,NULL,NULL,&tv); + #else + OSThread::Sleep( (UInt32) sleepTimeMilli); + #endif + } +} + +Float64 QTFileBroadcaster::Sleep(Float64 transmitTimeMilli) +{ + Float64 sleepTime; + Float64 timeToSend; + Float64 currentTime = (Float64) PlayListUtils::Milliseconds(); + + if (fMovieStart) + { + fMovieStart = false; + } + + + timeToSend = fMovieStartTime + transmitTimeMilli; + sleepTime = timeToSend - currentTime; + + + const Float64 maxSleepIntervalMilli = 100.0; + const Float64 minSleepIntervalMilli = 1.0; + Float64 intervalTime = sleepTime; + SInt32 sleepMilli = 0; + if (intervalTime > maxSleepIntervalMilli) + { while (intervalTime >= maxSleepIntervalMilli) + { + intervalTime -= maxSleepIntervalMilli; + sleepMilli = (SInt32) maxSleepIntervalMilli; + MilliSleep(sleepMilli); + fMediaStreamList.UpdateSenderReportsOnStreams(); + //qtss_printf("sleepIntervalMilli %u \n",sleepMilli); + } + } + if (intervalTime >= minSleepIntervalMilli) + { sleepMilli = (SInt32) intervalTime; + MilliSleep(sleepMilli); + //qtss_printf("sleepMilli %u \n",sleepMilli); + } + fMediaStreamList.UpdateSenderReportsOnStreams(); + + return sleepTime; +} + +/* changed by emil@popwire.com (see relaod.txt for info) */ +int QTFileBroadcaster::Play(char *mTimeFile) +/* ***************************************************** */ +{ + SInt16 err = 0; + Float64 transmitTime = 0; + MediaStream *theStreamPtr = NULL; + RTpPacket rtpPacket; + unsigned int sleptTime; + SInt32 movieStartOffset = 0; //z + Bool16 negativeTime = false; + fMovieDuration = fRTPFilePtr->GetMovieDuration(); + fSendTimeOffset = 0.0; + fMovieStart = true; + fNumMoviesPlayed ++; + + if (fMovieEndTime > 0) // take into account the movie load time as well as the last movie early end. + { UInt64 timeNow = PlayListUtils::Milliseconds(); + fMovieIntervalTime = timeNow - fMovieEndTime; + + SInt32 earlySleepTimeMilli = (SInt32)(fMovieTimeDiffMilli - fMovieIntervalTime); + earlySleepTimeMilli -= 40; // Don't sleep the entire time we need some time to execute or else we will be late + if (earlySleepTimeMilli > 0) + { OSThread::Sleep( earlySleepTimeMilli); + } + } + + fMovieStartTime = PlayListUtils::Milliseconds(); + fMediaStreamList.MovieStarted(fMovieStartTime); + +/* changed by emil@popwire.com (see relaod.txt for info) */ + if(mTimeFile!=NULL) + { + FILE *fTimeFile = NULL; + struct timeval start, dur, end; + struct tm tm_start, tm_dur, tm_end, timeResult; + + memset (&start,0, sizeof(start)); + + SInt64 timenow = OS::Milliseconds(); + start.tv_sec = (SInt32) OS::TimeMilli_To_UnixTimeSecs(timenow); + start.tv_usec = (SInt32) ((OS::TimeMilli_To_UnixTimeMilli(timenow) - (start.tv_sec * 1000)) * 1000); + + dur.tv_sec = (SInt32)fMovieDuration; + dur.tv_usec = (SInt32)((fMovieDuration - dur.tv_sec) * 1000000); + + end.tv_sec = start.tv_sec + dur.tv_sec + (SInt32)((start.tv_usec + dur.tv_usec) / 1000000); + end.tv_usec = (start.tv_usec + dur.tv_usec) % 1000000; + time_t startSecs = start.tv_sec; + time_t endSecs = end.tv_sec; + memcpy(&tm_start, qtss_localtime(&startSecs, &timeResult), sizeof(struct tm)); + memcpy(&tm_end, qtss_localtime(&endSecs, &timeResult), sizeof(struct tm)); + + tm_dur.tm_hour = dur.tv_sec / 3600; + tm_dur.tm_min = (dur.tv_sec % 3600) / 60; + tm_dur.tm_sec = (dur.tv_sec % 3600) % 60; + + // initialize all current movie parameters to unkown ("-"). + + ::strcpy(fCurrentMovieName, "-"); + ::strcpy(fCurrentMovieCopyright, "-"); + ::strcpy(fCurrentMovieComment, "-"); + ::strcpy(fCurrentMovieAuthor, "-"); + ::strcpy(fCurrentMovieArtist, "-"); + ::strcpy(fCurrentMovieAlbum, "-"); + + /* save start time, stop time and length of currently playing song to .current file */ + fTimeFile = fopen(mTimeFile, "a"); + if(fTimeFile) + { + SimpleString *theQTTextPtr = fMovieSDPParser->fQTTextLines.Begin(); + while (theQTTextPtr != NULL) + { + char tmp[256]; + ::memcpy(tmp, theQTTextPtr->fTheString, theQTTextPtr->fLen); + tmp[theQTTextPtr->fLen] = 0; + // if this SDP parameter is needed for logging then cache it here so + // we can log it later. + if (::strstr(theQTTextPtr->fTheString, "a=x-qt-text-nam:")!=NULL) + ::strcpy(fCurrentMovieName, &tmp[16]); + if (::strstr(theQTTextPtr->fTheString, "a=x-qt-text-cpy:")!=NULL) + ::strcpy(fCurrentMovieCopyright, &tmp[16]); + if (::strstr(theQTTextPtr->fTheString, "a=x-qt-text-cmt:")!=NULL) + ::strcpy(fCurrentMovieComment, &tmp[16]); + if (::strstr(theQTTextPtr->fTheString, "a=x-qt-text-aut:")!=NULL) + ::strcpy(fCurrentMovieAuthor, &tmp[16]); + if (::strstr(theQTTextPtr->fTheString, "a=x-qt-text-ART:")!=NULL) + ::strcpy(fCurrentMovieArtist, &tmp[16]); + if (::strstr(theQTTextPtr->fTheString, "a=x-qt-text-alb:")!=NULL) + ::strcpy(fCurrentMovieAlbum, &tmp[16]); + fwrite(theQTTextPtr->fTheString,theQTTextPtr->fLen, sizeof(char),fTimeFile); + qtss_fprintf(fTimeFile,"\n"); + theQTTextPtr = fMovieSDPParser->fQTTextLines.Next(); + } + + time_t startTime = (time_t) start.tv_sec; + time_t endTime = (time_t) end.tv_sec; + char buffer[kTimeStrSize]; + char *timestringStart = qtss_ctime(&startTime, buffer, sizeof(buffer)); + qtss_fprintf(fTimeFile,"b=%02d:%02d:%02d:%06d %"_S32BITARG_" %s", (int) tm_start.tm_hour, (int) tm_start.tm_min, (int) tm_start.tm_sec, (int)start.tv_usec, (SInt32) startTime, timestringStart); + char *timestringEnd = qtss_ctime(&endTime, buffer, sizeof(buffer)); + qtss_fprintf(fTimeFile,"e=%02d:%02d:%02d:%06d %"_S32BITARG_" %s", (int)tm_end.tm_hour, (int) tm_end.tm_min,(int) tm_end.tm_sec, (int) end.tv_usec,(SInt32) endTime, timestringEnd); + qtss_fprintf(fTimeFile,"d=%02d:%02d:%02d:%06d %d \n", (int) tm_dur.tm_hour, (int) tm_dur.tm_min,(int) tm_dur.tm_sec, (int) dur.tv_usec, (int)dur.tv_sec); + + fclose(fTimeFile); + } + } + + while (true) + { + if (fQuitImmediatePtr && *fQuitImmediatePtr){err = 0; break; } // quit now not an error + + if (fBroadcastDefPtr->mTheSession) + { UInt32 thePacketQLen = 0; + thePacketQLen = fBroadcastDefPtr->mTheSession->GetPacketQLen(); + SInt64 maxSleep = PlayListUtils::Milliseconds() + 1000; + if (thePacketQLen > eMaxPacketQLen) + { //qtss_printf("PacketQ too big = %"_U32BITARG_" \n", (UInt32) thePacketQLen); + while ( (eMaxPacketQLen/2) < fBroadcastDefPtr->mTheSession->GetPacketQLen()) + { this->SleepInterval(100.0); + if (maxSleep < PlayListUtils::Milliseconds()) + break; + } + //qtss_printf("PacketQ after sleep = %"_U32BITARG_" \n", (UInt32) fBroadcastDefPtr->mTheSession->GetPacketQLen()); + continue; + } + } + + + transmitTime = fRTPFilePtr->GetNextPacket(&rtpPacket.fThePacket, &rtpPacket.fLength); + theStreamPtr = (MediaStream*)fRTPFilePtr->GetLastPacketTrack()->Cookie1; + err = fRTPFilePtr->Error(); + if (err != QTRTPFile::errNoError) {err = eMovieFileInvalid; break; } // error getting packet + if (NULL == rtpPacket.fThePacket) {err = 0; break; } // end of movie not an error + if (NULL == theStreamPtr) {err = eMovieFileInvalid; break; }// an error + + + transmitTime *= (Float64) PlayListUtils::eMilli; // convert to milliseconds + if (transmitTime < 0.0 && negativeTime == false) // Deal with negative transmission times + { movieStartOffset += (SInt32) (transmitTime / 15.0); + negativeTime = true; + } + sleptTime = (unsigned int) Sleep(transmitTime); + + err = theStreamPtr->Send(&rtpPacket); + + if (err != 0) { break; } + err = fMediaStreamList.UpdateStreams(); + if (err != 0) { break; } + + if ( (fBroadcastDefPtr != NULL) + && (fBroadcastDefPtr->mTheSession != NULL) + && (fBroadcastDefPtr->mTheSession->GetReasonForDying() != BroadcasterSession::kDiedNormally) + ) + { break; } + }; + + fMovieEndTime = (SInt64) PlayListUtils::Milliseconds(); + fMediaStreamList.MovieEnded(fMovieEndTime); + + // see if the movie duration is greater than the time it took to send the packets. + // the difference is a delay that we insert before playing the next movie. + SInt64 playDurationMilli = (SInt64) fMovieEndTime - (SInt64) fMovieStartTime; + fMovieTimeDiffMilli = ((SInt64) ( (Float64) fMovieDuration * (Float64) PlayListUtils::eMilli)) - (SInt64) playDurationMilli; + fMovieTimeDiffMilli-= (movieStartOffset/2); + + return err; +} + +/* changed by emil@popwire.com (see relaod.txt for info) */ +int QTFileBroadcaster::PlayMovie(char *movieFileName, char *currentFile) +/* ***************************************************** */ +{ + + int err = eMovieFileInvalidName; + if (movieFileName != NULL) + { + + err = SetUpAMovie(movieFileName); + + if (!err && fPlay) +/* changed by emil@popwire.com (see relaod.txt for info) */ + { err = Play(currentFile); +/* ***************************************************** */ + } + } + return err; +} + + +int QTFileBroadcaster::EvalErrorCode(QTRTPFile::ErrorCode err) +{ + int result = eNoErr; + + switch( err ) + { + case QTRTPFile::errNoError: + break; + + case QTRTPFile::errFileNotFound: + result = eMovieFileNotFound; + break; + + case QTRTPFile::errNoHintTracks: + result = eMovieFileNoHintedTracks; + break; + + case QTRTPFile::errInvalidQuickTimeFile: + result = eMovieFileInvalid; + break; + + case QTRTPFile::errInternalError: + result = eInternalError; + break; + + default: + result = eInternalError; + } + return result; +} diff --git a/PlaylistBroadcaster.tproj/playlist_broadcaster.h b/PlaylistBroadcaster.tproj/playlist_broadcaster.h new file mode 100644 index 0000000..366c6a5 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_broadcaster.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@ + * + */ + +/* contributions by emil@popwire.com +*/ + +#ifndef playlist_broadcaster_H +#define playlist_broadcaster_H + +#include +#include +#include "SafeStdLib.h" +#include +#include +#include "OSHeaders.h" +#include "playlist_utils.h" +#include "playlist_elements.h" +#include "playlist_lists.h" +#include "playlist_parsers.h" +#include "QTRTPFile.h" +#include "PLBroadcastDef.h" + + +#ifndef __Win32__ + #include + #include +#endif + +static PlayListUtils gUtils; + + +class QTFileBroadcaster +{ + +protected: + + QTRTPFile *fRTPFilePtr ; + SDPFileParser fStreamSDPParser; + SDPFileParser *fMovieSDPParser; + SocketList fSocketlist; + MediaStreamList fMediaStreamList; + int fBasePort; + bool fDebug; + bool fDeepDebug; + bool *fQuitImmediatePtr; + +// transmit time trackers + Float64 fLastTransmitTime; + + SInt64 fStreamStartTime; + SInt64 fMovieStartTime; + SInt64 fMovieEndTime; + SInt64 fMovieIntervalTime; + SInt64 fMovieTimeDiffMilli; + + bool fMovieStart; + Float64 fSendTimeOffset; + Float64 fMovieDuration; + int fMovieTracks; + int fMappedMovieTracks; + UInt64 fNumMoviesPlayed; + + PayLoad * FindPayLoad(short id, ArrayList *PayLoadListPtr); + bool CompareRTPMaps(TypeMap *movieMediaTypePtr, TypeMap *streamMediaTypePtr, short id); + bool CompareMediaTypes(TypeMap *movieMediaTypePtr, TypeMap *streamMediaTypePtr); + UInt32 GetSDPTracks(QTRTPFile *newRTPFilePtr); + int SetUpAMovie(char *movieFileName); + int AddTrackAndStream(QTRTPFile *newRTPFilePtr); + int MapMovieToStream(); + int Play(char *mTimeFile); + Float64 SleepInterval(Float64 sleepTime) { return Sleep( (Float64) (PlayListUtils::Milliseconds() - fMovieStartTime) + sleepTime); }; + + Float64 Sleep(Float64 transmitTime); + void SetDebug(bool debug) {fDebug = debug;}; + void SetDeepDebug(bool debug) {fDeepDebug = debug;}; + PLBroadcastDef *fBroadcastDefPtr; +public: + QTFileBroadcaster(); + ~QTFileBroadcaster(); + + +static int EvalErrorCode(QTRTPFile::ErrorCode err); + int SetUp(PLBroadcastDef *broadcastDefPtr, bool *quitImmediatePtr); + int PlayMovie(char *movieFileName, char *currentFile); + int GetMovieTrackCount() { return fMovieTracks; }; + int GetMappedMovieTrackCount() { return fMappedMovieTracks; }; + void MilliSleep(SInt32 sleepTimeMilli); + bool fPlay; + bool fSend; + + enum { eClientBufferSecs = 0, + eMaxPacketQLen = 200 + }; + + enum ErrorID + { // General errors + eNoErr = 0 + ,eParam + ,eMem + ,eInternalError + ,eFailedBind + + // Setup Errors + ,eNoAvailableSockets + ,eSDPFileNotFound + ,eSDPDestAddrInvalid + ,eSDPFileInvalid + ,eSDPFileNoMedia + ,eSDPFileNoPorts + ,eSDPFileInvalidPort + ,eSDPFileInvalidName + ,eSDPFileInvalidTTL + + // eMem also + + // Play Errors, + ,eMovieFileNotFound + ,eMovieFileNoHintedTracks + ,eMovieFileNoSDPMatches + ,eMovieFileInvalid + ,eMovieFileInvalidName + + ,eNetworkConnectionError + ,eNetworkRequestError + ,eNetworkConnectionStopped + ,eNetworkAuthorization + ,eNetworkNotSupported + ,eNetworkSDPFileNameInvalidMissing + ,eNetworkSDPFileNameInvalidBadPath + ,eNetworkConnectionFailed + + ,eDescriptionInvalidDestPort + }; + + char fCurrentMovieName[256]; + char fCurrentMovieCopyright[256]; + char fCurrentMovieComment[256]; + char fCurrentMovieAuthor[256]; + char fCurrentMovieArtist[256]; + char fCurrentMovieAlbum[256]; + +private: + +}; + +#endif //playlist_broadcaster_H diff --git a/PlaylistBroadcaster.tproj/playlist_elements.cpp b/PlaylistBroadcaster.tproj/playlist_elements.cpp new file mode 100644 index 0000000..5ec11e8 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_elements.cpp @@ -0,0 +1,874 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "playlist_elements.h" +#include "playlist_utils.h" +#include "OS.h" +#include "SocketUtils.h" +#ifndef __Win32__ +#include +#endif + +// ************************ +// +// MediaStream +// +// ************************ +#define DROP_RTCP_TEST 0 +#define DROPCOUNT 1// drop count RTCPs + +MediaStream::MediaStream(void) +{ + memset( (char *)&fData, '\0', sizeof(fData)); + fSend = true; + fData.fSenderReportReady = true; +} +MediaStream::~MediaStream() +{ + if (fData.fSoundDescriptionBuffer) delete[] fData.fSoundDescriptionBuffer; +} + + +UInt32 MediaStream::GetACName(char* ioCNameBuffer) +{ + //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[2], " %s%"_64BITARG_"d", "QTSS", OS::Milliseconds() / 1000); + UInt32 cNameLen = ::strlen(&ioCNameBuffer[2]); + //2nd byte of CName should be length + ioCNameBuffer[1] = (UInt8) cNameLen ;//doesn't count indicator or length byte + cNameLen += 2; // add the identifier and len bytes to the result len + //pad length to a 4 byte boundary + UInt32 padLength = cNameLen % 4; + if (padLength > 0) + cNameLen += 4 - padLength; + + return cNameLen; +} + +void MediaStream::TestAndIncSoundDescriptor(RTpPacket *packetPtr) +{ // currently not executed + SInt16 test = 0; + do + { + if (!fData.fIsSoundStream) break; + if (!packetPtr->HasSoundDescription()) break; + if (!fData.fSoundDescriptionBuffer) break; + + SoundDescription *packetSDPtr = NULL; + SoundDescription *savedSDPtr = (SoundDescription *) fData.fSoundDescriptionBuffer; + (void) packetPtr->GetSoundDescriptionRef(&packetSDPtr); + SInt32 descSize = packetPtr->fSoundDescriptionLen; + + if (descSize == 0) break; + if (descSize > eMaxSoundDescriptionSize) break; + + if (fData.fSavedSoundDescSize == descSize) + { test = ::memcmp(packetSDPtr, fData.fSoundDescriptionBuffer, descSize); + } + else test = 1; // they are different sizes so it is a new sample description + + if (test != 0) // they are different + { ::memcpy(savedSDPtr, packetSDPtr, descSize); + fData.fSavedSoundDescSize = descSize; + fData.fSavedDataRefIndex ++ ; // it is different than saved so change the index + } + packetSDPtr->dataRefIndex = htons(fData.fSavedDataRefIndex); + + } while (false); +} + + +void MediaStream::UpdatePacketInStream(RTpPacket *packetPtr) +{ + UInt32 curSSRC = 0; + UInt32 curRTpTimeStamp = 0; + UInt16 curRTpSequenceNumber = 0; + UInt32 newRTpTimeStamp = 0; + UInt16 newRTpSequenceNumber = 0; + UInt32 newSSRC = 0; + unsigned char curPayload; + unsigned char newPayload; + packetPtr->GetHeaderInfo(&curRTpTimeStamp, &curRTpSequenceNumber, &curSSRC, &curPayload); + + newSSRC = fData.fInitSSRC; + MapToStream(curRTpTimeStamp, curRTpSequenceNumber, curPayload, &newRTpTimeStamp, &newRTpSequenceNumber, &newPayload); + + if (fData.fIsVideoStream) { LogStr("video "); } + if (fData.fIsSoundStream) { LogStr("audio "); } + + packetPtr->SetHeaderInfo(newRTpTimeStamp, newRTpSequenceNumber,newSSRC,newPayload); + + //TestAndIncSoundDescriptor(packetPtr); // put in to track QuickTime format sound descriptors and flag change in sample types +} + +void MediaStream::MapToStream(UInt32 curRTpTimeStamp, UInt16 curRTpSequenceNumber, unsigned char curPayload, UInt32 *outRTpTimeStampPtr, UInt16 *outRTpSequenceNumberPtr, unsigned char *outPayloadPtr) +{ + + if (fData.fNewMovieStarted == true) // this is the first packet in a new movie + { + fData.fNewMovieStarted = false; + } + + fData.fCurStreamRTpSequenceNumber++; // the stream sequence number + + UInt64 curSequenceNumber = (UInt64) fData.fCurStreamRTpSequenceNumber + (UInt64) fData.fSeqRandomOffset; + UInt64 curTimeStamp = (UInt64) curRTpTimeStamp + (UInt64) fData.fMediaStartOffsetMediaScale + (UInt64) fData.fRTpRandomOffset; + + UInt32 outTime = (UInt32) ( (UInt64) curTimeStamp & (UInt64) 0xFFFFFFFF ); + UInt16 outSeq = (UInt16) ( (UInt64) curSequenceNumber & (UInt64) 0xFFFF ); + unsigned char outPayload = curPayload; + Assert (fData.fMovieMediaTypePtr != NULL);// should always be valid + PayLoad *firstPayLoadPtr = (fData.fMovieMediaTypePtr)->fPayLoadTypes.Begin(); // potential problem; assumes first payload per track is this payload + if (firstPayLoadPtr) + { + outPayload = (unsigned char) ( 0x7F & firstPayLoadPtr->payloadID); + outPayload |= (curPayload & 0x80);// the movie payload marker + } + +// qtss_printf("MediaStream::MapToStream outTime = %"_U32BITARG_"\n", outTime); +// qtss_printf("MediaStream::MapToStream calculated time = %"_U32BITARG_"\n",(UInt32) curTimeInScale); + + if (outRTpTimeStampPtr) *outRTpTimeStampPtr = outTime; + if (outRTpSequenceNumberPtr) *outRTpSequenceNumberPtr = outSeq; + if (outPayloadPtr) *outPayloadPtr = outPayload; +} + +void MediaStream::BuildStaticRTCpReport() +{ + char theTempCName[kMaxCNameLen]; + UInt32 cNameLen = GetACName(theTempCName); + + //write the SR & SDES headers + UInt32* theSRWriter = (UInt32*)&fData.fSenderReportBuffer; + *theSRWriter = htonl(0x80c80006); + theSRWriter += 7; + //SDES length is the length of the CName, plus 2 32bit words + *theSRWriter = htonl(0x81ca0000 + (cNameLen >> 2) + 1); + ::memcpy(&fData.fSenderReportBuffer[kSenderReportSizeInBytes], theTempCName, cNameLen); + fData.fSenderReportSize = kSenderReportSizeInBytes + cNameLen; +} + +void MediaStream::InitIfAudio() +{ + if (fData.fStreamMediaTypePtr) + { + SimpleString audioStr("audio"); + fData.fIsSoundStream = SimpleParser::Compare(&audioStr, &(fData.fStreamMediaTypePtr->fTheTypeStr), false ); + if (fData.fIsSoundStream) + { + fData.fSoundDescriptionBuffer = new char[eMaxSoundDescriptionSize]; + ::memset(fData.fSoundDescriptionBuffer, 0, eMaxSoundDescriptionSize ); + } + else + { + SimpleString videoStr("video"); + fData.fIsVideoStream = SimpleParser::Compare(&videoStr, &(fData.fStreamMediaTypePtr->fTheTypeStr), false ); + } + } + +} + + +void MediaStream::StreamStart(SInt64 startTime) +{ + + fData.fStreamStartTime = startTime; + fData.fMovieEndTime = startTime; + fData.fLastSenderReportTime = 0; + + //for RTCp SRs, we also need to store the play time in NTP +// fData.fNTPPlayTime = OS::TimeMilli_To_1900Fixed64Secs(startTime); + fData.fNTPPlayTime = PlayListUtils::TimeMilli_To_1900Fixed64Secs(startTime); + fData.fCurStreamRTpSequenceNumber = 0; + fData.fMovieStartOffset = 0; + + fData.fNewStreamStarted = true; + fData.fSeqRandomOffset = PlayListUtils::Random(); + fData.fRTpRandomOffset = PlayListUtils::Random(); + + +// fData.fSeqRandomOffset = -1000; // test roll over +// fData.fRTpRandomOffset = -100000; // test roll over + + InitIfAudio(); + + //Build a static RTCp sender report (this way, only the info that changes + //on the fly will have to be written) + BuildStaticRTCpReport(); +} + +void MediaStream::MovieStart(SInt64 startTime) +{ + fData.fNewMovieStarted = true; + fData.fMovieStartTime = startTime; + + if (fData.fMovieMediaTypePtr && fData.fRTPFilePtr) + { UInt32 trackID = fData.fMovieMediaTypePtr->fTrackID; + fData.fMovieMediaTypePtr->fTimeScale = fData.fRTPFilePtr->GetTrackTimeScale(trackID); + + UInt64 lastMovieduration = (UInt64) ( (Float64) (fData.fLastMovieDurationSecs * (Float64) PlayListUtils::eMilli) ) ; // add the length of the last movie + fData.fMovieStartOffset += lastMovieduration; + + if (fData.fNewStreamStarted) + { fData.fNewStreamStarted = false; + fData.fMovieEndTime = startTime; // first movie in stream has 0 movieInterval time. + } + + Float64 mediaOffSet = (Float64) (SInt64)fData.fMovieStartOffset / (Float64) PlayListUtils::eMilli; // convert to float seconds + mediaOffSet = mediaOffSet * (Float64) fData.fMovieMediaTypePtr->fTimeScale; // mediaOffset in media time scale + fData.fMediaStartOffsetMediaScale = (UInt64) mediaOffSet; // convert float time units to UInt64 + + fData.fLastMovieDurationSecs = fData.fRTPFilePtr->GetMovieDuration(); + } + + +} + +void MediaStream::MovieEnd(SInt64 endTime) +{ + fData.fMovieEndTime = endTime; + fData.fMovieMediaTypePtr = NULL; + fData.fRTPFilePtr = NULL; +} + +SInt16 MediaStream::Accounting(RTpPacket *packetPtr) +{ + SInt16 result = -1; + + fData.fBytesSent += packetPtr->fLength; + fData.fPacketsSent ++; + + UInt32 lastTime = fData.fTimeStamp; + unsigned char payload; + packetPtr->GetHeaderInfo(&fData.fTimeStamp, &fData.fLastSequenceNumber, &fData.fSsrc, &payload); + + fData.fLastTimeStampOffset = fData.fTimeStamp - lastTime; + + result = 0; + + return result; +} + +SInt16 MediaStream::Send(RTpPacket *packetPtr) +{ + + SInt16 result = -1; + + do + { + if (fData.fMovieMediaTypePtr == NULL) break; + + UpdatePacketInStream(packetPtr); + + result = Accounting(packetPtr); + if (result) break; + + if (fSend) + { result = fData.fSocketPair->SendRTp(packetPtr->fThePacket, packetPtr->fLength); + } + } + while (false); + + return result; +} + +void MediaStream::ReceiveOnPorts() +{ + fData.fSocketPair->RecvRTp(fData.fPortRTpReadBuff.fReadBuffer, ReceiveBuffer::kReadBufferSize, &fData.fPortRTpReadBuff.fReceiveLen); + fData.fSocketPair->RecvRTCp(fData.fPortRTCpReadBuff.fReadBuffer, ReceiveBuffer::kReadBufferSize, &fData.fPortRTCpReadBuff.fReceiveLen); +} + +#if DROP_RTCP_TEST +static int numRTCPPackets = 0; +static int numStartDropPackets = 0; +#endif + +int MediaStream::UpdateSenderReport(SInt64 theTime) +{ + + if (NULL == fData.fMovieMediaTypePtr) + return 0; + + int result = 0; + + SInt64 timeToSend = fData.fLastSenderReportTime + (kSenderReportIntervalInSecs * PlayListUtils::eMilli); + +#if DROP_RTCP_TEST + if (theTime > timeToSend ) + { + if (fData.fIsSoundStream) + { numRTCPPackets ++; + if ( (numRTCPPackets <= numStartDropPackets + DROPCOUNT) ) + { qtss_printf("skip sound RTCP #%d time=%qd\n",numRTCPPackets, theTime / PlayListUtils::eMilli); + fData.fLastSenderReportTime = theTime; + return 0; + } + else + { qtss_printf("send sound RTCP #%d time=%qd\n",numRTCPPackets, theTime / PlayListUtils::eMilli); + numStartDropPackets = numRTCPPackets; + } + } + } +#endif + + if (theTime > timeToSend) + { + fData.fLastSenderReportTime = theTime; + UInt32* theReport = (UInt32*) fData.fSenderReportBuffer; + + theReport++; + *theReport = htonl(fData.fSsrc); + + theReport++; + SInt64* theNTPTimestampP = (SInt64*)theReport; + *theNTPTimestampP = OS::HostToNetworkSInt64(fData.fNTPPlayTime + + PlayListUtils::TimeMilli_To_Fixed64Secs(theTime - fData.fStreamStartTime)); + + theReport += 2; + *theReport = htonl(fData.fTimeStamp); + + Float64 curTimeInScale = (Float64) (SInt64) PlayListUtils::Milliseconds() / (Float64) PlayListUtils::eMilli; // convert to float seconds + curTimeInScale = curTimeInScale * (Float64) fData.fMovieMediaTypePtr->fTimeScale; // curTime in media time scale + curTimeInScale +=(Float64) fData.fRTpRandomOffset; + curTimeInScale = (UInt32) ( (UInt64) curTimeInScale & (UInt64) 0xFFFFFFFF ); + + //qtss_printf("MediaStream::UpdateSenderReport RTCP timestamp = %"_U32BITARG_"\n",(UInt32) curTimeInScale); + *theReport = htonl((UInt32) curTimeInScale); + + theReport++; + fData.fPacketCount = (UInt32) fData.fPacketsSent; + *theReport = htonl(fData.fPacketCount); + + theReport++; + fData.fByteCount = (UInt32) fData.fBytesSent; + *theReport = htonl(fData.fByteCount); + + theReport += 2; + *theReport = htonl(fData.fSsrc); + + LogStr("Sender Report\n"); + LogUInt("NTP ",(UInt32) ((*theNTPTimestampP) >> 32)," "); + LogUInt(" ",(UInt32) ((*theNTPTimestampP) & 0xFFFFFFFF), "\n" ); + LogUInt("time stamp = ", fData.fTimeStamp, "\n"); + LogInt("SSRC = ", fData.fSsrc, "\n"); + LogUInt("Packets sent = ", fData.fPacketCount, "\n"); + LogUInt("Bytes sent = ", fData.fByteCount, "\n"); + LogBuffer(); + result = fData.fSocketPair->SendRTCp(fData.fSenderReportBuffer, fData.fSenderReportSize); + + } + + return result; +} + +// ************************ +// +// UDPSOCKETPAIR +// +// ************************ + + +void UDPSocketPair::Close() +{ + if (fMultiCastJoined) + this->LeaveMulticast(); + +#ifdef __Win32__ + if (fSocketRTp != 0) ::closesocket(fSocketRTp); + if (fSocketRTCp != 0) ::closesocket(fSocketRTCp); +#else + if (fSocketRTp != 0) ::close(fSocketRTp); + if (fSocketRTCp != 0) ::close(fSocketRTCp); +#endif + fSocketRTp = 0; + fSocketRTCp = 0; +} + +SInt16 UDPSocketPair::Open() +{ + SInt16 result = 0; + do + { + Close(); + + fSocketRTp = ::socket(PF_INET, SOCK_DGRAM, 0); + if (fSocketRTp == kInvalidSocket) + { result = kInvalidSocket; + break; + } + + fSocketRTCp = ::socket(PF_INET, SOCK_DGRAM, 0); + if (fSocketRTCp == kInvalidSocket) + { result = kInvalidSocket; + break; + } + +#ifdef __Win32__ + u_long one = 1; + (void)::ioctlsocket(fSocketRTp, FIONBIO, &one); + (void)::ioctlsocket(fSocketRTCp, FIONBIO, &one); +#else + int flag; + int val; + flag = ::fcntl(fSocketRTp, F_GETFL, 0); + val = ::fcntl(fSocketRTp, F_SETFL, flag | O_NONBLOCK); + if( val < 0 ) { result = -1; break; } + + + flag = ::fcntl(fSocketRTCp, F_GETFL, 0); + val = ::fcntl(fSocketRTCp, F_SETFL, flag | O_NONBLOCK); + if( val < 0 ) { result = -1; break; } +#endif + } while (false); + + if (result != 0) + { if (fSocketRTp != 0) ::close(fSocketRTp); + if (fSocketRTCp != 0) ::close(fSocketRTCp); + fSocketRTp = 0; + fSocketRTCp = 0; + } + + return result; +} + +void UDPSocketPair::InitPorts(UInt32 addr) +{ + ::memset(&fLocalAddrRTp, 0, sizeof(fLocalAddrRTp)); + fLocalAddrRTp.sin_family = PF_INET; + fLocalAddrRTp.sin_port = htons(0); + fLocalAddrRTp.sin_addr.s_addr = htonl(addr); + + ::memset(&fLocalAddrRTCp, 0, sizeof(fLocalAddrRTCp)); + fLocalAddrRTCp.sin_family = PF_INET; + fLocalAddrRTCp.sin_port = htons(0); + fLocalAddrRTCp.sin_addr.s_addr = htonl(addr); + + ::memset(&fDestAddrRTp, 0, sizeof(fDestAddrRTp)); + fDestAddrRTp.sin_family = PF_INET; + fDestAddrRTp.sin_port = htons(0); + fDestAddrRTp.sin_addr.s_addr = htonl(addr); + + ::memset(&fDestAddrRTCp, 0, sizeof(fDestAddrRTCp)); + fDestAddrRTCp.sin_family = PF_INET; + fDestAddrRTCp.sin_port = htons(0); + fDestAddrRTCp.sin_addr.s_addr = htonl(addr); +} + +SInt16 UDPSocketPair::Bind(UInt32 addr) +{ + int err = -1; + + if ( (fSocketRTp == kInvalidSocket) || (fSocketRTCp == kInvalidSocket) ) + return -1; + + InitPorts(addr); + + UInt32 PortRTp = eSourcePortStart; + UInt32 PortRTCp = PortRTp + 1; // keep them together for clarity + + for (int count = eSourcePortStart; count < eSourcePortRange; count ++) + { + PortRTp = count; + Assert( (PortRTp & 1) == 0); // must be even + count += 1; + PortRTCp = count; + Assert( (PortRTCp & 1) == 1);// must be odd and one more than rtp port + + fLocalAddrRTp.sin_port = htons( (UInt16) PortRTp); + fLocalAddrRTCp.sin_port = htons( (UInt16) PortRTCp); + + //qtss_printf("Attempting to bind to rtp port %d \n",PortRTp); + + err = ::bind(fSocketRTp, (sockaddr *)&fLocalAddrRTp, sizeof(fLocalAddrRTp)); + if (err != 0) + { + //qtss_printf("UDPSocketPair::Bind Error binding to rtp port %d \n",PortRTp); + InitPorts(addr); + continue; + } + + err = ::bind(fSocketRTCp, (sockaddr *)&fLocalAddrRTCp, sizeof(fLocalAddrRTCp)); + if (err != 0) + { + //qtss_printf("UDPSocketPair::Bind Error binding to rtcp port %d \n",PortRTp); + Close(); + Open(); + InitPorts(addr); + continue; + } + + if (err == 0) + { + //qtss_printf("Bound to rtp port = %d, rtcp port = %d \n",PortRTp, PortRTCp); + fState |= kBound; + break; + } + } + + return err; +} + + +SInt16 UDPSocketPair::SendTo(int socket, sockaddr *destAddrPtr, char* inBuffer, UInt32 inLength ) +{ + SInt16 result = -1; + do + { + if (inBuffer == NULL) break; + if (destAddrPtr == NULL) break; + if (socket == kInvalidSocket) break; + + //qtss_printf("Sending data to %d. Addr = %d inLength = %d\n", ntohs(theAddr->sin_port), ntohl(theAddr->sin_addr.s_addr), inLength); + ::sendto(socket, inBuffer, inLength, 0, destAddrPtr, sizeof(sockaddr)); + + result = 0; + } while (false); + + //if (result != 0) qtss_printf("UDP SENDTO ERROR!\n"); + return result; +} + + +SInt16 UDPSocketPair::SendRTp(char* inBuffer, UInt32 inLength) +{ + if (fBroadcasterSession != NULL) + { OSMutexLocker locker(fBroadcasterSession->GetMutex()); + return (SInt16) fBroadcasterSession->SendPacket(inBuffer,inLength,fChannel); + } + else + return SendTo(fSocketRTp, (sockaddr*)&fDestAddrRTp, inBuffer, inLength ); +} + +SInt16 UDPSocketPair::SendRTCp(char* inBuffer, UInt32 inLength) +{ + if (fBroadcasterSession != NULL) + { OSMutexLocker locker(fBroadcasterSession->GetMutex()); + return (SInt16) fBroadcasterSession->SendPacket(inBuffer,inLength,fChannel+1); + } + else + return SendTo(fSocketRTCp, (sockaddr*)&fDestAddrRTCp, inBuffer, inLength ); +} + +SInt16 UDPSocketPair::SetDestination (char *destAddress,UInt16 destPortRTp, UInt16 destPortRTCp) +{ + SInt16 result = -1; + + if (destAddress != NULL) + { UInt32 netAddress = inet_addr(destAddress); + + fDestAddrRTp = fLocalAddrRTp; + fDestAddrRTp.sin_port = htons(destPortRTp); + fDestAddrRTp.sin_addr.s_addr = netAddress; + + fDestAddrRTCp = fLocalAddrRTCp; + fDestAddrRTCp.sin_port = htons(destPortRTCp); + fDestAddrRTCp.sin_addr.s_addr = netAddress; + + fIsMultiCast = SocketUtils::IsMulticastIPAddr(ntohl(netAddress)); + + result = 0; + } + return result; +} + +SInt16 UDPSocketPair::SetMulticastInterface() +{ + // set the outgoing interface for multicast datagrams on this socket + in_addr theLocalAddr; + ::memset(&theLocalAddr, 0, sizeof(theLocalAddr)); + + theLocalAddr.s_addr = fLocalAddrRTp.sin_addr.s_addr; + int err = setsockopt(fSocketRTp, IPPROTO_IP, IP_MULTICAST_IF, (char*)&theLocalAddr, sizeof(theLocalAddr)); + + return err; +} + +SInt16 UDPSocketPair::JoinMulticast() +{ + int err = 0; + + + UInt32 localAddr = fLocalAddrRTp.sin_addr.s_addr; // Already in network byte order + +#if __solaris__ + if( localAddr == htonl(INADDR_ANY) ) + localAddr = htonl(SocketUtils::GetIPAddr(0)); +#endif + + struct ip_mreq theMulti; + ::memset(&theMulti, 0, sizeof(theMulti)); + + theMulti.imr_multiaddr.s_addr = fDestAddrRTp.sin_addr.s_addr; + theMulti.imr_interface.s_addr = localAddr; + err = setsockopt(fSocketRTp, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&theMulti, sizeof(theMulti)); + (void) setsockopt(fSocketRTCp, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&theMulti, sizeof(theMulti)); + + if (err == 0) + fMultiCastJoined = true; + + return err; +} + + +SInt16 UDPSocketPair::SetTTL(SInt16 timeToLive) +{ + // set the ttl + int nOptVal = (int)timeToLive; + int err = 0; + + err = setsockopt(fSocketRTp, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&nOptVal, sizeof(nOptVal)); + if (err != 0) return err; + + err = setsockopt(fSocketRTCp, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&nOptVal, sizeof(nOptVal)); + return err; +} + +SInt16 UDPSocketPair::LeaveMulticast() +{ + UInt32 localAddr = fLocalAddrRTp.sin_addr.s_addr; // Already in network byte order + +#if __solaris__ + if( localAddr == htonl(INADDR_ANY) ) + localAddr = htonl(SocketUtils::GetIPAddr(0)); +#endif + + struct ip_mreq theMulti; + ::memset(&theMulti, 0, sizeof(theMulti)); + + theMulti.imr_multiaddr.s_addr = fDestAddrRTp.sin_addr.s_addr; + theMulti.imr_interface.s_addr = localAddr; + + int err = setsockopt(fSocketRTp, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&theMulti, sizeof(theMulti)); + (void) setsockopt(fSocketRTCp, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&theMulti, sizeof(theMulti)); + return err; +} + +SInt16 UDPSocketPair::RecvFrom(sockaddr *RecvRTpddrPtr, int socket, char* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen) +{ + SInt16 result = -1; + + do + { + if (ioBuffer == NULL) break; + if (RecvRTpddrPtr == NULL) break; + if (socket == kInvalidSocket) break; + + sockaddr_in theAddr; +#if __Win32__ || __osf__ || __sgi__ || __hpux__ + int addrLen = sizeof(theAddr); +#else + socklen_t addrLen = sizeof(theAddr); +#endif + + SInt32 theRecvLen = ::recvfrom(socket, ioBuffer, inBufLen, 0, (sockaddr*)&theAddr, &addrLen); + if (theRecvLen == -1) break; + + if (outRecvLen) *outRecvLen = (UInt32)theRecvLen; + } while (false); + return result; +} + +SInt16 UDPSocketPair::RecvRTp(char* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen) +{ + return RecvFrom( (sockaddr *)&fDestAddrRTp, fSocketRTp, ioBuffer, inBufLen, outRecvLen); +} + +SInt16 UDPSocketPair::RecvRTCp(char* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen) +{ + return RecvFrom( (sockaddr *)&fDestAddrRTCp, fSocketRTCp, ioBuffer, inBufLen, outRecvLen); +} + +SInt16 UDPSocketPair::SetMultiCastOptions(SInt16 ttl) +{ + SInt16 err = 0; + + if (this->fIsMultiCast) do// set by SetDestination + { + err = this->SetTTL(ttl); + WarnV(err == 0 , "failed to set ttl"); + if (err != 0) break; + + err = this->SetMulticastInterface(); + WarnV(err == 0 , "failed to set multicast socket option"); + if (err != 0) break; + + err = this->JoinMulticast(); + WarnV(err == 0 , "failed to join multicast"); + if (err != 0) break; + + } while (false); + + return err; +} + +SInt16 UDPSocketPair::OpenAndBind( UInt16 rtpPort,UInt16 rtcpPort,char *destAddress) +{ + SInt16 err = -1; + + do + { + err = this->Open(); + if (err != 0) break; + + err = this->Bind(INADDR_ANY); + if (err != 0) break; + + err = this->SetDestination (destAddress, rtpPort, rtcpPort); + if (err != 0) break; + + } while (false); + + if (err) + { + this->Close(); + } + + return err; +}; + +// ************************ +// +// RTpPacket +// +// ************************ + +SInt16 RTpPacket::GetSoundDescriptionRef(SoundDescription **soundDescriptionPtr) +{ + SInt16 result = -1; + + if (fThePacket && soundDescriptionPtr) + { + SInt32 minSoundLength = sizeof(SoundHeader) + sizeof(SoundDescription) + kRTpHeaderSize; + if ( fLength >= minSoundLength ) + { + char *offsetPtr = fThePacket + kRTpHeaderSize + sizeof(SoundHeader); + *soundDescriptionPtr = (SoundDescription *) offsetPtr; + SInt32 descSize = ntohl( (**soundDescriptionPtr).descSize); + fSoundDescriptionLen = descSize; + result = 0; + } + } + + return result; +} + +bool RTpPacket::HasSoundDescription() +{ + bool result = false; + + if (fThePacket) + { + +// WritePacketToLog(fThePacket, fLength); + SInt32 minSoundLength = sizeof(SoundHeader) + sizeof(SoundDescription) + kRTpHeaderSize; + if (fLength >= minSoundLength ) + { + char *offsetPtr = fThePacket + kRTpHeaderSize; + SoundHeader* testHeaderPtr = (SoundHeader*) offsetPtr; + do + { + if (testHeaderPtr->bytes[0] != 0x17) break; + if (testHeaderPtr->sndtype[0] != 's') break; + if (testHeaderPtr->sndtype[1] != 'o') break; + if (testHeaderPtr->sndtype[2] != 'u') break; + if (testHeaderPtr->sndtype[3] != 'n') break; + + fHasSoundDescription = result = true; + + } while (false); + } + + if (result) + { + LogStr("has sound description soun\n"); + } + PrintLogBuffer(true); + } + + return result; +} + +SInt16 RTpPacket::GetHeaderInfo(UInt32 *timeStampPtr, UInt16 *sequencePtr,UInt32 *SSRCPtr, unsigned char*payloadTypeAndMarkPtr) +{ + SInt16 result = -1; + + + unsigned char *header8Ptr = (unsigned char *) fThePacket; + UInt16* header16Ptr = (UInt16*)fThePacket; + UInt32* header32Ptr = (UInt32*)fThePacket; + + if (fThePacket && timeStampPtr && sequencePtr && SSRCPtr && payloadTypeAndMarkPtr) + { + *payloadTypeAndMarkPtr = header8Ptr[cPayloadType]; + *sequencePtr = ntohs(header16Ptr[cSequenceNumber]); + *timeStampPtr = ntohl(header32Ptr[cTimeStamp]); + *SSRCPtr = ntohl(header32Ptr[cSSRC]); + result = 0; + } + + return result; +} + + + +SInt16 RTpPacket::SetHeaderInfo(UInt32 timeStamp, UInt16 sequence, UInt32 SSRC, unsigned char payloadTypeAndMark) +{ + SInt16 result = -1; + + unsigned char *header8Ptr = (unsigned char *) fThePacket; + UInt16* header16Ptr = (UInt16*)fThePacket; + UInt32* header32Ptr = (UInt32*)fThePacket; + + + if (fThePacket) + { + LogInt("sequence = ", sequence, " "); + LogUInt("time = ", timeStamp, " "); +// LogUInt("old payload = ",( UInt32 ) (header8Ptr[cPayloadType] & 0x7F)," "); + LogUInt("payload = ",( UInt32 ) (payloadTypeAndMark & 0x7F) ," "); +// LogUInt("old marker = ", ( UInt32 ) (header8Ptr[cPayloadType] & 0x80) ," "); +// LogUInt("marker = ", ( UInt32 ) (payloadTypeAndMark & 0x80) ," "); + LogUInt("ssrc = ", SSRC, "\n"); + + header8Ptr[cPayloadType] = payloadTypeAndMark; + header16Ptr[cSequenceNumber] = htons(sequence); + header32Ptr[cTimeStamp] = htonl(timeStamp); + header32Ptr[cSSRC] = htonl(SSRC); + result = 0; + + LogBuffer(); + } + + + return result; +} + + diff --git a/PlaylistBroadcaster.tproj/playlist_elements.h b/PlaylistBroadcaster.tproj/playlist_elements.h new file mode 100644 index 0000000..3e61213 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_elements.h @@ -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@ + * + */ +#ifndef playlist_elements_H +#define playlist_elements_H + +#include +#include +#include "SafeStdLib.h" +#include +#include +#ifndef __Win32__ + #include + #include + #include + #include + #include + #include + #include +#endif + +#include "playlist_array.h" +#include "OSHeaders.h" +#include "playlist_SimpleParse.h" +#include "QTRTPFile.h" +#include "BroadcasterSession.h" + +class MediaStream; + +struct SoundDescription { + SInt32 descSize; /* total size of SoundDescription including extra data */ + SInt32 dataFormat; /* sound format */ + SInt32 resvd1; /* reserved for apple use. set to zero */ + SInt16 resvd2; /* reserved for apple use. set to zero */ + SInt16 dataRefIndex; + SInt16 version; /* which version is this data */ + SInt16 revlevel; /* what version of that codec did this */ + SInt32 vendor; /* whose codec compressed this data */ + SInt16 numChannels; /* number of channels of sound */ + SInt16 sampleSize; /* number of bits per sample */ + SInt16 compressionID; /* unused. set to zero. */ + SInt16 packetSize; /* unused. set to zero. */ + UInt32 sampleRate; /* sample rate sound is captured at */ +}; + + +class PayLoad { + public: + PayLoad(void) : payloadID(0), timeScale(1) {}; + int payloadID; + SimpleString payLoadString; + UInt32 timeScale; +}; + + +class TypeMap { + public: + TypeMap(void) : fMediaStreamPtr(0), fTrackID(0), fPort(0), fTimeScale(1) {}; + ~TypeMap() { } ; + + MediaStream *fMediaStreamPtr; + int fTrackID; + int fPort; + UInt32 fTimeScale; + SimpleString fTheTypeStr; + SimpleString fProtocolStr; + ArrayList fPayLoadTypes; + ArrayList fPayLoads; + +}; + + +class RTpPacket +{ + + +struct SoundHeader { + char bytes[4]; + SInt32 skip1; + char sndtype[4]; + SInt32 skip2; + char test[4]; +}; + + + public: + enum { kRTpHeaderSize = 12 }; + char *fThePacket; + int fLength; + bool fHasSoundDescription; + SInt32 fSoundDescriptionLen; + + RTpPacket() : fThePacket(NULL), fLength(0), fHasSoundDescription(false), fSoundDescriptionLen(0) {}; + + void SetPacketData(char *thePacket, int length) {fThePacket = thePacket; fLength = length; }; + SInt16 SetHeaderInfo( UInt32 timeStamp, UInt16 sequence,UInt32 SSRC,unsigned char payloadType); + SInt16 GetHeaderInfo( UInt32 *timeStampPtr, UInt16 *sequencePtr,UInt32 *SSRCPtr,unsigned char*payloadType); + + bool HasSoundDescription(); + SInt16 GetSoundDescriptionRef(SoundDescription **soundDescriptionPtr); + + protected: + + enum { cSequenceNumber = 1, cTimeStamp = 1, cSSRC = 2, cPayloadType = 1}; + +}; + + + +class UDPSocketPair +{ + public: + enum + { + kBound = 1L << 0, + kConnected = 1L << 1 + }; + + enum { eBindMaxTries = 100, + eSourcePortStart = 49152, // rtp + rtcp + eSourcePortRange = 65535 // 49152,49153 - 65534,65535 + }; + + enum + { + kInvalidSocket = -1, //int + kPortRTCpufSizeInBytes = 8, //UInt32 + kMaxIPAddrSizeInBytes = 20 //UInt32 + }; + + int fMaxBindAttempts; + int fState; + int fSocketRTp; + struct sockaddr_in fLocalAddrRTp; + struct sockaddr_in fDestAddrRTp; + + int fSocketRTCp; + struct sockaddr_in fLocalAddrRTCp; + struct sockaddr_in fDestAddrRTCp; + BroadcasterSession *fBroadcasterSession; + UInt8 fChannel; + Bool16 fIsMultiCast; + Bool16 fMultiCastJoined; + + UDPSocketPair() : fMaxBindAttempts(eBindMaxTries), + fState(0), + fSocketRTp(0), + fSocketRTCp(0), + fBroadcasterSession(NULL), + fChannel(0), + fIsMultiCast(false), + fMultiCastJoined(false) + {}; + ~UDPSocketPair() { Close(); }; + + SInt16 Open(); + void Close(); + void InitPorts(UInt32 addr); + SInt16 Bind(UInt32 addr); + SInt16 OpenAndBind( UInt16 rtpPort,UInt16 rtcpPort,char *destAddress); + + SInt16 SetDestination (char *destAddress,UInt16 destPortRTp, UInt16 destPortRTCp); + SInt16 SetTTL(SInt16 timeToLive); + SInt16 JoinMulticast(); + SInt16 LeaveMulticast(); + SInt16 SetMulticastInterface(); + SInt16 SetMultiCastOptions(SInt16 ttl); + + SInt16 SendTo(int socket, sockaddr *destAddrPtr, char* inBuffer, UInt32 inLength ); + SInt16 SendRTp(char* inBuffer, UInt32 inLength); + SInt16 SendRTCp(char* inBuffer, UInt32 inLength); + + SInt16 RecvFrom(sockaddr *recvAddrPtr, int socket, char* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen); + SInt16 RecvRTp(char* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen); + SInt16 RecvRTCp(char* ioBuffer, UInt32 inBufLen, UInt32* outRecvLen); + + void SetRTSPSession(BroadcasterSession *theSession,UInt8 channel) {fBroadcasterSession = theSession, fChannel=channel;} +}; + +class ReceiveBuffer +{ + public: + enum { kReadBufferSize = 256 }; //UInt32 + char fReadBuffer[kReadBufferSize]; + UInt32 fReceiveLen; + +}; + +class MediaStream +{ + protected: + + int SendRTp(RTpPacket *packet); + int CalcRTCps(); + int SendRTCp_SenderReport(); + static UInt32 GetACName(char* ioCNameBuffer); + + void MapToStream(UInt32 curRTpTimeStamp, UInt16 curRTpSequenceNumber, unsigned char curPayload, UInt32 *outRTpTimeStampPtr, UInt16 *outRTpSequenceNumberPtr, unsigned char *outPayloadPtr); + void UpdatePacketInStream(RTpPacket *packetPtr); + SInt16 Accounting(RTpPacket *packetPtr); + void BuildStaticRTCpReport(); + void InitIfAudio(); + void TestAndIncSoundDescriptor(RTpPacket *packetPtr); + public: + enum { kMaxCNameLen = 20 }; //UInt32 + enum { eMaxSoundDescriptionSize = 1024}; + enum { + kSenderReportSizeInBytes = 36, //UInt32 + kMaxRTCpPacketSizeInBytes = 1024, //All are UInt32s + kMaxSsrcSizeInBytes = 25, + kSenderReportIntervalInSecs = 5 + }; + + + enum { + eSocketNotOpen, + eSocketFailed + }; + + struct MemberData { + ReceiveBuffer fPortRTpReadBuff; + ReceiveBuffer fPortRTCpReadBuff; + + UInt64 fRTCpTimer; + SInt16 fState; + + + UInt64 fBytesSent; + UInt64 fPacketsSent; + + SInt64 fStreamStartTime; + SInt64 fNTPPlayTime; + + char fSenderReportBuffer[kSenderReportSizeInBytes + kMaxCNameLen]; + UInt32 fSenderReportSize; + + //who am i sending to? + UInt16 fRemoteRTpPort; + UInt16 fRemoteRTCpPort; + + //RTCp stuff + SInt64 fLastSenderReportTime; + UInt32 fPacketCount; + UInt32 fByteCount; + + Bool16 fSenderReportReady; + UInt32 fLastTimeStamp; + + // current RTP packet info + UInt32 fLastTimeStampOffset; + + UInt32 fTimeStamp; + UInt32 fSsrc; + + UInt16 fLastSequenceNumber; + + UInt32 fInitSSRC; // initial SSRC + UInt64 fCurStreamRTpSequenceNumber; // now + + TypeMap *fStreamMediaTypePtr; + TypeMap *fMovieMediaTypePtr; + SInt64 fMovieStartTime; + SInt64 fMovieEndTime; + + Float64 fLastMovieDurationSecs; + UInt64 fMediaStartOffsetMediaScale; + UInt64 fMovieStartOffset; + + UInt32 fSeqRandomOffset; + UInt32 fRTpRandomOffset; + + bool fNewMovieStarted; + bool fNewStreamStarted; + + bool fIsSoundStream; + bool fIsVideoStream; + + char *fSoundDescriptionBuffer; + SInt32 fSavedSoundDescSize; + SInt16 fSavedDataRefIndex; + UDPSocketPair *fSocketPair; + QTRTPFile *fRTPFilePtr; + }; + + MemberData fData; + bool fSend; + ~MediaStream(); + MediaStream(); + char* GetRTCpSR() { return fData.fSenderReportBuffer; } + UInt32 GetRTCpSRLen() { return fData.fSenderReportSize; } + SInt64 GetPlayTime() { return fData.fStreamStartTime; } + SInt64 GetNTPPlayTime() { return fData.fNTPPlayTime; } + SInt16 Send(RTpPacket *packetPtr); + void ReceiveOnPorts(); + int UpdateSenderReport(SInt64 theTime); + void StreamStart(SInt64 startTime); + void MovieStart(SInt64 startTime); + void MovieEnd(SInt64 endTime); +}; + + + + + + +#endif // playlist_elements_H diff --git a/PlaylistBroadcaster.tproj/playlist_lists.cpp b/PlaylistBroadcaster.tproj/playlist_lists.cpp new file mode 100644 index 0000000..70abf30 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_lists.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@ + * + */ + +#include "playlist_lists.h" +#include "OS.h" +#include "playlist_utils.h" + +// ************************ +// +// MEDIA STREAM LIST +// +// ************************ + +void MediaStreamList::SetUpStreamSSRCs() +{ + UInt32 ssrc; + MediaStream* setMediaStreamPtr; + MediaStream* aMediaStreamPtr; + bool found_duplicate; + + for (int i = 0; i < Size(); i++) + { + setMediaStreamPtr = SetPos(i); + if (setMediaStreamPtr != NULL) do + { + ssrc = PlayListUtils::Random() + ( (UInt32) OS::Milliseconds() ); // get a new ssrc + aMediaStreamPtr = Begin(); // start at the beginning of the stream list + found_duplicate = false; // default is don't loop + + while (aMediaStreamPtr != NULL) //check all the streams for a duplicate + { + if (aMediaStreamPtr->fData.fInitSSRC == ssrc) // it is a duplicate + { found_duplicate = true; // set to loop: try a new ssrc + break; + } + + aMediaStreamPtr = Next(); // keep checking for a duplicate + } + + if (!found_duplicate) // no duplicates found so keep this ssrc + setMediaStreamPtr->fData.fInitSSRC = ssrc; + + } while (found_duplicate); // we have a duplicate ssrc so find another one + } +} + + +void MediaStreamList::StreamStarted(SInt64 startTime) +{ + for ( MediaStream *theStreamPtr = Begin(); (theStreamPtr != NULL) ; theStreamPtr = Next() ) + { + theStreamPtr->StreamStart(startTime); + } +} + +void MediaStreamList::MovieStarted(SInt64 startTime) +{ + for ( MediaStream *theStreamPtr = Begin(); (theStreamPtr != NULL) ; theStreamPtr = Next() ) + { + theStreamPtr->MovieStart(startTime); + } +} + +void MediaStreamList::MovieEnded(SInt64 endTime) +{ + for ( MediaStream *theStreamPtr = Begin(); (theStreamPtr != NULL) ; theStreamPtr = Next() ) + { + theStreamPtr->MovieEnd(endTime); + } +} + + +SInt16 MediaStreamList::UpdateStreams() +{ + SInt16 err = 0; + for ( MediaStream *theStreamPtr = Begin(); (theStreamPtr != NULL) ; theStreamPtr = Next() ) + { + theStreamPtr->ReceiveOnPorts(); + }; + + return err; +} + +void MediaStreamList::UpdateSenderReportsOnStreams() +{ + SInt64 theTime = PlayListUtils::Milliseconds(); + for ( MediaStream *theStreamPtr = Begin(); (theStreamPtr != NULL) ; theStreamPtr = Next() ) + { + (void) theStreamPtr->UpdateSenderReport(theTime); + }; + +} + diff --git a/PlaylistBroadcaster.tproj/playlist_lists.h b/PlaylistBroadcaster.tproj/playlist_lists.h new file mode 100644 index 0000000..c40c8b7 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_lists.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@ + * + */ + +#ifndef playlist_lists_H +#define playlist_lists_H + + +#include +#include +#include "SafeStdLib.h" +#include +#include + + +#ifndef __Win32__ + #include + #include +#endif + +#include "OSHeaders.h" +#include "playlist_array.h" +#include "playlist_elements.h" +#include "playlist_utils.h" + + + +// ************************ +// +// SOCKET LIST +// +// ************************ + + +class SocketList : public ArrayList { + +}; + +// ************************ +// +// MEDIA STREAM LIST +// +// ************************ + + +class MediaStreamList : public ArrayList { + + protected: + + public: + SInt16 UpdateStreams(); + void UpdateSenderReportsOnStreams(); + void SetUpStreamSSRCs(); + void StreamStarted(SInt64 startTime); + void MovieStarted(SInt64 startTime); + void MovieEnded(SInt64 endTime); +}; + +// ************************ +// +// SDP MEDIA LIST +// +// ************************ + + +class SDPMediaList : public ArrayList { + +}; + +#endif //playlist_lists_H diff --git a/PlaylistBroadcaster.tproj/playlist_parsers.cpp b/PlaylistBroadcaster.tproj/playlist_parsers.cpp new file mode 100644 index 0000000..d06cf87 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_parsers.cpp @@ -0,0 +1,540 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "playlist_parsers.h" + +char* SDPFileParser::sMediaTag = "m"; +char* SDPFileParser::sAttributeTag = "a"; +char* SDPFileParser::sConnectionTag = "c"; + +SDPFileParser::~SDPFileParser() +{ + if (fSDPBuff) + { delete[] fSDPBuff; + fSDPBuff = NULL; + } +} + +bool SDPFileParser::IsCommented(SimpleString *theString) +{ + if ( NULL == theString) return false; + if ( theString->fLen == 0) return false; + if ( theString->fTheString[0] == '#' ) return true; // It's commented if the first non-white char is # + + return false; +} + + + +int TextLine::Parse (SimpleString *textStrPtr) +{ + short count = 0; + + do + { + if (!textStrPtr) break; + + count = CountDelimeters(textStrPtr,sWordDelimeters); + if (count < 1) break; + + fWords.SetSize(count); + fSource = *textStrPtr; + SimpleString *listStringPtr = fWords.Begin(); + + SimpleString currentString; + currentString.SetString(textStrPtr->fTheString, 0); + + for ( short i = 0; i < count; i ++) + { GetNextThing(textStrPtr,¤tString, sWordDelimeters, ¤tString); + *listStringPtr = currentString; + listStringPtr++; + } + + } while (false); + + + return count; +} + +int LineAndWordsParser::Parse (SimpleString *textStrPtr) +{ + short count = 0; + + do + { + if (!textStrPtr) break; + + count = CountDelimeters(textStrPtr,sLineDelimeters); + if (count < 1) break; + + fLines.SetSize(count); + fSource = *textStrPtr; + TextLine *listStringPtr = fLines.Begin(); + + SimpleString currentString; + currentString.SetString(textStrPtr->fTheString, 0); + + for ( short i = 0; i < count; i ++) + { GetNextThing(textStrPtr,¤tString, sLineDelimeters, ¤tString); + listStringPtr->Parse(¤tString); + listStringPtr++; + } + } while (false); + + return count; +} + + +short SDPFileParser::CountQTTextLines() +{ + short numlines = 0; + TextLine *theLinePtr = fParser.fLines.Begin(); + + while (theLinePtr) + { if (GetQTTextFromLine(theLinePtr)) + numlines ++; + + theLinePtr = fParser.fLines.Next(); + }; + + return numlines; +} + + +short SDPFileParser::CountMediaEntries() +{ + bool commented = false; + bool isEqual = false; + short numTracks = 0; + + TextLine *theLinePtr = fParser.fLines.Begin(); + SimpleString *firstWordPtr; + + while (theLinePtr) + { + do + { firstWordPtr = theLinePtr->fWords.Begin(); + if (!firstWordPtr) break; + + commented = IsCommented(firstWordPtr); + if (commented) break; + + isEqual = Compare(firstWordPtr, SDPFileParser::sMediaTag, true); + if (!isEqual) break; + + numTracks ++; + + } while (false); + + theLinePtr = fParser.fLines.Next(); + }; + + return numTracks; +} + +short SDPFileParser::CountRTPMapEntries() +{ + short startPos = fParser.fLines.GetPos(); + short result = 0; + TextLine *theLinePtr = fParser.fLines.Get(); + SimpleString mapString("rtpmap"); + SimpleString *aWordPtr; + bool isEqual; + + while (theLinePtr) + { + aWordPtr = theLinePtr->fWords.Begin(); + if (aWordPtr) + { + isEqual = Compare(aWordPtr, SDPFileParser::sAttributeTag, true); + if (isEqual) // see if this attribute is a rtpmap line + { + aWordPtr = theLinePtr->fWords.SetPos(1); + isEqual = Compare(aWordPtr, &mapString, false); + if (isEqual) result ++; + } + else // could be a comment or some other attribute + { isEqual = Compare(aWordPtr, SDPFileParser::sMediaTag, true); + if (isEqual) break; // its another media line so stop + } + } + theLinePtr = fParser.fLines.Next(); + }; + + fParser.fLines.SetPos(startPos); + + return result; +} + + +void SDPFileParser::GetPayLoadsFromLine(TextLine *theLinePtr, TypeMap *theTypeMapPtr) +{ + short count = 0; + if (theLinePtr == NULL || theTypeMapPtr == NULL) + return; + + SimpleString *aWordPtr = theLinePtr->fWords.SetPos(5);// get protocol ID str + while (aWordPtr) + { count ++; + aWordPtr = theLinePtr->fWords.Next();// get next protocol ID str + } + + theTypeMapPtr->fPayLoads.SetSize(count); + short* idPtr = theTypeMapPtr->fPayLoads.Begin();// get protocol ID ref + aWordPtr = theLinePtr->fWords.SetPos(5);// get protocol ID str + + while (aWordPtr && idPtr) + { + *idPtr = (short) aWordPtr->GetInt(); + aWordPtr = theLinePtr->fWords.Next();// get next protocol ID str + idPtr = theTypeMapPtr->fPayLoads.Next();// get next protocol ID ref + } +} + +bool SDPFileParser::GetQTTextFromLine(TextLine *theLinePtr) +{ +//a=x-qt-text-cpy:xxxxx +//a=x-qt-text-nam:xxxxxx +//a=x-qt-text-inf:xxxxxxx + + bool result = false; + SimpleString *aWordPtr; + char *xString ="a=x-qt-text"; + do + { + aWordPtr = theLinePtr->fWords.Begin(); + if (!aWordPtr) break; + + bool isEqual = (0 == strncmp(aWordPtr->fTheString, xString,strlen(xString) ) ) ? true: false; + if (!isEqual) break; + + result = true; + + } while (false); + + return result; +} + + +bool SDPFileParser::GetMediaFromLine(TextLine *theLinePtr, TypeMap *theTypeMapPtr) +{ + bool result = false; + SimpleString *aWordPtr; + + do + { + aWordPtr = theLinePtr->fWords.Begin(); + if (!aWordPtr) break; + + bool isEqual = Compare(aWordPtr, SDPFileParser::sMediaTag, true); + if (!isEqual) break; + + aWordPtr = theLinePtr->fWords.SetPos(1);// get type + if (!aWordPtr) break; + + theTypeMapPtr->fTheTypeStr = *aWordPtr; + + aWordPtr = theLinePtr->fWords.SetPos(2);// get movie port + if (!aWordPtr) break; + + theTypeMapPtr->fPort = aWordPtr->GetInt(); + + aWordPtr = theLinePtr->fWords.SetPos(3);// get protocol + if (!aWordPtr) break; + + theTypeMapPtr->fProtocolStr = *aWordPtr; + + GetPayLoadsFromLine(theLinePtr, theTypeMapPtr); + + result = true; + } while (false); + + return result; + +} + +bool SDPFileParser::GetRTPMap(TextLine *theLinePtr,PayLoad *payloadPtr) +{ + bool lineOK = false; + SimpleString *aWordPtr; + SimpleString mapString("rtpmap"); + + do + { + if (!theLinePtr || !payloadPtr) break; + + aWordPtr = theLinePtr->fWords.SetPos(1); // the attribute name + if (!aWordPtr) break; + if (!Compare(aWordPtr, &mapString, false)) + break; + + aWordPtr = theLinePtr->fWords.Next(); // the Payload ID + if (!aWordPtr) break; + payloadPtr->payloadID = aWordPtr->GetInt(); + + aWordPtr = theLinePtr->fWords.Next(); // the Payload type string + if (!aWordPtr) break; + payloadPtr->payLoadString = *aWordPtr; + + payloadPtr->timeScale = 0; + aWordPtr = theLinePtr->fWords.Next(); // the Payload timeScale + if (aWordPtr) + payloadPtr->timeScale = aWordPtr->GetInt(); + + lineOK = true; + + } while (false); + + return lineOK; + +} + +TextLine *SDPFileParser::GetRTPMapLines(TextLine *theLinePtr,TypeMap *theTypeMapPtr) +{ + + do + { + if (!theLinePtr || !theTypeMapPtr) break; + + short numAttributes = CountRTPMapEntries(); + theTypeMapPtr->fPayLoadTypes.SetSize(numAttributes); + PayLoad *payloadPtr = theTypeMapPtr->fPayLoadTypes.Begin(); + + while( theLinePtr && payloadPtr && (numAttributes > 0) ) + { + bool haveMAP = GetRTPMap(theLinePtr,payloadPtr); + if (haveMAP) + { numAttributes --; + payloadPtr = theTypeMapPtr->fPayLoadTypes.Next(); //skip to next payload entry + } + + theLinePtr = fParser.fLines.Next(); // skip to next line + if(theLinePtr == NULL || Compare(theLinePtr->fWords.Begin(), SDPFileParser::sMediaTag, true)) //stop checking if this is a new media line + break; + + } + } while (false); + + return theLinePtr; +} + +TextLine * SDPFileParser::GetTrackID(TextLine *theLinePtr,TypeMap *theTypeMapPtr) +{ + SimpleString *aFieldPtr; + SimpleString *aWordPtr; + Bool16 foundID = false; + + while(theLinePtr && !foundID) + { + if(Compare(theLinePtr->fWords.Begin(), SDPFileParser::sMediaTag, true)) //stop checking if this is a new media line + break; + + do + { + SimpleString controlString("control"); + + aFieldPtr = theLinePtr->fWords.Begin(); + if (!aFieldPtr) break; + + bool isEqual = Compare(aFieldPtr, SDPFileParser::sAttributeTag, true); + if (!isEqual) break; + + aWordPtr = theLinePtr->fWords.SetPos(1); + if (!aWordPtr) break; + + isEqual = Compare(aWordPtr, &controlString, false); + if (!isEqual) break; + + aWordPtr = theLinePtr->fWords.SetPos(3); + if (!aWordPtr) break; + + theTypeMapPtr->fTrackID = aWordPtr->GetInt(); + foundID = true; + + } while (false); + + + theLinePtr = fParser.fLines.Next(); + + } + + return theLinePtr; + +} +bool SDPFileParser::ParseIPString(TextLine *theLinePtr) +{ + bool result = false; + SimpleString *aWordPtr; + do + { + SimpleString ipIDString("IP4"); + + aWordPtr = theLinePtr->fWords.Begin(); + if (!aWordPtr) break; + + bool isEqual = Compare(aWordPtr,SDPFileParser::sConnectionTag, true); + if (!isEqual) break; + + aWordPtr = theLinePtr->fWords.SetPos(2); + if (!aWordPtr) break; + + isEqual = Compare(aWordPtr, &ipIDString, false); + if (!isEqual) break; + + aWordPtr = theLinePtr->fWords.SetPos(3); + if (!aWordPtr) break; + + fIPAddressString.SetString(aWordPtr->fTheString, aWordPtr->fLen); + result = true; + + } while (false); + + return result; + +} +SInt32 SDPFileParser::ParseSDP(char *theBuff) +{ + SInt32 result = 0; + bool found = false; + + SimpleString source(theBuff); + fSource.SetString( theBuff, strlen(theBuff) ); + fParser.Parse(&source); + + +// Test parse +#if 0 + qtss_printf("-----------------------------------------------------\n"); + char tempString[256]; + TextLine *theLine = fParser.fLines.Begin(); + while (theLine) + { SimpleString *theWord = theLine->fWords.Begin(); + while (theWord) + { theWord->GetString(tempString,256); + qtss_printf(tempString); + theWord = theLine->fWords.Next(); + if (theWord) qtss_printf(" _ "); + } + theLine = fParser.fLines.Next(); + qtss_printf("\n"); + } + // exit (0); +#endif + + fNumQTTextLines = CountQTTextLines(); + fQTTextLines.SetSize( (SInt16) fNumQTTextLines); + SimpleString *theQTTextPtr = fQTTextLines.Begin(); + + fNumTracks = CountMediaEntries(); + fSDPMediaList.SetSize((SInt16) fNumTracks); + + TextLine *theLinePtr = fParser.fLines.Begin(); + TypeMap *theTypeMapPtr = fSDPMediaList.Begin(); + + bool foundIP = false; + while (theLinePtr && theTypeMapPtr) + { + if (foundIP == false) + { foundIP = ParseIPString(theLinePtr); + } + + if (theQTTextPtr && GetQTTextFromLine(theLinePtr)) + { SimpleString *srcLinePtr = theLinePtr->fWords.Begin(); + theQTTextPtr->SetString(srcLinePtr->fTheString, strcspn(srcLinePtr->fTheString, "\r\n") ); + theQTTextPtr = fQTTextLines.Next(); + } + + found = GetMediaFromLine(theLinePtr, theTypeMapPtr); + if (found) + { + theLinePtr = fParser.fLines.Next(); + if (!theLinePtr) break; // no more lines to process + + int startLine = fParser.fLines.GetPos(); + + theLinePtr = fParser.fLines.SetPos(startLine); + (void) GetRTPMapLines(theLinePtr,theTypeMapPtr); + + theLinePtr = fParser.fLines.SetPos(startLine); + (void) GetTrackID(theLinePtr,theTypeMapPtr); + + theLinePtr = fParser.fLines.SetPos(startLine); + theTypeMapPtr = fSDPMediaList.Next(); + continue; + } + + theLinePtr = fParser.fLines.Next(); + } + + return result; +} + + + +SInt32 SDPFileParser::ReadSDP(char *filename) +{ + int result = -1; + SInt32 bytes= 0; + + FILE *f = NULL; + + if (fSDPBuff != NULL) + { delete[] fSDPBuff; + fSDPBuff = NULL; + } + + do + { + f = ::fopen(filename, "r"); + if (f == NULL) break; + + result = 1; + result = ::fseek(f, 0, SEEK_SET); + if (result != 0) break; + + fSDPBuff = new char[cMaxBytes + 1]; + if (NULL == fSDPBuff) break; + fSDPBuff[cMaxBytes] = 0; + + bytes = ::fread(fSDPBuff, sizeof(char), cMaxBytes, f); + if (bytes < 1) break; + fSDPBuff[bytes] = 0; + + result = ParseSDP(fSDPBuff); + if (result != 0) break; + + result = 0; + + } while (false); + + if (f != NULL) + { ::fclose (f); + f = NULL; + } + + return result; +} + + diff --git a/PlaylistBroadcaster.tproj/playlist_parsers.h b/PlaylistBroadcaster.tproj/playlist_parsers.h new file mode 100644 index 0000000..7b82c71 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_parsers.h @@ -0,0 +1,109 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 playlist_parsers_H +#define playlist_parsers_H + +#include +#include +#include "SafeStdLib.h" +#include +#include +#include + +#include "playlist_elements.h" +#include "OSHeaders.h" +#include "playlist_SimpleParse.h" +#include "playlist_utils.h" +#include "playlist_lists.h" + +#ifndef __Win32__ + #include +#endif + + +class TextLine : public SimpleParser +{ + public: + ArrayList fWords; + int Parse (SimpleString *textStrPtr); +}; + + + +class LineAndWordsParser : public SimpleParser +{ + public: + ArrayList fLines; + int Parse (SimpleString *textStrPtr); +}; + + +class SDPFileParser : SimpleParser +{ + public: + SDPFileParser(void) : fNumTracks(0), fSDPBuff(NULL) {} + ~SDPFileParser(void); + SInt32 ReadSDP(char *theFile); + SInt32 ParseSDP(char *theBuff) ; + SInt32 GetNumTracks() {return fNumTracks;} ; + bool IsCommented(SimpleString *aLine); + TextLine* GetRTPMapLines(TextLine *theLinePtr,TypeMap *theTypeMapPtr); + bool GetRTPMap(TextLine *theLinePtr,PayLoad *payloadPtr); + bool GetMediaFromLine(TextLine *theLinePtr, TypeMap *theTypeMapPtr); + void GetPayLoadsFromLine(TextLine *theLinePtr, TypeMap *theTypeMapPtr); + TextLine* GetTrackID(TextLine *theLinePtr,TypeMap *theTypeMapPtr); + bool ParseIPString(TextLine *theLinePtr); + SimpleString* GetIPString() { return &fIPAddressString; }; + + ArrayList fQTTextLines; + SInt32 GetNumQTTextLines() {return fNumQTTextLines;} ; + bool GetQTTextFromLine(TextLine *theLinePtr); + + SDPMediaList fSDPMediaList; + protected: + + UInt32 TimeScaleLookUp(int payLoadID, SimpleString *typeStringPtr); + short CountMediaEntries() ; + short CountRTPMapEntries() ; + short CountQTTextLines(); + + UInt32 fNumQTTextLines; + UInt32 fNumTracks; + LineAndWordsParser fParser; + SimpleString fIPAddressString; + + private: + enum { cMaxBytes = 4096}; // maximum accepted sdp file size + + static char* sMediaTag; + static char* sAttributeTag; + static char* sConnectionTag; + + char *fSDPBuff; +}; + + +#endif //playlist_parsers_H diff --git a/PlaylistBroadcaster.tproj/playlist_timestamp.h b/PlaylistBroadcaster.tproj/playlist_timestamp.h new file mode 100644 index 0000000..6b67b41 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_timestamp.h @@ -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@ + * + */ +/* + * + * History: + * 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 + +/* 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); + +#ifdef __cplusplus +} +#endif + +#endif /* _TIMESTAMP_H_ */ diff --git a/PlaylistBroadcaster.tproj/playlist_utils.cpp b/PlaylistBroadcaster.tproj/playlist_utils.cpp new file mode 100644 index 0000000..5a235d2 --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_utils.cpp @@ -0,0 +1,255 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 "playlist_utils.h" +#include "OS.h" + + +#if Log_Enabled + enum {gBuffSize = 532, gBuffMaxStr = 512}; + + enum {switchEntries, switchTime, switchBytes}; + + enum {gLogMaxBytes = 100000}; + enum {gLogMaxEntries = 1000}; + enum {gLogMaxMilliSecs = 3 * 60 * 1000}; + + + static int gLogSwitchSetting = switchEntries; + static SInt32 gLogEntries = 0; + static SInt64 gLogTimeStart = 0; + static SInt32 gLogNumBytes = 0; + static gLogNumPackets = 0; + static char gTempStr[256]; + static FILE *gLogFile_1 = 0; + static FILE *gLogFile_2 = 0; + static FILE *gLogFileCurrent = 0; + static int gFileNum = 1; + + static bool gLogStarted = false; + static char gLogBuff[gBuffSize]; + static int gBuffUsed = 0; + + + +void LogFileOpen(void) +{ + if (!gLogFile_1 && !gLogFile_2) + { gLogFile_1 = fopen("logfile_1.txt","w"); + gLogFileCurrent = gLogFile_1; + *gLogBuff = 0; + gLogTimeStart = PlayListUtils::Milliseconds(); + gLogStarted = true; + gLogEntries = 0; + gLogTimeStart = 0; + gLogNumBytes = 0; + gLogNumPackets = 0; + gFileNum = 1; + gBuffUsed = 0; + } +} + +void LogFileClose(void) +{ + fclose(gLogFile_1); + fclose(gLogFile_2); + gLogFileCurrent = gLogFile_1 = gLogFile_2 = 0; + +} + +bool TimeToSwitch(int len) +{ + bool timeToSwitch = false; + if (!gLogStarted) return timeToSwitch; + + switch( gLogSwitchSetting ) + { + case switchEntries: + if (gLogEntries >= gLogMaxEntries) + { timeToSwitch = true; + gLogEntries = 1; + } + else + gLogEntries ++; + break; + + case switchTime: + { SInt64 timeNow = PlayListUtils::Milliseconds(); + SInt64 timeThisFile = timeNow - gLogTimeStart; + if ( timeThisFile > gLogMaxMilliSecs ) + { timeToSwitch = true; + gLogTimeStart = timeNow; + } + } + break; + + case switchBytes: + if (gLogNumBytes > gLogMaxBytes) + { timeToSwitch = true; + gLogNumBytes = 0; + } + gLogNumBytes += len; + break; + }; + + + return timeToSwitch; + +} + +void WriteToLog(void *data, int len) +{ + if (gLogFileCurrent && data && len) + { + bool timetoswitch = TimeToSwitch(len); + if ( timetoswitch ) + { + if (gFileNum == 1) + { + if (gLogFile_1) fclose(gLogFile_1); + gLogFile_1 = 0; + gLogFile_2 = fopen("logfile_2.txt","w"); + gFileNum = 2; + + gLogFileCurrent = gLogFile_2; + fseek(gLogFileCurrent , 0, SEEK_SET); + } + else + { + if (gLogFile_2) fclose(gLogFile_2); + gLogFile_2 = 0; + gLogFile_1 = fopen("logfile_1.txt","w"); + gFileNum = 1; + + gLogFileCurrent = gLogFile_1; + fseek(gLogFileCurrent , 0, SEEK_SET); + } + } + fwrite(data, sizeof(char), len, gLogFileCurrent); + fflush(gLogFileCurrent); + } +} + +void WritePacketToLog(void *data, int len) +{ + gLogNumPackets ++; + LogUInt("Packet:", (UInt32) gLogNumPackets,"\n"); + LogBuffer(); + WriteToLog(data, len); +} + +void WritToBuffer(void *data, int len) +{ + if (data ) + { + if (len >= gBuffSize) + len = gBuffSize -1; + memcpy (gLogBuff, (char *) data, len); + gLogBuff[gBuffSize] = 0; + } + gBuffUsed = len; +} + +void LogBuffer(void) +{ + WriteToLog(gLogBuff, strlen(gLogBuff) ); + *gLogBuff =0; + gBuffUsed = strlen(gLogBuff); +} + +void PrintLogBuffer(bool log) +{ + qtss_printf(gLogBuff); + if (log) LogBuffer(); + *gLogBuff =0; + gBuffUsed = strlen(gLogBuff); +} + +void LogNum(char *str1,char *str2,char *str3) +{ + int size = strlen(str1) + strlen(str2) + strlen(str3); + if ( size < gBuffMaxStr ) + qtss_sprintf(gLogBuff, "%s%s%s%s",gLogBuff, str1, str2,str3); + + gBuffUsed = strlen(gLogBuff); +} + +void LogFloat(char *str, float num, char *str2) +{ + qtss_sprintf(gTempStr,"%f",num); + LogNum(str,gTempStr,str2); +} + +void LogInt(char *str, SInt32 num, char *str2) +{ + qtss_sprintf(gTempStr,"%"_S32BITARG_"",num); + LogNum(str,gTempStr,str2); +} + +void LogUInt (char *str, UInt32 num, char *str2) +{ + qtss_sprintf(gTempStr,"%"_U32BITARG_"",num); + LogNum(str,gTempStr,str2); +} + +void LogStr(char *str) +{ + *gLogBuff = 0; + int size = strlen(str) + gBuffUsed; + if ( size <= gBuffMaxStr ) + qtss_sprintf(gLogBuff, "%s%s",gLogBuff, str); + gBuffUsed = strlen(gLogBuff); +} + +#endif + + +SInt64 PlayListUtils::sInitialMsecOffset = 0; +UInt32 PlayListUtils::sRandomNum = 0; + +void PlayListUtils::Initialize() +{ + if (sInitialMsecOffset != 0) return; + sInitialMsecOffset = OS::Milliseconds(); //Milliseconds uses sInitialMsec to make a 0 offset millisecond so this assignment is valid only once. + //qtss_printf("sInitialMsecOffset =%qd\n",sInitialMsecOffset); + ::srand( (UInt16) OS::UnixTime_Secs() ); + sRandomNum = ::rand(); + +} + + +PlayListUtils::PlayListUtils() +{ +} + +UInt32 PlayListUtils::Random() +{ + + UInt32 seed = 1664525L * sRandomNum + 1013904223L; //1013904223 is prime .. Knuth D.E. + ::srand( seed ); + + sRandomNum = ::rand(); + + return sRandomNum; +} diff --git a/PlaylistBroadcaster.tproj/playlist_utils.h b/PlaylistBroadcaster.tproj/playlist_utils.h new file mode 100644 index 0000000..ca7ffab --- /dev/null +++ b/PlaylistBroadcaster.tproj/playlist_utils.h @@ -0,0 +1,101 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, 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 playlist_utils_H +#define playlist_utils_H + +#include +#include +#include "SafeStdLib.h" +#include +#include +#ifndef __Win32__ + #include + #include + #include + #include + #include + #include + + #include +#endif + +#include "OSHeaders.h" +#include "OS.h" + +#define Log_Enabled 0 + +#if Log_Enabled + + void LogFileOpen(void); + void LogFileClose(void); + bool TimeToSwitch(int len); + void WriteToLog(void *data, int len); + void WritePacketToLog(void *data, int len); + void WritToBuffer(void *data, int len); + void LogBuffer(void); + void PrintLogBuffer(bool log); + void LogNum(void); + void LogFloat(char *str, float num, char *str2= "\0"); + void LogInt(char *str, SInt32 num, char *str2= "\0"); + void LogUInt (char *str, UInt32 num, char *str2 = "\0"); + void LogStr(char *str); + +#else + #define LogFileOpen() + #define LogFileClose() + #define TimeToSwitch(len) + #define WriteToLog(data,len) + #define WritePacketToLog(data,len) + #define WritToBuffer(data, len) + #define LogBuffer() + #define PrintLogBuffer(log) + #define LogFloat(str,num,str2) + #define LogInt(str,num,str2) + #define LogUInt(str,num,str2) + #define LogStr(str) + #define LogNum() +#endif + +#define kFixed64 ( (UInt64) 1 << 32) + +class PlayListUtils +{ + public: + PlayListUtils(); + + + enum {eMicro = 1000000, eMilli = 1000}; + static SInt64 sInitialMsecOffset; + static UInt32 sRandomNum; + static void InitRandom() {}; + static void Initialize(); + static UInt32 Random(); + static SInt64 Milliseconds() {return OS::Milliseconds() - sInitialMsecOffset;}; + static SInt64 TimeMilli_To_1900Fixed64Secs(SInt64 inMilliseconds) { return OS::TimeMilli_To_1900Fixed64Secs(OS::TimeMilli_To_UnixTimeMilli(sInitialMsecOffset + inMilliseconds)); } + static SInt64 TimeMilli_To_Fixed64Secs(SInt64 inMilliseconds) { return OS::TimeMilli_To_Fixed64Secs(inMilliseconds); } +}; + +#endif //playlist_utils_H diff --git a/PlaylistBroadcaster.tproj/tailor.h b/PlaylistBroadcaster.tproj/tailor.h new file mode 100644 index 0000000..4a50641 --- /dev/null +++ b/PlaylistBroadcaster.tproj/tailor.h @@ -0,0 +1,352 @@ +/* + * 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@ + */ +/* tailor.h -- target dependent definitions + * Copyright (C) 1992-1993 Jean-loup Gailly. + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +/* The target dependent definitions should be defined here only. + * The target dependent functions should be defined in tailor.c. + */ + +/* $Id: tailor.h,v 1.1 2006/01/05 13:20:36 murata Exp $ */ + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif + +#if defined(__OS2__) && !defined(OS2) +# define OS2 +#endif + +#if defined(OS2) && defined(MSDOS) /* MS C under OS/2 */ +# undef MSDOS +#endif + +#ifdef MSDOS +# ifdef __GNUC__ + /* DJGPP version 1.09+ on MS-DOS. + * The DJGPP 1.09 stat() function must be upgraded before gzip will + * fully work. + * No need for DIRENT, since defines POSIX_SOURCE which + * implies DIRENT. + */ +# define near +# else +# define MAXSEG_64K +# ifdef __TURBOC__ +# define NO_OFF_T +# ifdef __BORLANDC__ +# define DIRENT +# else +# define NO_UTIME +# endif +# else /* MSC */ +# define HAVE_SYS_UTIME_H +# define NO_UTIME_H +# endif +# endif +# define PATH_SEP2 '\\' +# define PATH_SEP3 ':' +# define MAX_PATH_LEN 128 +# define NO_MULTIPLE_DOTS +# define MAX_EXT_CHARS 3 +# define Z_SUFFIX "z" +# define NO_CHOWN +# define PROTO +# define STDC_HEADERS +# define NO_SIZE_CHECK +# define casemap(c) tolow(c) /* Force file names to lower case */ +# include +# define OS_CODE 0x00 +# define SET_BINARY_MODE(fd) setmode(fd, O_BINARY) +# if !defined(NO_ASM) && !defined(ASMV) +# define ASMV +# endif +#else +# define near +#endif + +#ifdef OS2 +# define PATH_SEP2 '\\' +# define PATH_SEP3 ':' +# define MAX_PATH_LEN 260 +# ifdef OS2FAT +# define NO_MULTIPLE_DOTS +# define MAX_EXT_CHARS 3 +# define Z_SUFFIX "z" +# define casemap(c) tolow(c) +# endif +# define NO_CHOWN +# define PROTO +# define STDC_HEADERS +# include +# define OS_CODE 0x06 +# define SET_BINARY_MODE(fd) setmode(fd, O_BINARY) +# ifdef _MSC_VER +# define HAVE_SYS_UTIME_H +# define NO_UTIME_H +# define MAXSEG_64K +# undef near +# define near _near +# endif +# ifdef __EMX__ +# define HAVE_SYS_UTIME_H +# define NO_UTIME_H +# define DIRENT +# define EXPAND(argc,argv) \ + {_response(&argc, &argv); _wildcard(&argc, &argv);} +# endif +# ifdef __BORLANDC__ +# define DIRENT +# endif +# ifdef __ZTC__ +# define NO_DIR +# define NO_UTIME_H +# include +# define EXPAND(argc,argv) \ + {response_expand(&argc, &argv);} +# endif +#endif + +#ifdef WIN32 /* Windows NT */ +# define HAVE_SYS_UTIME_H +# define NO_UTIME_H +# define PATH_SEP2 '\\' +# define PATH_SEP3 ':' +# define MAX_PATH_LEN 260 +# define NO_CHOWN +# define PROTO +# define STDC_HEADERS +# define SET_BINARY_MODE(fd) setmode(fd, O_BINARY) +# include +# include +# ifdef NTFAT +# define NO_MULTIPLE_DOTS +# define MAX_EXT_CHARS 3 +# define Z_SUFFIX "z" +# define casemap(c) tolow(c) /* Force file names to lower case */ +# endif +# define OS_CODE 0x0b +#endif + +#ifdef MSDOS +# ifdef __TURBOC__ +# include +# define DYN_ALLOC + /* Turbo C 2.0 does not accept static allocations of large arrays */ + void * fcalloc (unsigned items, unsigned size); + void fcfree (void *ptr); +# else /* MSC */ +# include +# define fcalloc(nitems,itemsize) halloc((SInt32)(nitems),(itemsize)) +# define fcfree(ptr) hfree(ptr) +# endif +#else +# ifdef MAXSEG_64K +# define fcalloc(items,size) calloc((items),(size)) +# else +# define fcalloc(items,size) malloc((size_t)(items)*(size_t)(size)) +# endif +# define fcfree(ptr) free(ptr) +#endif + +#if defined(VAXC) || defined(VMS) +# define PATH_SEP ']' +# define PATH_SEP2 ':' +# define SUFFIX_SEP ';' +# define NO_MULTIPLE_DOTS +# define Z_SUFFIX "-gz" +# define RECORD_IO 1 +# define casemap(c) tolow(c) +# define OS_CODE 0x02 +# define OPTIONS_VAR "GZIP_OPT" +# define STDC_HEADERS +# define NO_UTIME +# define EXPAND(argc,argv) vms_expand_args(&argc,&argv); +# include +# define unlink delete +# ifdef VAXC +# define NO_FCNTL_H +# include +# endif +#endif + +#ifdef AMIGA +# define PATH_SEP2 ':' +# define STDC_HEADERS +# define OS_CODE 0x01 +# define ASMV +# ifdef __GNUC__ +# define DIRENT +# define HAVE_UNISTD_H +# else /* SASC */ +# define NO_STDIN_FSTAT +# define SYSDIR +# define NO_SYMLINK +# define NO_CHOWN +# define NO_FCNTL_H +# include /* for read() and write() */ +# define direct dirent + extern void _expand_args(int *argc, char ***argv); +# define EXPAND(argc,argv) _expand_args(&argc,&argv); +# undef O_BINARY /* disable useless --ascii option */ +# endif +#endif + +#if defined(ATARI) || defined(atarist) +# ifndef STDC_HEADERS +# define STDC_HEADERS +# define HAVE_UNISTD_H +# define DIRENT +# endif +# define ASMV +# define OS_CODE 0x05 +# ifdef TOSFS +# define PATH_SEP2 '\\' +# define PATH_SEP3 ':' +# define MAX_PATH_LEN 128 +# define NO_MULTIPLE_DOTS +# define MAX_EXT_CHARS 3 +# define Z_SUFFIX "z" +# define NO_CHOWN +# define casemap(c) tolow(c) /* Force file names to lower case */ +# define NO_SYMLINK +# endif +#endif + +#ifdef MACOS +# define PATH_SEP ':' +# define DYN_ALLOC +# define PROTO +# define NO_STDIN_FSTAT +# define NO_CHOWN +# define NO_UTIME +# define chmod(file, mode) (0) +# define OPEN(name, flags, mode) open(name, flags) +# define OS_CODE 0x07 +# ifdef MPW +# define isatty(fd) ((fd) <= 2) +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define PATH_SEP '>' +# define STDC_HEADERS +# define NO_MEMORY_H +# define NO_UTIME_H +# define NO_UTIME +# define NO_CHOWN +# define NO_STDIN_FSTAT +# define NO_SIZE_CHECK +# define NO_SYMLINK +# define RECORD_IO 1 +# define casemap(c) tolow(c) /* Force file names to lower case */ +# define put_char(c) put_byte((c) & 0x7F) +# define get_char(c) ascii2pascii(get_byte()) +# define OS_CODE 0x0F /* temporary, subject to change */ +# ifdef SIGTERM +# undef SIGTERM /* We don't want a signal handler for SIGTERM */ +# endif +#endif + +#if defined(pyr) && !defined(NOMEMCPY) /* Pyramid */ +# define NOMEMCPY /* problem with overlapping copies */ +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifndef unix +# define NO_ST_INO /* don't rely on inode numbers */ +#endif + + + /* Common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef PATH_SEP +# define PATH_SEP '/' +#endif + +#ifndef casemap +# define casemap(c) (c) +#endif + +#ifndef OPTIONS_VAR +# define OPTIONS_VAR "GZIP" +#endif + +#ifndef Z_SUFFIX +# define Z_SUFFIX ".gz" +#endif + +#ifdef MAX_EXT_CHARS +# define MAX_SUFFIX MAX_EXT_CHARS +#else +# define MAX_SUFFIX 30 +#endif + +#ifndef MAKE_LEGAL_NAME +# ifdef NO_MULTIPLE_DOTS +# define MAKE_LEGAL_NAME(name) make_simple_name(name) +# else +# define MAKE_LEGAL_NAME(name) +# endif +#endif + +#ifndef MIN_PART +# define MIN_PART 3 + /* keep at least MIN_PART chars between dots in a file name. */ +#endif + +#ifndef EXPAND +# define EXPAND(argc,argv) +#endif + +#ifndef RECORD_IO +# define RECORD_IO 0 +#endif + +#ifndef SET_BINARY_MODE +# define SET_BINARY_MODE(fd) +#endif + +#ifndef OPEN +# define OPEN(name, flags, mode) open(name, flags, mode) +#endif + +#ifndef get_char +# define get_char() get_byte() +#endif + +#ifndef put_char +# define put_char(c) put_byte(c) +#endif diff --git a/PrefsSourceLib/FilePrefsSource.cpp b/PrefsSourceLib/FilePrefsSource.cpp new file mode 100644 index 0000000..c4f2145 --- /dev/null +++ b/PrefsSourceLib/FilePrefsSource.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: FilePrefsSource.cpp + + Contains: Implements object defined in FilePrefsSource.h. + + Written by: Chris LeCroy +*/ + +#include "FilePrefsSource.h" +#include +#include + +#include + +#include "MyAssert.h" +#include "OSMemory.h" +#include "ConfParser.h" + +const int kMaxLineLen = 2048; +const int kMaxValLen = 1024; + +class KeyValuePair +{ + public: + + char* GetValue() { return fValue; } + + private: + friend class FilePrefsSource; + + KeyValuePair(const char* inKey, const char* inValue, KeyValuePair* inNext); + ~KeyValuePair(); + + char* fKey; + char* fValue; + KeyValuePair* fNext; + + void ResetValue(const char* inValue); +}; + + +KeyValuePair::KeyValuePair(const char* inKey, const char* inValue, KeyValuePair* inNext) : + fKey(NULL), + fValue(NULL), + fNext(NULL) +{ + fKey = NEW char[::strlen(inKey)+1]; + ::strcpy(fKey, inKey); + fValue = NEW char[::strlen(inValue)+1]; + ::strcpy(fValue, inValue); + fNext = inNext; +} + + +KeyValuePair::~KeyValuePair() +{ + delete [] fKey; + delete [] fValue; +} + + +void KeyValuePair::ResetValue(const char* inValue) +{ + delete [] fValue; + fValue = NEW char[::strlen(inValue)+1]; + ::strcpy(fValue, inValue); +} + + +FilePrefsSource::FilePrefsSource( Bool16 allowDuplicates) +: fKeyValueList(NULL), + fNumKeys(0), + fAllowDuplicates(allowDuplicates) +{ + +} + +FilePrefsSource::~FilePrefsSource() +{ + while (fKeyValueList != NULL) + { + KeyValuePair* keyValue = fKeyValueList; + fKeyValueList = fKeyValueList->fNext; + delete keyValue; + } + +} + +int FilePrefsSource::GetValue(const char* inKey, char* ioValue) +{ + return (this->FindValue(inKey, ioValue) != NULL); +} + + +int FilePrefsSource::GetValueByIndex(const char* inKey, UInt32 inIndex, char* ioValue) +{ + KeyValuePair* thePair = this->FindValue(inKey, ioValue, inIndex); + + if (thePair == NULL) + return false; + + return true; + + /* + char* valuePtr = thePair->fValue; + + //this function makes the assumption that fValue doesn't start with whitespace + Assert(*valuePtr != '\t'); + Assert(*valuePtr != ' '); + + for (UInt32 count = 0; ((count < inIndex) && (valuePtr != '\0')); count++) + { + //go through all the "words" on this line (delimited by whitespace) + //until we hit the one specified by inIndex + + //we aren't at the proper word yet, so skip... + while ((*valuePtr != ' ') && (*valuePtr != '\t') && (*valuePtr != '\0')) + valuePtr++; + + //skip over all the whitespace between words + while ((*valuePtr == ' ') || (*valuePtr == '\t')) + valuePtr++; + + } + + //We've exhausted the data on this line before getting to our pref, + //so return an error. + if (*valuePtr == '\0') + return false; + + //if we are here, then valuePtr is pointing to the beginning of the right word + while ((*valuePtr != ' ') && (*valuePtr != '\t') && (*valuePtr != '\0')) + *ioValue++ = *valuePtr++; + *ioValue = '\0'; + + return true; + */ +} + +char* FilePrefsSource::GetValueAtIndex(UInt32 inIndex) +{ + // Iterate through the queue until we have the right entry + KeyValuePair* thePair = fKeyValueList; + while ((thePair != NULL) && (inIndex-- > 0)) + thePair = thePair->fNext; + + if (thePair != NULL) + return thePair->fValue; + return NULL; +} + +char* FilePrefsSource::GetKeyAtIndex(UInt32 inIndex) +{ + // Iterate through the queue until we have the right entry + KeyValuePair* thePair = fKeyValueList; + while ((thePair != NULL) && (inIndex-- > 0)) + thePair = thePair->fNext; + + if (thePair != NULL) + return thePair->fKey; + return NULL; +} + +void FilePrefsSource::SetValue(const char* inKey, const char* inValue) +{ + KeyValuePair* keyValue = NULL; + + // If the key/value already exists update the value. + // If duplicate keys are allowed, however, add a new entry regardless + if ((!fAllowDuplicates) && ((keyValue = this->FindValue(inKey, NULL)) != NULL)) + { + keyValue->ResetValue(inValue); + } + else + { + fKeyValueList = NEW KeyValuePair(inKey, inValue, fKeyValueList); + fNumKeys++; + } +} + + + +Bool16 FilePrefsSource::FilePrefsConfigSetter( const char* paramName, const char* paramValue[], void* userData ) +{ +/* + static callback routine for ParseConfigFile +*/ + int valueIndex = 0; + + FilePrefsSource *theFilePrefs = (FilePrefsSource*)userData; + + Assert( theFilePrefs ); + Assert( paramName ); +// Assert( paramValue[0] ); + + + // multiple values are passed in the paramValue array as distinct strs + while ( paramValue[valueIndex] != NULL ) + { + //qtss_printf("Adding config setting \n", paramName, paramValue[valueIndex] ); + theFilePrefs->SetValue(paramName, paramValue[valueIndex] ); + valueIndex++; + } + + return false; // always succeeds +} + + +int FilePrefsSource::InitFromConfigFile(const char* configFilePath) +{ + /* + load config from specified file. return non-zero + in the event of significant error(s). + + */ + + return ::ParseConfigFile( true, configFilePath, FilePrefsConfigSetter, this ); + + /* + int err = 0; + char bufLine[kMaxLineLen]; + char key[kMaxValLen]; + char value[kMaxLineLen]; + + FILE* fileDesc = ::fopen( configFilePath, "r"); + + if (fileDesc == NULL) + { + // report some problem here... + err = OSThread::GetErrno(); + + Assert( err ); + } + else + { + + while (fgets(bufLine, sizeof(bufLine) - 1, fileDesc) != NULL) + { + if (bufLine[0] != '#' && bufLine[0] != '\0') + { + int i = 0; + int n = 0; + + while ( bufLine[i] == ' ' || bufLine[i] == '\t') + { ++i;} + + n = 0; + while ( bufLine[i] != ' ' && + bufLine[i] != '\t' && + bufLine[i] != '\n' && + bufLine[i] != '\r' && + bufLine[i] != '\0' && + n < (kMaxLineLen - 1) ) + { + key[n++] = bufLine[i++]; + } + key[n] = '\0'; + + while (bufLine[i] == ' ' || bufLine[i] == '\t') + {++i;} + + n = 0; + while ((bufLine[i] != '\n') && (bufLine[i] != '\0') && + (bufLine[i] != '\r') && (n < kMaxLineLen - 1)) + { + value[n++] = bufLine[i++]; + } + value[n] = '\0'; + + if (key[0] != '#' && key[0] != '\0' && value[0] != '\0') + { + qtss_printf("Adding config setting \n", key, value); + this->SetValue(key, value); + } + else + { + //assert(false); + } + } + } + + + int closeErr = ::fclose(fileDesc); + Assert(closeErr == 0); + } + + return err; + */ + + +} + +void FilePrefsSource::DeleteValue(const char* inKey) +{ + KeyValuePair* keyValue = fKeyValueList; + KeyValuePair* prevKeyValue = NULL; + + while (keyValue != NULL) + { + if (::strcmp(inKey, keyValue->fKey) == 0) + { + if (prevKeyValue != NULL) + { + prevKeyValue->fNext = keyValue->fNext; + delete keyValue; + } + else + { + fKeyValueList = prevKeyValue; + } + + return; + + } + prevKeyValue = keyValue; + keyValue = keyValue->fNext; + } +} + + +void FilePrefsSource::WriteToConfigFile(const char* configFilePath) +{ + int err = 0; + FILE* fileDesc = ::fopen( configFilePath, "w"); + + if (fileDesc != NULL) + { + err = ::fseek(fileDesc, 0, SEEK_END); + Assert(err == 0); + + KeyValuePair* keyValue = fKeyValueList; + + while (keyValue != NULL) + { + (void)qtss_fprintf(fileDesc, "%s %s\n\n", keyValue->fKey, keyValue->fValue); + + keyValue = keyValue->fNext; + } + + err = ::fclose(fileDesc); + Assert(err == 0); + } +} + + +KeyValuePair* FilePrefsSource::FindValue(const char* inKey, char* ioValue, UInt32 index ) +{ + KeyValuePair *keyValue = fKeyValueList; + UInt32 foundIndex = 0; + + if ( ioValue != NULL) + ioValue[0] = '\0'; + + while (keyValue != NULL) + { + if (::strcmp(inKey, keyValue->fKey) == 0) + { + if ( foundIndex == index ) + { + if (ioValue != NULL) + ::strcpy(ioValue, keyValue->fValue); + return keyValue; + } + foundIndex++; + } + keyValue = keyValue->fNext; + } + + return NULL; +} diff --git a/PrefsSourceLib/FilePrefsSource.h b/PrefsSourceLib/FilePrefsSource.h new file mode 100644 index 0000000..6f0a92b --- /dev/null +++ b/PrefsSourceLib/FilePrefsSource.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: FilePrefsSource.h + + Contains: Implements the PrefsSource interface, getting the prefs from a file. + + Written by: Chris LeCroy + + + + +*/ + +#ifndef __FILEPREFSSOURCE_H__ +#define __FILEPREFSSOURCE_H__ + +#include "PrefsSource.h" +#include "OSHeaders.h" + +class KeyValuePair; //only used in the implementation + +class FilePrefsSource : public PrefsSource +{ + public: + + FilePrefsSource( Bool16 allowDuplicates = false ); + virtual ~FilePrefsSource(); + + virtual int GetValue(const char* inKey, char* ioValue); + virtual int GetValueByIndex(const char* inKey, UInt32 inIndex, char* ioValue); + + // Allows caller to iterate over all the values in the file. + char* GetValueAtIndex(UInt32 inIndex); + char* GetKeyAtIndex(UInt32 inIndex); + UInt32 GetNumKeys() { return fNumKeys; } + + int InitFromConfigFile(const char* configFilePath); + void WriteToConfigFile(const char* configFilePath); + + void SetValue(const char* inKey, const char* inValue); + void DeleteValue(const char* inKey); + + private: + + static Bool16 FilePrefsConfigSetter( const char* paramName, const char* paramValue[], void* userData ); + + KeyValuePair* FindValue(const char* inKey, char* ioValue, UInt32 index = 0); + KeyValuePair* fKeyValueList; + UInt32 fNumKeys; + Bool16 fAllowDuplicates; +}; + +#endif //__FILEPREFSSOURCE_H__ diff --git a/PrefsSourceLib/PrefsSource.h b/PrefsSourceLib/PrefsSource.h new file mode 100644 index 0000000..aa0e6c9 --- /dev/null +++ b/PrefsSourceLib/PrefsSource.h @@ -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: PrefsSource.h + + Contains: Defines an abstract common interface for extracting prefs + from some data source. Very general, low level + + Written by: Denis Serenyi + + Change History (most recent first): + + + + +*/ + +#ifndef __PREFSSOURCE_H__ +#define __PREFSSOURCE_H__ + +#include "OSHeaders.h" + +class PrefsSource +{ + public: + + virtual int GetValue(const char* inKey, char* ioValue) = 0; + virtual int GetValueByIndex(const char* inKey, UInt32 inIndex, char* ioValue) = 0; + virtual ~PrefsSource(){}; +}; + +#endif diff --git a/PrefsSourceLib/XMLParser.cpp b/PrefsSourceLib/XMLParser.cpp new file mode 100644 index 0000000..a472f65 --- /dev/null +++ b/PrefsSourceLib/XMLParser.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@ + * + */ + +#include +#include +#include + +#ifndef __Win32__ +#include +#endif + +#include "XMLParser.h" +#include "OSMemory.h" + +XMLParser::XMLParser( char* inPath, DTDVerifier* verifier) + : fRootTag(NULL), fFilePath(NULL) +{ + StrPtrLen thePath(inPath); + fFilePath = thePath.GetAsCString(); + fFile.Set(inPath); + fVerifier = verifier; +} + +XMLParser::~XMLParser() +{ + if (fRootTag) + delete fRootTag; + + delete [] fFilePath; +} + +Bool16 XMLParser::ParseFile(char* errorBuffer, int errorBufferSize) +{ + if (fRootTag != NULL) + { + delete fRootTag; // flush old data + fRootTag = NULL; + } + + fFile.Set(fFilePath); + + if (errorBufferSize < 500) errorBuffer = NULL; // Just a hack to avoid checking everywhere + if ((fFile.GetLength() == 0) || fFile.IsDir()) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize, "Couldn't read xml file"); + return false; // we don't have a valid file; + } + + char* fileData = NEW char[ (SInt32) (fFile.GetLength() + 1)]; + UInt32 theLengthRead = 0; + fFile.Read(0, fileData, (UInt32) fFile.GetLength(), &theLengthRead); + + StrPtrLen theDataPtr(fileData, theLengthRead); + StringParser theParser(&theDataPtr); + + fRootTag = NEW XMLTag(); + Bool16 result = fRootTag->ParseTag(&theParser, fVerifier, errorBuffer, errorBufferSize); + if (!result) + { + // got error parsing file + delete fRootTag; + fRootTag = NULL; + } + + delete fileData; + + fFile.Close(); + + return result; +} + +Bool16 XMLParser::DoesFileExist() +{ + Bool16 itExists = false; + fFile.Set(fFilePath); + if ((fFile.GetLength() > 0) && (!fFile.IsDir())) + itExists = true; + fFile.Close(); + + return itExists; +} + +Bool16 XMLParser::DoesFileExistAsDirectory() +{ + Bool16 itExists = false; + fFile.Set(fFilePath); + if (fFile.IsDir()) + itExists = true; + fFile.Close(); + + return itExists; +} + +Bool16 XMLParser::CanWriteFile() +{ + // + // First check if it exists for reading + FILE* theFile = ::fopen(fFilePath, "r"); + if (theFile == NULL) + return true; + + ::fclose(theFile); + + // + // File exists for reading, check if we can write it + theFile = ::fopen(fFilePath, "a"); + if (theFile == NULL) + return false; + + // + // We can read and write + ::fclose(theFile); + return true; +} + +void XMLParser::SetRootTag(XMLTag* tag) +{ + if (fRootTag != NULL) + delete fRootTag; + fRootTag = tag; +} + +void XMLParser::WriteToFile(char** fileHeader) +{ + char theBuffer[8192]; + ResizeableStringFormatter formatter(theBuffer, 8192); + + // + // Write the file header + for (UInt32 a = 0; fileHeader[a] != NULL; a++) + { + formatter.Put(fileHeader[a]); + formatter.Put(kEOLString); + } + + if (fRootTag) + fRootTag->FormatData(&formatter, 0); + + // + // New libC code. This seems to work better on Win32 + formatter.PutTerminator(); + FILE* theFile = ::fopen(fFilePath, "w"); + if (theFile == NULL) + return; + + qtss_fprintf(theFile, "%s", formatter.GetBufPtr()); + ::fclose(theFile); + +#if __MacOSX__ + (void) ::chown(fFilePath,76,80);//owner qtss, group admin +#endif + +#ifndef __Win32__ + ::chmod(fFilePath, S_IRUSR | S_IWUSR | S_IRGRP ); +#endif +} + +UInt8 XMLTag::sNonNameMask[] = +{ + 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, 0, 1, 0, 0, //40-49 '.' and '-' are name chars + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, //50-59 ':' is a name char + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, //60-69 //stop on every character except a letter or number + 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 name char + 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 +}; + +XMLTag::XMLTag() : + fTag(NULL), + fValue(NULL), + fElem(NULL) +{ fElem = this; +} + +XMLTag::XMLTag(char* tagName) : + fTag(NULL), + fValue(NULL), + fElem(NULL) +{ fElem = this; + StrPtrLen temp(tagName); + fTag = temp.GetAsCString(); +} + +XMLTag::~XMLTag() +{ + if (fTag) + delete fTag; + if (fValue) + delete fValue; + + OSQueueElem* elem; + while ((elem = fAttributes.DeQueue()) != NULL) + { + XMLAttribute* attr = (XMLAttribute*)elem->GetEnclosingObject(); + delete attr; + } + + while ((elem = fEmbeddedTags.DeQueue()) != NULL) + { + XMLTag* tag = (XMLTag*)elem->GetEnclosingObject(); + delete tag; + } + + if (fElem.IsMemberOfAnyQueue()) + fElem.InQueue()->Remove(&fElem); // remove from parent tag +} + +void XMLTag::ConsumeIfComment(StringParser* parser) +{ + if ((parser->GetDataRemaining() > 2) && ((*parser)[1] == '-') && ((*parser)[2] == '-')) + { + // this is a comment, so skip to end of comment + parser->ConsumeLength(NULL, 2); // skip '--' + + // look for --> + while((parser->GetDataRemaining() > 2) && ((parser->PeekFast() != '-') || + ((*parser)[1] != '-') || ((*parser)[2] != '>'))) + { + if (parser->PeekFast() == '-') parser->ConsumeLength(NULL, 1); + parser->ConsumeUntil(NULL, '-'); + } + + if (parser->GetDataRemaining() > 2) parser->ConsumeLength(NULL, 3); // consume --> + } +} + +bool XMLTag::ParseTag(StringParser* parser, DTDVerifier* verifier, char* errorBuffer, int errorBufferSize) +{ + while (true) + { + if (!parser->GetThru(NULL, '<')) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize, "Couldn't find a valid tag"); + return false; // couldn't find beginning of tag + } + + char c = parser->PeekFast(); + if (c == '/') + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize, "End tag with no begin tag on line %d", parser->GetCurrentLineNumber()); + return false; // we shouldn't be seeing a close tag here + } + + if ((c != '!') && (c != '?')) + break; // this should be the beginning of a regular tag + + ConsumeIfComment(parser); + // otherwise this is a processing instruction or a c-data, so look for the next tag + } + + int tagStartLine = parser->GetCurrentLineNumber(); + + StrPtrLen temp; + parser->ConsumeUntil(&temp, sNonNameMask); + if (temp.Len == 0) + { + if (errorBuffer != NULL) + { + if (parser->GetDataRemaining() == 0) + qtss_snprintf(errorBuffer, errorBufferSize, "Unexpected end of file on line %d", parser->GetCurrentLineNumber()); + else + qtss_snprintf(errorBuffer, errorBufferSize,"Unexpected character (%c) on line %d", parser->PeekFast(), parser->GetCurrentLineNumber()); + } + return false; // bad file + } + + fTag = temp.GetAsCString(); + + parser->ConsumeWhitespace(); + while ((parser->PeekFast() != '>') && (parser->PeekFast() != '/')) + { + // we must have an attribute value for this tag + XMLAttribute* attr = new XMLAttribute; + fAttributes.EnQueue(&attr->fElem); + parser->ConsumeUntil(&temp, sNonNameMask); + if (temp.Len == 0) + { + if (errorBuffer != NULL) + { + if (parser->GetDataRemaining() == 0) + qtss_snprintf(errorBuffer, errorBufferSize, "Unexpected end of file on line %d", parser->GetCurrentLineNumber()); + else + qtss_snprintf(errorBuffer, errorBufferSize,"Unexpected character (%c) on line %d", parser->PeekFast(), parser->GetCurrentLineNumber()); + } + return false; // bad file + } + + attr->fAttrName = temp.GetAsCString(); + + if (!parser->Expect('=')) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize,"Missing '=' after attribute %s on line %d", attr->fAttrName, parser->GetCurrentLineNumber()); + return false; // bad attribute specification + } + if (!parser->Expect('"')) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize,"Attribute %s value not in quotes on line %d", attr->fAttrName, parser->GetCurrentLineNumber()); + return false; // bad attribute specification + } + + parser->ConsumeUntil(&temp, '"'); + attr->fAttrValue = temp.GetAsCString(); + if (!parser->Expect('"')) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize, "Attribute %s value not in quotes on line %d", attr->fAttrName, parser->GetCurrentLineNumber()); + return false; // bad attribute specification + } + + if (verifier && !verifier->IsValidAttributeName(fTag, attr->fAttrName)) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize, "Attribute %s not allowed in tag %s on line %d", attr->fAttrName, fTag, parser->GetCurrentLineNumber()); + return false; // bad attribute specification + } + + if (verifier && !verifier->IsValidAttributeValue(fTag, attr->fAttrName, attr->fAttrValue)) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize,"Bad value for attribute %s on line %d", attr->fAttrName, parser->GetCurrentLineNumber()); + return false; // bad attribute specification + } + + parser->ConsumeWhitespace(); + } + + if (parser->PeekFast() == '/') + { + // this is an empty element tag, i.e. no contents or end tag (e.g + parser->Expect('/'); + if (!parser->Expect('>')) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize,"'>' must follow '/' on line %d", parser->GetCurrentLineNumber()); + return false; // bad attribute specification + } + + return true; // we're done with this tag + } + + if (!parser->Expect('>')) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize,"Bad format for tag <%s> on line %d", fTag, parser->GetCurrentLineNumber()); + return false; // bad attribute specification + } + + while(true) + { + parser->ConsumeUntil(&temp, '<'); // this is either value or whitespace + if (parser->GetDataRemaining() < 4) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize, "Reached end of file without end for tag <%s> declared on line %d", fTag, tagStartLine); + return false; + } + if ((*parser)[1] == '/') + { + // we'll only assign a value if there were no embedded tags + if (fEmbeddedTags.GetLength() == 0 && (!verifier || verifier->CanHaveValue(fTag))) + fValue = temp.GetAsCString(); + else + { + // otherwise this needs to have been just whitespace + StringParser tempParser(&temp); + tempParser.ConsumeWhitespace(); + if (tempParser.GetDataRemaining() > 0) + { + if (errorBuffer) + { + if (fEmbeddedTags.GetLength() > 0) + qtss_snprintf(errorBuffer, errorBufferSize,"Unexpected text outside of tag on line %d", tagStartLine); + else + qtss_snprintf(errorBuffer, errorBufferSize, "Tag <%s> on line %d not allowed to have data", fTag, tagStartLine); + } + } + } + break; // we're all done with this tag + } + + if (((*parser)[1] != '!') && ((*parser)[1] != '?')) + { + // this must be the beginning of an embedded tag + XMLTag* tag = NEW XMLTag(); + fEmbeddedTags.EnQueue(&tag->fElem); + if (!tag->ParseTag(parser, verifier, errorBuffer, errorBufferSize)) + return false; + + if (verifier && !verifier->IsValidSubtag(fTag, tag->GetTagName())) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize, "Tag %s not allowed in tag %s on line %d", tag->GetTagName(), fTag, parser->GetCurrentLineNumber()); + return false; // bad attribute specification + } + } + else + { + parser->ConsumeLength(NULL, 1); // skip '<' + ConsumeIfComment(parser); + } + } + + parser->ConsumeLength(NULL, 2); // skip 'ConsumeUntil(&temp, sNonNameMask); + if (!temp.Equal(fTag)) + { + char* newTag = temp.GetAsCString(); + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize,"End tag on line %d doesn't match tag <%s> declared on line %d", newTag, parser->GetCurrentLineNumber(),fTag, tagStartLine); + delete newTag; + return false; // bad attribute specification + } + + if (!parser->GetThru(NULL, '>')) + { + if (errorBuffer != NULL) + qtss_snprintf(errorBuffer, errorBufferSize,"Couldn't find end of tag <%s> declared on line %d", fTag, tagStartLine); + return false; // bad attribute specification + } + + return true; +} + +char* XMLTag::GetAttributeValue(const char* attrName) +{ + for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next()) + { + XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject(); + if (!strcmp(attr->fAttrName, attrName)) + return attr->fAttrValue; + } + + return NULL; +} + +XMLTag* XMLTag::GetEmbeddedTag(const UInt32 index) +{ + if (fEmbeddedTags.GetLength() <= index) + return NULL; + + OSQueueIter iter(&fEmbeddedTags); + for (UInt32 i = 0; i < index; i++) + { + iter.Next(); + } + OSQueueElem* result = iter.GetCurrent(); + + return (XMLTag*)result->GetEnclosingObject(); +} + +XMLTag* XMLTag::GetEmbeddedTagByName(const char* tagName, const UInt32 index) +{ + if (fEmbeddedTags.GetLength() <= index) + return NULL; + + XMLTag* result = NULL; + UInt32 curIndex = 0; + for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next()) + { + XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject(); + if (!strcmp(temp->GetTagName(), tagName)) + { + if (curIndex == index) + { + result = temp; + break; + } + + curIndex++; + } + } + + return result; +} + +XMLTag* XMLTag::GetEmbeddedTagByAttr(const char* attrName, const char* attrValue, const UInt32 index) +{ + if (fEmbeddedTags.GetLength() <= index) + return NULL; + + XMLTag* result = NULL; + UInt32 curIndex = 0; + for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next()) + { + XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject(); + if ((temp->GetAttributeValue(attrName) != NULL) && (!strcmp(temp->GetAttributeValue(attrName), attrValue))) + { + if (curIndex == index) + { + result = temp; + break; + } + + curIndex++; + } + } + + return result; +} + +XMLTag* XMLTag::GetEmbeddedTagByNameAndAttr(const char* tagName, const char* attrName, const char* attrValue, const UInt32 index) +{ + if (fEmbeddedTags.GetLength() <= index) + return NULL; + + XMLTag* result = NULL; + UInt32 curIndex = 0; + for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next()) + { + XMLTag* temp = (XMLTag*)iter.GetCurrent()->GetEnclosingObject(); + if (!strcmp(temp->GetTagName(), tagName) && (temp->GetAttributeValue(attrName) != NULL) && + (!strcmp(temp->GetAttributeValue(attrName), attrValue))) + { + if (curIndex == index) + { + result = temp; + break; + } + + curIndex++; + } + } + + return result; +} + +void XMLTag::AddAttribute( char* attrName, char* attrValue) +{ + XMLAttribute* attr = NEW XMLAttribute; + StrPtrLen temp(attrName); + attr->fAttrName = temp.GetAsCString(); + temp.Set(attrValue); + attr->fAttrValue = temp.GetAsCString(); + + fAttributes.EnQueue(&attr->fElem); +} + +void XMLTag::RemoveAttribute(char* attrName) +{ + for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next()) + { + XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject(); + if (!strcmp(attr->fAttrName, attrName)) + { + fAttributes.Remove(&attr->fElem); + delete attr; + return; + } + } +} + +void XMLTag::AddEmbeddedTag(XMLTag* tag) +{ + fEmbeddedTags.EnQueue(&tag->fElem); +} + +void XMLTag::RemoveEmbeddedTag(XMLTag* tag) +{ + fEmbeddedTags.Remove(&tag->fElem); +} + +void XMLTag::SetTagName( char* name) +{ + Assert (name != NULL); // can't have a tag without a name! + + if (fTag != NULL) + delete fTag; + + StrPtrLen temp(name); + fTag = temp.GetAsCString(); +} + +void XMLTag::SetValue( char* value) +{ + if (fEmbeddedTags.GetLength() > 0) + return; // can't have a value with embedded tags + + if (fValue != NULL) + delete fValue; + + if (value == NULL) + fValue = NULL; + else + { + StrPtrLen temp(value); + fValue = temp.GetAsCString(); + } +} + +void XMLTag::FormatData(ResizeableStringFormatter* formatter, UInt32 indent) +{ + for (UInt32 i=0; iPutChar('\t'); + + formatter->PutChar('<'); + formatter->Put(fTag); + if (fAttributes.GetLength() > 0) + { + formatter->PutChar(' '); + for (OSQueueIter iter(&fAttributes); !iter.IsDone(); iter.Next()) + { + XMLAttribute* attr = (XMLAttribute*)iter.GetCurrent()->GetEnclosingObject(); + formatter->Put(attr->fAttrName); + formatter->Put("=\""); + formatter->Put(attr->fAttrValue); + formatter->Put("\" "); + } + } + formatter->PutChar('>'); + + if (fEmbeddedTags.GetLength() == 0) + { + if (fValue > 0) + formatter->Put(fValue); + } + else + { + formatter->Put(kEOLString); + for (OSQueueIter iter(&fEmbeddedTags); !iter.IsDone(); iter.Next()) + { + XMLTag* current = (XMLTag*)iter.GetCurrent()->GetEnclosingObject(); + current->FormatData(formatter, indent + 1); + } + + for (UInt32 i=0; iPutChar('\t'); + } + + formatter->Put("Put(fTag); + formatter->PutChar('>'); + formatter->Put(kEOLString); +} + +XMLAttribute::XMLAttribute() + : fAttrName(NULL), + fAttrValue(NULL) +{ fElem = this; +} + +XMLAttribute::~XMLAttribute() +{ + if (fAttrName) + delete fAttrName; + if (fAttrValue) + delete fAttrValue; +} diff --git a/PrefsSourceLib/XMLParser.h b/PrefsSourceLib/XMLParser.h new file mode 100644 index 0000000..1de563d --- /dev/null +++ b/PrefsSourceLib/XMLParser.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@ + * + */ + +#ifndef __XMLParser_h__ +#define __XMLParser_h__ + +#include "StringParser.h" +#include "OSQueue.h" +#include "OSFileSource.h" +#include "ResizeableStringFormatter.h" + +class DTDVerifier +{ +public: + virtual bool IsValidSubtag(char* tagName, char* subTagName) = 0; + virtual bool IsValidAttributeName(char* tagName, char* attrName) = 0; + virtual bool IsValidAttributeValue(char* tagName, char* attrName, char* attrValue) = 0; + virtual char* GetRequiredAttribute(char* tagName, int index) = 0; + virtual bool CanHaveValue(char* tagName) = 0; + virtual ~DTDVerifier(){}; +}; + +class XMLTag +{ +public: + XMLTag(); + XMLTag(char* tagName); + ~XMLTag(); + + bool ParseTag(StringParser* parser, DTDVerifier* verifier, char* errorBuffer = NULL, int errorBufferSize = 0); + + char* GetAttributeValue(const char* attrName); + char* GetValue() { return fValue; } + char* GetTagName() { return fTag; } + + UInt32 GetNumEmbeddedTags() { return fEmbeddedTags.GetLength(); } + + XMLTag* GetEmbeddedTag(const UInt32 index = 0); + XMLTag* GetEmbeddedTagByName(const char* tagName, const UInt32 index = 0); + XMLTag* GetEmbeddedTagByAttr(const char* attrName, const char* attrValue, const UInt32 index = 0); + XMLTag* GetEmbeddedTagByNameAndAttr(const char* tagName, const char* attrName, const char* attrValue, const UInt32 index = 0); + + void AddAttribute(char* attrName, char* attrValue); + void RemoveAttribute(char* attrName); + void AddEmbeddedTag(XMLTag* tag); + void RemoveEmbeddedTag(XMLTag* tag); + + void SetTagName( char* name); + void SetValue( char* value); + + void FormatData(ResizeableStringFormatter* formatter, UInt32 indent); + +private: + void ConsumeIfComment(StringParser* parser); + + char* fTag; + char* fValue; + OSQueue fAttributes; + OSQueue fEmbeddedTags; + + OSQueueElem fElem; + + static UInt8 sNonNameMask[]; // stop when you hit a word +}; + +class XMLAttribute +{ +public: + XMLAttribute(); + ~XMLAttribute(); + + char* fAttrName; + char* fAttrValue; + + OSQueueElem fElem; +}; + +class XMLParser +{ +public: + XMLParser( char* inPath, DTDVerifier* verifier = NULL); + ~XMLParser(); + + // Check for existence, man. + Bool16 DoesFileExist(); + Bool16 DoesFileExistAsDirectory(); + Bool16 CanWriteFile(); + + Bool16 ParseFile(char* errorBuffer = NULL, int errorBufferSize = 0); + + XMLTag* GetRootTag() { return fRootTag; } + void SetRootTag(XMLTag* tag); + + void WriteToFile(char** fileHeader); + +private: + XMLTag* fRootTag; + + OSFileSource fFile; + char* fFilePath; + DTDVerifier* fVerifier; +}; + +#endif diff --git a/PrefsSourceLib/XMLPrefsParser.cpp b/PrefsSourceLib/XMLPrefsParser.cpp new file mode 100644 index 0000000..9f8a06f --- /dev/null +++ b/PrefsSourceLib/XMLPrefsParser.cpp @@ -0,0 +1,410 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ +/* + File: XMLPrefsParser.cpp + + Contains: Prototype implementation of XMLPrefsParser object. + + +*/ + +#include +#include +#include + +#ifndef __Win32__ +#include +#endif + +#include "XMLPrefsParser.h" +#include "OSMemory.h" +#include "OSHeaders.h" + +static const UInt32 kPrefArrayMinSize = 20; + +static char* kMainTag = "CONFIGURATION"; +static char* kServer = "SERVER"; +static char* kModule = "MODULE"; +static char* kPref = "PREF"; +static char* kListPref = "LIST-PREF"; +static char* kEmptyObject = "EMPTY-OBJECT"; +static char* kObject = "OBJECT"; +static char* kObjectList = "LIST-OBJECT"; +static char* kValue = "VALUE"; +static char* kNameAttr = "NAME"; +static char* kTypeAttr = "TYPE"; + +static char* kFileHeader[] = +{ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "]>", + NULL +}; + +XMLPrefsParser::XMLPrefsParser(char* inPath) +: XMLParser(inPath) +{} + +XMLPrefsParser::~XMLPrefsParser() +{} + + +ContainerRef XMLPrefsParser::GetConfigurationTag() +{ + ContainerRef result = GetRootTag(); + if (result == NULL) + { + result = new XMLTag(kMainTag); + SetRootTag(result); + } + + return result; +} + +ContainerRef XMLPrefsParser::GetRefForModule(char* inModuleName, Bool16 create) +{ + if (inModuleName == NULL) + return GetRefForServer(); + + ContainerRef result = GetConfigurationTag()->GetEmbeddedTagByNameAndAttr(kModule, kNameAttr, inModuleName); + if (result == NULL) + { + result = new XMLTag(kModule); + result->AddAttribute( kNameAttr, (char*)inModuleName); + GetRootTag()->AddEmbeddedTag(result); + } + + return result; +} + +ContainerRef XMLPrefsParser::GetRefForServer() +{ + ContainerRef result = GetConfigurationTag()->GetEmbeddedTagByName(kServer); + if (result == NULL) + { + result = new XMLTag(kServer); + GetRootTag()->AddEmbeddedTag(result); + } + + return result; +} + +UInt32 XMLPrefsParser::GetNumPrefValues(ContainerRef pref) +{ + if (!strcmp(pref->GetTagName(), kPref)) + { + if (pref->GetValue() == NULL) + return 0; + else + return 1; + } + else if (!strcmp(pref->GetTagName(), kObject)) + return 1; + else if (!strcmp(pref->GetTagName(), kEmptyObject)) + return 0; + + return pref->GetNumEmbeddedTags(); // it must be a list +} + +UInt32 XMLPrefsParser::GetNumPrefsByContainer(ContainerRef container) +{ + return container->GetNumEmbeddedTags(); +} + +char* XMLPrefsParser::GetPrefValueByIndex(ContainerRef container, const UInt32 inPrefsIndex, const UInt32 inValueIndex, + char** outPrefName, char** outDataType) +{ + if (outPrefName != NULL) + *outPrefName = NULL; + if (outPrefName != NULL) + *outDataType = NULL; + XMLTag* pref = container->GetEmbeddedTag(inPrefsIndex); + if (pref == NULL) + return NULL; + + return GetPrefValueByRef(pref, inValueIndex, outPrefName, outDataType); +} + +char* XMLPrefsParser::GetPrefValueByRef(ContainerRef pref, const UInt32 inValueIndex, + char** outPrefName, char** outDataType) +{ + if (outPrefName != NULL) + *outPrefName = pref->GetAttributeValue(kNameAttr); + if (outDataType != NULL) + { + *outDataType = pref->GetAttributeValue(kTypeAttr); + if (*outDataType == NULL) + *outDataType = "CharArray"; + } + + if (!strcmp(pref->GetTagName(), kPref)) + { + if (inValueIndex > 0) + return NULL; + else + return pref->GetValue(); + } + + if (!strcmp(pref->GetTagName(), kListPref)) + { + XMLTag* value = pref->GetEmbeddedTag(inValueIndex); + if (value != NULL) + return value->GetValue(); + } + + if (!strcmp(pref->GetTagName(), kObject) || !strcmp(pref->GetTagName(), kObjectList)) + *outDataType = "QTSS_Object"; + + return NULL; +} + +ContainerRef XMLPrefsParser::GetObjectValue(ContainerRef pref, const UInt32 inValueIndex) +{ + if (!strcmp(pref->GetTagName(), kObject) && (inValueIndex == 0)) + return pref; + if (!strcmp(pref->GetTagName(), kObjectList)) + return pref->GetEmbeddedTag(inValueIndex); + + return NULL; +} + +ContainerRef XMLPrefsParser::GetPrefRefByName( ContainerRef container, + const char* inPrefName) +{ + return container->GetEmbeddedTagByAttr(kNameAttr, inPrefName); +} + +ContainerRef XMLPrefsParser::GetPrefRefByIndex( ContainerRef container, + const UInt32 inPrefsIndex) +{ + return container->GetEmbeddedTag(inPrefsIndex); +} + +ContainerRef XMLPrefsParser::AddPref( ContainerRef container, char* inPrefName, + char* inPrefDataType ) +{ + XMLTag* pref = container->GetEmbeddedTagByAttr(kNameAttr, inPrefName); + if (pref != NULL) + return pref; // it already exists + + pref = NEW XMLTag(kPref); // start it out as a pref + pref->AddAttribute(kNameAttr, inPrefName); + if (!strcmp(inPrefDataType, "QTSS_Object")) + pref->SetTagName(kEmptyObject); + else if (strcmp(inPrefDataType, "CharArray")) + pref->AddAttribute(kTypeAttr, (char*)inPrefDataType); + + container->AddEmbeddedTag(pref); + + return pref; +} + +void XMLPrefsParser::AddPrefValue( ContainerRef pref, char* inNewValue) +{ + if (!strcmp(pref->GetTagName(), kPref)) // is this a PREF tag + { + if (pref->GetValue() == NULL) + { + // easy case, no existing value, so just add a vlue + pref->SetValue(inNewValue); + return; + } + else + { + // it already has a value, so change the pref to be a list pref and go to code below + char* firstValue = pref->GetValue(); + XMLTag* value = NEW XMLTag(kValue); + value->SetValue(firstValue); + + pref->SetTagName(kListPref); + pref->SetValue(NULL); + pref->AddEmbeddedTag(value); + } + } + + // we want to fall through from second case above, so this isn't an else + if (!strcmp(pref->GetTagName(), kListPref)) + { + XMLTag* value = NEW XMLTag(kValue); + value->SetValue(inNewValue); + pref->AddEmbeddedTag(value); + } +} + +void XMLPrefsParser::AddNewObject( ContainerRef pref ) +{ + if (!strcmp(pref->GetTagName(), kEmptyObject)) + { + // just flag that this is now a real object instead of a placeholder + pref->SetTagName(kObject); + return; + } + + if (!strcmp(pref->GetTagName(), kObject)) + { + // change the object to be an object list and go to code below + XMLTag* subObject = NEW XMLTag(kObject); + XMLTag* objectPref; + // copy all this objects tags into the new listed object + while((objectPref = pref->GetEmbeddedTag()) != NULL) + { + pref->RemoveEmbeddedTag(objectPref); + subObject->AddEmbeddedTag(objectPref); + } + + pref->SetTagName(kObjectList); + pref->AddEmbeddedTag(subObject); + } + + // we want to fall through from second case above, so this isn't an else + if (!strcmp(pref->GetTagName(), kObjectList)) + { + XMLTag* subObject = NEW XMLTag(kObject); + pref->AddEmbeddedTag(subObject); + } +} + +void XMLPrefsParser::ChangePrefType( ContainerRef pref, char* inNewPrefDataType) +{ + pref->RemoveAttribute(kTypeAttr); // remove it if it exists + if (strcmp(inNewPrefDataType, "CharArray")) + pref->AddAttribute(kTypeAttr, inNewPrefDataType); +} + +void XMLPrefsParser::SetPrefValue( ContainerRef pref, const UInt32 inValueIndex, + char* inNewValue) +{ + UInt32 numValues = GetNumPrefValues(pref); + + if (((numValues == 0) || (numValues == 1)) && (inValueIndex == 0)) + { + pref->SetValue(inNewValue); + } + else if (inValueIndex == numValues) // this is an additional value + AddPrefValue(pref, inNewValue); + else + { + XMLTag* value = pref->GetEmbeddedTag(inValueIndex); + if (value != NULL) + value->SetValue(inNewValue); + } +} + +void XMLPrefsParser::RemovePrefValue( ContainerRef pref, const UInt32 inValueIndex) +{ + UInt32 numValues = GetNumPrefValues(pref); + if (inValueIndex >= numValues) + return; + + if (numValues == 1) + { + delete pref; // just remove the whole pref + } + else if (numValues == 2) + { + XMLTag* value = pref->GetEmbeddedTag(inValueIndex); // get the one we're removing + delete value; // delete it + value = pref->GetEmbeddedTag(0); // get the remaining tag index always 0 for 2 vals + pref->RemoveEmbeddedTag(value); // pull it out of the parent + if (!strcmp(pref->GetTagName(), kObjectList)) + { + pref->SetTagName(kObject); // set it back to a simple pref + // move all this objects tags into the parent + XMLTag* objectPref; + while((objectPref = value->GetEmbeddedTag()) != NULL) + { + value->RemoveEmbeddedTag(objectPref); + pref->AddEmbeddedTag(objectPref); + } + } + else + { + char* temp = value->GetValue(); + pref->SetTagName(kPref); // set it back to a simple pref + pref->SetValue(temp); + } + + delete value; // get rid of the other one + } + else + { + XMLTag* value = pref->GetEmbeddedTag(inValueIndex); + if (value) + delete value; + } +} + +void XMLPrefsParser::RemovePref( ContainerRef pref ) +{ + delete pref; +} + +int XMLPrefsParser::Parse() +{ + char error[500]; + + if (!ParseFile(error, sizeof(error))) + { + qtss_printf("%s\n", error); + return -1; + } + + + + // the above routine checks that it's a valid XML file, we should check that + // all the tags conform to our prefs format + + return 0; +} + +int XMLPrefsParser::WritePrefsFile() +{ + GetConfigurationTag(); // force it to be created if it doesn't exist + WriteToFile(kFileHeader); + return 0; +} diff --git a/PrefsSourceLib/XMLPrefsParser.h b/PrefsSourceLib/XMLPrefsParser.h new file mode 100644 index 0000000..977fb30 --- /dev/null +++ b/PrefsSourceLib/XMLPrefsParser.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: XMLPrefsParser.h + + Contains: A generic interface for pulling prefs. + + +*/ + +#ifndef __XML_PREFS_PARSER__ +#define __XML_PREFS_PARSER__ + +#include "OSFileSource.h" +#include "OSQueue.h" +#include "StringParser.h" +#include "XMLParser.h" + +typedef XMLTag* ContainerRef; + +class XMLPrefsParser : public XMLParser +{ + public: + + XMLPrefsParser(char* inPath); + ~XMLPrefsParser(); + + // + // Check for existence, man. + + // + // PARSE & WRITE THE FILE. Returns true if there was an error + int Parse(); + + // Completely replaces old prefs file. Returns true if there was an error + int WritePrefsFile(); + + // + // ACCESSORS + + ContainerRef GetRefForModule( char* inModuleName, Bool16 create = true); + + ContainerRef GetRefForServer(); + + // + // Returns the number of pref values for the pref at this index + UInt32 GetNumPrefValues(ContainerRef pref); + + // + // Returns the number of prefs associated with this given module + UInt32 GetNumPrefsByContainer(ContainerRef container); + + // + // Returns the pref value at the specfied location + char* GetPrefValueByIndex(ContainerRef container, const UInt32 inPrefsIndex, const UInt32 inValueIndex, + char** outPrefName, char** outDataType); + + char* GetPrefValueByRef(ContainerRef pref, const UInt32 inValueIndex, + char** outPrefName, char** outDataType); + + ContainerRef GetObjectValue(ContainerRef pref, const UInt32 inValueIndex); + + ContainerRef GetPrefRefByName( ContainerRef container, + const char* inPrefName); + + ContainerRef GetPrefRefByIndex( ContainerRef container, + const UInt32 inPrefsIndex); + + // + // MODIFIERS + + // + // Creates a new pref. Returns the index of that pref. If pref already + // exists, returns existing index. + ContainerRef AddPref( ContainerRef container, char* inPrefName, char* inPrefDataType ); + + void ChangePrefType( ContainerRef pref, char* inNewPrefDataType); + + void AddNewObject( ContainerRef pref ); + + void AddPrefValue( ContainerRef pref, char* inNewValue); + + // + // If this value index does not exist yet, and it is one higher than + // the highest one, this function implictly adds the new value. + void SetPrefValue( ContainerRef pref, const UInt32 inValueIndex, + char* inNewValue); + + // + // Removes the pref entirely if # of values drops to 0 + void RemovePrefValue( ContainerRef pref, const UInt32 inValueIndex); + + void RemovePref( ContainerRef pref ); + + private: + + XMLTag* GetConfigurationTag(); +}; + +#endif //__XML_PREFS_PARSER__ diff --git a/StreamingProxy.tproj/get_opt.c b/StreamingProxy.tproj/get_opt.c new file mode 100644 index 0000000..0e6bdb8 --- /dev/null +++ b/StreamingProxy.tproj/get_opt.c @@ -0,0 +1,154 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + * get_opt.c + * + */ + +#include +#include +#include +#include +#if defined(mac) +#include +#endif + +int get_opt(int argc, char *argv[], char *optList); + +char *optarg = NULL; +char *whatOption = NULL; +int currentOpt = 0; +int optind = 1; +extern char gOptionsChar; + +/* compareOptionString takes an option parameter with the following format: + * -REQuired : the characters in CAPS are required to match. + */ +static int compareOptionString(char *option, char *compareit) { + int i, l1, l2; + + l1 = strlen(option); + l2 = strlen(compareit); + for (i=0; i compareit[i]) + return 1; /* option > compareit */ + else + return -1; /* option < compareit */ + } + } + /* if we got here, all of the characters in compareit are in option. */ + /* now we've got to check if there are any more required characters. */ + if (l1 > l2) { /* only need to check if option is longer than compareit */ + if (option[l2] == toupper(option[l2])) + return 1; /* there was an additional character[s] needed. */ + } + + /* if we got here, then all is hunky dory and we got the required stuff. */ + return 0; +} + +int get_opt(int argc, char *argv[], char *optList) { + char option[256]; + int l, i, c, opt; + + currentOpt = optind; + optind++; + if (currentOpt >= argc) { + return EOF; + } + optarg = NULL; + whatOption = argv[currentOpt]; + + if (whatOption[0] != gOptionsChar) { + optarg = whatOption; + return 0; + } + l = strlen(optList); + i = 1; + c = 0; + opt = 1; + option[c++] = gOptionsChar; + while (i<=l) { + if (optList[i] == gOptionsChar) { + option[c++] = '\0'; +// if (strncasecmp(option, whatOption, strlen(whatOption)) == 0) + if (compareOptionString(option, whatOption) == 0) +// return opt; + return option[1]; + c = 1; // reset option string to '-' since we've just seen it + opt++; // check next option + } + else if (optList[i] == ':') { + option[c++] = '\0'; +// if (strncasecmp(option, whatOption, strlen(whatOption)) == 0) { + if (compareOptionString(option, whatOption) == 0) { + currentOpt++; optind++; + optarg = argv[currentOpt]; +// return opt; + return option[1]; + } + c = 1; // reset option string to '-' + opt++; // check next option + i++; // pass over the : + if (optList[i] == gOptionsChar) { + ; // this is where we want to be + } + else if (optList[i] == '\0') { +// return EOF; // this was the last option to check + return 0; // this was the last option to check + } + else { + fprintf(stderr, "Malformed getopt string '%s': character %d is '%c' was expecting %c\n", optList, i, optList[i], gOptionsChar); + return 0; + } + + } + else if (optList[i] == '\0') { + option[c++] = '\0'; +// if (strncasecmp(option, whatOption, strlen(whatOption)) == 0) { + if (compareOptionString(option, whatOption) == 0) { +// return opt; + return option[1]; + } + optarg = argv[currentOpt]; +// return EOF; // didn't find anything, return EOF + return 0; // didn't find anything, return EOF + } + else { + option[c++] = optList[i]; + } + i++; + } + + optarg = argv[currentOpt]; + return 0; +} + diff --git a/StreamingProxy.tproj/get_opt.h b/StreamingProxy.tproj/get_opt.h new file mode 100644 index 0000000..fd7270b --- /dev/null +++ b/StreamingProxy.tproj/get_opt.h @@ -0,0 +1,42 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + * get_opt.h + * + + */ +#ifndef __GETOPT_H__ +#define __GETOPT_H__ + +extern char *optarg; +extern char *whatOption; +extern int currentOpt; +extern int optind; + +int get_opt(int argc, char *argv[], char *optList); + +#endif + diff --git a/StreamingProxy.tproj/proxy.c b/StreamingProxy.tproj/proxy.c new file mode 100644 index 0000000..64a5657 --- /dev/null +++ b/StreamingProxy.tproj/proxy.c @@ -0,0 +1,1983 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + * proxy.c + * + * + */ + +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "./proxy_plat.h" +#include "util.h" +#include "proxy.h" + +#include + +#ifndef __MacOSX__ +#include "get_opt.h" +#endif + +#include "../revision.h" +/**********************************************/ +// Globals +int gQuitting = 0; +int gVerbose = 0; +int gDebug = 0; +#if defined(mac) || defined(WIN32) +int gStats = 1; +#else +int gStats = 0; +#ifndef unix + #define unix +#endif + +#endif + +#define ANY_ADDRESS -1 +#define MAX_CMD_BUFF 512 +#define MAX_CONFIG_LINE_LEN 512 +#define MAX_LINE_BUFF 2048 + +int gNeedsService = 0; + +#ifndef __MacOSX__ + extern char *gConfigFilePath; +#endif + +rtsp_session *gSessions = NULL; + +subnet_allow *gAllowedNets = NULL; +rtsp_listener *gListeners = NULL; +int gUserLimit = 0; +int gNumUsers = 0; + +//int gUDPPortMin = 4000; +//int gUDPPortMax = 65535; + +int gProxyIP = -1; +int gRTSPIP = ANY_ADDRESS; +int gMaxPorts = 0; + +unsigned long gBytesReceived = 0; +unsigned long gBytesSent = 0; +unsigned long gPacketsReceived = 0; +unsigned long gPacketsSent = 0; +unsigned long gLastPacketsReceived = 0; +unsigned long gLastPacketsSent = 0; + +/**********************************************/ + +int gDropEnabled = 0; +float gDropPercent = 0.0; +time_t gStartDropOffset = 0; + +/**********************************************/ +#if defined(unix) +void sig_catcher(int sig) +{ + if (gDropEnabled) + { + + if (sig == SIGUSR1) + { gDropPercent = 0; +#if __solaris__ + signal(SIGUSR1, sig_catcher); +#endif + return; + } + + if (sig == SIGUSR2) + { gDropPercent += 1.0; + if (gDropPercent > 100.0) + gDropPercent = 100.0; + +#if __solaris__ + signal(SIGUSR2, sig_catcher); +#endif + return; + } + + if (sig == SIGHUP) + { + FILE * dropParamFile = fopen("drop","r"); + if (dropParamFile != NULL) + { char inBuff[256]; + while (fgets(inBuff, sizeof(inBuff), dropParamFile) != 0) + { char *tag; + char *value; + tag = strtok(inBuff, " "); + if (tag != NULL && NULL != strstr(tag,"drop_percent")) + { value = strtok(NULL, " \r\n"); + printf("drop_percent value=%s\n",value); + if (value != NULL) + sscanf(value, "%f",&gDropPercent); + } + + } + + fclose(dropParamFile); + } +#if __solaris__ + signal(SIGHUP, sig_catcher); +#endif + return; + } + } + + // do nothing cases + if ( (sig == SIGUSR1) || (sig == SIGUSR2) || (sig == SIGHUP) ) + { +#if __solaris__ + sigignore(sig); +#endif + return; + } + gQuitting = 1; +} +#endif + +/**********************************************/ +static void print_usage(char *name) +{ + printf("%s/%s Built on %s, %s: [-dDvh] [-p #] [-c ]\n", name, kVersionString, __DATE__, __TIME__ ); + printf(" -d : debug\n"); + printf(" -D : verbose debug\n"); + printf(" -v : print usage\n"); + printf(" -h : help (this message)\n"); + printf(" -p # : listen on port # (defaults to 554)\n"); + printf(" -c : configuration file (defaults to %s)\n", gConfigFilePath); + printf(" -i : RTP Hostname/IP Address bind address.\n"); + printf(" -s : statistics\n"); + printf(" -x : enable packet drop mode (defaults to 0). SIGUSR1 to reset to 0. SIGUSR2 to add 1 to drop percent\n"); + printf(" use SIGHUP to read tag and value 'drop_percent 5' from the local file 'drop'. Use -s to see drop statistics\n"); + +} + +/**********************************************/ +int main(int argc, char *argv[]) +{ + int i, j; + int numOptions = 0; // num command line options spec'd + signed char ch; + int listening_port = 554, user_listener = false; + time_t time_zero, now, last; + time_t usnow, uslast; + char *hostname = NULL; + +#ifndef __MacOSX__ + extern char *optarg; + extern int optind; +#endif + + time_zero = time(0); + last = time_zero; + +#if defined(unix) + // + // increase file descriptor limit + { + struct rlimit rl; + rl.rlim_cur = 4096; + rl.rlim_max = 4096; + setrlimit(RLIMIT_NOFILE, &rl); + } + // + signal(SIGHUP, sig_catcher); + signal(SIGTERM, sig_catcher); + signal(SIGUSR1, sig_catcher); + signal(SIGUSR2, sig_catcher); + signal(SIGPIPE, SIG_IGN); + + // + // boost our priority +#if defined( _sparc) || defined(HAVE_SETPRIORITY) || defined(__sgi__) + i = setpriority(PRIO_PROCESS, 0, -20); + if (i != 0) + fprintf(stderr, "schedctl failed %d\n", errno); +#endif +#endif // defined(unix) + // + // initialize the network stacks + init_network(); + init_ui(); + + // + // read command line options +#if __MacOSX__ + while ((ch = getopt(argc, argv, gOptionsString)) != -1) +#else + while ((ch = get_opt(argc, argv, gOptionsString)) != -1) +#endif + { + numOptions++; + switch (ch) + { + case 'p': + listening_port = atoi(optarg); + user_listener = true; + break; + + case 'd': + gDebug = 1; + signal(SIGINT, sig_catcher); // try to catch the signal so we show what's still running. + break; + + case 'D': + gVerbose = 1; + gDebug = 1; + signal(SIGINT, sig_catcher); // try to catch the signal so we show what's still running. + break; + + case 'x': + gDropEnabled = 1; + break; + + case 'v': + print_usage(argv[0]); + exit(0); + break; + case 's': + gStats = 1; + break; + case 'h': + print_usage(argv[0]); + numOptions -= 2; // not an option, if -h only, print usage and quit + break; + case 'i': + hostname = optarg; + break; + case 'c': + if ( optarg ) + gConfigFilePath = str_dup(optarg); + else + { + printf("-c option requires a file path as an argument.\n"); + exit(-1); + } + break; + } + } + + // will be less < 0 if asked for -h only, if -h with other options we'll + // continue to run, if no options at all we'll continute to run using + // the default config gile path + + if ( numOptions < 0 ) + exit(0); + + argc -= optind; + argv += optind; + + // + // read and deal with config + read_config(); + + // + // set up rtsp listener (if necessary) + // -- if it wasn't specified in the config file, or the user had + // one on the command line + if (!gListeners || user_listener) + add_rtsp_port_listener(ANY_ADDRESS,listening_port); + + /* exit if we can't listen */ + if (!gListeners) { + ErrorString("Can't set up any RTSP listeners. Exiting...\n"); + return -1; + } + + /* if a hostname was specified, it over-writes whatever's in + * the config file. */ + if (hostname) + name_to_ip_num(hostname, &gProxyIP, false); + + // + if (gProxyIP == -1) + { //gProxyIP = get_local_ip_address(); this returns the local loopback (127.0.0.1) and is useless + ErrorString("An rtp-bind-addr configuration line or -i command line option is required.\n"); + return -1; + } + + if (gVerbose) + printf("rtp-bind-addr: %s\n", ip_to_string(gProxyIP)); + + if (!(gVerbose || gDebug || gStats)) + daemonize(); +// + + // + // compile regular expressions for RTSP + uslast = microseconds(); + usnow = uslast; + // + // main event loop + i = 0, j = 0; + while (!gQuitting) { + if ((i++ >= 50) || !gNeedsService) { + service_listeners(); + i = 0; + } + if ((j++ > 25) || !gNeedsService) { + service_sessions(); + j = 0; + } + service_shoks(); + + if (gStats) { + now = time(0); + if ((now - last) >= 2) { + time_t msElapsed; + static unsigned long lastBPSReceived = 0, lastBPSSent = 0; + unsigned long bpsReceived, bpsSent; + stats_chunk stats; + stats.numClients = gNumUsers; + stats.elapsedSeconds = now - time_zero; + usnow = microseconds(); + msElapsed = (usnow - uslast) / USEC_PER_MSEC; + bpsReceived = ((gBytesReceived * USEC_PER_MSEC) / msElapsed) * 8; + bpsSent = ((gBytesSent * USEC_PER_MSEC) / msElapsed) * 8; + if (lastBPSReceived) { + stats.bpsReceived = (bpsReceived + lastBPSReceived) / 2; + stats.bpsSent = (bpsSent + lastBPSSent) / 2; + } + else { + stats.bpsReceived = bpsReceived; + stats.bpsSent = bpsSent; + } + stats.ppsReceived = ((gPacketsReceived - gLastPacketsReceived) * USEC_PER_MSEC) / msElapsed; + stats.ppsSent = ((gPacketsSent - gLastPacketsSent) * USEC_PER_MSEC) / msElapsed; + stats.totalPacketsReceived = gPacketsReceived; + stats.totalPacketsSent = gPacketsSent; + if (stats.ppsReceived > 0) + stats.percentLostPackets = 100.0 - ((float) stats.ppsSent / (float) stats.ppsReceived * (float)100.0); + else + stats.percentLostPackets = 0.0; + stats.numPorts = gMaxPorts; + DoStats(&stats); + gBytesReceived = 0; + gBytesSent = 0; + gLastPacketsReceived = gPacketsReceived; + gLastPacketsSent = gPacketsSent; + lastBPSReceived = bpsReceived; + lastBPSSent = bpsSent; + uslast = usnow; + last = now; + } + } + if (!gNeedsService) { + sleep_milliseconds(1); + } + else { + service_ui(0); + gNeedsService = 0; + } + } + + cleanup_sessions(); + cleanup_listeners(); + + // + // terminate the network stacks + term_network(); + + return 0; +} + +/**********************************************/ +int service_listeners() +{ + rtsp_listener *listener; + + listener = gListeners; + while (listener) + { + answer_new_connection(listener); + listener = listener->next; + } + return 0; +} + +/**********************************************/ +void cleanup_listeners() +{ + rtsp_listener *listener, *next; + + listener = gListeners; + while (listener) + { + next = listener->next; + if (gDebug) + DebugString1("Closing listener socket %d", listener->skt); + close_socket(listener->skt); + free(listener); + listener = next; + } +} + +/**********************************************/ +void add_rtsp_port_listener(int address,int port) +{ + rtsp_listener *listener; + int skt; + + if (gVerbose) + printf("Listening for RTSP messages on port %d\n", port); + + if ((skt = new_socket_tcp(true)) == INVALID_SOCKET) { + ErrorString1("couldn't set up RTSP listening socket (%d): %m\n", + port); + return; + } + set_socket_reuse_address(skt); + + if (bind_socket_to_address(skt, address, port, true) == SOCKET_ERROR) + { + close_socket(skt); + perror("binding RTSP listening socket"); + return; + } + + make_socket_nonblocking(skt); + + if (listen_to_socket(skt) == SOCKET_ERROR) + { + close_socket(skt); + ErrorString1("listening on RTSP socket (%d): %m\n", port); + return; + } + + listener = (rtsp_listener *)malloc(sizeof(rtsp_listener)); + if (!listener) { + ErrorString("Couldn't allocate memory for listener.\n"); + exit(1); + } + listener->skt = skt; + listener->port = port; + listener->next = gListeners; + gListeners = listener; +} + +/**********************************************/ +void answer_new_connection(rtsp_listener *listener) +{ + int skt; + rtsp_session *session; + + if ( !call_is_waiting(listener->skt, &skt)) + return; + + session = new_session(); + if (!session) { + ErrorString("Couldn't create a new session\n"); + close_socket(skt); + return; + } + + session->client_skt = skt; + make_socket_nonblocking(session->client_skt); + session->client_ip = get_remote_address(session->client_skt, NULL); + session->client_interface_addr = get_interface_addr(session->client_skt); + session->newSession = true; + + // + // add him to our session list + add_session(session); + + if (gVerbose) + printf("Added connection for client %s.\n", ip_to_string(session->client_ip)); + + gNeedsService++; +} + +/**********************************************/ +void add_session(rtsp_session *session) +{ + if (gSessions == NULL) + { + gSessions = session; + } + else + { + session->next = gSessions; + gSessions = session; + } + gNumUsers++; +} + +/**********************************************/ +void remove_session(rtsp_session *session) +{ + if (gSessions == NULL) + { + return; + } + else + { + if (gSessions == session) + { + gSessions = session->next; + } + else + { + rtsp_session *cur, *last = gSessions; + cur = last->next; + while (cur != NULL) + { + if (cur == session) + { + last->next = cur->next; + break; + } + last = cur; + cur = cur->next; + } + } + } +} + +/**********************************************/ +rtsp_session *new_session(void) +{ + rtsp_session *s; + int i; + + s = (rtsp_session*)calloc(1, sizeof(rtsp_session)); + if (s != NULL) + { + s->next = NULL; + s->die = false; + s->client_skt = INVALID_SOCKET; + s->client_ip = -1; + s->server_address = NULL; + s->server_skt = INVALID_SOCKET; + s->server_interface_addr = 0; + s->client_interface_addr = 0; + s->server_ip = -1; + s->server_port = 554; + s->server_skt_pending_connection = false; + s->state = stRecvClientCommand; + s->transaction_type = ttNone; + s->sessionID = NULL; + + s->cur_trk = 0; + for (i=0; itrk[i].ID = 0; + s->trk[i].ClientRTPPort = -1; + s->trk[i].ServerRTPPort = -1; + + s->trk[i].RTP_S2P = NULL; + s->trk[i].RTCP_S2P = NULL; + s->trk[i].RTP_P2C = NULL; + s->trk[i].RTCP_P2C = NULL; + + s->trk[i].RTP_S2C_tpb.status = NULL; + s->trk[i].RTP_S2C_tpb.send_from = NULL; + s->trk[i].RTP_S2C_tpb.send_to_ip = -1; + s->trk[i].RTP_S2C_tpb.send_to_port = -1; + s->trk[i].RTP_S2C_tpb.packetSendCount = 0; + s->trk[i].RTP_S2C_tpb.nextDropPacket = 0; + s->trk[i].RTP_S2C_tpb.droppedPacketCount = 0; + + s->trk[i].RTCP_S2C_tpb.status = NULL; + s->trk[i].RTCP_S2C_tpb.send_from = NULL; + s->trk[i].RTCP_S2C_tpb.send_to_ip = -1; + s->trk[i].RTCP_S2C_tpb.send_to_port = -1; + s->trk[i].RTCP_S2C_tpb.packetSendCount = 0; + s->trk[i].RTCP_S2C_tpb.nextDropPacket = 0; + s->trk[i].RTCP_S2C_tpb.droppedPacketCount = 0; + + s->trk[i].RTCP_C2S_tpb.status = NULL; + s->trk[i].RTCP_C2S_tpb.send_from = NULL; + s->trk[i].RTCP_C2S_tpb.send_to_ip = -1; + s->trk[i].RTCP_C2S_tpb.send_to_port = -1; + s->trk[i].RTCP_C2S_tpb.packetSendCount = 0; + s->trk[i].RTCP_C2S_tpb.nextDropPacket = 0; + s->trk[i].RTCP_C2S_tpb.droppedPacketCount = 0; + } + s->numTracks = 0; + + s->amtInClientInBuffer = 0; + s->amtInClientOutBuffer = 0; + s->amtInServerInBuffer = 0; + s->amtInServerOutBuffer = 0; + s->totalContentLength = 0; // headers + body + s->contentLength = 0; // just body + s->haveParsedServerReplyHeaders = 0; + } + return s; +} + +/**********************************************/ +void cleanup_session(rtsp_session *s) +{ + int i; + + if (s->client_skt != INVALID_SOCKET) + { + if (gDebug) + printf("Closing client rtsp socket %d (ip %s)\n", s->client_skt, ip_to_string(s->client_ip)); + close_socket(s->client_skt); + s->client_skt = INVALID_SOCKET; + } + + if (s->server_skt != INVALID_SOCKET) + { + if (gDebug) + printf("Closing server rtsp socket %d (ip %s)\n", s->server_skt, ip_to_string(s->server_ip)); + close_socket(s->server_skt); + s->server_skt = INVALID_SOCKET; + + } + + if (s->server_address) + { + free(s->server_address); + s->server_address = NULL; + } + + if (s->sessionID) + { + free(s->sessionID); + s->sessionID = NULL; + } + + for (i=0; inumTracks; i++) + { + if (s->trk[i].RTP_S2P) + remove_shok_ref(s->trk[i].RTP_S2P, s->server_interface_addr, s->server_ip, true); + if (s->trk[i].RTP_P2C) + remove_shok_ref(s->trk[i].RTP_P2C, s->client_interface_addr, s->client_ip, true); + } +} + +/**********************************************/ +void cleanup_sessions(void) +{ + rtsp_session *cur, *next; + + cur = gSessions; + while (cur) + { + next = cur->next; + cleanup_session(cur); + free(cur); + cur = next; + } + gSessions = NULL; +} + +/**********************************************/ +int service_sessions() +{ + rtsp_session *cur, *next; + + cur = gSessions; + while (cur) + { + next = cur->next; + if (cur->newSession) + { + cur->newSession = false; + // + // check to see if this user is allowed + if (! allow_ip(cur->client_ip)) + { + cur->die = true; + send_rtsp_error(cur->client_skt, kPermissionDenied); + if (gVerbose) + printf("Refusing connection for client %s - not allowed\n", + ip_to_string(cur->client_ip)); + } + // + // see if we're going beyond our user limit + else if (gUserLimit && (gNumUsers > gUserLimit)) + { + cur->die = true; + send_rtsp_error(cur->client_skt, kTooManyUsers); + if (gVerbose) + printf("Refusing connection for client %s - too many users\n", + ip_to_string(cur->client_ip)); + } + } + if (cur->die) + { + gNumUsers--; + remove_session(cur); + cleanup_session(cur); + free(cur); + cur = NULL; + } + else + { + if (cur->client_skt != INVALID_SOCKET || cur->server_skt != INVALID_SOCKET) + service_session(cur); + } + cur = next; + } + return 0; +} + +/**********************************************/ +static const t_cmd_map cmd_map[] = { + {"DESCRIBE", ttDescribe}, + {"SETUP", ttSetup}, + {"PLAY", ttPlay}, + {"PAUSE", ttPause}, + {"STOP", ttStop}, + {"TEARDOWN", ttTeardown}, + {"OPTIONS", ttOptions}, + {"ANNOUNCE", ttAnnounce}, + {"REDIRECT", ttRedirect}, + {"GET_PARAMETER", ttGetParameter}, + {"SET_PARAMETER", ttSetParameter}, + {NULL, ttNone} +}; + +static int cmd_to_transaction_type(char *cmd) +{ + const t_cmd_map *map; + map = cmd_map; + while (map->cmd != NULL) { + if (str_casecmp(map->cmd, cmd) == 0) + return map->type; + map++; + } + return ttNone; +} + +/**********************************************/ +static int track_id_to_idx(rtsp_session *s, int id) +{ + int i; + for (i=0; inumTracks; i++) { + if (s->trk[i].ID == id) + return i; + } + return -1; +} + +/**********************************************/ +static int has_two_crlfs(char *s) +{ + int l, n; + char *p; + l = strlen(s); + if (l < 4) + return 0; + n = 3; + p = s + n; + while (n < l) { + if (s[n] != '\n') + n += 1; + else if (s[n-1] != '\r' || s[n-2] != '\n' || s[n-3] != '\r') + n += 2; + else + return n+1; + } + return 0; +} + + +/**********************************************/ +static int is_command(char *inp, char *cmd, int cmdLen, char *server, int serverLen) +{ + int l; + char *p; + char *firstCmdChar = cmd; + char *firstServerChar = server; + + char *lastCmdChar = NULL; + if ( cmd && cmdLen > 2) + lastCmdChar = &cmd[cmdLen -1]; + + char *lastServerChar = NULL; + if ( server && serverLen > 2) + lastServerChar = &server[serverLen -1]; + + if (lastServerChar == NULL || lastCmdChar == NULL) + return 0; + + l = strlen(inp); + + if (l < 17) /* "RTSP/1.0" (8) + " rtsp:// " (9) */ + return 0; + + if (strn_casecmp(inp + l - 8, "RTSP/1.0", 8) != 0) + return 0; + + if (gVerbose) + printf("command hex="); + + p = inp; + while (*p && (*p != ' ') && (cmd < lastCmdChar) ) + { *cmd++ = *p++; + if (gVerbose) + printf("%x",*cmd); + } + *cmd = '\0'; + if (gVerbose) + printf("%x\ncommand str=%s\ncommand count with term=%d\n",*cmd, firstCmdChar, (cmd - firstCmdChar) + 1); + + if (strn_casecmp(p, " rtsp://", 8) != 0) + return 0; + + if (gVerbose) + printf("server hex="); + p += 8; + while (*p && (*p != '/') && (server < lastServerChar) ) + { *server++ = *p++; + if (gVerbose) + printf("%x",*server); + } + *server = '\0'; + if (gVerbose) + printf("%x\nserver str=%s\nserver count with term=%d\n",*server, firstServerChar, (server - firstServerChar) + 1); + + return 1; +} + +/**********************************************/ +static int has_trackID(char *inp, int *trackID) +{ + int l; + char *p; + l = strlen(inp); + + if (l < 18) /* "RTSP/1.0" (8) + "trackID=n " (10) */ + return 0; + if (strn_casecmp(inp + l - 8, "RTSP/1.0", 8) != 0) + return 0; + + p = inp; + while (p) { + p = strchr(p, '='); + if (p - 7 < inp) { + p++; + continue; + } + if (strn_casecmp(p - 7, "trackid=", 8) != 0) { + p++; + continue; + } + *trackID = atoi(p + 1); + return 1; + } + return 0; +} + +/**********************************************/ +static int has_content_length(char *inp, int *len) +{ + int l; + char *p; + l = strlen(inp); + + if (l < 16) /* "Content-Length:n" (16) */ + return 0; + if (strn_casecmp(inp, "content-length", 14) != 0) + return 0; + p = strchr(inp, ':'); + p++; + while (*p && (*p == ' ')) + p++; + if (p) { + *len = atoi(p); + return 1; + } + else + return 0; +} + +/**********************************************/ +static int has_IN_IP(char *inp, char *str) +{ + int l; + char *p; + l = strlen(inp); + + if (l < 10) /* "c=IN IP4 n" (10) */ + return 0; + if (strn_casecmp(inp, "c=IN IP4 ", 9) != 0) + return 0; + p = inp + 9; + while (*p && (*p == ' ')) + p++; + + while (*p && ((*p >= '0' && *p <= '9') || *p == '.')) + *str++ = *p++; + *str = '\0'; + return 1; +} + +/**********************************************/ +static int has_sessionID(char *inp, char *sessionID) +{ + int l; + char *p; + l = strlen(inp); + + if (l < 9) /* "Session:x" (9) */ + return 0; + if (strn_casecmp(inp, "session", 7) != 0) + return 0; + p = strchr(inp, ':'); + p++; + while (*p && (*p == ' ')) + p++; + if (p) { + strcpy(sessionID, p); + return 1; + } + else + return 0; +} + +/**********************************************/ +static int has_client_port(char *inp, int *port) +{ + int l; + char *p; + l = strlen(inp); + + if (l < 23) /* "Transport:<>client_port=n" (23) */ + return 0; + if (strn_casecmp(inp, "transport", 9) != 0) + return 0; + + p = inp; + while (p) { + p = strchr(p, '='); + if (p - 11 < inp) { + p++; + continue; + } + if (strn_casecmp(p - 11, "client_port=", 12) != 0) { + p++; + continue; + } + *port = atoi(p + 1); + *++p = '\0'; + return 1; + } + return 0; +} + +/**********************************************/ +static char *find_transport_header(char *inp) +{ + inp = strchr(inp, ';'); + if (inp != NULL) + inp++; + return inp; +} + +/**********************************************/ +static int has_ports(char *inp, int *client_port, int *server_port) +{ + int l, got_server = 0, got_client = 0; + char *p; + l = strlen(inp); + + if (l < 40) /* "Transport:<>client_port=n-nserver_port=n-n" (40) */ + return 0; + if (strn_casecmp(inp, "transport", 9) != 0) + return 0; + + p = inp; + while (p && !(got_client && got_server)) { + p = strchr(p, '='); + if (p - 11 < inp) { + } + else if (p == NULL) + { + } + else if (strn_casecmp(p - 11, "client_port=", 12) == 0) { + got_client = 1; + *client_port = atoi(p + 1); + } + else if (strn_casecmp(p - 11, "server_port=", 12) == 0) { + got_server = 1; + *server_port = atoi(p + 1); + } + p++; + } + if (got_client && got_server) + return 1; + else + return 0; +} + +/**********************************************/ +void service_session(rtsp_session *s) +{ + int i, canRead, numDelims = 0; + int num = SOCKET_ERROR; + char *pBuf, *p; + char temp[RTSP_SESSION_BUF_SIZE + 1]; + char lineBuff[MAX_LINE_BUFF + 1]; + track_info *t; + char cmd[MAX_CMD_BUFF], *w; + int responseHeaderLen = 0; + char *startBuff; + + /* see if we have any commands coming in */ + pBuf = s->cinbuf + s->amtInClientInBuffer; + canRead = sizeof(s->cinbuf) - s->amtInClientInBuffer - 1; + if (canRead > 0) { + if ((num = recv_tcp(s->client_skt, pBuf, canRead)) == SOCKET_ERROR) { + switch (GetLastSocketError(s->client_skt)) { + case EAGAIN: + /* do nothing, no data to be read. */ + break; + case EPIPE: // connection broke + case ENOTCONN: // shut down + case ECONNRESET: + s->state = stClientShutdown; + break; + default: + ErrorString1("problems reading from session socket (%d)\n", GetLastSocketError(s->client_skt)); + break; + } + } + else if (num == 0) { + // if readable and # of bytes is 0, then the client has shut down + s->state = stClientShutdown; + } + else { + pBuf[num] = '\0'; + s->amtInClientInBuffer += num; + } + } + + /* see what we have to do as a result of this or previous commands */ + switch (s->state) + { + case stIdle: + break; + + case stRecvClientCommand: + // + // have we read a full command yet? + if (s->amtInClientInBuffer == 0 || ! has_two_crlfs(s->cinbuf)) + break; + memset(temp, 0, sizeof(temp)); + +#if __MacOSX__ + strlcpy(temp, s->cinbuf, sizeof(temp)); +#else + strncpy(temp, s->cinbuf, sizeof(temp) -1); //relies on memset above to 0 term last char. +#endif + + pBuf = temp; + if ( (p = str_sep(&pBuf, "\r\n")) != NULL ) + { + if (is_command(p, cmd, sizeof(cmd), temp, sizeof(temp))) + { + if (s->server_address != NULL) + free(s->server_address); + s->server_address = malloc(strlen(temp) + 1); + assert(s->server_address != NULL); + strcpy(s->server_address, temp); + // + // take port off address (if any) + if ((w = strchr(s->server_address, ':')) != NULL) + *w++ = '\0'; + // + // make an async request for the IP number +#if USE_THREAD + name_to_ip_num(s->server_address, &s->tempIP, true); +#else + name_to_ip_num(s->server_address, &s->tempIP, false); +#endif + s->state = stWaitingForIPAddress; + } + else { + ErrorStringS("Couldn't make sense of client command [%s]\n", temp); + s->state = stError; + } + } + else { + ErrorStringS("Couldn't make sense of client command [%s]\n", temp); + s->state = stError; + } + break; + + case stWaitingForIPAddress: + if (s->tempIP != kPENDING_ADDRESS) + { + add_to_IP_cache(s->server_address, s->tempIP); + s->state = stParseClientCommand; + } + if (s->tempIP == -1) + { + send_rtsp_error(s->client_skt, kServerNotFound); + s->state = stBadServerName; + } + break; + + case stParseClientCommand: + if (gDebug) + DebugStringS("service_session stParseClientCommand start=%s", s->cinbuf); + startBuff = s->soutbuf; + // + // see what the command and server address is + // + // munge the data and snarf what we need + pBuf = s->cinbuf; + while ((p = str_sep(&pBuf, "\r\n")) != NULL) + { + // + // Count the empty fields; three in a row is the end of the header + if (*p == '\0') { + if (++numDelims == 3) + break; + continue; + } + else + numDelims = 0; + // + // see if we can snarf our data out of the headers + if (is_command(p, cmd,sizeof(cmd), temp, sizeof(temp) )) + { + int ip; + // + // get server address + if (s->server_address != NULL) + free(s->server_address); + s->server_address = malloc(strlen(temp) + 1); + assert(s->server_address != NULL); + strcpy(s->server_address, temp); + // + // get server port (if any) + if ((w = strchr(s->server_address, ':')) != NULL) + { + *w++ = '\0'; + s->server_port = atoi(w); + } + // + // check to see if command is pointing to the same server that + // we're already connected to. + name_to_ip_num(s->server_address, &ip, false); + if ((ip != s->server_ip) && (s->server_skt != INVALID_SOCKET)) { + close_socket(s->server_skt); + s->server_skt = INVALID_SOCKET; + s->server_ip = ip; + } + + if (ip == -1) { + s->state = stBadServerName; + return; + } + + if (gProxyIP == ip ) // don't connect to yourself + { + ErrorStringS("Invalid session: destination IP is the same as this proxy IP (%s).\n",ip_to_string(ip)); + close_socket(s->server_skt); + s->server_skt = INVALID_SOCKET; + s->state = stBadServerName; + return; + } + + s->server_ip = ip; + if (gVerbose) + printf("%s command for server %s:%d (ip %s)\n", + cmd, s->server_address, s->server_port, + ip_to_string(s->server_ip)); + s->transaction_type = cmd_to_transaction_type(cmd); + + if (s->transaction_type == ttSetup && has_trackID(p, &i)) + { + + num = track_id_to_idx(s, i); + if (num == -1 ) + { + if (s->numTracks == MAX_TRACKS) //stop before indexing the track array out of bounds + { + ErrorString1("Invalid session: The number of tracks are greater than the allowed maximum = (%d).\n",MAX_TRACKS); + close_socket(s->server_skt); + s->server_skt = INVALID_SOCKET; + s->state = stError; + return; + } + + num = s->numTracks; + s->trk[s->numTracks++].ID = i; + } + s->cur_trk = num; + } + } + else if (s->transaction_type == ttSetup + && has_client_port(p, &(s->trk[s->cur_trk].ClientRTPPort))) + { + t = s->trk + s->cur_trk; + if (gDebug) + printf("Client ports for track %d are %d-%d\n",s->cur_trk, t->ClientRTPPort, t->ClientRTPPort + 1); + // + // make rtp/rtcp port pair for proxy=>server + if (make_udp_port_pair(s->server_interface_addr, s->server_ip, &t->RTP_S2P, &t->RTCP_S2P) == -1) + { s->server_skt = INVALID_SOCKET; + ErrorString1("Couldn't create udp port pair for proxy=>server\n", GetLastSocketError(s->server_skt)); + s->state = stError; + break; + } + + if (gDebug) + printf("Created ports for server to proxy on track %d are %d-%d sockets %d-%d\n", + s->cur_trk, t->RTP_S2P->port, t->RTCP_S2P->port, + t->RTP_S2P->socket, t->RTCP_S2P->socket); + // + // reconstruct the client port string + sprintf(temp, "%s%d-%d", p, t->RTP_S2P->port, t->RTCP_S2P->port); + p = temp; + } + + if (0 == strn_casecmp(p, "x-dynamic-rate", 14))// don't send to server not supported. + p = "x-dynamic-rate: 0"; + + // + // put the line in the outgoing buffer + num = strlen(p); + memcpy(s->soutbuf + s->amtInServerOutBuffer, p, (size_t)num); + s->amtInServerOutBuffer += num; + + s->soutbuf[s->amtInServerOutBuffer++] = '\r'; + s->soutbuf[s->amtInServerOutBuffer++] = '\n'; + } + + if (*(s->cinbuf + s->amtInServerOutBuffer) == 0) + { + s->soutbuf[s->amtInServerOutBuffer++] = '\r'; + s->soutbuf[s->amtInServerOutBuffer++] = '\n'; + } + + s->amtInClientInBuffer -= s->amtInServerOutBuffer; + if (s->amtInClientInBuffer > 0) + { + memcpy(s->cinbuf, s->cinbuf + s->amtInServerOutBuffer, (size_t) s->amtInClientInBuffer); + s->cinbuf[s->amtInClientInBuffer] = 0; + if (gDebug) + DebugStringS("service_session stParseClientCommand memcpy to s->cinbuf=%s", s->cinbuf); + } + else if (s->amtInClientInBuffer < 0) + s->amtInClientInBuffer = 0; + s->state = stServerTransactionSend; + + if (s->soutbuf[s->amtInServerOutBuffer - 4] != '\r') + { + s->soutbuf[s->amtInServerOutBuffer++] = '\r'; + s->soutbuf[s->amtInServerOutBuffer++] = '\n'; + } + + if (gDebug) + DebugStringS("service_session stParseClientCommand SEND TO CLIENT=%s", s->soutbuf); + gNeedsService++; + break; + + case stServerTransactionSend: + // + // check to see if we've got a connection to the server open + if (s->server_skt == INVALID_SOCKET) + { + // + // create a connection if we don't have one + if ((s->server_skt = new_socket_tcp(false)) == INVALID_SOCKET) { + ErrorString("Couldn't open a socket to connect to server.\n"); + s->state = stError; + return; + } + set_socket_reuse_address(s->server_skt); + make_socket_nonblocking(s->server_skt); + s->server_skt_pending_connection = true; +#if DO_ASYNC + if ((i = connect_to_address(s, conn_finished_proc, s->server_skt, s->server_ip, s->server_port)) == SOCKET_ERROR) { +#else + if ((i = connect_to_address(s->server_skt, s->server_ip, s->server_port)) == SOCKET_ERROR) { +#endif + num = GetLastSocketError(s->server_skt); + switch (GetLastSocketError(s->server_skt)) { + case EISCONN: /* already connected */ + break; + case EINPROGRESS: /* connection can't be completed immediately */ + case EAGAIN: /* connection can't be completed immediately */ + case EALREADY: /* previous connection attempt hasn't been completed */ + return; + default: + close_socket(s->server_skt); + s->server_skt = INVALID_SOCKET; + ErrorString1("Couldn't connect to server %d\n", GetLastSocketError(s->server_skt)); + s->state = stCantConnectToServer; + return; + } + } + s->server_interface_addr = get_interface_addr(s->server_skt); + s->server_skt_pending_connection = false; + + } + + // + // check to see if we're connected (writable) and send the command + if (s->amtInServerOutBuffer) { + if ((num = send_tcp(s->server_skt, s->soutbuf, s->amtInServerOutBuffer)) == SOCKET_ERROR) { + switch (GetLastSocketError(s->server_skt)) { + case EPIPE: // connection broke + case ENOTCONN: // shut down + case ECONNRESET: + s->state = stServerShutdown; + break; + case EAGAIN: // was busy - try again + case EINTR: // got interrupted - try again + break; + default: + ErrorString1("writing to server error (%d)\n", GetLastSocketError(s->server_skt)); + s->state = stError; + return; + } + } + else if (num == 0) + s->state = stServerShutdown; + else { + s->amtInServerOutBuffer -= num; + if (s->amtInServerOutBuffer == 0) + s->state = stServerTransactionRecv; + } + } + + gNeedsService++; + break; + + case stServerTransactionRecv: + // + // check to see if we've got a response from the server + if (s->server_skt == INVALID_SOCKET) + { + if (gDebug) printf("s->server_skt == INVALID_SOCKET\n"); + s->state = stServerShutdown; + break; + } + + pBuf = s->sinbuf + s->amtInServerInBuffer; + canRead = sizeof(s->sinbuf) - s->amtInServerInBuffer - 1; + + if (canRead > 0) + { + if ((num = recv_tcp(s->server_skt, pBuf, canRead)) == SOCKET_ERROR) + { + //if (gDebug) printf("problems reading from server (%d)", + // GetLastSocketError(s->server_skt)); + + switch (GetLastSocketError(s->server_skt)) + { + case EAGAIN: + /* do nothing, no data to be read. */ + gNeedsService++; + break; + case EPIPE: // connection broke + case ENOTCONN: // shut down + case ECONNRESET: + s->state = stServerShutdown; + break; + default: + ErrorString1("problems reading from server (%d)\n", GetLastSocketError(s->server_skt)); + break; + } + + + } + else + { + pBuf[num] = '\0'; + if (gDebug) + printf("\nread %d bytes from server:%s\n", num, pBuf); + if (0 == num) + sleep_milliseconds(1); + s->amtInServerInBuffer += num; + } + } + else + if (gDebug) printf("can't read now!\n"); + + + // DMS - if there is a content-length, make sure we've gotten all that data too. + if ((s->totalContentLength > 0) && (s->amtInServerInBuffer < s->totalContentLength)) + { + //if (gDebug) printf("1-not enough in buffer content length %li, amt in buffer %li\n", + // (long)s->totalContentLength, (long)s->amtInServerInBuffer); + break; + } + + if ( s->totalContentLength == 0 ) + { //-rt if totalContentLength != 0,then we must have seen "has_two_crlfs" + // and already parsed out the content-length header. + // now we won't be able to find it again becuase str_sep + // in the parsing code below has already replaced the CRLFs with \0's. + + + // + // did we get complete response headers yet? + responseHeaderLen = has_two_crlfs(s->sinbuf); + + if (responseHeaderLen == 0) + { + break; + } + } + + + if ( !s->haveParsedServerReplyHeaders ) + { + // we can only do this one time! + + // + // munge the data for the client, while snarfing what we need + + for (pBuf = s->sinbuf; (p = str_sep(&pBuf, "\r\n")) != NULL; ) + { + // + // Count the empty fields; three in a row is end of the header + if (*p == '\0') + { + if (++numDelims == 3) + break; + continue; + } + else + numDelims = 0; + + if (0 == strn_casecmp(p, "x-Accept-Dynamic-Rate", 21)) + p = "x-Accept-Dynamic-Rate: 0"; + + // see if we can snarf any data out of the headers + if (has_content_length(p, &s->contentLength)) + { + // DMS - Set the total content length, if applicable + s->totalContentLength = s->contentLength + responseHeaderLen; + } + else if (has_sessionID(p, temp)) + { + if (!s->sessionID) + { + s->sessionID = malloc(strlen(temp) + 1); + if(s->sessionID == NULL) + { + ErrorString("Can't allocate session ID"); + exit(1); + } + strcpy(s->sessionID, temp); + } + else if (str_casecmp(s->sessionID, temp) != 0) + ErrorString("Bad session ID in response from server\n"); + + } + else if (s->transaction_type == ttSetup && has_ports(p, &i, &num)) + { + t = s->trk + s->cur_trk; + t->ServerRTPPort = num; + + if (gDebug) + printf("Server ports for track %d are %d-%d \n", + s->cur_trk, t->ServerRTPPort, t->ServerRTPPort + 1); + // + // make rtp/rtcp port pair here proxy=>client + if (make_udp_port_pair(s->client_interface_addr, s->client_ip, &t->RTP_P2C, &t->RTCP_P2C) == -1) + { s->server_skt = INVALID_SOCKET; + ErrorString1("Couldn't create udp port pair for proxy=>client\n", GetLastSocketError(s->server_skt)); + s->state = stError; + break; + } + + // + // set up transfer param blocks + t->RTP_S2C_tpb.status = &s->die; + t->RTP_S2C_tpb.send_from = t->RTP_P2C; + t->RTP_S2C_tpb.send_to_ip = s->client_ip; + t->RTP_S2C_tpb.send_to_port = t->ClientRTPPort; + strcpy(t->RTP_S2C_tpb.socketName, "RTP Server to Client"); + upon_receipt_from(t->RTP_S2P, s->server_ip, + transfer_data, &(t->RTP_S2C_tpb)); + + t->RTCP_S2C_tpb.status = &s->die; + t->RTCP_S2C_tpb.send_from = t->RTCP_P2C; + t->RTCP_S2C_tpb.send_to_ip = s->client_ip; + t->RTCP_S2C_tpb.send_to_port = t->ClientRTPPort + 1; + strcpy(t->RTCP_S2C_tpb.socketName,"RTCP Server to Client"); + upon_receipt_from(t->RTCP_S2P, s->server_ip, + transfer_data, &(t->RTCP_S2C_tpb)); + + t->RTCP_C2S_tpb.status = &s->die; + t->RTCP_C2S_tpb.send_from = t->RTCP_S2P; + t->RTCP_C2S_tpb.send_to_ip = s->server_ip; + t->RTCP_C2S_tpb.send_to_port = t->ServerRTPPort + 1; + strcpy(t->RTCP_C2S_tpb.socketName,"RTCP Client to Server"); + upon_receipt_from(t->RTCP_P2C, s->client_ip, + transfer_data, &(t->RTCP_C2S_tpb)); + + if (gDebug) + printf( "Created ports for proxy to client on track %d are %d-%d sockets %d-%d\n", + s->cur_trk, + t->RTP_P2C->port, t->RTCP_P2C->port, + t->RTP_P2C->socket, t->RTCP_P2C->socket + ); + + // + // reconstruct the client;server string + w = find_transport_header(p); + if (w != NULL) + *w = '\0'; + sprintf(temp, "%sclient_port=%d-%d;server_port=%d-%d;source=%s", p, + t->ClientRTPPort, t->ClientRTPPort+1, + t->RTP_P2C->port, t->RTCP_P2C->port, + ip_to_string(gProxyIP)); + p = temp; + + } + + // + // put the line in the outgoing buffer + num = strlen(p); + + assert( num + s->amtInClientOutBuffer + 2 <= RTSP_SESSION_BUF_SIZE ); + + memcpy(s->coutbuf + s->amtInClientOutBuffer, p, (size_t) num); + s->amtInClientOutBuffer += num; + s->coutbuf[s->amtInClientOutBuffer++] = '\r'; + s->coutbuf[s->amtInClientOutBuffer++] = '\n'; + + // Drop a pointer to where the content body might begin, + // if this is in fact the last line + s->responseBodyP = pBuf + 3; + } + + assert( s->amtInClientOutBuffer + 2 <= RTSP_SESSION_BUF_SIZE ); + s->coutbuf[s->amtInClientOutBuffer++] = '\r'; + s->coutbuf[s->amtInClientOutBuffer++] = '\n'; + } + + // the headers are done now. + s->haveParsedServerReplyHeaders = 1; + + // DMS - if there is a content-length, make sure we've gotten all that data too. + if ((s->totalContentLength > 0) && (s->amtInServerInBuffer < s->totalContentLength)) + { + //if (gDebug) + // printf("2- not enough in buffer content length %li, amt in buffer %li\n", + // (long)s->totalContentLength, (long)s->amtInServerInBuffer); + break; + } + + //if (gDebug) printf("have complete response\n" ); + + pBuf = s->responseBodyP; + + // + // munge and add the content if there is any + if (s->contentLength) + { + if (s->transaction_type == ttDescribe) + { + char *nextBuffPos = pBuf; + + // use "get_line_str" so that we preserve the EOL format + // of the describe resonse *exactly* + nextBuffPos = get_line_str( lineBuff, nextBuffPos, MAX_LINE_BUFF ); + + while ( *lineBuff ) + { + // c=IN IP0 ? + if (has_IN_IP(lineBuff, temp)) + { + //char *nextChar = lineBuff; + + // + // reconstruct the IN IP string, but + // make sure our replacement string is the + // same length as the original string + // so as not to change the Content-Length string + // while ( *nextChar ) + // { + // replace all digits on the line with zeros... + // if ( isdigit( *nextChar ) ) + // *nextChar = '0'; + // nextChar++; + // + // } + } + + // + // put the line in the outgoing buffer + num = strlen(lineBuff); + + assert( num + s->amtInClientOutBuffer <= RTSP_SESSION_BUF_SIZE ); + + memcpy(s->coutbuf + s->amtInClientOutBuffer, lineBuff, (size_t)num); + s->amtInClientOutBuffer += num; + + nextBuffPos = get_line_str( lineBuff, nextBuffPos, MAX_LINE_BUFF ); + } + + + + } + else + { + assert( s->contentLength + s->amtInClientOutBuffer <= RTSP_SESSION_BUF_SIZE ); + + memcpy(&s->coutbuf[s->amtInClientOutBuffer], pBuf, (size_t) s->contentLength); + s->amtInClientOutBuffer += s->contentLength; + } + } + + s->state = stSendClientResponse; + s->amtInServerInBuffer = 0; + s->totalContentLength = 0; + s->haveParsedServerReplyHeaders = 0; + s->contentLength = 0; + //printf("NEXT: stSendClientResponse\n" ); + gNeedsService++; + break; + + case stSendClientResponse: + //printf("stSendClientResponse\n" ); + // + // check to see that we're still connected (writable) and send the response + if (s->amtInClientOutBuffer && isWritable(s->client_skt)) + { + if ((num = send_tcp(s->client_skt, s->coutbuf, s->amtInClientOutBuffer)) == SOCKET_ERROR) + { + switch (GetLastSocketError(s->client_skt)) + { + case EPIPE: // connection broke + case ENOTCONN: // shut down + case ECONNRESET: + s->state = stClientShutdown; + break; + case EAGAIN: // was busy - try again + case EINTR: // got interrupted - try again + break; + default: + ErrorString1("writing to client error (%d)\n", GetLastSocketError(s->client_skt)); + s->state = stError; + return; + } + } + else + { + s->amtInClientOutBuffer -= num; + if (s->amtInClientOutBuffer == 0) { + if (s->transaction_type == ttTeardown) + s->state = stClientShutdown; + else + s->state = stRecvClientCommand; + } + } + } + + gNeedsService++; + break; + + + case stClientShutdown: + if (gDebug && s->client_ip != -1) DebugString1("Client shutdown (ip %s)", ip_to_string(s->client_ip)); + s->die = true; + gNeedsService++; + break; + + case stBadServerName: + send_rtsp_error(s->client_skt, kServerNotFound); + s->state = stServerShutdown; + break; + + case stCantConnectToServer: + send_rtsp_error(s->client_skt, kServerNotFound); + s->state = stServerShutdown; + break; + + case stServerShutdown: + if (gDebug && s->server_ip != -1) DebugString1("Server shutdown (ip %s)", ip_to_string(s->server_ip)); + s->die = true; + gNeedsService++; + break; + + case stError: + send_rtsp_error(s->client_skt, kUnknownError); + if (gDebug) DebugString("error condition.\n"); + s->die = true; + gNeedsService++; + break; + } +} + +/**********************************************/ + +void read_config() { + int fd, eof, num; + char buf, line[MAX_CONFIG_LINE_LEN], temp[MAX_CONFIG_LINE_LEN]; + int line_pos; + regmatch_t pmatch[3]; + regex_t regexpAllow, regexpComment, regexpUsers; + regex_t regexpListen, regexpPortRange; + regex_t regexpRTPAddr; + + if ((fd = open(gConfigFilePath, O_RDONLY)) == -1) { + switch (errno) { + case EACCES: + ErrorStringS("Config file %s inaccessible\n", gConfigFilePath); + break; + default: + ErrorString1("Problems opening config file (%d)\n", errno); + break; + } + return; + } + + if (gVerbose) + printf("Opened config file %s\n", gConfigFilePath); + + regcomp(®expAllow, "^[ \t]*allow[ \t]+([0-9\\.]+)/([0-9]+).*$", + REG_EXTENDED | REG_ICASE); + regcomp(®expUsers, "^[ \t]*users[ \t]+([0-9]+).*$", + REG_EXTENDED | REG_ICASE); + regcomp(®expListen, "^[ \t]*listen[ \t]+([0-9\\.]+[ \t]+)?([0-9]+).*$", + REG_EXTENDED | REG_ICASE); + + regcomp(®expPortRange, "^[ \t]*port-range[ \t]+([0-9]+)-([0-9]+).*$", + REG_EXTENDED | REG_ICASE); + regcomp(®expComment, "^[ \t]*#.*$", REG_EXTENDED | REG_ICASE); + + regcomp(®expRTPAddr, "^[ \t]*rtp-bind-addr[ \t]+([a-z0-9\\.\\-]+).*$",REG_EXTENDED | REG_ICASE); + + eof = false; + while (!eof) { + // + // read a line from the configuration file + line_pos = 0; + while (1) { + num = read(fd, &buf, 1); + if (num <= 0) { + eof = true; + break; + } + if (buf == '\n' || buf == '\r') + break; + if (line_pos < MAX_CONFIG_LINE_LEN) + line[line_pos++] = buf; + } + line[line_pos] = '\0'; + if (gDebug) DebugStringS("Read config line: %s", line); + + // + // if the line has anything, try to parse it + if (line_pos > 0) { + if (regexec(®expAllow, line, 3, pmatch, 0) == 0) { + int ip, range; + + num = pmatch[1].rm_eo - pmatch[1].rm_so; + memcpy(temp, line + pmatch[1].rm_so, (size_t) num); + temp[num] = '\0'; + range = atoi(line + pmatch[2].rm_so); + //name_to_ip_num(temp, &ip, false); + ip = ntohl(inet_addr(temp)); + if (gVerbose) + printf("Allow connections from %s/%d\n", ip_to_string(ip), range); + add_allow_subnet(ip, range); + } + else if (regexec(®expUsers, line, 3, pmatch, 0) == 0) { + gUserLimit = atoi(line + pmatch[1].rm_so); + if (gVerbose) + printf("Limit number of users to %d\n", gUserLimit); + } + else if (regexec(®expListen, line, 3, pmatch, 0) == 0) { + struct in_addr bindaddr; + int port = atoi(line + pmatch[2].rm_so); + + if(pmatch[1].rm_so==-1) + bindaddr.s_addr= (in_addr_t) htonl(ANY_ADDRESS); + else + { + *(line+pmatch[1].rm_eo)='\0'; + #if __solaris__ + bindaddr.s_addr = inet_addr(line+pmatch[1].rm_so); + if( 0 == bindaddr.s_addr) + #else + if( inet_aton(line+pmatch[1].rm_so, (void *) &bindaddr)==0 ) + #endif + printf("listen: failed to parse IP address %s\n",line+pmatch[1].rm_so); + } + + add_rtsp_port_listener( (int) ntohl(bindaddr.s_addr),port); + } + else if (regexec(®expPortRange, line, 3, pmatch, 0) == 0) { + int minPort, maxPort; + minPort = atoi(line + pmatch[1].rm_so); + maxPort = atoi(line + pmatch[2].rm_so); + set_udp_port_min_and_max(minPort, maxPort); + if (gVerbose) + printf("Use UDP ports %d through %d\n", minPort, maxPort); + } + else if (regexec(®expComment, line, 0, NULL, 0) == 0) { + // comment - do nothing + } + else if (regexec(®expRTPAddr,line,3,pmatch,0)==0) { + num = pmatch[1].rm_eo - pmatch[1].rm_so; + memcpy(temp, line + pmatch[1].rm_so, (size_t) num); + temp[num] = '\0'; + name_to_ip_num(temp, &gProxyIP, false); + if (gProxyIP == -1) { + printf("unable to configure rtp-bind-addr: %s\n", temp); + } else if (gVerbose) { + printf("configured rtp-bind-addr: %s (%s)\n", temp, ip_to_string(gProxyIP)); + } + } + else { + ErrorStringS("invalid configuration line [%s]\n", line); + } + } + } + + regfree(®expAllow); + regfree(®expComment); + regfree(®expUsers); + regfree(®expListen); + regfree(®expPortRange); + regfree(®expRTPAddr); + + close(fd); +} + +/**********************************************/ +void add_allow_subnet(int ip, int range) +{ + subnet_allow *allow; + + allow = (subnet_allow*)malloc(sizeof(subnet_allow)); + assert(allow != NULL); + allow->ip = ip; + allow->range = range; + allow->next = gAllowedNets; + gAllowedNets = allow; +} + +/**********************************************/ +static int bitfill(int bits) +{ + int mask, i; + + /* unroll the bit fill and deal with common subnet masks. */ + if (bits >= 24) { + mask = 0xffffff00; + bits -= 24; + i = 24; + } else if (bits >= 16) { + mask = 0xffff0000; + bits -= 16; + i = 16; + } else if (bits >= 8) { + mask = 0xff000000; + bits -= 8; + i = 8; + } else { + mask = i = 0; + } + + /* only 7 more bits to go! */ + while (bits) { + mask |= (1 << (32 - i - bits)); + bits--; + } + return mask; +} + +/**********************************************/ +bool allow_ip(int ip) +{ + int mask; + subnet_allow *cur; + + cur = gAllowedNets; + if (cur == NULL) + return true; + + while (cur) { + mask = bitfill(cur->range); + if ((cur->ip & mask) == (ip & mask)) + return true; + cur = cur->next; + } + + return false; +} + +/**********************************************/ +void send_rtsp_error(int skt, int refusal) +{ + char *refusal_string; + + switch (refusal) { + case kServerNotFound: + refusal_string = "RTSP/1.0 462 Destination unreachable\r\n"; + break; + case kUnknownError: + refusal_string = "RTSP/1.0 500 Unknown proxy error\r\n"; + break; + case kPermissionDenied: + refusal_string = "RTSP/1.0 403 Proxy denied\r\n"; + break; + case kTooManyUsers: + refusal_string = "RTSP/1.0 503 Too many proxy users\r\n"; + break; + default: + refusal_string = "RTSP/1.0 500 Unknown proxy error\r\n"; + break; + } + + ErrorStringS("RTSP error: %s", refusal_string); + send_tcp(skt, refusal_string, (int) strlen(refusal_string)); +} + +/**********************************************/ +/**********************************************/ diff --git a/StreamingProxy.tproj/proxy.h b/StreamingProxy.tproj/proxy.h new file mode 100644 index 0000000..53abf5d --- /dev/null +++ b/StreamingProxy.tproj/proxy.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@ + * + */ + /* + * proxy.h + * + * + */ + +#ifndef __PROXY_H__ +#define __PROXY_H__ + +#include "shared_udp.h" +/**********************************************/ +enum { + stIdle, + stError, + + stRecvClientCommand, + stWaitingForIPAddress, + stParseClientCommand, + stSendClientResponse, + stServerTransactionSend, + stServerTransactionRecv, + stClientShutdown, + stServerShutdown, + stBadServerName, + stCantConnectToServer, + stDone +}; // rtsp session states + +enum { + ttNone, + ttDescribe, + ttSetup, + ttPlay, + ttPause, + ttStop, + ttTeardown, + ttOptions, + ttAnnounce, + ttRedirect, + ttGetParameter, + ttSetParameter +}; // rtsp command types + +enum { + kPermissionDenied, + kTooManyUsers, + kServerNotFound, + kUnknownError +}; // refusal type + +typedef struct { + char *cmd; + int type; +} t_cmd_map; + +#define MAX_TRACKS 32 + +typedef struct { + int ID; + shok *RTP_S2P; + shok *RTCP_S2P; + shok *RTP_P2C; + shok *RTCP_P2C; + int ClientRTPPort; + int ServerRTPPort; + trans_pb RTP_S2C_tpb; + trans_pb RTCP_S2C_tpb; + trans_pb RTCP_C2S_tpb; +} track_info; + +/* This size will fit nicely in a standard ethernet frame */ +#define RTSP_SESSION_BUF_SIZE 4096 + +typedef struct rtsp_session { + struct rtsp_session *next; + int die; + int newSession; + int client_skt; + int client_ip; + char *server_address; + int server_skt; + int server_interface_addr; + int client_interface_addr; + int server_ip; + int server_port; + int server_skt_pending_connection; + int state; + int transaction_type; + char *sessionID; + + int cur_trk; + int numTracks; + track_info trk[MAX_TRACKS]; + + char cinbuf[RTSP_SESSION_BUF_SIZE]; + int amtInClientInBuffer; + char coutbuf[RTSP_SESSION_BUF_SIZE]; + int amtInClientOutBuffer; + char sinbuf[RTSP_SESSION_BUF_SIZE]; + int amtInServerInBuffer; + char soutbuf[RTSP_SESSION_BUF_SIZE]; + int amtInServerOutBuffer; + int totalContentLength; + int haveParsedServerReplyHeaders; + int contentLength; + char* responseBodyP; + + int tempIP; +} rtsp_session; + +typedef struct subnet_allow { + struct subnet_allow *next; + int ip; + int range; +} subnet_allow; + +typedef struct rtsp_listener { + struct rtsp_listener *next; + int port; + int skt; +} rtsp_listener; + +/**********************************************/ +int service_listeners(); +int service_sessions(); + +void add_rtsp_port_listener(int address,int port); +void cleanup_listeners(void); +void answer_new_connection(rtsp_listener *listener); +void add_session(rtsp_session *session); +void remove_session(rtsp_session *session); +rtsp_session *new_session(void); +void cleanup_sessions(void); +void cleanup_session(rtsp_session *session); +void service_session(rtsp_session *session); +void service_session_rtp(rtsp_session *session); +void read_config(void); +void add_allow_subnet(int ip, int range); +bool allow_ip(int ip); +void send_rtsp_error(int skt, int refusal); + +#endif // __PROXY_H__ + diff --git a/StreamingProxy.tproj/proxy_plat.h b/StreamingProxy.tproj/proxy_plat.h new file mode 100644 index 0000000..caebc5b --- /dev/null +++ b/StreamingProxy.tproj/proxy_plat.h @@ -0,0 +1,166 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + /* + * proxy_plat.h + * + * + */ + +#ifndef _PLAT_H_ +#define _PLAT_H_ + +/**********************************************/ +#define bool char +#if !defined(mac) +#define true 1 +#define false 0 +#endif +#if !defined(WIN32) +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#endif + +/**********************************************/ +#if defined(WIN32) +#define EACCES WSAEACCES +#define EINTR WSAEINTR +#define EAGAIN WSAEWOULDBLOCK /* good enough? */ +#define EPIPE WSAESHUTDOWN /* good enough? */ +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define EISCONN WSAEISCONN +#define EINPROGRESS WSAEINPROGRESS +#define EALREADY WSAEALREADY +#include "WINSOCK.H" +#elif defined(mac) +#define EACCES kEACCESErr +#define EPIPE kEPIPEErr +#define EINTR kEINTRErr +#define EAGAIN kEAGAINErr +#define ENOTCONN kENOTCONNErr +#define ECONNRESET kECONNRESETErr +#define EISCONN kEISCONNErr +#define EINPROGRESS kEINPROGRESSErr +#define EALREADY kEALREADYErr +#endif + +/**********************************************/ +#define MSEC_PER_SEC 1000 +#define USEC_PER_SEC 1000000 +#define USEC_PER_MSEC 1000 +#define timer_sub(ntime,subtime,eqtime) \ + if ((subtime).tv_usec > (ntime).tv_usec) { \ + (eqtime).tv_sec = ((ntime).tv_sec - 1) - (subtime).tv_sec; \ + (eqtime).tv_usec = (ntime).tv_usec + USEC_PER_SEC - \ + (subtime).tv_usec; \ + } \ + else { \ + (eqtime).tv_sec = (ntime).tv_sec - (subtime).tv_sec; \ + (eqtime).tv_usec = (ntime).tv_usec - (subtime).tv_usec; \ + } + + +extern char *gConfigFilePath; +extern char *gOptionsString; +extern char gOptionsChar; + +typedef struct stats_chunk { + unsigned long elapsedSeconds; + unsigned long bpsReceived; + unsigned long bpsSent; + unsigned long ppsReceived; + unsigned long ppsSent; + unsigned long totalPacketsReceived; + unsigned long totalPacketsSent; + unsigned long numClients; + unsigned long numPorts; + float percentLostPackets; +} stats_chunk; + + +void daemonize(void); +int init_network(void); +int term_network(void); +int init_ui(void); +int service_ui(int sleep_ticks); + +void sleep_milliseconds(int ms); +time_t microseconds(); +#define kPENDING_ADDRESS -2 +int name_to_ip_num(char *name, int *ip_num, int async); +int get_remote_address(int skt, int *port); +int get_local_address(int skt, int *port); +int get_local_ip_address(void); +bool isReadable(int fd); +bool isWritable(int fd); + +int new_socket_udp(void); +int new_socket_tcp(int is_listener); +void close_socket(int skt); +void set_socket_reuse_address(int skt); +void set_socket_max_buf(int skt); +void make_socket_nonblocking(int skt); +int bind_socket_to_address(int skt, int address, int port, int is_listener); +int listen_to_socket(int skt); +int call_is_waiting(int skt, int *incoming_skt); +int accept_connection(int from, int *to); +int get_interface_addr(int skt); +#if DO_ASYNC +pascal void conn_finished_proc(void* contextPtr, OTEventCode code, OTResult /*result*/, void* /*cookie*/); +int connect_to_address(void *context, OTNotifyProcPtr proc, int skt, int address, int port); +#else +int connect_to_address(int skt, int address, int port); +#endif + +int tcp_data_ready(int skt); + +int recv_udp(int skt, char *buf, int amt, int *fromAddress, int *fromPort); +int send_udp(int skt, char *buf, int amt, int address, int port); +int recv_tcp(int skt, char *buf, int amt); +int send_tcp(int skt, char *buf, int amt); + +// int make_udp_port_pair(int *socket1, int *socket2); + +int GetLastSocketError(int skt); +void DoStats(stats_chunk *stats); + +#if defined(mac) || defined(WIN32) +extern char gLastErrorString[256]; +#define ErrorString(a) sprintf(gLastErrorString, a) +#define ErrorString1(a,b) sprintf(gLastErrorString, a, b) +#define ErrorStringS(a,b) sprintf(gLastErrorString, a, b) +#define DebugString(a) printf(a "\n") +#define DebugString1(a,b) printf(a "\n", b) +#define DebugStringS(a,b) printf(a "\n", b) +#else +void ErrorString(char *string); +void ErrorString1(char *string, int d); +void ErrorStringS(char *string, char *arg); +#define DebugString(a) printf(a "\n") +#define DebugString1(a,b) printf(a "\n", b) +#define DebugStringS(a,b) printf(a "\n", b) +#endif +#endif // _PLAT_H_ + diff --git a/StreamingProxy.tproj/proxy_unix.c b/StreamingProxy.tproj/proxy_unix.c new file mode 100644 index 0000000..21443bd --- /dev/null +++ b/StreamingProxy.tproj/proxy_unix.c @@ -0,0 +1,579 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + proxy_unix.c + +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "util.h" +#include "proxy_plat.h" + +#if USE_THREAD +#include "pthread.h" +#endif + +#include "../defaultPaths.h" + +char *gConfigFilePath = DEFAULTPATHS_ETC_DIR "streamingproxy.conf"; + +char *gOptionsString = "-c:-p:-d-D-v-h-s-x-i:"; +char gOptionsChar = '-'; + +extern int gMaxPorts; +extern float gDropPercent; + +/**********************************************/ +int init_network() +{ + return 0; +} + +/**********************************************/ +#define kKILL_THREAD -3 +int term_network() +{ + int send = kKILL_THREAD; + name_to_ip_num("", &send, true); + return 0; +} + +/**********************************************/ +typedef struct ghpb_rec { + char name[256]; + int *result; +} ghpb_rec, *ghpb; + +#if USE_THREAD +void *gethostthread(void *param) +{ + struct hostent *hent; + ghpb pb = (ghpb)param; + int id; + pthread_t tid; + int tryCount = 0; + + if (*pb->result == kKILL_THREAD) + exit(0); + tid = pthread_self(); +again: + hent = gethostbyname(pb->name); + if (hent == NULL) do + { + tryCount ++; + if (h_errno == TRY_AGAIN) + { if (tryCount < 10) + goto again; + else + return 0; + } + *pb->result = -1; + pthread_exit(NULL); + } while(0); + id = ntohl(((struct in_addr *)(hent->h_addr_list[0]))->s_addr); + *pb->result = id; + free(pb); + pthread_exit(NULL); + return NULL; +} +#endif +/**********************************************/ +int name_to_ip_num(char *name, int *ip, int async) +{ + int ret; + struct in_addr addr; + int tryAgain = 0; +#if USE_THREAD + ghpb pb = NULL; + pthread_t tid; +#endif + struct hostent *hent; + + if (check_IP_cache(name, &ret) != -1) + { + *ip = ret; + return 0; + } + +#if USE_THREAD + if (async) + { + *ip = kPENDING_ADDRESS; + pb = (ghpb)malloc(sizeof(ghpb_rec)); + strcpy(pb->name, name); + pb->result = ip; + pthread_create(&tid, NULL, gethostthread, (void*)pb); + pthread_detach(tid); + return 1; + } +#endif + +again: + + tryAgain ++; + if ( inet_aton( name, &addr ) ) + { *ip = ntohl( addr.s_addr ); + add_to_IP_cache(name, *ip ); + return 0; + } + + hent = gethostbyname(name); + if (hent == NULL) { + if (h_errno == TRY_AGAIN) + if (tryAgain < 10) + goto again; + add_to_IP_cache(name, -1); + return -1; + } + *ip = ntohl(((struct in_addr *) (hent->h_addr_list[0]))->s_addr); + add_to_IP_cache(name, *ip); + return 0; +} + + +/**********************************************/ +int get_remote_address(int skt, int *port) +{ +#if !defined(sparc) && !defined(SGI) && !defined(WIN32) + unsigned +#endif + int nAddrSize = sizeof(struct sockaddr_in); + struct sockaddr_in remAddr; + int status; + remAddr.sin_addr.s_addr = INADDR_ANY; + status = getpeername(skt, (struct sockaddr*)&remAddr, &nAddrSize); + if (status >= 0) + { + if (port) + *port = ntohs(remAddr.sin_port); + return ntohl(remAddr.sin_addr.s_addr); + } + return -1; +} + +/**********************************************/ +int get_local_address(int skt, int *port) { +#if !defined(sparc) && !defined(SGI) && !defined(WIN32) + unsigned +#endif + int nAddrSize = sizeof(struct sockaddr_in); + struct sockaddr_in remAddr; + int status; + remAddr.sin_addr.s_addr = INADDR_ANY; + status = getsockname(skt, (struct sockaddr*)&remAddr, &nAddrSize); + if (status >= 0) + { + if (port) + *port = ntohs(remAddr.sin_port); + return ntohl(remAddr.sin_addr.s_addr); + } + return -1; +} + +/**********************************************/ +static int __local_ip_address = -1; + +int get_local_ip_address() +{ + char buf[256]; + struct hostent *hent; + int tryCount = 0; + + if (__local_ip_address != -1) + return __local_ip_address; + + if (gethostname(buf, 256) < 0) + return -1; +again: + tryCount ++; + hent = gethostbyname(buf); + if (hent == NULL) + { if (h_errno == TRY_AGAIN) + { if (tryCount < 10) + { + goto again; + } + else + { + return 0; + } + } + return -1; + } + __local_ip_address = ntohl(((struct in_addr *)hent->h_addr)->s_addr); + return __local_ip_address; +} + +/**********************************************/ +void make_socket_nonblocking(int socket) +{ + int flag; + flag = fcntl(socket, F_GETFL, 0); + fcntl(socket, F_SETFL, flag | O_NONBLOCK); +} + +/**********************************************/ +void sleep_milliseconds(int ms) +{ + struct timeval tv; + + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms - (tv.tv_sec * 1000) ) * 1000; + select(0, NULL, NULL, NULL, &tv); +} + +/**********************************************/ +time_t microseconds() +{ + static bool us_initted = false; + static struct timeval us_time_zero; + struct timeval tv, t; + struct timezone tz; + + gettimeofday(&tv, &tz); + if (us_initted == false) + { + us_initted = true; + us_time_zero = tv; + return 0; + } + else + { + timer_sub(tv, us_time_zero, t); + return (t.tv_sec * USEC_PER_SEC + t.tv_usec); + } +} + +/**********************************************/ +bool isReadable(int fd) +{ + +/* causes crash fd_set is wrong size if num users > 255 +// not needed anyway + + fd_set set; + struct timeval tv; + int err; + + if (fd == INVALID_SOCKET) + return false; + + tv.tv_sec = 0; tv.tv_usec = 0; + + FD_ZERO(&set); + + FD_SET(fd, &set); + + err = select(fd+1, &set, NULL, NULL, &tv); + if (err > 0) + if (FD_ISSET(fd, &set)) + return true; + return false; +*/ + return true; +} + +/**********************************************/ +bool isWritable(int fd) +{ +/* causes crash fd_set is wrong size if num users > 255 +// not needed anyway + + fd_set set; + struct timeval tv; + int err; + + if (fd == INVALID_SOCKET) + return false; + tv.tv_sec = 0; tv.tv_usec = 0; + FD_ZERO(&set); + FD_SET(fd, &set); + err = select(fd+1, NULL, &set, NULL, &tv); + if (err > 0) + if (FD_ISSET(fd, &set)) + return true; + return false; +*/ + + return true; +} + +/**********************************************/ +int new_socket_udp(void) +{ + int ret; + ret = socket(PF_INET, SOCK_DGRAM, 0); + gMaxPorts++; + set_socket_max_buf(ret); + return ret; +} + +/**********************************************/ +int new_socket_tcp(int is_listener) +{ + int ret; + ret = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + gMaxPorts++; + return ret; +} + +/**********************************************/ +void set_socket_reuse_address(int skt) +{ + int i = 1; + setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (char*)&i, sizeof(i)); +} + +/**********************************************/ +void set_socket_max_buf(int skt) +{ + int i = 1; + unsigned int len; + len = sizeof(i); + getsockopt(skt, SOL_SOCKET, SO_SNDBUF, (char*)&i, &len); + /*fprintf(stderr, "sndbuf for socket %d was %d\n", skt, i);*/ + i *= 2; + setsockopt(skt, SOL_SOCKET, SO_SNDBUF, (char*)&i, len); + getsockopt(skt, SOL_SOCKET, SO_SNDBUF, (char*)&i, &len); + /*fprintf(stderr, "sndbuf for socket %d is now %d\n", skt, i);*/ + getsockopt(skt, SOL_SOCKET, SO_RCVBUF, (char*)&i, &len); + /*fprintf(stderr, "rcvbuf for socket %d was %d\n", skt, i);*/ + i *= 2; + setsockopt(skt, SOL_SOCKET, SO_RCVBUF, (char*)&i, len); + getsockopt(skt, SOL_SOCKET, SO_RCVBUF, (char*)&i, &len); + /*fprintf(stderr, "rcvbuf for socket %d is now %d\n", skt, i);*/ +} + +/**********************************************/ +int bind_socket_to_address(int skt, int address, int port, int is_listener) +{ + struct sockaddr_in sin; + + if (address == -1) + address = INADDR_ANY; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(address); + return bind(skt, (struct sockaddr*)&sin, sizeof(sin)); +} + +/**********************************************/ +void close_socket(int skt) +{ + if (skt != INVALID_SOCKET) + { + gMaxPorts--; + close(skt); + } +} + +/**********************************************/ +int listen_to_socket(int skt) +{ + return listen(skt, 5); +} + +/**********************************************/ +int call_is_waiting(int skt, int *incoming_skt) +{ + int ret; + + ret = isReadable(skt); + + if (ret) + { + *incoming_skt = accept(skt, 0, 0); + + if (*incoming_skt <= 0) + { + ret = 0; + } + else + gMaxPorts++; + } + + return ret; +} + +/**********************************************/ +int accept_connection(int from, int *to) +{ +// return accept(from, 0, 0); + return *to; +} + +/**********************************************/ +int connect_to_address(int skt, int address, int port) +{ + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(address);; + return connect(skt, (struct sockaddr*)&sin, sizeof(sin)); +} + +int get_interface_addr(int skt) +{ + int err = 0; + struct sockaddr_in localAddr; + unsigned int len = sizeof(localAddr); + memset(&localAddr, 0, sizeof(localAddr)); + + err = getsockname(skt, (struct sockaddr*)&localAddr, &len); + return ntohl(localAddr.sin_addr.s_addr); +} + +/**********************************************/ +int recv_udp(int socket, char *buf, int amt, int *fromip, int *fromport) +{ + struct sockaddr_in sin; + int ret; + unsigned int len; + + len = sizeof(sin); + memset(&sin, 0, sizeof(sin)); + ret = recvfrom(socket, buf, (size_t) amt, 0, (struct sockaddr*)&sin, &len); + if (ret != -1) { + if (fromip) + *fromip = ntohl(sin.sin_addr.s_addr); + if (fromport) + *fromport = ntohs(sin.sin_port); + } + return ret; +} + +/**********************************************/ +int send_udp(int skt, char *buf, int amt, int address, int port) +{ + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = htonl(address); + return sendto(skt, buf, (size_t) amt, 0, (struct sockaddr*)&sin, sizeof(sin)); +} + +/**********************************************/ +int recv_tcp(int socket, char *buf, int amt) +{ + return read(socket, buf, (size_t) amt); +} + +/**********************************************/ +int send_tcp(int socket, char *buf, int amt) +{ + return write(socket, buf, (size_t) amt); +} + +/**********************************************/ +int GetLastSocketError(int skt) +{ + return errno; +} + +/**********************************************/ +int init_ui() +{ + return 0; +} + +/**********************************************/ +int service_ui(int sleep_time) +{ + return 0; +} + +/**********************************************/ +void DoStats(stats_chunk *stats) +{ + printf("\033[2J\033[H"); + printf("Elapsed Time (seconds) : %lu\n", stats->elapsedSeconds); + printf("Number of clients : %lu\n", stats->numClients); + printf("bps Received : %lu\n", stats->bpsReceived); + printf("bps Sent : %lu\n", stats->bpsSent); + printf("Total Packets Received : %lu\n", stats->totalPacketsReceived); + printf("Total Packets Sent : %lu\n", stats->totalPacketsSent); + printf("pps Received : %lu\n", stats->ppsReceived); + printf("pps Sent : %lu\n", stats->ppsSent); + printf("number of ports used : %lu\n", stats->numPorts); + printf("packet loss percent : %f\n", stats->percentLostPackets); + printf("force drop percent : %f\n",gDropPercent); +} + +/**********************************************/ +void ErrorString(char *string) +{ + fprintf(stderr, string); +} + +/**********************************************/ +void ErrorString1(char *string, int d) +{ + fprintf(stderr, string, d); +} + +/**********************************************/ +void ErrorStringS(char *string, char *arg) +{ + fprintf(stderr, string, arg); +} + +void daemonize() +{ + switch (fork()) { + case -1: /* error */ + fprintf(stderr, "can't daemonize!\n"); + case 0: /* child */ + break; + default: /* parent */ + exit(0); + } +} diff --git a/StreamingProxy.tproj/shared_udp.c b/StreamingProxy.tproj/shared_udp.c new file mode 100644 index 0000000..04516db --- /dev/null +++ b/StreamingProxy.tproj/shared_udp.c @@ -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: shared_udp.c + Contains: UDP Sockets implementation with shared ports + + + +*/ + + +#include +#include +#include +#include +#include +#include + +#if defined(unix) +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#elif defined(win32) +#include "WINSOCK.H" +#include "regex.h" +#elif defined(mac) +#include +#include "OpenTransport.h" +#endif + +#include "shared_udp.h" +#include "proxy_plat.h" + +#if DEBUG + #define DEBUGPRINT(x) printf x +#else + #define DEBUGPRINT(x) +#endif + +/**********************************************/ +shok *gShokList = NULL; + +/**********************************************/ +ipList *find_ip_in_list(ipList *list, int ip) +{ + ipList *cur = list; + + DEBUGPRINT(( "-- -- looking for IP %x in IP list\n", ip)); + while (cur) { + DEBUGPRINT(("-- -- vs. %x\n", cur->ip)); + if (cur->ip == ip) { + DEBUGPRINT(("-- -- FOUND\n")); + return cur; + } + cur = cur->next; + } + DEBUGPRINT(("-- -- NOT FOUND\n")); + return NULL; +} + +/**********************************************/ +int add_ip_to_list(ipList **list, int ip) +{ + ipList *newEl; + + newEl = (ipList*)malloc(sizeof(ipList)); + if (!newEl) + return false; + newEl->ip = ip; + newEl->what_to_do = NULL; + newEl->what_to_do_it_with = NULL; + newEl->next = *list; + *list = newEl; + + return true; +} + +/**********************************************/ +int remove_ip_from_list(ipList **list, int ip) +{ + ipList *last, *theEl = *list; + + if (theEl->ip == ip) { + *list = theEl->next; + free(theEl); + return true; + } + + last = theEl; + theEl = theEl->next; + while (theEl) { + if (theEl->ip == ip) { + last->next = theEl->next; + free(theEl); + return true; + } + last = theEl; + theEl = theEl->next; + } + return false; +} + +/**********************************************/ +shok *find_available_shok(int fromIP, int toIP, int withSib) +{ + shok *cur = gShokList; + + while (cur) { + DEBUGPRINT(("-- looking for IP %x in shok %p\n", toIP, cur)); + if (find_ip_in_list(cur->ips, toIP) == NULL) { + if (withSib) { + DEBUGPRINT(("-- looking for IP %x in SIB shok %p\n", toIP, cur->sib)); + if (find_ip_in_list(cur->sib->ips, toIP) == NULL) + return cur; + } + else + return cur; + } + cur = cur->next; + } + + return NULL; +} + +/**********************************************/ +int add_ips_to_shok(shok *theShok, int fromIP, int toIP, int withSib) +{ + add_ip_to_list(&(theShok->ips), toIP); + if (withSib) + add_ip_to_list(&(theShok->sib->ips), toIP); + return 1; +} + +/**********************************************/ +static int gUDPPortMin = 4000; +static int gUDPPortMax = 65536; +#define sInvalidPort -1 +static int gNextPort = sInvalidPort; + +void set_udp_port_min_and_max(int min, int max) +{ + gUDPPortMin = min; + gUDPPortMax = max; + if (gUDPPortMin & 0x1) + gUDPPortMin++; +} + +/**********************************************/ +int remove_shok(shok *theShok, int withSib) +{ + shok *cur = NULL, *last; + + cur = gShokList; + if (cur == theShok) { + gShokList = cur->next; + goto doSib; + } + + last = cur; + cur = cur->next; + while (cur) { + if (cur == theShok) { + last->next = cur->next; + goto doSib; + } + last = cur; + cur = cur->next; + } + return false; + +doSib: + if (cur->sib) + cur->sib->sib = NULL; + if (withSib && cur->sib) + remove_shok(cur->sib, false); + + { + ipList *ipn, *ipl = cur->ips; + while (ipl) { + ipn = ipl->next; + free(ipn); + ipl = ipn; + } + } + close_socket(cur->socket); + free(cur); + return true; +} + +/**********************************************/ +void remove_shok_ref(shok *theShok, int fromIP, int toIP, int withSib) +{ + remove_ip_from_list(&(theShok->ips), toIP); + if (withSib) + remove_ip_from_list(&(theShok->sib->ips), toIP); + if (theShok->sib->ips == NULL) + remove_shok(theShok->sib, false); + if (theShok->ips == NULL) + remove_shok(theShok, false); +} + +/**********************************************/ +shok *make_new_shok(int fromIP, int toIP, int withSib) +{ + shok *theShok1 = NULL, *theShok2 = NULL; + int skt1 = INVALID_SOCKET, skt2 = INVALID_SOCKET; + int port1 = sInvalidPort; + int port2 = sInvalidPort; + + theShok1 = (shok*)malloc(sizeof(shok)); + if (!theShok1) + goto bail_error; + if (withSib) { + theShok2 = (shok*)malloc(sizeof(shok)); + if (!theShok2) + goto bail_error; + } + + if (gNextPort == -1) + gNextPort = gUDPPortMin; +retry: + if ((skt1 = new_socket_udp()) == SOCKET_ERROR) + goto bail_error; + if (skt1 == 0) { + if (GetLastSocketError(skt1) == EINPROGRESS || GetLastSocketError(skt1) == EAGAIN) + goto retry; + else + goto bail_error; + } + do { + if ((gNextPort & 0x1) && withSib) + gNextPort++; + if (gNextPort > gUDPPortMax) + gNextPort = gUDPPortMin; + } while (bind_socket_to_address(skt1, fromIP, port1 = gNextPort++, false) != 0); + + if (withSib) { +retry_rtcp: + if ((skt2 = new_socket_udp()) == SOCKET_ERROR) + goto bail_error; + if (skt2 == 0) { + if (GetLastSocketError(skt2) == EINPROGRESS || GetLastSocketError(skt2) == EAGAIN) + goto retry_rtcp; + else + goto bail_error; + } + if (bind_socket_to_address(skt2, fromIP, port2 = gNextPort++, false) != 0) { + close_socket(skt1); + close_socket(skt2); + skt1 = INVALID_SOCKET; + skt2 = INVALID_SOCKET; + goto retry; + } + } + + make_socket_nonblocking(skt1); + theShok1->socket = skt1; + theShok1->port = port1; + theShok1->ips = NULL; + + if (withSib) { + make_socket_nonblocking(skt2); + theShok2->socket = skt2; + theShok2->port = port2; + theShok2->ips = NULL; + theShok2->sib = theShok1; + + theShok1->sib = theShok2; + theShok1->next = theShok2; + theShok2->next = gShokList; + } + else { + theShok1->sib = NULL; + theShok1->next = gShokList; + } + + add_ips_to_shok(theShok1, fromIP, toIP, withSib); + gShokList = theShok1; + + return theShok1; + +bail_error: + printf("make_new_shok bail_error\n"); + close_socket(skt1); + close_socket(skt2); + if (theShok1 != NULL) + free(theShok1); + if (theShok2 != NULL) + free(theShok2); + return NULL; +} + +/**********************************************/ +int make_udp_port_pair(int fromIP, int toIP, shok **rtpSocket, shok **rtcpSocket) +{ + shok *theShok; + + DEBUGPRINT(("MAKE_UDP_PORT_PAIR from %x to %x\n", fromIP, toIP)); + DEBUGPRINT(("looking for available shok\n")); + + if ((theShok = find_available_shok(fromIP, toIP, true)) != NULL) { + DEBUGPRINT(("found available shok : SOCKET [%d] PORT [%d]\n", theShok->socket, theShok->port)); + add_ips_to_shok(theShok, fromIP, toIP, true); + } + else { + theShok = make_new_shok(fromIP, toIP, true); + DEBUGPRINT(("couldn't find shok - made new one : SOCKET [%d] PORT [%d]\n", theShok->socket, theShok->port)); + } + + if (theShok && theShok->sib) { + *rtpSocket = theShok; + *rtcpSocket = theShok->sib; + return 1; + } + else + return -1; +} + +/**********************************************/ +int upon_receipt_from(shok *theShok, int fromIP, do_routine doThis, void *withThis) +{ + ipList *listEl; + DEBUGPRINT(( "UPON_RECEIPT_FROM %x do routine %p\n", fromIP, doThis)); + listEl = find_ip_in_list(theShok->ips, fromIP); + if (!listEl) + return -1; + listEl->what_to_do = doThis; + listEl->what_to_do_it_with = withThis; + return 0; +} + +/**********************************************/ +extern int gNeedsService; +extern unsigned long gPacketsReceived; +extern unsigned long gPacketsSent; +extern unsigned long gBytesReceived; +extern unsigned long gBytesSent; +extern float gDropPercent; +extern unsigned long gDropDelta; + + +/**********************************************/ +int service_shoks() +{ + shok *cur; + char buf[2048]; + + cur = gShokList; + while (cur) { + do_routine doit; + int ret, fromPort, fromIP; + +again: + doit = NULL; + ret = recv_udp(cur->socket, buf, 2048, &fromIP, &fromPort); + if (ret > 0) { + ipList *ipl = NULL; + + DEBUGPRINT(("Got %d bytes for %x on port %d on socket %d\n", ret, fromIP, fromPort, cur->socket)); + gBytesReceived += ret; + gPacketsReceived++; + ipl = find_ip_in_list(cur->ips, fromIP); + if (ipl) + doit = ipl->what_to_do; + if (doit && ipl) { + gNeedsService++; + ret = (*doit)(ipl->what_to_do_it_with, buf, ret); + if (ret == -1) { + // what to do about termination, etc. + DEBUGPRINT(("put returns error %d\n", errno)); + } + else if (ret == 0) { + // client/server whatever died + if (((trans_pb*)ipl->what_to_do_it_with)->status) + *(((trans_pb*)ipl->what_to_do_it_with)->status) = 1; + DEBUGPRINT(("client/server died\n")); + } + // what to do about incomplete packet transmission + } + goto again; + } + else if (ret < 0) { + if (errno != 11) + DEBUGPRINT(("recv_udp returns errno %d\n", errno)); + // what to do about termination, etc. + } + cur = cur->next; + } + return 0; +} + +/**********************************************/ +int transfer_data(void *refCon, char *buf, int bufSize) +{ + trans_pb *tpb = (trans_pb*)refCon; + int ret; + int isRTCP = 0; + if (!tpb) + return -1; + + if (strstr(tpb->socketName,"RTCP")) + { //printf("shared_udp.c transfer_data %s\n",tpb->socketName); + isRTCP = 1; + } + tpb->packetCount++; + if (gDropPercent > 0.0 ) + { int packetDropped = 0; + //printf("transfer_data tpb->nextDropPacket=%qd tpb->packetCount=%qd\n",tpb->nextDropPacket, tpb->packetCount); + if (tpb->packetCount == tpb->nextDropPacket) + { tpb->droppedPacketCount ++; + tpb->nextDropPacket = 0; + //printf("transfer_data tpb->droppedPacketCount=%qd tpb->packetCount=%qd\n",tpb->droppedPacketCount, tpb->packetCount); + packetDropped = 1; + } + + if (tpb->nextDropPacket == 0) + { + int offset = 0; + + if (gDropPercent <= 50.0) + offset = 100.0 / gDropPercent; // drop the percent packet + else if (gDropPercent < 100.0 ) + offset = 1 + ( ( (rand() % 49) <= ( gDropPercent - 47.0) ) ? 0 : 1);// drop random 1 to 2 packets 1 == next packet or 100% 2== every other 50% + else + offset = 1; // 100% = drop next packet + + tpb->nextDropPacket = tpb->packetCount + offset; + } + + if (packetDropped) + return bufSize; + + } + + ret = send_udp(tpb->send_from->socket, buf, bufSize, tpb->send_to_ip, tpb->send_to_port); + DEBUGPRINT(("Sent %d bytes to %x on port %d on socket %d\n", + ret, tpb->send_to_ip, tpb->send_to_port, tpb->send_from->socket)); + if (ret > 0) + { gBytesSent += ret; + gPacketsSent++; + } + return ret; +} + + diff --git a/StreamingProxy.tproj/shared_udp.h b/StreamingProxy.tproj/shared_udp.h new file mode 100644 index 0000000..fdb4996 --- /dev/null +++ b/StreamingProxy.tproj/shared_udp.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@ + * + */ +/* + File: shared_udp.h + Contains: udp sockets implementation with shared ports + +*/ + + +#ifndef __SHARED_UDP_H__ +#define __SHARED_UDP_H__ + +#define MAX_SOCKET_NAME 32 + +/**********************************************/ +typedef int (*do_routine)(void * refCon, char *buf, int bufSize); + +typedef struct ipList { + struct ipList *next; + int ip; + do_routine what_to_do; + void *what_to_do_it_with; +} ipList; + +typedef struct shok { + struct shok *next; + int socket; + int port; + ipList *ips; + struct shok *sib; // sibling - rtcp or rtp +} shok; + +typedef struct trans_pb { + int *status; // set to 1 when needs to die + shok *send_from; + int send_to_ip; + int send_to_port; + long long int packetSendCount; + long long int nextDropPacket; + long long int droppedPacketCount; + long long int packetCount; + char socketName[MAX_SOCKET_NAME]; + +} trans_pb; + +/**********************************************/ +ipList *find_ip_in_list(ipList *list, int ip); +int add_ip_to_list(ipList **list, int ip); +int remove_ip_from_list(ipList **list, int ip); +shok *find_available_shok(int fromIP, int toIP, int withSib); +int add_ips_to_shok(shok *theShok, int fromIP, int toIP, int withSib); +void set_udp_port_min_and_max(int min, int max); +int remove_shok(shok *theShok, int withSib); +void remove_shok_ref(shok *theShok, int fromIP, int toIP, int withSib); +shok *make_new_shok(int fromIP, int toIP, int withSib); +int make_udp_port_pair(int fromIP, int toIP, shok **rtpSocket, shok **rtcpSocket); +int upon_receipt_from(shok *theShok, int fromIP, do_routine doThis, void *withThis); +int service_shoks(); +int transfer_data(void *refCon, char *buf, int bufSize); + +#endif // __SHARED_UDP_H__ + diff --git a/StreamingProxy.tproj/util.c b/StreamingProxy.tproj/util.c new file mode 100644 index 0000000..e992cc3 --- /dev/null +++ b/StreamingProxy.tproj/util.c @@ -0,0 +1,273 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + * util.c + * + * + */ + +#include +#include +#include +#include +#include +#include + +#include "util.h" +static char to_lower(int c); + +/**********************************************/ +static char ip_string_buffer[20]; +char *ip_to_string(int ip) { + sprintf(ip_string_buffer, "%d.%d.%d.%d", + (ip & 0xff000000) >> 24, (ip & 0x00ff0000) >> 16, + (ip & 0x0000ff00) >> 8, (ip & 0x000000ff)); + return ip_string_buffer; +} + +/**********************************************/ +static char to_lower(int c) +{ + if (c >= 'A' && c <= 'Z') + return ((c - 'A') + 'a'); + return c; +} + +/**********************************************/ +char *str_dup(char *str) +{ + char *ret; + + ret = (char*)malloc(strlen(str)+1); + strcpy(ret, str); + return ret; +} + +/**********************************************/ +int str_casecmp(char *str1, char *str2) +{ + int ret; + + ret = *str1 - *str2; + while (*str1 && *str2 && ((ret = *str1++ - *str2++) == 0)) + ; + return ret; +} + +/**********************************************/ +int strn_casecmp(char *str1, char *str2, int l) +{ + int ret; + + ret = to_lower( (char) *str1) - to_lower((char) *str2); + while (l-- && to_lower(*str1) && to_lower(*str2) && ((ret = to_lower(*str1++) - to_lower(*str2++)) == 0)) + ; + return ret; +} + +/* + grab a full line from input + put into strBuff as 0 terminated + must maintain the exact eol that the + input string has. + + return next position. +*/ + +char* get_line_str( char* strBuff, char *input, int buffSize) +{ + char *p = input; + int sawEOLChar = 0; + + assert( strBuff != NULL && input != NULL && buffSize > 0); + + memset(strBuff, 0, (size_t) buffSize); + while( *p ) + { + assert( buffSize > 0 ); + + if ( *p == '\r' || *p == '\n' ) + { // grab all eol chars + sawEOLChar = 1; + *strBuff = *p; + strBuff++; + buffSize--; + + } + else if ( sawEOLChar ) + { + // we saw eol char(s) and now this is not one + // that means we're done + break; + } + else + { + // grab all line chars + *strBuff = *p; + strBuff++; + buffSize--; + + } + + p++; + } + + *strBuff = 0; + + // we're not going to change the contents + // of input, but the caller may have the right too. + return (char*)p; + +} + + +/********************************************** + + subdivide a string along the delimeter + points in delim + + + return the next start point in stringp + + return the current string start; + +*/ + +char *str_sep(char **stringp, char *delim) +{ + int j, dl, i, sl; + char *newstring, *ret; + + if (*stringp == NULL) + return NULL; + + dl = strlen(delim); + sl = strlen(*stringp); + newstring = NULL; + ret = *stringp; + + for (i=0; iname) == 0) { + *ip = cur->ip; + return 0; + } + cur = cur->next; + } + return -1; +} + +/**********************************************/ +int add_to_IP_cache(char *name, int ip) +{ + t_ip_cache *cur; + + cur = (t_ip_cache*)malloc(sizeof(t_ip_cache)); + if (cur == NULL) + return -1; + cur->ip = ip; + cur->name = malloc(strlen(name) + 1); + strcpy(cur->name, name); + cur->next = gIPcache; + gIPcache = cur; + return 0; +} + +/**********************************************/ +int inet_aton_(char *s, int *retval) +{ + int i, l, x, el[4], ret, good = 1; + + x = 0, ret = 0; + l = strlen(s); + el[0] = 0; + for (i=0; i 3) { + good = 0; + break; + } + el[x] = 0; + } + else if (s[i] >= '0' && s[i] <= '9') { + el[x] *= 10; + el[x] += s[i] - '0'; + } + else + good = 0; + } + switch (x) { + case 3: + ret = ( ((el[0] << 24) & 0xff000000) | + ((el[1] << 16) & 0x00ff0000) | + ((el[2] << 8 ) & 0x0000ff00) | + (el[3] & 0x000000ff) ); + break; + case 2: + ret = ( ((el[0] << 24) & 0xff000000) | + ((el[1] << 16) & 0x00ff0000) | + (el[2] & 0x0000ffff) ); + break; + case 1: + ret = (((el[0] << 24) & 0xff000000) | + (el[1] & 0x00ffffff) ); + break; + case 0: + ret = el[0]; + break; + } + *retval = ret; + return good; +} + diff --git a/StreamingProxy.tproj/util.h b/StreamingProxy.tproj/util.h new file mode 100644 index 0000000..7006b55 --- /dev/null +++ b/StreamingProxy.tproj/util.h @@ -0,0 +1,42 @@ +/* + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + */ + +/* + * util.h + * + * + */ + +/**********************************************/ +char *ip_to_string(int ip); +int inet_aton_(char *s, int *retval); +char *str_sep(char **stringp, char *delim); +char *str_dup(char *str); +int str_casecmp(char *str1, char *str2); +int strn_casecmp(char *str1, char *str2, int l); + +int check_IP_cache(char *name, int *ip); +int add_to_IP_cache(char *name, int ip); +char* get_line_str( char* strBuff, char *input, int buffSize ); diff --git a/defaultPaths.h b/defaultPaths.h new file mode 100644 index 0000000..23b35c9 --- /dev/null +++ b/defaultPaths.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@ + * + */ + +/* + * defaultPaths.h - define the default paths to hardcode into the executables + * + * IMPORTANT NOTE : The trailering directory separators are required on all + * DEFAULTPATHS_*_DIR* defines + * + * Contributed by: Peter Bray + */ + +#ifdef __Win32__ + +# define DEFAULTPATHS_DIRECTORY_SEPARATOR "\\" + +# define DEFAULTPATHS_ETC_DIR "c:\\Program Files\\Darwin Streaming Server\\" +# define DEFAULTPATHS_ETC_DIR_OLD "c:\\Program Files\\Darwin Streaming Server\\" +# define DEFAULTPATHS_SSM_DIR "c:\\Program Files\\Darwin Streaming Server\\QTSSModules\\" +# define DEFAULTPATHS_LOG_DIR "c:\\Program Files\\Darwin Streaming Server\\Logs\\" +# define DEFAULTPATHS_MOVIES_DIR "c:\\Program Files\\Darwin Streaming Server\\Movies\\" +# define DEFAULTPATHS_PID_FILE "" +# define DEFAULTPATHS_PID_DIR "" +#elif __MacOSX__ + +# define DEFAULTPATHS_DIRECTORY_SEPARATOR "/" + +# define DEFAULTPATHS_ETC_DIR "/Library/QuickTimeStreaming/Config/" +# define DEFAULTPATHS_ETC_DIR_OLD "/etc/" +# define DEFAULTPATHS_SSM_DIR "/Library/QuickTimeStreaming/Modules/" +# define DEFAULTPATHS_LOG_DIR "/Library/QuickTimeStreaming/Logs/" +# define DEFAULTPATHS_MOVIES_DIR "/Library/QuickTimeStreaming/Movies/" +# define DEFAULTPATHS_PID_DIR "/var/run/" + +#else + +# define DEFAULTPATHS_DIRECTORY_SEPARATOR "/" + +# define DEFAULTPATHS_ETC_DIR "/etc/streaming/" +# define DEFAULTPATHS_ETC_DIR_OLD "/etc/" +# define DEFAULTPATHS_SSM_DIR "/usr/local/sbin/StreamingServerModules/" +# define DEFAULTPATHS_LOG_DIR "/var/streaming/logs/" +# define DEFAULTPATHS_MOVIES_DIR "/usr/local/movies/" +# define DEFAULTPATHS_PID_DIR "/var/run/" + +#endif diff --git a/revision.h b/revision.h new file mode 100644 index 0000000..81349a3 --- /dev/null +++ b/revision.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@ + * + */ +/* revision.h -- define the version number + +*/ + +// Use no http/rtsp tspecial chars in kVersionString and kBuildString defines +// tspecials = ()<>@,;:\/"[]?= + +#define kVersionString "6.0.3" +#define kBuildString "526.3" + +// Use kCommentString for seed or other release info +// Do not use '(' or ')' in the kCommentString +// form = token1/info; token2/info; +// example "Release/public seed 1; Event/Big Event; state/half-baked" + +#define kCommentString "Release/Darwin Streaming Server; State/Development; " + diff --git a/sample.mp3 b/sample.mp3 new file mode 100644 index 0000000..53da89c Binary files /dev/null and b/sample.mp3 differ diff --git a/sample_100kbit.mov b/sample_100kbit.mov new file mode 100644 index 0000000..d7de342 Binary files /dev/null and b/sample_100kbit.mov differ diff --git a/sample_100kbit.mp4 b/sample_100kbit.mp4 new file mode 100644 index 0000000..0b6224a Binary files /dev/null and b/sample_100kbit.mp4 differ diff --git a/sample_300kbit.mov b/sample_300kbit.mov new file mode 100644 index 0000000..7b4cf91 Binary files /dev/null and b/sample_300kbit.mov differ diff --git a/sample_300kbit.mp4 b/sample_300kbit.mp4 new file mode 100644 index 0000000..05d289c Binary files /dev/null and b/sample_300kbit.mp4 differ diff --git a/sample_h264_100kbit.mp4 b/sample_h264_100kbit.mp4 new file mode 100644 index 0000000..012f2ba Binary files /dev/null and b/sample_h264_100kbit.mp4 differ diff --git a/sample_h264_1mbit.mp4 b/sample_h264_1mbit.mp4 new file mode 100644 index 0000000..d72a571 Binary files /dev/null and b/sample_h264_1mbit.mp4 differ diff --git a/sample_h264_300kbit.mp4 b/sample_h264_300kbit.mp4 new file mode 100644 index 0000000..d2af5e7 Binary files /dev/null and b/sample_h264_300kbit.mp4 differ