Very rough first start, not even everything added
This commit is contained in:
commit
af3619d4fa
88 changed files with 24251 additions and 0 deletions
681
APICommonCode/QTAccessFile.cpp
Normal file
681
APICommonCode/QTAccessFile.cpp
Normal file
|
@ -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 <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#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 <grp.h>
|
||||
#if __MacOSX__
|
||||
#include <membership.h>
|
||||
#endif
|
||||
#include <pwd.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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("<Limit") ) // a limit line
|
||||
{
|
||||
currentFlags = qtssActionFlagsNoFlags; // clear to no access
|
||||
lineParser.ConsumeWhitespace();
|
||||
lineParser.ConsumeUntil( &word, sWhitespaceAndGreaterThanMask); // the flag <limit Read> or <limit Read >
|
||||
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("</Limit>") )
|
||||
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("<Limit") ) // a limit line
|
||||
{
|
||||
currentFlags = qtssActionFlagsNoFlags; // clear to no access
|
||||
lineParser.ConsumeWhitespace();
|
||||
lineParser.ConsumeUntil( &word, sWhitespaceAndGreaterThanMask); // the flag <limit Read> or <limit Read >
|
||||
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("</Limit>") )
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
117
APICommonCode/QTAccessFile.h
Normal file
117
APICommonCode/QTAccessFile.h
Normal file
|
@ -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 <stdlib.h>
|
||||
#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_
|
||||
|
128
APICommonCode/QTSS3GPPModuleUtils.cpp
Normal file
128
APICommonCode/QTSS3GPPModuleUtils.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
|
74
APICommonCode/QTSS3GPPModuleUtils.h
Normal file
74
APICommonCode/QTSS3GPPModuleUtils.h
Normal file
|
@ -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 <stdlib.h>
|
||||
#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_
|
66
APICommonCode/QTSSMemoryDeleter.h
Normal file
66
APICommonCode/QTSSMemoryDeleter.h
Normal file
|
@ -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 T>
|
||||
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<char> QTSSCharArrayDeleter;
|
||||
|
||||
#endif //__QTSS_MEMORY_DELETER_H__
|
||||
|
||||
|
1132
APICommonCode/QTSSModuleUtils.cpp
Normal file
1132
APICommonCode/QTSSModuleUtils.cpp
Normal file
File diff suppressed because it is too large
Load diff
291
APICommonCode/QTSSModuleUtils.h
Normal file
291
APICommonCode/QTSSModuleUtils.h
Normal file
|
@ -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 <stdlib.h>
|
||||
#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_
|
550
APICommonCode/QTSSRollingLog.cpp
Normal file
550
APICommonCode/QTSSRollingLog.cpp
Normal file
|
@ -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 <time.h>
|
||||
#include <stdlib.h>
|
||||
#include "SafeStdLib.h"
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#ifndef __Win32__
|
||||
#include <sys/time.h>
|
||||
#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);
|
||||
|
||||
}
|
142
APICommonCode/QTSSRollingLog.h
Normal file
142
APICommonCode/QTSSRollingLog.h
Normal file
|
@ -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 <stdio.h>
|
||||
#include <time.h>
|
||||
#ifndef __Win32__
|
||||
#include <sys/time.h>
|
||||
#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__
|
||||
|
437
APICommonCode/SDPSourceInfo.cpp
Normal file
437
APICommonCode/SDPSourceInfo.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
|
77
APICommonCode/SDPSourceInfo.h
Normal file
77
APICommonCode/SDPSourceInfo.h
Normal file
|
@ -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__
|
||||
|
329
APICommonCode/SourceInfo.cpp
Normal file
329
APICommonCode/SourceInfo.cpp
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
|
174
APICommonCode/SourceInfo.h
Normal file
174
APICommonCode/SourceInfo.h
Normal file
|
@ -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__
|
Loading…
Add table
Add a link
Reference in a new issue