/*
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
    File:       QTSSReflectorModule.cpp

    Contains:   Implementation of QTSSReflectorModule class. 
                    
    
    
*/

#include "QTSSReflectorModule.h"
#include "QTSSModuleUtils.h"
#include "ReflectorSession.h"
#include "OSArrayObjectDeleter.h"
#include "QTSS_Private.h"
#include "QTSSMemoryDeleter.h"
#include "OSMemory.h"
#include "OSRef.h"
#include "IdleTask.h"
#include "Task.h"
#include "OS.h"
#include "Socket.h"
#include "SocketUtils.h"
#include "FilePrefsSource.h"
#include "ResizeableStringFormatter.h"
#include "StringParser.h"
#include "QTAccessFile.h"
#include "QTSSModuleUtils.h"

//ReflectorOutput objects
#include "RTPSessionOutput.h"

//SourceInfo objects
#include "SDPSourceInfo.h"

#include "SDPUtils.h"

#ifndef __Win32__
    #include <unistd.h>
#endif

#if DEBUG
#define REFLECTOR_MODULE_DEBUGGING 0
#else
#define REFLECTOR_MODULE_DEBUGGING 0
#endif

// ATTRIBUTES
static QTSS_AttributeID         sOutputAttr                 =   qtssIllegalAttrID;
static QTSS_AttributeID         sSessionAttr                =   qtssIllegalAttrID;
static QTSS_AttributeID         sStreamCookieAttr           =   qtssIllegalAttrID;
static QTSS_AttributeID         sRequestBodyAttr            =   qtssIllegalAttrID;
static QTSS_AttributeID         sBufferOffsetAttr           =   qtssIllegalAttrID;
static QTSS_AttributeID         sExpectedDigitFilenameErr   =   qtssIllegalAttrID;
static QTSS_AttributeID         sReflectorBadTrackIDErr     =   qtssIllegalAttrID;
static QTSS_AttributeID         sDuplicateBroadcastStreamErr=   qtssIllegalAttrID;
static QTSS_AttributeID         sClientBroadcastSessionAttr =   qtssIllegalAttrID;
static QTSS_AttributeID         sRTSPBroadcastSessionAttr   =   qtssIllegalAttrID;
static QTSS_AttributeID         sAnnounceRequiresSDPinNameErr  = qtssIllegalAttrID;
static QTSS_AttributeID         sAnnounceDisabledNameErr    = qtssIllegalAttrID;
static QTSS_AttributeID         sSDPcontainsInvalidMinimumPortErr  = qtssIllegalAttrID;
static QTSS_AttributeID         sSDPcontainsInvalidMaximumPortErr  = qtssIllegalAttrID;
static QTSS_AttributeID         sStaticPortsConflictErr  = qtssIllegalAttrID;
static QTSS_AttributeID         sInvalidPortRangeErr  = qtssIllegalAttrID;

static QTSS_AttributeID         sKillClientsEnabledAttr  = qtssIllegalAttrID;
static QTSS_AttributeID         sRTPInfoWaitTimeAttr  =   qtssIllegalAttrID;

// STATIC DATA

// ref to the prefs dictionary object
static OSRefTable*      sSessionMap = NULL;
static const StrPtrLen  kCacheControlHeader("no-cache");
static QTSS_PrefsObject sServerPrefs = NULL;
static QTSS_ServerObject sServer = NULL;
static QTSS_ModulePrefsObject       sPrefs = NULL;

//
// Prefs
static Bool16   sAllowNonSDPURLs = true;
static Bool16   sDefaultAllowNonSDPURLs = true;

static Bool16   sRTPInfoDisabled = false;
static Bool16   sDefaultRTPInfoDisabled = false;

static Bool16   sAnnounceEnabled = true;
static Bool16   sDefaultAnnounceEnabled = true;
static Bool16   sBroadcastPushEnabled = true;
static Bool16   sDefaultBroadcastPushEnabled = true;
static Bool16   sAllowDuplicateBroadcasts = false;
static Bool16   sDefaultAllowDuplicateBroadcasts = false;

static UInt32   sMaxBroadcastAnnounceDuration = 0;
static UInt32   sDefaultMaxBroadcastAnnounceDuration = 0;
static UInt16   sMinimumStaticSDPPort = 0;
static UInt16   sDefaultMinimumStaticSDPPort = 20000;
static UInt16   sMaximumStaticSDPPort = 0;
static UInt16   sDefaultMaximumStaticSDPPort = 65535;

static Bool16   sTearDownClientsOnDisconnect = false;
static Bool16   sDefaultTearDownClientsOnDisconnect = false;

static Bool16   sOneSSRCPerStream = true;
static Bool16   sDefaultOneSSRCPerStream = true;
                
static UInt32   sTimeoutSSRCSecs = 30;
static UInt32   sDefaultTimeoutSSRCSecs = 30;

static UInt32   sBroadcasterSessionTimeoutSecs = 20;
static UInt32   sDefaultBroadcasterSessionTimeoutSecs = 20;
static UInt32   sBroadcasterSessionTimeoutMilliSecs = sBroadcasterSessionTimeoutSecs * 1000;
                                
static UInt16 sLastMax = 0;
static UInt16 sLastMin = 0;

static Bool16   sEnforceStaticSDPPortRange = false;
static Bool16   sDefaultEnforceStaticSDPPortRange = false;

static UInt32   sMaxAnnouncedSDPLengthInKbytes = 4;
//static UInt32   sDefaultMaxAnnouncedSDPLengthInKbytes = 4;

static QTSS_AttributeID sIPAllowListID = qtssIllegalAttrID;
static char*            sIPAllowList = NULL;
static char*            sLocalLoopBackAddress = "127.0.0.*";

static Bool16   sAuthenticateLocalBroadcast = false;
static Bool16   sDefaultAuthenticateLocalBroadcast = false;

static Bool16	sDisableOverbuffering = false;
static Bool16	sDefaultDisableOverbuffering = false;
static Bool16	sFalse = false;

static Bool16   sReflectBroadcasts = true;
static Bool16   sDefaultReflectBroadcasts = true;

static Bool16   sAnnouncedKill = true;
static Bool16   sDefaultAnnouncedKill = true;

static Bool16   sRTPInfoFullURL = true;
static Bool16   sDefaultRTPInfoFullURL = true;

static Bool16   sPlayResponseRangeHeader = true;
static Bool16   sDefaultPlayResponseRangeHeader = true;

static Bool16   sPlayerCompatibility = true;
static Bool16   sDefaultPlayerCompatibility = true;

static UInt32   sAdjustMediaBandwidthPercent = 50;
static UInt32   sAdjustMediaBandwidthPercentDefault = 50;

static Bool16   sForceRTPInfoSeqAndTime = false;
static Bool16   sDefaultForceRTPInfoSeqAndTime = false;

static char*	sRedirectBroadcastsKeyword = NULL;
static char*    sDefaultRedirectBroadcastsKeyword = "";
static char*    sBroadcastsRedirectDir = NULL;
static char*    sDefaultBroadcastsRedirectDir = ""; // match none
static char*    sDefaultBroadcastsDir = ""; // match all
static char*	sDefaultsBroadcasterGroup = "broadcaster";
static StrPtrLen sBroadcasterGroup;

static QTSS_AttributeID sBroadcastDirListID = qtssIllegalAttrID;
                                
static SInt32   sWaitTimeLoopCount = 10;  

// Important strings
static StrPtrLen    sSDPKillSuffix(".kill");
static StrPtrLen    sSDPSuffix(".sdp");
static StrPtrLen    sMOVSuffix(".mov");
static StrPtrLen    sSDPTooLongMessage("Announced SDP is too long");
static StrPtrLen    sSDPNotValidMessage("Announced SDP is not a valid SDP");
static StrPtrLen    sKILLNotValidMessage("Announced .kill is not a valid SDP");
static StrPtrLen    sSDPTimeNotValidMessage("SDP time is not valid or movie not available at this time.");
static StrPtrLen    sBroadcastNotAllowed("Broadcast is not allowed.");
static StrPtrLen    sBroadcastNotActive("Broadcast is not active.");
static StrPtrLen    sTheNowRangeHeader("npt=now-");

const int kBuffLen = 512;

// FUNCTION PROTOTYPES

static QTSS_Error QTSSReflectorModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams);
static QTSS_Error Register(QTSS_Register_Params* inParams);
static QTSS_Error Initialize(QTSS_Initialize_Params* inParams);
static QTSS_Error Shutdown();
static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams);
static QTSS_Error DoAnnounce(QTSS_StandardRTSP_Params* inParams);
static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams);
ReflectorSession* FindOrCreateSession(StrPtrLen* inPath, QTSS_StandardRTSP_Params* inParams, StrPtrLen* inData = NULL,Bool16 isPush=false, Bool16 *foundSessionPtr = NULL);
static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams);
static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession);
static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams);
static void RemoveOutput(ReflectorOutput* inOutput, ReflectorSession* inSession, Bool16 killClients);
static ReflectorSession* DoSessionSetup(QTSS_StandardRTSP_Params* inParams, QTSS_AttributeID inPathType,Bool16 isPush=false,Bool16 *foundSessionPtr= NULL, char** resultFilePath = NULL);
static QTSS_Error RereadPrefs();
static QTSS_Error ProcessRTPData(QTSS_IncomingData_Params* inParams);
static QTSS_Error ReflectorAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams);
static Bool16 InfoPortsOK(QTSS_StandardRTSP_Params* inParams, SDPSourceInfo* theInfo, StrPtrLen* inPath);
void KillCommandPathInList();
Bool16 KillSession(StrPtrLen *sdpPath, Bool16 killClients);
QTSS_Error IntervalRole();
static Bool16 AcceptSession(QTSS_StandardRTSP_Params* inParams);
static QTSS_Error RedirectBroadcast(QTSS_StandardRTSP_Params* inParams);
static Bool16 AllowBroadcast(QTSS_RTSPRequestObject inRTSPRequest);
static Bool16 InBroadcastDirList(QTSS_RTSPRequestObject inRTSPRequest);
static Bool16 IsAbsolutePath(StrPtrLen *inPathPtr);

inline void KeepSession(QTSS_RTSPRequestObject theRequest,Bool16 keep)
{
    (void)QTSS_SetValue(theRequest, qtssRTSPReqRespKeepAlive, 0, &keep, sizeof(keep));
}

    

// FUNCTION IMPLEMENTATIONS

QTSS_Error QTSSReflectorModule_Main(void* inPrivateArgs)
{
    return _stublibrary_main(inPrivateArgs, QTSSReflectorModuleDispatch);
}


QTSS_Error  QTSSReflectorModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams)
{
    switch (inRole)
    {
        case QTSS_Register_Role:
            return Register(&inParams->regParams);
        case QTSS_Initialize_Role:
            return Initialize(&inParams->initParams);
        case QTSS_RereadPrefs_Role:
            return RereadPrefs();
        case QTSS_RTSPRoute_Role:
            return RedirectBroadcast(&inParams->rtspRouteParams);
        case QTSS_RTSPPreProcessor_Role:
            return ProcessRTSPRequest(&inParams->rtspRequestParams);
        case QTSS_RTSPIncomingData_Role:
            return ProcessRTPData(&inParams->rtspIncomingDataParams);
        case QTSS_ClientSessionClosing_Role:
            return DestroySession(&inParams->clientSessionClosingParams);
        case QTSS_Shutdown_Role:
            return Shutdown();
        case QTSS_RTSPAuthorize_Role:
            return ReflectorAuthorizeRTSPRequest(&inParams->rtspRequestParams);
        case QTSS_Interval_Role:
            return IntervalRole();
   }
    return QTSS_NoErr;
}


QTSS_Error Register(QTSS_Register_Params* inParams)
{
    // Do role & attribute setup
    (void)QTSS_AddRole(QTSS_Initialize_Role);
    (void)QTSS_AddRole(QTSS_Shutdown_Role);
    (void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role);
    (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role);
    (void)QTSS_AddRole(QTSS_RTSPIncomingData_Role); // call me with interleaved RTP streams on the RTSP session
    (void)QTSS_AddRole(QTSS_RTSPAuthorize_Role);
    (void)QTSS_AddRole(QTSS_RereadPrefs_Role);
    (void)QTSS_AddRole(QTSS_RTSPRoute_Role);
    
    
    // Add text messages attributes
    static char*        sExpectedDigitFilenameName      = "QTSSReflectorModuleExpectedDigitFilename";
    static char*        sReflectorBadTrackIDErrName     = "QTSSReflectorModuleBadTrackID";
    static char*        sDuplicateBroadcastStreamName   = "QTSSReflectorModuleDuplicateBroadcastStream";
    static char*        sAnnounceRequiresSDPinName      = "QTSSReflectorModuleAnnounceRequiresSDPSuffix";
    static char*        sAnnounceDisabledName           = "QTSSReflectorModuleAnnounceDisabled";
    
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sDuplicateBroadcastStreamName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sDuplicateBroadcastStreamName, &sDuplicateBroadcastStreamErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sAnnounceRequiresSDPinName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sAnnounceRequiresSDPinName, &sAnnounceRequiresSDPinNameErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sAnnounceDisabledName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sAnnounceDisabledName, &sAnnounceDisabledNameErr);


    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr);

    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sReflectorBadTrackIDErrName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sReflectorBadTrackIDErrName, &sReflectorBadTrackIDErr);
    
    static char* sSDPcontainsInvalidMinumumPortErrName  = "QTSSReflectorModuleSDPPortMinimumPort";
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSDPcontainsInvalidMinumumPortErrName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSDPcontainsInvalidMinumumPortErrName, &sSDPcontainsInvalidMinimumPortErr);

    static char* sSDPcontainsInvalidMaximumPortErrName  = "QTSSReflectorModuleSDPPortMaximumPort";
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSDPcontainsInvalidMaximumPortErrName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSDPcontainsInvalidMaximumPortErrName, &sSDPcontainsInvalidMaximumPortErr);
    
    static char* sStaticPortsConflictErrName    = "QTSSReflectorModuleStaticPortsConflict";
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sStaticPortsConflictErrName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sStaticPortsConflictErrName, &sStaticPortsConflictErr);

    static char* sInvalidPortRangeErrName   = "QTSSReflectorModuleStaticPortPrefsBadRange";
    (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sInvalidPortRangeErrName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sInvalidPortRangeErrName, &sInvalidPortRangeErr);
    
    
    // Add an RTP session attribute for tracking ReflectorSession objects
    static char*        sOutputName         = "QTSSReflectorModuleOutput";
    static char*        sSessionName        = "QTSSReflectorModuleSession";
    static char*        sStreamCookieName   = "QTSSReflectorModuleStreamCookie";
    static char*        sRequestBufferName  = "QTSSReflectorModuleRequestBuffer";
    static char*        sRequestBufferLenName= "QTSSReflectorModuleRequestBufferLen";
    static char*        sBroadcasterSessionName= "QTSSReflectorModuleBroadcasterSession";
    static char*        sKillClientsEnabledName= "QTSSReflectorModuleTearDownClients";

    static char*        sRTPInfoWaitTime         = "QTSSReflectorModuleRTPInfoWaitTime";
    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sRTPInfoWaitTime, NULL, qtssAttrDataTypeSInt32);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sRTPInfoWaitTime, &sRTPInfoWaitTimeAttr);

    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sOutputName, NULL, qtssAttrDataTypeVoidPointer);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sOutputName, &sOutputAttr);

    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sSessionName, NULL, qtssAttrDataTypeVoidPointer);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sSessionName, &sSessionAttr);

    (void)QTSS_AddStaticAttribute(qtssRTPStreamObjectType, sStreamCookieName, NULL, qtssAttrDataTypeVoidPointer);
    (void)QTSS_IDForAttr(qtssRTPStreamObjectType, sStreamCookieName, &sStreamCookieAttr);

    (void)QTSS_AddStaticAttribute(qtssRTSPRequestObjectType, sRequestBufferName, NULL, qtssAttrDataTypeVoidPointer);
    (void)QTSS_IDForAttr(qtssRTSPRequestObjectType, sRequestBufferName, &sRequestBodyAttr);

    (void)QTSS_AddStaticAttribute(qtssRTSPRequestObjectType, sRequestBufferLenName, NULL, qtssAttrDataTypeUInt32);
    (void)QTSS_IDForAttr(qtssRTSPRequestObjectType, sRequestBufferLenName, &sBufferOffsetAttr);
    
    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sBroadcasterSessionName, NULL, qtssAttrDataTypeVoidPointer);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sBroadcasterSessionName, &sClientBroadcastSessionAttr);
    
    (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sKillClientsEnabledName, NULL, qtssAttrDataTypeBool16);
    (void)QTSS_IDForAttr(qtssClientSessionObjectType, sKillClientsEnabledName, &sKillClientsEnabledAttr);
 
     // keep the same attribute name for the RTSPSessionObject as used int he ClientSessionObject
    (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sBroadcasterSessionName, NULL, qtssAttrDataTypeVoidPointer);
    (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sBroadcasterSessionName, &sRTSPBroadcastSessionAttr);

    // Reflector session needs to setup some parameters too.
    ReflectorStream::Register();
    // RTPSessionOutput needs to do the same
    RTPSessionOutput::Register();

    // Tell the server our name!
    static char* sModuleName = "QTSSReflectorModule";
    ::strcpy(inParams->outModuleName, sModuleName);

    return QTSS_NoErr;
}


QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
{
    // Setup module utils
    QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);
    QTAccessFile::Initialize();
    sSessionMap = NEW OSRefTable();
    sServerPrefs = inParams->inPrefs;
    sServer = inParams->inServer;
#if QTSS_REFLECTOR_EXTERNAL_MODULE
    // The reflector is dependent on a number of objects in the Common Utilities
    // library that get setup by the server if the reflector is internal to the
    // server.
    //
    // So, if the reflector is being built as a code fragment, it must initialize
    // those pieces itself
#if !MACOSXEVENTQUEUE
    ::select_startevents();//initialize the select() implementation of the event queue
#endif
    OS::Initialize();
    Socket::Initialize();
    SocketUtils::Initialize();

    const UInt32 kNumReflectorThreads = 8;
    TaskThreadPool::AddThreads(kNumReflectorThreads);
    IdleTask::Initialize();
    Socket::StartThread();
#endif
    
    sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule);

    // Call helper class initializers
    ReflectorStream::Initialize(sPrefs);
    ReflectorSession::Initialize();
    
    // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN
	static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod, qtssAnnounceMethod, qtssRecordMethod };
	QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 7);

    RereadPrefs();
    
   return QTSS_NoErr;
}

char *GetTrimmedKeyWord(char *prefKeyWord)
{
    StrPtrLen redirKeyWordStr(prefKeyWord);
	StringParser theRequestPathParser(&redirKeyWordStr);
	
	// trim leading / from the keyword
	while(theRequestPathParser.Expect(kPathDelimiterChar)) {};

	StrPtrLen theKeyWordStr;
	theRequestPathParser.ConsumeUntil(&theKeyWordStr, kPathDelimiterChar); // stop when we see a / and don't include

   char *keyword = NEW char[theKeyWordStr.Len +1];
    ::memcpy(keyword, theKeyWordStr.Ptr, theKeyWordStr.Len);
    keyword[theKeyWordStr.Len] = 0;
   
   return keyword;
}

void SetMoviesRelativeDir()
{
    char* movieFolderString = NULL;
    (void) QTSS_GetValueAsString (sServerPrefs, qtssPrefsMovieFolder, 0, &movieFolderString);
    OSCharArrayDeleter deleter(movieFolderString);
    
    ResizeableStringFormatter redirectPath(NULL,0);
    redirectPath.Put(movieFolderString);
    if (redirectPath.GetBytesWritten() > 0 && kPathDelimiterChar != redirectPath.GetBufPtr()[redirectPath.GetBytesWritten() -1])
        redirectPath.PutChar(kPathDelimiterChar);
    redirectPath.Put(sBroadcastsRedirectDir);
    
    char *newMovieRelativeDir = NEW char[redirectPath.GetBytesWritten() +1];
    ::memcpy(newMovieRelativeDir, redirectPath.GetBufPtr(), redirectPath.GetBytesWritten());
    newMovieRelativeDir[redirectPath.GetBytesWritten()] = 0;
    
    delete [] sBroadcastsRedirectDir;
    sBroadcastsRedirectDir = newMovieRelativeDir;
        
}

QTSS_Error RereadPrefs()
{
    //
    // Use the standard GetPref routine to retrieve the correct values for our preferences
    QTSSModuleUtils::GetAttribute(sPrefs, "disable_rtp_play_info",  qtssAttrDataTypeBool16,
                                &sRTPInfoDisabled, &sDefaultRTPInfoDisabled, sizeof(sDefaultRTPInfoDisabled));

    QTSSModuleUtils::GetAttribute(sPrefs, "allow_non_sdp_urls",     qtssAttrDataTypeBool16,
                                &sAllowNonSDPURLs, &sDefaultAllowNonSDPURLs, sizeof(sDefaultAllowNonSDPURLs));
                                                                
    QTSSModuleUtils::GetAttribute(sPrefs, "enable_broadcast_announce",  qtssAttrDataTypeBool16,
                                &sAnnounceEnabled, &sDefaultAnnounceEnabled, sizeof(sDefaultAnnounceEnabled));
    QTSSModuleUtils::GetAttribute(sPrefs, "enable_broadcast_push",  qtssAttrDataTypeBool16,
                                &sBroadcastPushEnabled, &sDefaultBroadcastPushEnabled, sizeof(sDefaultBroadcastPushEnabled));
    QTSSModuleUtils::GetAttribute(sPrefs, "max_broadcast_announce_duration_secs",   qtssAttrDataTypeUInt32,
                                &sMaxBroadcastAnnounceDuration, &sDefaultMaxBroadcastAnnounceDuration, sizeof(sDefaultMaxBroadcastAnnounceDuration));
    QTSSModuleUtils::GetAttribute(sPrefs, "allow_duplicate_broadcasts",     qtssAttrDataTypeBool16,
                                &sAllowDuplicateBroadcasts, &sDefaultAllowDuplicateBroadcasts, sizeof(sDefaultAllowDuplicateBroadcasts));
                                
    QTSSModuleUtils::GetAttribute(sPrefs, "enforce_static_sdp_port_range",  qtssAttrDataTypeBool16,
                                &sEnforceStaticSDPPortRange, &sDefaultEnforceStaticSDPPortRange, sizeof(sDefaultEnforceStaticSDPPortRange));
    QTSSModuleUtils::GetAttribute(sPrefs, "minimum_static_sdp_port",    qtssAttrDataTypeUInt16,
                                &sMinimumStaticSDPPort, &sDefaultMinimumStaticSDPPort, sizeof(sDefaultMinimumStaticSDPPort));
    QTSSModuleUtils::GetAttribute(sPrefs, "maximum_static_sdp_port",    qtssAttrDataTypeUInt16,
                                &sMaximumStaticSDPPort, &sDefaultMaximumStaticSDPPort, sizeof(sDefaultMaximumStaticSDPPort));
    
    QTSSModuleUtils::GetAttribute(sPrefs, "kill_clients_when_broadcast_stops",  qtssAttrDataTypeBool16,
                                &sTearDownClientsOnDisconnect, &sDefaultTearDownClientsOnDisconnect, sizeof(sDefaultTearDownClientsOnDisconnect));
    QTSSModuleUtils::GetAttribute(sPrefs, "use_one_SSRC_per_stream",    qtssAttrDataTypeBool16,
                                &sOneSSRCPerStream, &sDefaultOneSSRCPerStream, sizeof(sDefaultOneSSRCPerStream));
    QTSSModuleUtils::GetAttribute(sPrefs, "timeout_stream_SSRC_secs",   qtssAttrDataTypeUInt32,
                                &sTimeoutSSRCSecs, &sDefaultTimeoutSSRCSecs, sizeof(sDefaultTimeoutSSRCSecs));

    QTSSModuleUtils::GetAttribute(sPrefs, "timeout_broadcaster_session_secs",   qtssAttrDataTypeUInt32,
                                &sBroadcasterSessionTimeoutSecs, &sDefaultBroadcasterSessionTimeoutSecs, sizeof(sDefaultTimeoutSSRCSecs));

    QTSSModuleUtils::GetAttribute(sPrefs, "authenticate_local_broadcast",   qtssAttrDataTypeBool16,
                                &sAuthenticateLocalBroadcast, &sDefaultAuthenticateLocalBroadcast, sizeof(sDefaultAuthenticateLocalBroadcast));
    
	QTSSModuleUtils::GetAttribute(sPrefs, "disable_overbuffering", 	qtssAttrDataTypeBool16,
								&sDisableOverbuffering, &sDefaultDisableOverbuffering, sizeof(sDefaultDisableOverbuffering));

    QTSSModuleUtils::GetAttribute(sPrefs, "allow_broadcasts",   qtssAttrDataTypeBool16,
                                &sReflectBroadcasts, &sDefaultReflectBroadcasts, sizeof(sDefaultReflectBroadcasts));
    
	QTSSModuleUtils::GetAttribute(sPrefs, "allow_announced_kill", 	qtssAttrDataTypeBool16,
								&sAnnouncedKill, &sDefaultAnnouncedKill, sizeof(sDefaultAnnouncedKill));

	QTSSModuleUtils::GetAttribute(sPrefs, "force_rtp_info_full_url", 	qtssAttrDataTypeBool16,
								&sRTPInfoFullURL, &sDefaultRTPInfoFullURL, sizeof(sDefaultRTPInfoFullURL));

	QTSSModuleUtils::GetAttribute(sPrefs, "enable_play_response_range_header", 	qtssAttrDataTypeBool16,
								&sPlayResponseRangeHeader, &sDefaultPlayResponseRangeHeader, sizeof(sDefaultPlayResponseRangeHeader));

    QTSSModuleUtils::GetAttribute(sPrefs, "enable_player_compatibility",   qtssAttrDataTypeBool16,
                                &sPlayerCompatibility, &sDefaultPlayerCompatibility, sizeof(sDefaultPlayerCompatibility));

    QTSSModuleUtils::GetAttribute(sPrefs, "compatibility_adjust_sdp_media_bandwidth_percent",   qtssAttrDataTypeUInt32,
                                &sAdjustMediaBandwidthPercent, &sAdjustMediaBandwidthPercentDefault, sizeof(sAdjustMediaBandwidthPercentDefault));
                                
    if (sAdjustMediaBandwidthPercent > 100)
        sAdjustMediaBandwidthPercent = 100;
        
    if (sAdjustMediaBandwidthPercent < 1)
        sAdjustMediaBandwidthPercent = 1;

    QTSSModuleUtils::GetAttribute(sPrefs, "force_rtp_info_sequence_and_time",   qtssAttrDataTypeBool16,
                                &sForceRTPInfoSeqAndTime, &sDefaultForceRTPInfoSeqAndTime, sizeof(sDefaultForceRTPInfoSeqAndTime));

    sBroadcasterGroup.Delete();
    sBroadcasterGroup.Set(QTSSModuleUtils::GetStringAttribute(sPrefs, "BroadcasterGroup", sDefaultsBroadcasterGroup)); 

    delete [] sRedirectBroadcastsKeyword;
    char* tempKeyWord = QTSSModuleUtils::GetStringAttribute(sPrefs, "redirect_broadcast_keyword", sDefaultRedirectBroadcastsKeyword); 

    sRedirectBroadcastsKeyword = GetTrimmedKeyWord(tempKeyWord);
    delete [] tempKeyWord;
    
    delete [] sBroadcastsRedirectDir;
    sBroadcastsRedirectDir = QTSSModuleUtils::GetStringAttribute(sPrefs, "redirect_broadcasts_dir", sDefaultBroadcastsRedirectDir);                           
    if (sBroadcastsRedirectDir && sBroadcastsRedirectDir[0] != kPathDelimiterChar)
        SetMoviesRelativeDir();   

        
    delete [] QTSSModuleUtils::GetStringAttribute(sPrefs, "broadcast_dir_list", sDefaultBroadcastsDir); // initialize if there isn't one
    sBroadcastDirListID = QTSSModuleUtils::GetAttrID(sPrefs, "broadcast_dir_list");
 
    delete [] sIPAllowList;
    sIPAllowList = QTSSModuleUtils::GetStringAttribute(sPrefs, "ip_allow_list", sLocalLoopBackAddress);
    sIPAllowListID = QTSSModuleUtils::GetAttrID(sPrefs, "ip_allow_list");


    sBroadcasterSessionTimeoutMilliSecs = sBroadcasterSessionTimeoutSecs * 1000;
    


    if (sEnforceStaticSDPPortRange)
    {   Bool16 reportErrors = false;
        if (sLastMax != sMaximumStaticSDPPort)
        {   sLastMax = sMaximumStaticSDPPort;
            reportErrors = true;
        }
        
        if (sLastMin != sMinimumStaticSDPPort)
        {   sLastMin = sMinimumStaticSDPPort;
            reportErrors = true;
        }

        if (reportErrors)
        {
            UInt16 minServerPort = 6970;
            UInt16 maxServerPort = 9999;
            char min[32];
            char max[32];
                    
            if  (   ( (sMinimumStaticSDPPort <= minServerPort) && (sMaximumStaticSDPPort >= minServerPort) )
                ||  ( (sMinimumStaticSDPPort >= minServerPort) && (sMinimumStaticSDPPort <= maxServerPort) )
                )
            {
                qtss_sprintf(min,"%u",minServerPort);
                qtss_sprintf(max,"%u",maxServerPort);    
                QTSSModuleUtils::LogError(  qtssWarningVerbosity, sStaticPortsConflictErr, 0, min, max);
            }
            
            if  (sMinimumStaticSDPPort > sMaximumStaticSDPPort) 
            { 
                qtss_sprintf(min,"%u",sMinimumStaticSDPPort);
                qtss_sprintf(max,"%u",sMaximumStaticSDPPort);    
                QTSSModuleUtils::LogError(  qtssWarningVerbosity, sInvalidPortRangeErr, 0, min, max);
            }
        }
    }    

    KillCommandPathInList();
    
                        
    return QTSS_NoErr;
}

QTSS_Error Shutdown()
{
#if QTSS_REFLECTOR_EXTERNAL_MODULE
    TaskThreadPool::RemoveThreads();
#endif
    return QTSS_NoErr;
}

QTSS_Error IntervalRole() // not used
{   
    (void) QTSS_SetIntervalRoleTimer(0); // turn off
    
    return QTSS_NoErr;
}


QTSS_Error ProcessRTPData(QTSS_IncomingData_Params* inParams)
{
    if (!sBroadcastPushEnabled)
        return QTSS_NoErr;
        
    //qtss_printf("QTSSReflectorModule:ProcessRTPData inRTSPSession=%lu inClientSession=%lu\n",inParams->inRTSPSession, inParams->inClientSession);
    ReflectorSession* theSession = NULL;
    UInt32 theLen = sizeof(theSession);
    QTSS_Error theErr = QTSS_GetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &theSession, &theLen);
    //qtss_printf("QTSSReflectorModule.cpp:ProcessRTPData    sClientBroadcastSessionAttr=%lu theSession=%lu err=%ld \n",sClientBroadcastSessionAttr, theSession,theErr);
    if (theSession == NULL || theErr != QTSS_NoErr) 
        return QTSS_NoErr;
    
    // it is a broadcaster session
    //qtss_printf("QTSSReflectorModule.cpp:is broadcaster session\n");

    SourceInfo* theSoureInfo = theSession->GetSourceInfo(); 
    Assert(theSoureInfo != NULL);
    if (theSoureInfo == NULL)
        return QTSS_NoErr;
            

    UInt32  numStreams = theSession->GetNumStreams();
    //qtss_printf("QTSSReflectorModule.cpp:ProcessRTPData numStreams=%lu\n",numStreams);

{
/*
   Stream data such as RTP packets is encapsulated by an ASCII dollar
   sign (24 hexadecimal), followed by a one-byte channel identifier,
   followed by the length of the encapsulated binary data as a binary,
   two-byte integer in network byte order. The stream data follows
   immediately afterwards, without a CRLF, but including the upper-layer
   protocol headers. Each $ block contains exactly one upper-layer
   protocol data unit, e.g., one RTP packet.
*/
    char*   packetData= inParams->inPacketData;
    
    UInt8   packetChannel;
    packetChannel = (UInt8) packetData[1];

    UInt16  packetDataLen;
    memcpy(&packetDataLen,&packetData[2],2);
    packetDataLen = ntohs(packetDataLen);
    
    char*   rtpPacket = &packetData[4];
    
    //UInt32    packetLen = inParams->inPacketLen;
    //qtss_printf("QTSSReflectorModule.cpp:ProcessRTPData channel=%u theSoureInfo=%lu packetLen=%lu packetDatalen=%u\n",(UInt16) packetChannel,theSoureInfo,inParams->inPacketLen,packetDataLen);

    if (1)
    {
        UInt32 inIndex = packetChannel / 2; // one stream per every 2 channels rtcp channel handled below
        ReflectorStream* theStream = NULL;
        if (inIndex < numStreams) 
        {   theStream = theSession->GetStreamByIndex(inIndex);

            SourceInfo::StreamInfo* theStreamInfo =theStream->GetStreamInfo();  
            UInt16 serverReceivePort =theStreamInfo->fPort;         

            Bool16 isRTCP =false;
            if (theStream != NULL)
            {   if (packetChannel & 1)
                {   serverReceivePort ++;
                    isRTCP = true;
                }
                theStream->PushPacket(rtpPacket,packetDataLen, isRTCP);
                //qtss_printf("QTSSReflectorModule.cpp:ProcessRTPData Send RTSP packet channel=%u to UDP localServerAddr=%lu serverReceivePort=%lu packetDataLen=%u \n", (UInt16) packetChannel, localServerAddr, serverReceivePort,packetDataLen);
            }
        }
    }
    
}
    return theErr;
}

QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams)
{
    QTSS_RTSPMethod* theMethod = NULL;
    //qtss_printf("QTSSReflectorModule:ProcessRTSPRequest inClientSession=%lu\n", (UInt32) inParams->inClientSession);
    UInt32 theLen = 0;
    if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0,
            (void**)&theMethod, &theLen) != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod)))
    {
        Assert(0);
        return QTSS_RequestFailed;
    }

    if (*theMethod == qtssAnnounceMethod)
        return DoAnnounce(inParams);
    if (*theMethod == qtssDescribeMethod)
        return DoDescribe(inParams);
    if (*theMethod == qtssSetupMethod)
        return DoSetup(inParams);
        
    RTPSessionOutput** theOutput = NULL;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*))) // a broadcaster push session
    {   if (*theMethod == qtssPlayMethod || *theMethod == qtssRecordMethod)
            return DoPlay(inParams, NULL);
        else
            return QTSS_RequestFailed;
    }
    
    switch (*theMethod)
    {
        case qtssPlayMethod:
            return DoPlay(inParams, (*theOutput)->GetReflectorSession());
        case qtssTeardownMethod:
            // Tell the server that this session should be killed, and send a TEARDOWN response
            (void)QTSS_Teardown(inParams->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
            break;
        case qtssPauseMethod:
            (void)QTSS_Pause(inParams->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
            break;
        default:
            break;
    }           
    return QTSS_NoErr;
}

ReflectorSession* DoSessionSetup(QTSS_StandardRTSP_Params* inParams, QTSS_AttributeID inPathType,Bool16 isPush, Bool16 *foundSessionPtr, char** resultFilePath)
{
    char* theFullPathStr = NULL;
    QTSS_Error theErr = QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr);
    Assert(theErr == QTSS_NoErr);
    QTSSCharArrayDeleter theFullPathStrDeleter(theFullPathStr);
        
    if (theErr != QTSS_NoErr)
        return NULL;

    StrPtrLen theFullPath(theFullPathStr);

    if (theFullPath.Len > sMOVSuffix.Len )
    {   StrPtrLen endOfPath2(&theFullPath.Ptr[theFullPath.Len -  sMOVSuffix.Len], sMOVSuffix.Len);
        if (endOfPath2.Equal(sMOVSuffix)) // it is a .mov so it is not meant for us
        {   
            return NULL;
        }
    }


    if (sAllowNonSDPURLs && !isPush)
    {
        // Check and see if the full path to this file matches an existing ReflectorSession
        StrPtrLen thePathPtr;
        OSCharArrayDeleter sdpPath(QTSSModuleUtils::GetFullPath(    inParams->inRTSPRequest,
                                                                    inPathType,
                                                                    &thePathPtr.Len, &sSDPSuffix));
        
        thePathPtr.Ptr = sdpPath.GetObject();

        // If the actual file path has a .sdp in it, first look for the URL without the extra .sdp
        if (thePathPtr.Len > (sSDPSuffix.Len * 2))
        {
            // Check and see if there is a .sdp in the file path.
            // If there is, truncate off our extra ".sdp", cuz it isn't needed
            StrPtrLen endOfPath(&sdpPath.GetObject()[thePathPtr.Len - (sSDPSuffix.Len * 2)], sSDPSuffix.Len);
            if (endOfPath.Equal(sSDPSuffix))
            {
                sdpPath.GetObject()[thePathPtr.Len - sSDPSuffix.Len] = '\0';
                thePathPtr.Len -= sSDPSuffix.Len;
            }
        }
        if (resultFilePath != NULL)
            *resultFilePath = thePathPtr.GetAsCString();
        return FindOrCreateSession(&thePathPtr, inParams);
    }
    else
    {
        if (!sDefaultBroadcastPushEnabled)
            return NULL;
        //
        // We aren't supposed to auto-append a .sdp, so just get the URL path out of the server
        //StrPtrLen theFullPath;
        //QTSS_Error theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, (void**)&theFullPath.Ptr, &theFullPath.Len);
        //Assert(theErr == QTSS_NoErr);
        
        if (theFullPath.Len > sSDPSuffix.Len)
        {
            //
            // Check to make sure this path has a .sdp at the end. If it does,
            // attempt to get a reflector session for this URL.
            StrPtrLen endOfPath2(&theFullPath.Ptr[theFullPath.Len - sSDPSuffix.Len], sSDPSuffix.Len);
            if (endOfPath2.Equal(sSDPSuffix))
            {   if (resultFilePath != NULL)
                    *resultFilePath = theFullPath.GetAsCString();
                return FindOrCreateSession(&theFullPath, inParams,NULL, isPush,foundSessionPtr);
            }
        }
        return NULL;
    }
}

void DoAnnounceAddRequiredSDPLines(QTSS_StandardRTSP_Params* inParams, ResizeableStringFormatter *editedSDP, char* theSDPPtr)
{
    SDPContainer checkedSDPContainer;
    checkedSDPContainer.SetSDPBuffer( theSDPPtr );  
    if (!checkedSDPContainer.HasReqLines())
    {
        if (!checkedSDPContainer.HasLineType('v'))
        { // add v line
            editedSDP->Put("v=0\r\n");
        }
        
        if (!checkedSDPContainer.HasLineType('s'))
        { // add s line
            char* theSDPName = NULL; 
                        
            (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFilePath, 0, &theSDPName);
            QTSSCharArrayDeleter thePathStrDeleter(theSDPName);
            if (theSDPName == NULL)
                editedSDP->Put("s=unknown\r\n");
            else
            {
                editedSDP->Put("s=");
                editedSDP->Put(theSDPName);
                editedSDP->PutEOL();
            }
        }
        
       if (!checkedSDPContainer.HasLineType('t'))
       { // add t line
            editedSDP->Put("t=0 0\r\n");
       }

       if (!checkedSDPContainer.HasLineType('o'))
       { // add o line
            editedSDP->Put("o=");
            char tempBuff[256] = ""; tempBuff[255] = 0;
            char *nameStr = tempBuff;
            UInt32 buffLen = sizeof(tempBuff) - 1;
            (void)QTSS_GetValue(inParams->inClientSession, qtssCliSesFirstUserAgent, 0, nameStr, &buffLen);
            for (UInt32 c = 0; c < buffLen; c++)
            {   
                if (StringParser::sEOLWhitespaceMask[nameStr[c]])
                {   nameStr[c] = 0;             
                    break;
                }
            }

            buffLen = ::strlen(nameStr);
            if (buffLen == 0)
                editedSDP->Put("announced_broadcast");
            else
                editedSDP->Put(nameStr, buffLen);
                      
            editedSDP->Put(" ");
                            
            buffLen = sizeof(tempBuff) -1;
            (void)QTSS_GetValue(inParams->inClientSession, qtssCliSesRTSPSessionID, 0, &tempBuff, &buffLen);
            editedSDP->Put(tempBuff, buffLen );

            editedSDP->Put(" ");
            qtss_snprintf(tempBuff, sizeof(tempBuff) -1, "%"_64BITARG_"d", (SInt64) OS::UnixTime_Secs() + 2208988800LU);
            editedSDP->Put(tempBuff);

            editedSDP->Put(" IN IP4 ");
            (void)QTSS_GetValue(inParams->inClientSession, qtssCliRTSPSessRemoteAddrStr, 0, tempBuff, &buffLen);
            editedSDP->Put(tempBuff, buffLen);

            editedSDP->PutEOL();
        }
    }

    editedSDP->Put(theSDPPtr);


}


QTSS_Error DoAnnounce(QTSS_StandardRTSP_Params* inParams)
{
    if (!sAnnounceEnabled)
        return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssPreconditionFailed, sAnnounceDisabledNameErr);

    //
    // If this is SDP data, the reflector has the ability to write the data
    // to the file system location specified by the URL.
    
    //
    // This is a completely stateless action. No ReflectorSession gets created (obviously).
    
    //
    // Eventually, we should really require access control before we do this.
    //qtss_printf("QTSSReflectorModule:DoAnnounce\n");
    //
    // Get the full path to this file
    char* theFullPathStr = NULL;
    (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr);
    QTSSCharArrayDeleter theFullPathStrDeleter(theFullPathStr);
    StrPtrLen theFullPath(theFullPathStr);
    
    // Check for a .kill at the end
    Bool16 pathOK = false;
    Bool16 killBroadcast = false;
    if (sAnnouncedKill && theFullPath.Len > sSDPKillSuffix.Len)
    {
        StrPtrLen endOfPath(theFullPath.Ptr + (theFullPath.Len - sSDPKillSuffix.Len), sSDPKillSuffix.Len);
        if (endOfPath.Equal(sSDPKillSuffix))
        {   
            pathOK = true;
            killBroadcast = true;
        }
     }


    // Check for a .sdp at the end
     if (!pathOK)
     {
        if (theFullPath.Len <= sSDPSuffix.Len)
        {
            StrPtrLen endOfPath(theFullPath.Ptr + (theFullPath.Len - sSDPSuffix.Len), sSDPSuffix.Len);
            if (!endOfPath.Equal(sSDPSuffix))
                return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssPreconditionFailed, sAnnounceRequiresSDPinNameErr);

        }       
     }

    
    //
    // Ok, this is an sdp file. Retreive the entire contents of the SDP.
    // This has to be done asynchronously (in case the SDP stuff is fragmented across
    // multiple packets. So, we have to have a simple state machine.

    //
    // We need to know the content length to manage memory
    UInt32 theLen = 0;
    UInt32* theContentLenP = NULL;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqContentLen, 0, (void**)&theContentLenP, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(UInt32)))
    {
        //
        // RETURN ERROR RESPONSE: ANNOUNCE WITHOUT CONTENT LENGTH
        return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,0);
    }

    // Check if the content-length is more than the imposed maximum
	// if it is then return error response
    if ( (sMaxAnnouncedSDPLengthInKbytes != 0) && (*theContentLenP > (sMaxAnnouncedSDPLengthInKbytes * 1024)) )
        return QTSSModuleUtils::SendErrorResponseWithMessage( inParams->inRTSPRequest, qtssPreconditionFailed, &sSDPTooLongMessage );
    
    //
    // Check for the existence of 2 attributes in the request: a pointer to our buffer for
    // the request body, and the current offset in that buffer. If these attributes exist,
    // then we've already been here for this request. If they don't exist, add them.
    UInt32 theBufferOffset = 0;
    char* theRequestBody = NULL;

    theLen = sizeof(theRequestBody);
    theErr = QTSS_GetValue(inParams->inRTSPRequest, sRequestBodyAttr, 0, &theRequestBody, &theLen);

    //qtss_printf("QTSSReflectorModule:DoAnnounce theRequestBody =%s\n",theRequestBody);
    if (theErr != QTSS_NoErr)
    {
        //
        // First time we've been here for this request. Create a buffer for the content body and
        // shove it in the request.
        theRequestBody = NEW char[*theContentLenP + 1];
        memset(theRequestBody,0,*theContentLenP + 1);
        theLen = sizeof(theRequestBody);
        theErr = QTSS_SetValue(inParams->inRTSPRequest, sRequestBodyAttr, 0, &theRequestBody, theLen);// SetValue creates an internal copy.
        Assert(theErr == QTSS_NoErr);
        
        //
        // Also store the offset in the buffer
        theLen = sizeof(theBufferOffset);
        theErr = QTSS_SetValue(inParams->inRTSPRequest, sBufferOffsetAttr, 0, &theBufferOffset, theLen);
        Assert(theErr == QTSS_NoErr);
    }
    
    theLen = sizeof(theBufferOffset);
    theErr = QTSS_GetValue(inParams->inRTSPRequest, sBufferOffsetAttr, 0, &theBufferOffset, &theLen);

    //
    // We have our buffer and offset. Read the data.
    theErr = QTSS_Read(inParams->inRTSPRequest, theRequestBody + theBufferOffset, *theContentLenP - theBufferOffset, &theLen);
    Assert(theErr != QTSS_BadArgument);

    if (theErr == QTSS_RequestFailed)
    {
        OSCharArrayDeleter charArrayPathDeleter(theRequestBody);
        //
        // NEED TO RETURN RTSP ERROR RESPONSE
        return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,0);
    }
    
    if ((theErr == QTSS_WouldBlock) || (theLen < (*theContentLenP - theBufferOffset)))
    {
		//
        // Update our offset in the buffer
        theBufferOffset += theLen;
        (void)QTSS_SetValue(inParams->inRTSPRequest, sBufferOffsetAttr, 0, &theBufferOffset, sizeof(theBufferOffset));
        //qtss_printf("QTSSReflectorModule:DoAnnounce Request some more data \n");
        //
        // The entire content body hasn't arrived yet. Request a read event and wait for it.
        // Our DoAnnounce function will get called again when there is more data.
        theErr = QTSS_RequestEvent(inParams->inRTSPRequest, QTSS_ReadableEvent);
        Assert(theErr == QTSS_NoErr);
        return QTSS_NoErr;
    }

    Assert(theErr == QTSS_NoErr);
    

//
// If we've gotten here, we have the entire content body in our buffer.
//

    if (killBroadcast)
    {  
        theFullPath.Len -= sSDPKillSuffix.Len;
        if (KillSession(&theFullPath, killBroadcast))
            return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerInternal,0);
        else
            return QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssClientNotFound, &sKILLNotValidMessage);  
    }

// ------------  Clean up missing required SDP lines

    ResizeableStringFormatter editedSDP(NULL,0);
    DoAnnounceAddRequiredSDPLines(inParams, &editedSDP, theRequestBody);
    StrPtrLen editedSDPSPL(editedSDP.GetBufPtr(),editedSDP.GetBytesWritten());

// ------------ Check the headers

    SDPContainer checkedSDPContainer;
    checkedSDPContainer.SetSDPBuffer( &editedSDPSPL );  
    if (!checkedSDPContainer.IsSDPBufferValid())
    {  
        return QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssUnsupportedMediaType, &sSDPNotValidMessage);
    }

    SDPSourceInfo theSDPSourceInfo(editedSDPSPL.Ptr, editedSDPSPL.Len );
    OSCharArrayDeleter charArrayPathDeleter(theRequestBody);
                        
    if (!InfoPortsOK(inParams,&theSDPSourceInfo,&theFullPath)) // All validity checks like this check should be done before touching the file.
    {   return QTSS_NoErr; // InfoPortsOK is sending back the error.
    }

// ------------ reorder the sdp headers to make them proper.
		
    SDPLineSorter sortedSDP(&checkedSDPContainer );

// ------------ Write the SDP 

    char* sessionHeaders = sortedSDP.GetSessionHeaders()->GetAsCString();
    OSCharArrayDeleter sessionHeadersDeleter(sessionHeaders);

    char* mediaHeaders = sortedSDP.GetMediaHeaders()->GetAsCString();
    OSCharArrayDeleter mediaHeadersDeleter(mediaHeaders);

   // sortedSDP.GetSessionHeaders()->PrintStrEOL();
   // sortedSDP.GetMediaHeaders()->PrintStrEOL();

    // write the file !! need error reporting
    FILE* theSDPFile= ::fopen(theFullPath.Ptr, "wb");//open 
    if (theSDPFile != NULL)
    {  
        qtss_fprintf(theSDPFile, "%s", sessionHeaders);
        qtss_fprintf(theSDPFile, "%s", mediaHeaders);
        ::fflush(theSDPFile);
        ::fclose(theSDPFile);   
    }
    else
    {   return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientForbidden,0);
    }
    

    //qtss_printf("QTSSReflectorModule:DoAnnounce SendResponse OK=200\n");
    
    return QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
}

void DoDescribeAddRequiredSDPLines(QTSS_StandardRTSP_Params* inParams, ReflectorSession* theSession, QTSS_TimeVal modDate,  ResizeableStringFormatter *editedSDP, StrPtrLen* theSDPPtr)
{
    SDPContainer checkedSDPContainer;
    checkedSDPContainer.SetSDPBuffer( theSDPPtr );  
    if (!checkedSDPContainer.HasReqLines())
    {
        if (!checkedSDPContainer.HasLineType('v'))
        { // add v line
            editedSDP->Put("v=0\r\n");
        }
        
        if (!checkedSDPContainer.HasLineType('s'))
        { // add s line
           char* theSDPName = NULL;            
            (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFilePath, 0, &theSDPName);
            QTSSCharArrayDeleter thePathStrDeleter(theSDPName);
            editedSDP->Put("s=");
            editedSDP->Put(theSDPName);
            editedSDP->PutEOL();
        }
        
       if (!checkedSDPContainer.HasLineType('t'))
       { // add t line
            editedSDP->Put("t=0 0\r\n");
       }

       if (!checkedSDPContainer.HasLineType('o'))
       { // add o line
            editedSDP->Put("o=broadcast_sdp ");
            char tempBuff[256]= "";               
            tempBuff[255] = 0;
            qtss_snprintf(tempBuff,sizeof(tempBuff) - 1, "%lu", (UInt32) theSession);
            editedSDP->Put(tempBuff);

            editedSDP->Put(" ");
            // modified date is in milliseconds.  Convert to NTP seconds as recommended by rfc 2327
            qtss_snprintf(tempBuff, sizeof(tempBuff) - 1, "%"_64BITARG_"d", (SInt64) (modDate/1000) + 2208988800LU);
            editedSDP->Put(tempBuff);

            editedSDP->Put(" IN IP4 ");
            UInt32 buffLen = sizeof(tempBuff) -1;
            (void)QTSS_GetValue(inParams->inClientSession, qtssCliSesHostName, 0, &tempBuff, &buffLen);
            editedSDP->Put(tempBuff, buffLen);

            editedSDP->PutEOL();
        }
    } 

    editedSDP->Put(*theSDPPtr);

}

QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParams)
{
    char *theFilepath = NULL;
    ReflectorSession* theSession = DoSessionSetup(inParams, qtssRTSPReqFilePath, false, NULL, &theFilepath );
    OSCharArrayDeleter tempFilePath(theFilepath);
    
    if (theSession == NULL)
        return QTSS_RequestFailed;

    RTPSessionOutput** theOutput = NULL;
    UInt32 theLen = 0;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen);

    // If there already  was an RTPSessionOutput attached to this Client Session,
    // destroy it.
    if (theErr == QTSS_NoErr)
        RemoveOutput(*theOutput, (*theOutput)->GetReflectorSession(), false);

    //ok, we've found or setup the proper reflector session, create an RTPSessionOutput object,
    //and add it to the session's list of outputs
    RTPSessionOutput* theNewOutput = NEW RTPSessionOutput(inParams->inClientSession, theSession, sServerPrefs, sStreamCookieAttr );
    theSession->AddOutput(theNewOutput,true);

    // And vice-versa, store this reflector session in the RTP session.
    (void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theNewOutput, sizeof(theNewOutput));
        
    // Finally, send the DESCRIBE response
    
    //above function has signalled that this request belongs to us, so let's respond
    iovec theDescribeVec[3] = { {0 }};
    
    Assert(theSession->GetLocalSDP()->Ptr != NULL);
    

    StrPtrLen theFileData;
    QTSS_TimeVal outModDate = 0;
    QTSS_TimeVal inModDate = -1;
    (void)QTSSModuleUtils::ReadEntireFile(theFilepath, &theFileData, inModDate, &outModDate);
    OSCharArrayDeleter fileDataDeleter(theFileData.Ptr); 

// -------------- process SDP to remove connection info and add track IDs, port info, and default c= line

    StrPtrLen theSDPData;
    SDPSourceInfo tempSDPSourceInfo(theFileData.Ptr, theFileData.Len); // will make a copy and delete in destructor
    theSDPData.Ptr = tempSDPSourceInfo.GetLocalSDP(&theSDPData.Len); // returns a new buffer with processed sdp
    OSCharArrayDeleter sdpDeleter(theSDPData.Ptr); // delete the temp sdp source info buffer returned by GetLocalSDP
    
    if (theSDPData.Len <= 0) // can't find it on disk or it failed to parse just use the one in the session.
    {
        theSDPData.Ptr = theSession->GetLocalSDP()->Ptr; // this sdp isn't ours it must not be deleted
        theSDPData.Len = theSession->GetLocalSDP()->Len;
    }


// ------------  Clean up missing required SDP lines

    ResizeableStringFormatter editedSDP(NULL,0);
    DoDescribeAddRequiredSDPLines(inParams, theSession, outModDate, &editedSDP, &theSDPData);
    StrPtrLen editedSDPSPL(editedSDP.GetBufPtr(),editedSDP.GetBytesWritten());

// ------------ Check the headers

    SDPContainer checkedSDPContainer;
    checkedSDPContainer.SetSDPBuffer( &editedSDPSPL );  
    if (!checkedSDPContainer.IsSDPBufferValid())
    {  
        return QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssUnsupportedMediaType, &sSDPNotValidMessage);
    }
            
 
// ------------ Put SDP header lines in correct order
    Float32 adjustMediaBandwidthPercent = 1.0;
    Bool16 adjustMediaBandwidth = false;

    if (sPlayerCompatibility )
        adjustMediaBandwidth = QTSSModuleUtils::HavePlayerProfile(inParams,QTSSModuleUtils::kAdjustBandwidth);

    if (adjustMediaBandwidth)
        adjustMediaBandwidthPercent = (Float32) sAdjustMediaBandwidthPercent / 100.0;

    SDPLineSorter sortedSDP(&checkedSDPContainer,adjustMediaBandwidthPercent);

// ------------ Write the SDP 

    UInt32 sessLen = sortedSDP.GetSessionHeaders()->Len;
    UInt32 mediaLen = sortedSDP.GetMediaHeaders()->Len;
    theDescribeVec[1].iov_base = sortedSDP.GetSessionHeaders()->Ptr;
    theDescribeVec[1].iov_len = sortedSDP.GetSessionHeaders()->Len;

    theDescribeVec[2].iov_base = sortedSDP.GetMediaHeaders()->Ptr;
    theDescribeVec[2].iov_len = sortedSDP.GetMediaHeaders()->Len;

    (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader,
                                kCacheControlHeader.Ptr, kCacheControlHeader.Len);
    QTSSModuleUtils::SendDescribeResponse(inParams->inRTSPRequest, inParams->inClientSession,
                                            &theDescribeVec[0], 3, sessLen + mediaLen ); 
    return QTSS_NoErr;
}

Bool16 InfoPortsOK(QTSS_StandardRTSP_Params* inParams, SDPSourceInfo* theInfo, StrPtrLen* inPath)
{   // Check the ports based on the Pref whether to enforce a static SDP port range.

    Bool16 isOK = true;

    if (sEnforceStaticSDPPortRange)
    {   UInt16 theInfoPort = 0;
        for (UInt32 x = 0; x < theInfo->GetNumStreams(); x++)
        {   theInfoPort = theInfo->GetStreamInfo(x)->fPort;
            QTSS_AttributeID theErrorMessageID = qtssIllegalAttrID;
            if (theInfoPort != 0)
            {   if  (theInfoPort < sMinimumStaticSDPPort) 
                    theErrorMessageID = sSDPcontainsInvalidMinimumPortErr;
                else if (theInfoPort > sMaximumStaticSDPPort)
                    theErrorMessageID = sSDPcontainsInvalidMaximumPortErr;
            }
            
            if (theErrorMessageID != qtssIllegalAttrID)
            {   
                char thePort[32];
                qtss_sprintf(thePort,"%u",theInfoPort);

                char *thePath = inPath->GetAsCString();
                OSCharArrayDeleter charArrayPathDeleter(thePath);
                
                char *thePathPort = NEW char[inPath->Len + 32];
                OSCharArrayDeleter charArrayPathPortDeleter(thePathPort);
                
                qtss_sprintf(thePathPort,"%s:%s",thePath,thePort);
                (void) QTSSModuleUtils::LogError(qtssWarningVerbosity, theErrorMessageID, 0, thePathPort);
                
                StrPtrLen thePortStr(thePort);                  
                (void) QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssUnsupportedMediaType, theErrorMessageID,&thePortStr);

                return false;
            }
        }
    }
    
    return isOK;
}

ReflectorSession* FindOrCreateSession(StrPtrLen* inPath, QTSS_StandardRTSP_Params* inParams, StrPtrLen* inData, Bool16 isPush, Bool16 *foundSessionPtr)
{   
    // This function assumes that inPath is NULL terminated
    // Ok, look for a reflector session matching this full path as the ID
    OSMutexLocker locker(sSessionMap->GetMutex());
    OSRef* theSessionRef = sSessionMap->Resolve(inPath);
    ReflectorSession* theSession = NULL;
     
    if (theSessionRef == NULL)
    {
        //If this URL doesn't already have a reflector session, we must make a new
        //one. The first step is to create an SDPSourceInfo object.
        
        StrPtrLen theFileData;
        StrPtrLen theFileDeleteData;
        
        //
        // If no file data is provided by the caller, read the file data out of the file.
        // If file data is provided, use that as our SDP data
        if (inData == NULL)
        {   (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileDeleteData);
            theFileData = theFileDeleteData;
        }
        else
            theFileData = *inData;
        OSCharArrayDeleter fileDataDeleter(theFileDeleteData.Ptr); 
            
        if (theFileData.Len <= 0)
            return NULL;
            
        SDPSourceInfo* theInfo = NEW SDPSourceInfo(theFileData.Ptr, theFileData.Len); // will make a copy
            
        if (!theInfo->IsReflectable())
        {   delete theInfo;
            return NULL;
        }
        
        
        if (!InfoPortsOK(inParams, theInfo, inPath))
        {   delete theInfo;
            return NULL;
        }
       
        // Check if broadcast is allowed before doing anything else
        // At this point we know it is a definitely a reflector session
        // It is either incoming automatic broadcast setup or a client setup to view broadcast
        // In either case, verify whether the broadcast is allowed, and send forbidden response
        // back
        if (!AllowBroadcast(inParams->inRTSPRequest))
        {
            (void) QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssClientForbidden, &sBroadcastNotAllowed);
            return NULL;
        }
    
        //
        // Setup a ReflectorSession and bind the sockets. If we are negotiating,
        // make sure to let the session know that this is a Push Session so
        // ports may be modified.
        UInt32 theSetupFlag = ReflectorSession::kMarkSetup;
        if (isPush)
            theSetupFlag |= ReflectorSession::kIsPushSession;
        
        theSession = NEW ReflectorSession(inPath);
		if (theSession == NULL)
		{	return NULL;
		}
		
		theSession->SetHasBufferedStreams(true); // buffer the incoming streams for clients
        
        // SetupReflectorSession stores theInfo in theSession so DONT delete the Info if we fail here, leave it alone.
        // deleting the session will delete the info.
        QTSS_Error theErr = theSession->SetupReflectorSession(theInfo, inParams, theSetupFlag,sOneSSRCPerStream, sTimeoutSSRCSecs);
        if (theErr != QTSS_NoErr)
        {   delete theSession;
            return NULL;
        }
        
        //qtss_printf("Created reflector session = %lu theInfo=%lu \n", (UInt32) theSession,(UInt32)theInfo);
        //put the session's ID into the session map.
        theErr = sSessionMap->Register(theSession->GetRef());
        Assert(theErr == QTSS_NoErr);

        //unless we do this, the refcount won't increment (and we'll delete the session prematurely
        if (!isPush)
        {   OSRef* debug = sSessionMap->Resolve(inPath);
            Assert(debug == theSession->GetRef());
        }
    }
    else
    {
        // Check if broadcast is allowed before doing anything else
        // At this point we know it is a definitely a reflector session
        // It is either incoming automatic broadcast setup or a client setup to view broadcast
        // In either case, verify whether the broadcast is allowed, and send forbidden response
        // back
        if (!AllowBroadcast(inParams->inRTSPRequest))
        {
            (void) QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssClientForbidden, &sBroadcastNotAllowed);
            return NULL;
        }
        
        if (isPush) 
            sSessionMap->Release(theSessionRef); // don't need if a push;// don't need if a push; A Release is necessary or we will leak ReflectorSessions.

        if (foundSessionPtr)
            *foundSessionPtr = true;
            
        StrPtrLen theFileData;
        SDPSourceInfo* theInfo = NULL;
        
        if (inData == NULL)
            (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileData);
        OSCharArrayDeleter charArrayDeleter(theFileData.Ptr);
            
        if (theFileData.Len <= 0)
            return NULL;
        
        theInfo = NEW SDPSourceInfo(theFileData.Ptr, theFileData.Len);
        if (theInfo == NULL) 
            return NULL;
      
        if (!InfoPortsOK(inParams, theInfo, inPath))
        {   delete theInfo;
            return NULL;
        }
        
        delete theInfo;
        
        theSession = (ReflectorSession*)theSessionRef->GetObject(); 
        if (isPush && theSession)
        {
            UInt32 theSetupFlag = ReflectorSession::kMarkSetup | ReflectorSession::kIsPushSession;
            QTSS_Error theErr = theSession->SetupReflectorSession(NULL, inParams, theSetupFlag);
            if (theErr != QTSS_NoErr)
            {   return NULL;
            }
        }
    }
            
    Assert(theSession != NULL);

    return theSession;
    if (theSessionRef == NULL)
    {
        //If this URL doesn't already have a reflector session, we must make a new
        //one. The first step is to create an SDPSourceInfo object.
        
        StrPtrLen theFileData;
        StrPtrLen theFileDeleteData;
        
        //
        // If no file data is provided by the caller, read the file data out of the file.
        // If file data is provided, use that as our SDP data
        if (inData == NULL)
        {   (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileDeleteData);
            theFileData = theFileDeleteData;
        }
        else
            theFileData = *inData;
        OSCharArrayDeleter fileDataDeleter(theFileDeleteData.Ptr); 
            
        if (theFileData.Len <= 0)
            return NULL;
            
        SDPSourceInfo* theInfo = NEW SDPSourceInfo(theFileData.Ptr, theFileData.Len); // will make a copy
            
        if (!theInfo->IsReflectable())
        {   delete theInfo;
            return NULL;
        }
        if ( !theInfo->IsActiveNow() && !isPush)
        {   delete theInfo;
            return NULL;
        }
        
        if (!InfoPortsOK(inParams, theInfo, inPath))
        {   delete theInfo;
            return NULL;
        }
        //
        // Setup a ReflectorSession and bind the sockets. If we are negotiating,
        // make sure to let the session know that this is a Push Session so
        // ports may be modified.
        UInt32 theSetupFlag = ReflectorSession::kMarkSetup;
        if (isPush)
            theSetupFlag |= ReflectorSession::kIsPushSession;
        
        theSession = NEW ReflectorSession(inPath);
        
        // SetupReflectorSession stores theInfo in theSession so DONT delete the Info if we fail here, leave it alone.
        // deleting the session will delete the info.
        QTSS_Error theErr = theSession->SetupReflectorSession(theInfo, inParams, theSetupFlag,sOneSSRCPerStream, sTimeoutSSRCSecs);
        if (theErr != QTSS_NoErr || theSession == NULL)
        {   delete theSession;
            return NULL;
        }
//      infoPtrDeleter.ClearObject(); // don't delete info we are going to keep it in the session
        
        //printf("Created reflector session = %lu theInfo=%lu \n", (UInt32) theSession,(UInt32)theInfo);
        //put the session's ID into the session map.
        theErr = sSessionMap->Register(theSession->GetRef());
        Assert(theErr == QTSS_NoErr);

        //unless we do this, the refcount won't increment (and we'll delete the session prematurely
        if (!isPush)
        {   OSRef* debug = sSessionMap->Resolve(inPath);
            Assert(debug == theSession->GetRef());
        }
    }
    else
    {
    
        if (isPush) 
            sSessionMap->Release(theSessionRef); // don't need if a push;// don't need if a push; A Release is necessary or we will leak ReflectorSessions.

        if (foundSessionPtr)
            *foundSessionPtr = true;
            
        StrPtrLen theFileData;
        SDPSourceInfo* theInfo = NULL;
        
        if (inData == NULL)
            (void)QTSSModuleUtils::ReadEntireFile(inPath->Ptr, &theFileData);
        OSCharArrayDeleter charArrayDeleter(theFileData.Ptr);
            
        if (theFileData.Len <= 0)
            return NULL;
        
        theInfo = NEW SDPSourceInfo(theFileData.Ptr, theFileData.Len);
        if (theInfo == NULL) 
            return NULL;
            
        if ( !theInfo->IsActiveNow() && !isPush)
        {   delete theInfo;
            return NULL;
        }
        
        if (!InfoPortsOK(inParams, theInfo, inPath))
        {   delete theInfo;
            return NULL;
        }
        
        delete theInfo;
        
        theSession = (ReflectorSession*)theSessionRef->GetObject(); 
        if (isPush && theSession)
        {
            UInt32 theSetupFlag = ReflectorSession::kMarkSetup | ReflectorSession::kIsPushSession;
            QTSS_Error theErr = theSession->SetupReflectorSession(NULL, inParams, theSetupFlag);
            if (theErr != QTSS_NoErr)
            {   return NULL;
            }
        }
    }
            
    Assert(theSession != NULL);

	// Turn off overbuffering if the "disable_overbuffering" pref says so
	if (sDisableOverbuffering)
		(void)QTSS_SetValue(inParams->inClientSession, qtssCliSesOverBufferEnabled, 0, &sFalse, sizeof(sFalse));

    return theSession;
}

// ONLY call when performing a setup.
void DeleteReflectorPushSession(QTSS_StandardRTSP_Params* inParams, ReflectorSession* theSession, Bool16 foundSession)
{
    ReflectorSession* stopSessionProcessing = NULL;
    QTSS_Error theErr  = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &stopSessionProcessing, sizeof(stopSessionProcessing));
    Assert(theErr == QTSS_NoErr);

    if (foundSession) 
        return; // we didn't allocate the session so don't delete

    OSRef* theSessionRef = theSession->GetRef();
    if (theSessionRef != NULL) 
    {               
        theSession->TearDownAllOutputs(); // just to be sure because we are about to delete the session.
        sSessionMap->UnRegister(theSessionRef);// we had an error while setting up-- don't let anyone get the session
        delete theSession;  
    }
}

QTSS_Error AddRTPStream(ReflectorSession* theSession,QTSS_StandardRTSP_Params* inParams, QTSS_RTPStreamObject *newStreamPtr)
{       
    // Ok, this is completely crazy but I can't think of a better way to do this that's
    // safe so we'll do it this way for now. Because the ReflectorStreams use this session's
    // stream queue, we need to make sure that each ReflectorStream is not reflecting to this
    // session while we call QTSS_AddRTPStream. One brutal way to do this is to grab each
    // ReflectorStream's mutex, which will stop every reflector stream from running.
    Assert(newStreamPtr != NULL);
    
    if (theSession != NULL)
        for (UInt32 x = 0; x < theSession->GetNumStreams(); x++)
            theSession->GetStreamByIndex(x)->GetMutex()->Lock();
    
    //
    // Turn off reliable UDP transport, because we are not yet equipped to
    // do overbuffering.
    QTSS_Error theErr = QTSS_AddRTPStream(inParams->inClientSession, inParams->inRTSPRequest, newStreamPtr, qtssASFlagsForceUDPTransport);

    if (theSession != NULL)
        for (UInt32 y = 0; y < theSession->GetNumStreams(); y++)
            theSession->GetStreamByIndex(y)->GetMutex()->Unlock();

    return theErr;
}

QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams)
{
    ReflectorSession* theSession = NULL;
    //qtss_printf("QTSSReflectorModule.cpp:DoSetup \n");

    // See if this is a push from a Broadcaster
    UInt32 theLen = 0;
    UInt32 *transportModePtr = NULL;
    QTSS_Error theErr  = QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqTransportMode, 0, (void**)&transportModePtr, &theLen);
	Bool16 isPush = (transportModePtr != NULL && *transportModePtr == qtssRTPTransportModeRecord) ? true : false;
    Bool16 foundSession = false;
    
    // Check to see if we have a RTPSessionOutput for this Client Session. If we don't,
    // we should make one
    RTPSessionOutput** theOutput = NULL;
    theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen);
    if (theLen != sizeof(RTPSessionOutput*))
    {
        //
        // This may be an incoming data session. If that's the case, there will be a Reflector
        // Session in the ClientSession
        //theLen = sizeof(theSession);
        //theErr = QTSS_GetValue(inParams->inClientSession, sSessionAttr, 0, &theSession, &theLen);
        
        if (theErr != QTSS_NoErr  && !isPush)
        {
            // This is not an incoming data session...
            // Do the standard ReflectorSession setup, create an RTPSessionOutput
            theSession = DoSessionSetup(inParams, qtssRTSPReqFilePathTrunc);
            if (theSession == NULL)
                return QTSS_RequestFailed;
            
            RTPSessionOutput* theNewOutput = NEW RTPSessionOutput(inParams->inClientSession, theSession, sServerPrefs, sStreamCookieAttr );
            theSession->AddOutput(theNewOutput,true);
            (void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theNewOutput, sizeof(theNewOutput));
        }
        else 
        {           
            theSession = DoSessionSetup(inParams, qtssRTSPReqFilePathTrunc,isPush,&foundSession); 
            if (theSession == NULL)
                return QTSS_RequestFailed;  
                
            // This is an incoming data session. Set the Reflector Session in the ClientSession
            theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));
            Assert(theErr == QTSS_NoErr);
            //qtss_printf("QTSSReflectorModule.cpp:SETsession    sClientBroadcastSessionAttr=%lu theSession=%lu err=%ld \n",(UInt32)sClientBroadcastSessionAttr, (UInt32) theSession,theErr);
            (void) QTSS_SetValue(inParams->inClientSession, qtssCliSesTimeoutMsec, 0, &sBroadcasterSessionTimeoutMilliSecs, sizeof(sBroadcasterSessionTimeoutMilliSecs));
       }
    }
    else
    {   theSession = (*theOutput)->GetReflectorSession();
        if (theSession == NULL)
            return QTSS_RequestFailed;  
    }
    
    //unless there is a digit at the end of this path (representing trackID), don't
    //even bother with the request
    char* theDigitStr = NULL;
    (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr);
    QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr);
    if (theDigitStr == NULL)
    {
        if (isPush)
            DeleteReflectorPushSession(inParams,theSession, foundSession);
        return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,sExpectedDigitFilenameErr);
    }
    
    UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10);
    
    //
    // If this is an incoming data session, skip everything having to do with setting up a new
    // RTP Stream. 
    if (isPush)
    {
        //qtss_printf("QTSSReflectorModule.cpp:DoSetup is push setup\n");

        // Get info about this trackID
        SourceInfo::StreamInfo* theStreamInfo = theSession->GetSourceInfo()->GetStreamInfoByTrackID(theTrackID);
        // If theStreamInfo is NULL, we don't have a legit track, so return an error
        if (theStreamInfo == NULL)
        {   
            DeleteReflectorPushSession(inParams,theSession, foundSession);
            return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,
                                                        sReflectorBadTrackIDErr);
        }
        
        if (!sAllowDuplicateBroadcasts && theStreamInfo->fSetupToReceive) 
        {
            DeleteReflectorPushSession(inParams,theSession, foundSession);  
            return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssPreconditionFailed, sDuplicateBroadcastStreamErr);
        }

        UInt16 theReceiveBroadcastStreamPort = theStreamInfo->fPort;
        theErr = QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqSetUpServerPort, 0, &theReceiveBroadcastStreamPort, sizeof(theReceiveBroadcastStreamPort));
        Assert(theErr == QTSS_NoErr);
        

        QTSS_RTPStreamObject newStream = NULL;
        theErr = AddRTPStream(theSession,inParams,&newStream);
        Assert(theErr == QTSS_NoErr);
        if (theErr != QTSS_NoErr)
        {   
            DeleteReflectorPushSession(inParams,theSession, foundSession);
            return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, 0);
        }
            
        //send the setup response

        (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader,
                                    kCacheControlHeader.Ptr, kCacheControlHeader.Len);

        (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, newStream, 0);
        
        theStreamInfo->fSetupToReceive = true;
        // This is an incoming data session. Set the Reflector Session in the ClientSession
        theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));
        Assert(theErr == QTSS_NoErr);
            
        if (theSession != NULL)
            theSession->AddBroadcasterClientSession(inParams);
            
        return QTSS_NoErr;      
    }

    
    // Get info about this trackID
    SourceInfo::StreamInfo* theStreamInfo = theSession->GetSourceInfo()->GetStreamInfoByTrackID(theTrackID);
    // If theStreamInfo is NULL, we don't have a legit track, so return an error
    if (theStreamInfo == NULL)
        return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,
                                                    sReflectorBadTrackIDErr);
                                                    
    StrPtrLen* thePayloadName = &theStreamInfo->fPayloadName;
    QTSS_RTPPayloadType thePayloadType = theStreamInfo->fPayloadType;

    StringParser parser(thePayloadName);
    
    parser.GetThru(NULL, '/');
    theStreamInfo->fTimeScale = parser.ConsumeInteger(NULL);
    if (theStreamInfo->fTimeScale == 0)
        theStreamInfo->fTimeScale = 90000;

    QTSS_RTPStreamObject newStream = NULL;
    {
        // Ok, this is completely crazy but I can't think of a better way to do this that's
        // safe so we'll do it this way for now. Because the ReflectorStreams use this session's
        // stream queue, we need to make sure that each ReflectorStream is not reflecting to this
        // session while we call QTSS_AddRTPStream. One brutal way to do this is to grab each
        // ReflectorStream's mutex, which will stop every reflector stream from running.
        
        for (UInt32 x = 0; x < theSession->GetNumStreams(); x++)
            theSession->GetStreamByIndex(x)->GetMutex()->Lock();
            
        theErr = QTSS_AddRTPStream(inParams->inClientSession, inParams->inRTSPRequest, &newStream, 0);

        for (UInt32 y = 0; y < theSession->GetNumStreams(); y++)
            theSession->GetStreamByIndex(y)->GetMutex()->Unlock();
            
        if (theErr != QTSS_NoErr)
            return theErr;
    }
    
    // Set up dictionary items for this stream
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayloadName->Ptr, thePayloadName->Len);
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTimescale, 0, &theStreamInfo->fTimeScale, sizeof(theStreamInfo->fTimeScale));
    Assert(theErr == QTSS_NoErr);

    // We only want to allow over buffering to dynamic rate clients   
    SInt32  canDynamicRate = -1;
    theLen = sizeof(canDynamicRate);
    (void) QTSS_GetValue(inParams->inRTSPRequest, qtssRTSPReqDynamicRateState, 0, (void*) &canDynamicRate, &theLen);
    if (canDynamicRate < 1) // -1 no rate field, 0 off
        (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesOverBufferEnabled, 0, &sFalse, sizeof(sFalse));

    // Place the stream cookie in this stream for future reference
    void* theStreamCookie = theSession->GetStreamCookie(theTrackID);
    Assert(theStreamCookie != NULL);
    theErr = QTSS_SetValue(newStream, sStreamCookieAttr, 0, &theStreamCookie, sizeof(theStreamCookie));
    Assert(theErr == QTSS_NoErr);

    // Set the number of quality levels.
    static UInt32 sNumQualityLevels = ReflectorSession::kNumQualityLevels;
    theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels));
    Assert(theErr == QTSS_NoErr);
    
    //send the setup response
    (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader,
                                kCacheControlHeader.Ptr, kCacheControlHeader.Len);
    (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, newStream, qtssSetupRespDontWriteSSRC);
    return QTSS_NoErr;
}

Bool16 SessionSupportsPlayInfo(QTSS_Object inClientSession)
{ 
    Bool16 playInfoOK = true;
    UInt32  theLen = sizeof(playInfoOK);
    (void) QTSS_GetValue(inClientSession, qtssCliSesOverBufferEnabled, 0, (void*) &playInfoOK, &theLen);
     
   return playInfoOK;
}



Bool16 HaveStreamBuffers(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession)
{
    if (inSession == NULL || inParams == NULL)
        return false;
        
    UInt32 firstTimeStamp = 0;
    UInt16 firstSeqNum = 0;            
    ReflectorSender* theSender = NULL;
    ReflectorStream* theReflectorStream = NULL;
    QTSS_RTPStreamObject* theRef = NULL;
    UInt32 theStreamIndex = 0;
    UInt32 theLen = 0; 
    QTSS_Error theErr = QTSS_NoErr;
    Bool16 haveBufferedStreams = true; // set to false and return if we can't set the packets
    UInt32 y = 0;
    
    
    SInt64 currentTime = OS::Milliseconds();
    SInt64 packetArrivalTime = 0;

    //lock all streams
    for (y = 0; y < inSession->GetNumStreams(); y++)
        inSession->GetStreamByIndex(y)->GetMutex()->Lock();

           
    for (   theStreamIndex = 0;
            QTSS_GetValuePtr(inParams->inClientSession, qtssCliSesStreamObjects, theStreamIndex, (void**)&theRef, &theLen) == QTSS_NoErr;
            theStreamIndex++)
    {        
        theReflectorStream = inSession->GetStreamByIndex(theStreamIndex);

      //  if (!theReflectorStream->HasFirstRTCP())
      //      printf("theStreamIndex =%lu no rtcp\n", theStreamIndex);
        
      //  if (!theReflectorStream->HasFirstRTP())
      //      printf("theStreamIndex = %lu no rtp\n", theStreamIndex);
            
        if ((theReflectorStream == NULL) || (false == theReflectorStream->HasFirstRTP()) )
        {    
            haveBufferedStreams = false;
            //printf("1 breaking no buffered streams\n");
             break;
        }                
        
        theSender = theReflectorStream->GetRTPSender();                
        haveBufferedStreams =  theSender->GetFirstPacketInfo(&firstSeqNum, &firstTimeStamp, &packetArrivalTime);
        //printf("theStreamIndex= %lu haveBufferedStreams=%d, seqnum=%d, timestamp=%lu\n", theStreamIndex, haveBufferedStreams, firstSeqNum, firstTimeStamp);
       
       if (!haveBufferedStreams)
        {    
            //printf("2 breaking no buffered streams\n");
            break;
        }                        
        
        theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstSeqNumber, 0, &firstSeqNum, sizeof(firstSeqNum));
        Assert(theErr == QTSS_NoErr);
        
        theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstTimestamp, 0, &firstTimeStamp, sizeof(firstTimeStamp));
        Assert(theErr == QTSS_NoErr);
        
   
    }     
    //unlock all streams
    for (y = 0; y < inSession->GetNumStreams(); y++)
        inSession->GetStreamByIndex(y)->GetMutex()->Unlock();
            
    return haveBufferedStreams;
}

QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession)
{
    QTSS_Error theErr = QTSS_NoErr;
    UInt32 flags = 0;
    UInt32 theLen = 0;
    Bool16 rtpInfoEnabled = false; 
    
    if (inSession == NULL) // it is a broadcast session so store the broadcast session.
    {   if (!sDefaultBroadcastPushEnabled)
            return QTSS_RequestFailed;

        theLen = sizeof(inSession);
        theErr = QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &inSession, &theLen);
        if (theErr != QTSS_NoErr)
            return QTSS_RequestFailed;
            
        theErr = QTSS_SetValue(inParams->inClientSession, sKillClientsEnabledAttr, 0, &sTearDownClientsOnDisconnect, sizeof(sTearDownClientsOnDisconnect));
        if (theErr != QTSS_NoErr)
            return QTSS_RequestFailed;

        Assert(inSession != NULL);
        
        theErr = QTSS_SetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &inSession, sizeof(inSession));
        if (theErr != QTSS_NoErr)
            return QTSS_RequestFailed;
            
            
    
        //qtss_printf("QTSSReflectorModule:SET for att err=%ld id=%ld\n",theErr,inParams->inRTSPSession);
            
        // this code needs to be cleaned up
        // Check and see if the full path to this file matches an existing ReflectorSession
        StrPtrLen thePathPtr;
        OSCharArrayDeleter sdpPath(QTSSModuleUtils::GetFullPath(    inParams->inRTSPRequest,
                                                                    qtssRTSPReqFilePath,
                                                                    &thePathPtr.Len, &sSDPSuffix));
        
        thePathPtr.Ptr = sdpPath.GetObject();
        

        // remove trackID designation from the path if it is there
        char *trackStr = thePathPtr.FindString("/trackID=");
        if (trackStr != NULL && *trackStr != 0)
        {
            *trackStr = 0; // terminate the string.
            thePathPtr.Len = ::strlen(thePathPtr.Ptr);
        }
        
        // If the actual file path has a .sdp in it, first look for the URL without the extra .sdp
        if (thePathPtr.Len > (sSDPSuffix.Len * 2))
        {
            // Check and see if there is a .sdp in the file path.
            // If there is, truncate off our extra ".sdp", cuz it isn't needed
            StrPtrLen endOfPath(&sdpPath.GetObject()[thePathPtr.Len - (sSDPSuffix.Len * 2)], sSDPSuffix.Len);
            if (endOfPath.Equal(sSDPSuffix))
            {
                sdpPath.GetObject()[thePathPtr.Len - sSDPSuffix.Len] = '\0';
                thePathPtr.Len -= sSDPSuffix.Len;
            }
        }

         // do all above so we can add the session to the map with Resolve here.
        // we must only do this once.
        OSRef* debug = sSessionMap->Resolve(&thePathPtr);
        if (debug != inSession->GetRef())
        {   
             return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, 0);
        }
        
    
        KeepSession(inParams->inRTSPRequest,true);
        //qtss_printf("QTSSReflectorModule.cpp:DoPlay (PUSH) inRTSPSession=%lu inClientSession=%lu\n",(UInt32)inParams->inRTSPSession,(UInt32)inParams->inClientSession);
    }
    else// it is NOT a broadcaster session
    {   
        // Tell the session what the bitrate of this reflection is. This is nice for logging,
        // it also allows the server to scale the TCP buffer size appropriately if we are
        // interleaving the data over TCP. This must be set before calling QTSS_Play so the
        // server can use it from within QTSS_Play
        UInt32 bitsPerSecond =  inSession->GetBitRate();
        (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond));
   
        if (sPlayResponseRangeHeader)
        {
            StrPtrLen temp;
            theErr = QTSS_GetValuePtr(inParams->inClientSession, sRTPInfoWaitTimeAttr, 0, &temp.Ptr, &temp.Len);
            if (theErr != QTSS_NoErr)
                QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssRangeHeader,sTheNowRangeHeader.Ptr, sTheNowRangeHeader.Len);
  
        }
        
     
        if (sPlayerCompatibility )
            rtpInfoEnabled = QTSSModuleUtils::HavePlayerProfile(inParams, QTSSModuleUtils::kRequiresRTPInfoSeqAndTime);

        if (sForceRTPInfoSeqAndTime)
            rtpInfoEnabled = true;

        if (sRTPInfoDisabled )
            rtpInfoEnabled = false; 

        if (rtpInfoEnabled)
        {
            flags = qtssPlayRespWriteTrackInfo; //write first timestampe and seq num to rtpinfo
        
            Bool16 haveBufferedStreams = HaveStreamBuffers(inParams,inSession);
            if (haveBufferedStreams) // send the cached rtp time and seq number in the response.
            {    
 
                QTSS_Error theErr = QTSS_Play(inParams->inClientSession, inParams->inRTSPRequest, qtssPlayRespWriteTrackInfo);
                if (theErr != QTSS_NoErr)
                    return theErr;
            
            }
            else
            {   
                SInt32 waitTimeLoopCount = 0;
                theLen = sizeof(waitTimeLoopCount);
                theErr = QTSS_GetValue(inParams->inClientSession, sRTPInfoWaitTimeAttr, 0, &waitTimeLoopCount, &theLen);
                if (theErr != QTSS_NoErr)
                    (void)QTSS_SetValue(inParams->inClientSession, sRTPInfoWaitTimeAttr, 0, &sWaitTimeLoopCount, sizeof(sWaitTimeLoopCount));
                else
                {
                    if (waitTimeLoopCount < 1)
                        return QTSSModuleUtils::SendErrorResponseWithMessage(inParams->inRTSPRequest, qtssClientNotFound, &sBroadcastNotActive); 
                    
                    waitTimeLoopCount --; 
                    (void)QTSS_SetValue(inParams->inClientSession, sRTPInfoWaitTimeAttr, 0, &waitTimeLoopCount, sizeof(waitTimeLoopCount));
                
                }
                
                SInt64 interval = 1 * 1000; // 1 second
                QTSS_SetIdleTimer( interval );
                return QTSS_NoErr;
            }
            
 
        }
        else
        {
            QTSS_Error theErr = QTSS_Play(inParams->inClientSession, inParams->inRTSPRequest, qtssPlayFlagsAppendServerInfo);
            if (theErr != QTSS_NoErr)
                return theErr;
                
        }
  
    }
    
    if (sRTPInfoFullURL)
        flags |= qtssPlayFlagsRTPInfoFullURL;

    (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, flags);
    return QTSS_NoErr;
}


Bool16 KillSession(StrPtrLen *sdpPathStr, Bool16 killClients)
{
    OSRef* theSessionRef = sSessionMap->Resolve(sdpPathStr);
    if (theSessionRef != NULL)
    {
        ReflectorSession*   theSession = (ReflectorSession*)theSessionRef->GetObject();
        RemoveOutput(NULL, theSession, killClients);
        (void)QTSS_Teardown(theSession->GetBroadcasterSession());
        return true;
    }
    return false;
}
 
   
void KillCommandPathInList()
{
    char filePath[128] = "";
    ResizeableStringFormatter commandPath( (char*) filePath, sizeof(filePath)); // ResizeableStringFormatter is safer and more efficient than StringFormatter for most paths.
    OSMutexLocker locker (sSessionMap->GetMutex());

    for (OSRefHashTableIter theIter(sSessionMap->GetHashTable()); !theIter.IsDone(); theIter.Next())
    {
        OSRef* theRef = theIter.GetCurrent();
        if (theRef == NULL)
            continue;
        
        commandPath.Reset();
        commandPath.Put(*(theRef->GetString()));
        commandPath.Put(sSDPKillSuffix);
        commandPath.PutTerminator();
        
        char *theCommandPath =  commandPath.GetBufPtr();
        QTSS_Object outFileObject;
        QTSS_Error  err = QTSS_OpenFileObject(theCommandPath, qtssOpenFileNoFlags, &outFileObject);
        if (err == QTSS_NoErr)
        {   
           (void)  QTSS_CloseFileObject(outFileObject);
           ::unlink(theCommandPath);
           KillSession(theRef->GetString(), true);
        }        
    }
    
}
 
QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams)
{
    RTPSessionOutput**  theOutput = NULL;
    ReflectorOutput*    outputPtr = NULL;
    ReflectorSession*   theSession = NULL;
    
    UInt32 theLen = sizeof(theSession);
    QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, &theLen);
    //qtss_printf("QTSSReflectorModule.cpp:DestroySession    sClientBroadcastSessionAttr=%lu theSession=%lu err=%ld \n",(UInt32)sClientBroadcastSessionAttr, (UInt32)theSession,theErr);
    if (theSession != NULL) // it is a broadcaster session
    {   
        ReflectorSession*   deletedSession = NULL;
        theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &deletedSession, sizeof(deletedSession));

        SourceInfo* theSoureInfo = theSession->GetSourceInfo(); 
        if (theSoureInfo == NULL)
            return QTSS_NoErr;
            
        UInt32  numStreams = theSession->GetNumStreams();
        SourceInfo::StreamInfo* theStreamInfo = NULL;
            
        for (UInt32 index = 0; index < numStreams; index++)
        {   theStreamInfo = theSoureInfo->GetStreamInfo(index);
            if (theStreamInfo != NULL)
                theStreamInfo->fSetupToReceive = false;
        }

        Bool16 killClients = false; // the pref as the default
        UInt32 theLen = sizeof(killClients);
        (void) QTSS_GetValue(inParams->inClientSession, sKillClientsEnabledAttr, 0, &killClients, &theLen);


        //qtss_printf("QTSSReflectorModule.cpp:DestroySession broadcaster theSession=%lu\n", (UInt32) theSession);
        theSession->RemoveSessionFromOutput(inParams->inClientSession);
        RemoveOutput(NULL, theSession, killClients);
    }
    else
    {
        theLen = 0;
        theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen);
        if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*)) || (theOutput == NULL))
            return QTSS_RequestFailed;
        theSession = (*theOutput)->GetReflectorSession();
    
        if (theOutput != NULL)
            outputPtr = (ReflectorOutput*) *theOutput;
            
        if (outputPtr != NULL)
            RemoveOutput(outputPtr, theSession, false);
                
    }

    return QTSS_NoErr;
}

void RemoveOutput(ReflectorOutput* inOutput, ReflectorSession* inSession, Bool16 killClients)
{
    //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput\n");
    // This function removes the output from the ReflectorSession, then
    Assert(inSession);
    if (inSession != NULL)
    {
        if (inOutput != NULL)
        {   inSession->RemoveOutput(inOutput,true);
            //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput it is a client session\n");
        }
        else
        {   // it is a Broadcaster session
            //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput it is a broadcaster session\n");
            SourceInfo* theInfo = inSession->GetSourceInfo();         
            Assert(theInfo);
            
            if (theInfo->IsRTSPControlled())
            {   
                FileDeleter(inSession->GetSourcePath());
            }
                
 
            if (killClients || sTearDownClientsOnDisconnect)
            {    inSession->TearDownAllOutputs();
            }
        }
    
        //qtss_printf("QTSSReflectorModule.cpp:RemoveOutput refcount =%lu\n", inSession->GetRef()->GetRefCount() );

        //check if the ReflectorSession should be deleted
        //(it should if its ref count has dropped to 0)
        OSMutexLocker locker (sSessionMap->GetMutex());
        //decrement the ref count
        
        OSRef* theSessionRef = inSession->GetRef();
        if (theSessionRef != NULL) 
        {               
            if (theSessionRef->GetRefCount() == 0)
            {   sSessionMap->UnRegister(theSessionRef);// we had an error while setting up
                delete inSession;
            }
            else if (theSessionRef->GetRefCount() == 1)
            {       
                //qtss_printf("QTSSReflector.cpp:RemoveOutput Delete SESSION=%lu\n",(UInt32)inSession);
                sSessionMap->Release(theSessionRef);
                sSessionMap->UnRegister(theSessionRef);  // the last session so get rid of the ref
                delete inSession;
            }
            else
            {
                //qtss_printf("QTSSReflector.cpp:RemoveOutput Release SESSION=%lu\n",(UInt32)inSession);
                sSessionMap->Release(theSessionRef); //  one of the sessions on the ref is ending just decrement the count
            }
                
        }
    }
    
    delete inOutput;
}


Bool16 AcceptSession(QTSS_StandardRTSP_Params* inParams)
{   
	QTSS_RTSPSessionObject inRTSPSession = inParams->inRTSPSession;
    QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest;

 	if (QTSSModuleUtils::UserInGroup(QTSSModuleUtils::GetUserProfileObject(theRTSPRequest), sBroadcasterGroup.Ptr, sBroadcasterGroup.Len))
   		return true; // ok we are allowing this broadcaster user
 
    char remoteAddress[20] = {0};
    StrPtrLen theClientIPAddressStr(remoteAddress,sizeof(remoteAddress));
    QTSS_Error err = QTSS_GetValue(inRTSPSession, qtssRTSPSesRemoteAddrStr, 0, (void*)theClientIPAddressStr.Ptr, &theClientIPAddressStr.Len);
    if (err != QTSS_NoErr) 
        return false;
    
    if (IPComponentStr(&theClientIPAddressStr).IsLocal())
    {   
        if (sAuthenticateLocalBroadcast)
           return false;
        else
           return true;
    }

    if  (QTSSModuleUtils::AddressInList(sPrefs, sIPAllowListID, &theClientIPAddressStr))
        return true;

    return false;
}

QTSS_Error ReflectorAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams)
{   
    if ( AcceptSession(inParams) )
        return QTSS_NoErr;

    Bool16 allowNoAccessFiles = false;
    QTSS_ActionFlags noAction = qtssActionFlagsRead;
    QTSS_ActionFlags authorizeAction = qtssActionFlagsWrite;

    return QTAccessFile::AuthorizeRequest(inParams,allowNoAccessFiles, noAction, authorizeAction);
}

QTSS_Error RedirectBroadcast(QTSS_StandardRTSP_Params* inParams)
{
	QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest;
	
	char* requestPathStr;
	(void)QTSS_GetValueAsString(theRequest, qtssRTSPReqFilePath, 0, &requestPathStr);
    QTSSCharArrayDeleter requestPathStrDeleter(requestPathStr);
	StrPtrLen theRequestPath(requestPathStr);
	StringParser theRequestPathParser(&theRequestPath);
	
	// request path begins with a '/' for ex. /mysample.mov or /redirect_broadcast_keyword/mysample.mov
	theRequestPathParser.Expect(kPathDelimiterChar);
		
	StrPtrLen theFirstPath;
	theRequestPathParser.ConsumeUntil(&theFirstPath, kPathDelimiterChar);
	Assert (theFirstPath.Len != 0);

	// If the redirect_broadcast_keyword and redirect_broadcast_dir prefs are set & the first part of the path matches the keyword
	if ( (sRedirectBroadcastsKeyword && sRedirectBroadcastsKeyword[0] != 0)
		&& (sBroadcastsRedirectDir && sBroadcastsRedirectDir[0] != 0)
		&& theFirstPath.EqualIgnoreCase(sRedirectBroadcastsKeyword, ::strlen(sRedirectBroadcastsKeyword)) )
	{
		// set qtssRTSPReqRootDir
		(void)QTSS_SetValue(theRequest, qtssRTSPReqRootDir, 0, sBroadcastsRedirectDir, ::strlen(sBroadcastsRedirectDir));
	
		// set the request file path to the new path with the keyword stripped
		StrPtrLen theStrippedRequestPath;
		theRequestPathParser.ConsumeLength(&theStrippedRequestPath, theRequestPathParser.GetDataRemaining());
		(void) QTSS_SetValue(theRequest, qtssRTSPReqFilePath, 0, theStrippedRequestPath.Ptr, theStrippedRequestPath.Len);	
	}

	return QTSS_NoErr;
}

Bool16 AllowBroadcast(QTSS_RTSPRequestObject inRTSPRequest)
{	
	// If reflection of broadcasts is disabled, return false
	if (!sReflectBroadcasts)
		return false;

	// If request path is not in any of the broadcast_dir paths, return false
	if (!InBroadcastDirList(inRTSPRequest))
		return false;
		
	return true;
}

Bool16 InBroadcastDirList(QTSS_RTSPRequestObject inRTSPRequest)
{
	Bool16 allowed = false;
	
	char* theURIPathStr;
	(void)QTSS_GetValueAsString(inRTSPRequest, qtssRTSPReqFilePath, 0, &theURIPathStr);
	QTSSCharArrayDeleter requestPathStrDeleter(theURIPathStr);
	
	char* theLocalPathStr;
	(void)QTSS_GetValueAsString(inRTSPRequest, qtssRTSPReqLocalPath, 0, &theLocalPathStr);
	StrPtrLen requestPath(theLocalPathStr);
	
	char* theRequestPathStr = NULL;
    char* theBroadcastDirStr = NULL;
	Bool16 isURI = true;
	
	UInt32 index = 0;
	UInt32 numValues = 0;


    (void) QTSS_GetNumValues(sPrefs, sBroadcastDirListID, &numValues);
    
	if (numValues == 0)
		return true;
		
    while (!allowed && (index < numValues))
    {
        (void) QTSS_GetValueAsString(sPrefs, sBroadcastDirListID, index, &theBroadcastDirStr);
		StrPtrLen theBroadcastDir(theBroadcastDirStr);
		
        if (theBroadcastDir.Len == 0) // an empty dir matches all
            return true;

		if (IsAbsolutePath(&theBroadcastDir))
		{
			theRequestPathStr = theLocalPathStr;
			isURI = false;
		}
		else
			theRequestPathStr = theURIPathStr;

		StrPtrLen requestPath(theRequestPathStr);
		StringParser requestPathParser(&requestPath);
		StrPtrLen pathPrefix;
		if (isURI)
			requestPathParser.Expect(kPathDelimiterChar);
		requestPathParser.ConsumeLength(&pathPrefix, theBroadcastDir.Len);
		
		// if the first part of the request path matches the broadcast_dir path, return true
		if (pathPrefix.Equal(theBroadcastDir))
			allowed = true;
		
        (void) QTSS_Delete(theBroadcastDirStr);		
        
        index ++;
	}

    return allowed;
}

Bool16 IsAbsolutePath(StrPtrLen *inPathPtr)
{
	StringParser thePathParser(inPathPtr);
	
#ifdef __Win32__
	if ((thePathParser[1] == ':') && (thePathParser[2] == kPathDelimiterChar))
#else
	if (thePathParser.PeekFast() == kPathDelimiterChar)
#endif
		return true;
		
	return false;
}
