/*
 *
 * @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:       QTSSRawFileModule.cpp

    Contains:   Implementation of Raw File module

    

*/

#include "QTSSRawFileModule.h"
#include "OSHeaders.h"
#include "StrPtrLen.h"
#include "OSArrayObjectDeleter.h"
#include "QTSSModuleUtils.h"
#include "OSMemory.h"
#include "ev.h"
#include "QTSSMemoryDeleter.h"

#define RAWFILE_FILE_ASYNC 1
#define RAW_FILE_DEBUGGING 0


// ATTRIBUTES IDs

static QTSS_AttributeID sStateAttr                  = qtssIllegalAttrID;
static QTSS_AttributeID sFileAttr                   = qtssIllegalAttrID;
static QTSS_AttributeID sFileBufferAttr             = qtssIllegalAttrID;
static QTSS_AttributeID sReadOffsetAttr             = qtssIllegalAttrID;
static QTSS_AttributeID sWriteOffsetAttr            = qtssIllegalAttrID;

// STATIC DATA

static StrPtrLen    sRawSuffix(".raw");

static const UInt32 kReadingBufferState = 0;
static const UInt32 kWritingBufferState = 1;

// FUNCTIONS

static QTSS_Error QTSSRawFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams);
static QTSS_Error   Register(QTSS_Register_Params* inParams);
static QTSS_Error Preprocess(QTSS_StandardRTSP_Params* inParams);



QTSS_Error QTSSRawFileModule_Main(void* inPrivateArgs)
{
    return _stublibrary_main(inPrivateArgs, QTSSRawFileModuleDispatch);
}


QTSS_Error QTSSRawFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams)
{
    switch (inRole)
    {
        case QTSS_Register_Role:
            return Register(&inParams->regParams);
        case QTSS_RTSPPreProcessor_Role:
            return Preprocess(&inParams->rtspPreProcessorParams);
    }
    return QTSS_NoErr;
}

QTSS_Error Register(QTSS_Register_Params* inParams)
{
    (void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role);

    static char*        sStateName          =   "QTSSRawFileModuleState";
    static char*        sFileName           =   "QTSSRawFileModuleFile";
    static char*        sFileBufferName     =   "QTSSRawFileModuleFileBuffer";
    static char*        sReadOffsetName     =   "QTSSRawFileModuleReadOffset";
    static char*        sWriteOffsetName    =   "QTSSRawFileModuleWriteOffset";

    (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sStateName, NULL, qtssAttrDataTypeUInt32);
    (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sStateName, &sStateAttr);
    
    (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileName, NULL, qtssAttrDataTypeUInt32);
    (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileName, &sFileAttr);

    (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sFileBufferName, NULL, qtssAttrDataTypeCharArray);
    (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sFileBufferName, &sFileBufferAttr);

    (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sReadOffsetName, NULL, qtssAttrDataTypeUInt32);
    (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sReadOffsetName, &sReadOffsetAttr);

    (void)QTSS_AddStaticAttribute(qtssRTSPSessionObjectType, sWriteOffsetName, NULL, qtssAttrDataTypeUInt32);
    (void)QTSS_IDForAttr(qtssRTSPSessionObjectType, sWriteOffsetName, &sWriteOffsetAttr);

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

    return QTSS_NoErr;
}


QTSS_Error Preprocess(QTSS_StandardRTSP_Params* inParams)
{
    static UInt32 sFileBufSize = 32768;
    static UInt32 sInitialState = kReadingBufferState;
    static UInt32 sZero = 0;
    
    UInt32 theLen = 0;
    UInt32* theStateP = NULL;
    QTSS_Error theErr = QTSS_NoErr;

    QTSS_Object theFile = NULL;
    
    (void)QTSS_GetValuePtr(inParams->inRTSPSession, sStateAttr, 0, (void**)&theStateP, &theLen);
    if ((theStateP == NULL) || (theLen != sizeof(UInt32)))
    {
        // Initial state. We haven't started sending the file yet, so
        // check to see if this is our request, and if it is, set everything up.
        
        // Only operate if this is a DESCRIBE
        QTSS_RTSPMethod* theMethod = NULL;
        UInt32 theLen = 0;
        if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0,
                (void**)&theMethod, &theLen) != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod)))
        {
            Assert(0);
            return QTSS_RequestFailed;
        }

        if (*theMethod != qtssDescribeMethod)
            return QTSS_RequestFailed;

        // Check to see if this is a raw file request
        char* theFilePath = NULL;          
        (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFilePath);
		QTSSCharArrayDeleter theFilePathDeleter(theFilePath);
        theLen = ::strlen(theFilePath);
		
        // Copy the full path, and append a ".raw"
        OSCharArrayDeleter rawPath(NEW char[theLen + sRawSuffix.Len + 4]);
        ::memcpy(rawPath.GetObject(), theFilePath, theLen);
        ::strcpy(rawPath.GetObject() + theLen, sRawSuffix.Ptr);
        
#if RAWFILE_FILE_ASYNC
        theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile);
#else
        theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile);
#endif
        
        // If the file doesn't exist, and if this is a path with a '.raw' at the end,
        // check to see if the path without the extra .raw exists
        if (theErr != QTSS_NoErr)
        {
            theFile = NULL;
            rawPath.GetObject()[theLen] = '\0';
            
            if (theLen > sRawSuffix.Len)
            {
                StrPtrLen comparer((theFilePath + theLen) - sRawSuffix.Len, sRawSuffix.Len);
                if (comparer.Equal(sRawSuffix))
                {
#if RAWFILE_FILE_ASYNC
                    theErr = QTSS_OpenFileObject(rawPath.GetObject(), qtssOpenFileAsync, &theFile);
#else
                    theErr = QTSS_OpenFileObject(rawPath.GetObject(), kOpenFileNoFlags, &theFile);
#endif
                }
            }
        }

        // If the file doesn't exist, we should probably return a 404 not found.
        if (theErr != QTSS_NoErr)
            return QTSS_RequestFailed;

        // Before sending any response, set keep alive to off for this connection
        // Regardless of what the client sends, the server always closes the connection after sending the file
        static Bool16 sFalse = false;
        (void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse));

        // We have a real file. Setup all the dictionary values we need
        (void)QTSS_SetValue(inParams->inRTSPSession, sFileAttr, 0, &theFile, sizeof(theFile));

        // Create a buffer to store data.
        char* theFileBuffer = NEW char[sFileBufSize];
        (void)QTSS_SetValue(inParams->inRTSPSession, sFileBufferAttr, 0, &theFileBuffer, sizeof(theFileBuffer));

        // Store our initial state
        (void)QTSS_SetValue(inParams->inRTSPSession, sStateAttr, 0, &sInitialState, sizeof(sInitialState));
        theStateP = &sInitialState; // so we can proceed normally

        (void)QTSS_SetValue(inParams->inRTSPSession, sReadOffsetAttr, 0, &sZero, sizeof(sZero));
        (void)QTSS_SetValue(inParams->inRTSPSession, sWriteOffsetAttr, 0, &sZero, sizeof(sZero));
    }
    
    // Get our attributes
    char** theFileBufferP = NULL;
    (void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileBufferAttr, 0, (void**)&theFileBufferP, &theLen);
    Assert(theFileBufferP != NULL);
    Assert(theLen == sizeof(char*));
    
    QTSS_Object* theFileP = NULL;
    (void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileAttr, 0, (void**)&theFileP, &theLen);
    Assert(theFileP != NULL);
    Assert(theLen == sizeof(QTSS_Object));
    
    UInt32* theReadOffsetP = NULL;
    (void)QTSS_GetValuePtr(inParams->inRTSPSession, sReadOffsetAttr, 0, (void**)&theReadOffsetP, &theLen);
    Assert(theReadOffsetP != NULL);
    Assert(theLen == sizeof(UInt32));

    UInt32* theWriteOffsetP = NULL;
    (void)QTSS_GetValuePtr(inParams->inRTSPSession, sWriteOffsetAttr, 0, (void**)&theWriteOffsetP, &theLen);
    Assert(theWriteOffsetP != NULL);
    Assert(theLen == sizeof(UInt32));
    
    UInt32 theReadOffset = *theReadOffsetP;
    UInt32 theWriteOffset = *theWriteOffsetP;
    UInt32 theState = *theStateP;

    Bool16 isBlocked = false;
    
    // Get the length of the file onto the stack
    theLen = sizeof(UInt64);
    UInt64 theFileLength = 0;
    theErr = QTSS_GetValue(*theFileP, qtssFlObjLength, 0, (void*)&theFileLength, &theLen);
    Assert(theErr == QTSS_NoErr);
    Assert(theLen == sizeof(UInt64));

    // Get the offset in the file onto the stack
    theLen = sizeof(UInt64);
    UInt64 theOffset = 0;
    theErr = QTSS_GetValue(*theFileP, qtssFlObjPosition, 0, (void*)&theOffset, &theLen);
    Assert(theErr == QTSS_NoErr);
    Assert(theLen == sizeof(UInt64));

    while (!isBlocked)
    {
        // If we have less than the full buffer size left to go in the file,
        // down adjust our buffer size to be the amount of data remaining in the file
        UInt32 theBufferSize = sFileBufSize;
        if ((theFileLength - theOffset) < sFileBufSize)
            theBufferSize = (UInt32) (theFileLength - theOffset);

        switch (theState)
        {
            case kReadingBufferState:
            {
                // Read as much data as possible out of the file
                UInt32 theRecvLen = 0;
                (void)QTSS_Read(*theFileP,
                                (*theFileBufferP) + theReadOffset,
                                    theBufferSize - theReadOffset,
                                    &theRecvLen);
                theReadOffset += theRecvLen;
                theOffset += theRecvLen;
#if RAW_FILE_DEBUGGING
                qtss_printf("Got %lu bytes back from file read. Now at: %"_64BITARG_"u\n", theRecvLen, theOffset);
#endif
                if (theReadOffset < theBufferSize)
                {
#if RAW_FILE_DEBUGGING
                    qtss_printf("Flow controlled on file. Waiting for read event\n");
#endif
                    isBlocked = true;
                    break;
                }
                
                theReadOffset = 0;
                Assert(theWriteOffset == 0);
                theState = kWritingBufferState;
            }
            case kWritingBufferState:
            {
                UInt32 theWrittenLen = 0;
                
                // for debugging purposes, construct an IOVec out of this data
                iovec theVec[5];
                UInt32 units = (theBufferSize - theWriteOffset) /4;
                UInt32 offset = theWriteOffset;
                theVec[1].iov_base = (*theFileBufferP) + offset;
                theVec[1].iov_len = units;
                offset += units;
                theVec[2].iov_base = (*theFileBufferP) + offset;
                theVec[2].iov_len = units;
                offset += units;
                theVec[3].iov_base = (*theFileBufferP) + offset;
                theVec[3].iov_len = units;
                offset += units;
                theVec[4].iov_base = (*theFileBufferP) + offset;
                theVec[4].iov_len = theBufferSize - offset;
                
                (void)QTSS_WriteV(  inParams->inRTSPSession,
                                    theVec, 5,
                                    theBufferSize - theWriteOffset,
                                    &theWrittenLen);
                theWriteOffset += theWrittenLen;
#if RAW_FILE_DEBUGGING
                qtss_printf("Got %lu bytes back from socket write.\n", theWrittenLen);
#endif
                if (theWriteOffset < theBufferSize)
                {
#if RAW_FILE_DEBUGGING
                    qtss_printf("Flow controlled on socket. Waiting for write event.\n");
#endif
                    isBlocked = true;
                    break;
                }
                
                // Check to see if we're done. If we are, delete stuff and return
                if (theOffset == theFileLength)
                {
#if RAW_FILE_DEBUGGING
                    qtss_printf("File transfer complete\n");
#endif
                    return QTSS_NoErr;
                }
                
                theWriteOffset = 0;
                Assert(theReadOffset == 0);
                theState = kReadingBufferState;
            }
        }
    }
    
    Assert(isBlocked);
    
    // We've reached a blocking condition for some reason.
    // Save our state, request an event, and return.
    (void)QTSS_SetValue(inParams->inRTSPSession, sReadOffsetAttr, 0, &theReadOffset, sizeof(theReadOffset));
    (void)QTSS_SetValue(inParams->inRTSPSession, sWriteOffsetAttr, 0, &theWriteOffset, sizeof(theWriteOffset));
    (void)QTSS_SetValue(inParams->inRTSPSession, sStateAttr, 0, &theState, sizeof(theState));

    // If we're reading, wait for the file to become readable
    if (theState == kReadingBufferState)
        (void)QTSS_RequestEvent(*theFileP, QTSS_ReadableEvent);
    // If we're writing, wait for the socket to become writable
    else
        (void)QTSS_RequestEvent(inParams->inRTSPSession, QTSS_WriteableEvent);
    return QTSS_NoErr;
}

QTSS_Error CloseRTSPSession(QTSS_RTSPSession_Params* inParams)
{
    // In this role, the allocated resources are deleted before closing the RTSP session
    UInt32 theLen = 0;
    
    // Get our file buffer pointer and delete it
    char** theFileBufferP = NULL;                   // File buffer pointer
    (void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileBufferAttr, 0, (void**)&theFileBufferP, &theLen);
    if (theFileBufferP != NULL) 
        delete [] *theFileBufferP;
    
    QTSS_Object* theFileP = NULL;
    // Get our file pointer and delete it
    (void)QTSS_GetValuePtr(inParams->inRTSPSession, sFileAttr, 0, (void**)&theFileP, &theLen);
    Assert(theFileP != NULL);
    Assert(theLen == sizeof(QTSS_Object));
    (void)QTSS_CloseFileObject(*theFileP);

    return QTSS_NoErr;
}
