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

    Contains:   Implementation of object defined in .h file


*/

#include "SDPSourceInfo.h"

#include "StringParser.h"
#include "StringFormatter.h"
#include "OSMemory.h"
#include "SocketUtils.h"
#include "StrPtrLen.h"
#include "SDPUtils.h"
#include "OSArrayObjectDeleter.h"

static StrPtrLen    sCLine("c=IN IP4 0.0.0.0\r\na=control:*\r\n");
static StrPtrLen    sVideoStr("video");
static StrPtrLen    sAudioStr("audio");
static StrPtrLen    sRtpMapStr("rtpmap");
static StrPtrLen    sControlStr("control");
static StrPtrLen    sBufferDelayStr("x-bufferdelay");
static StrPtrLen    sBroadcastControlStr("x-broadcastcontrol");
static StrPtrLen    sAutoDisconnect("RTSP");
static StrPtrLen    sAutoDisconnectTime("TIME");

SDPSourceInfo::~SDPSourceInfo()
{
    // Not reqd as the destructor of the 
    // base class will take care of delete the stream array
    // and output array if allocated
    /* 
    if (fStreamArray != NULL)
    {
        char* theCharArray = (char*)fStreamArray;
        delete [] theCharArray;
    }
    */
    
    fSDPData.Delete();
}

char* SDPSourceInfo::GetLocalSDP(UInt32* newSDPLen)
{
    Assert(fSDPData.Ptr != NULL);

    Bool16 appendCLine = true;
    UInt32 trackIndex = 0;
    
    char *localSDP = NEW char[fSDPData.Len * 2];
    OSCharArrayDeleter charArrayPathDeleter(localSDP);
    StringFormatter localSDPFormatter(localSDP, fSDPData.Len * 2);

    StrPtrLen sdpLine;
    StringParser sdpParser(&fSDPData);
    char trackIndexBuffer[50];
    
    // Only generate our own trackIDs if this file doesn't have 'em.
    // Our assumption here is that either the file has them, or it doesn't.
    // A file with some trackIDs, and some not, won't work.
    Bool16 hasTrackIDs = false;

    while (sdpParser.GetDataRemaining() > 0)
    {
        //stop when we reach an empty line.
        sdpParser.GetThruEOL(&sdpLine);
        if (sdpLine.Len == 0)
            continue;
            
        switch (*sdpLine.Ptr)
        {
            case 'c':
                break;//ignore connection information
            case 'm':
            {
                //append new connection information right before the first 'm'
                if (appendCLine)
                {
                    localSDPFormatter.Put(sCLine);
                    appendCLine = false;
                }
                //the last "a=" for each m should be the control a=
                if ((trackIndex > 0) && (!hasTrackIDs))
                {
                    qtss_sprintf(trackIndexBuffer, "a=control:trackID=%ld\r\n",trackIndex);
                    localSDPFormatter.Put(trackIndexBuffer, ::strlen(trackIndexBuffer));
                }
                //now write the 'm' line, but strip off the port information
                StringParser mParser(&sdpLine);
                StrPtrLen mPrefix;
                mParser.ConsumeUntil(&mPrefix, StringParser::sDigitMask);
                localSDPFormatter.Put(mPrefix);
                localSDPFormatter.Put("0", 1);
                (void)mParser.ConsumeInteger(NULL);
                localSDPFormatter.Put(mParser.GetCurrentPosition(), mParser.GetDataRemaining());
                localSDPFormatter.PutEOL();
                trackIndex++;
                break;
            }
            case 'a':
            {
                localSDPFormatter.Put(sdpLine);
                localSDPFormatter.PutEOL();

                StringParser aParser(&sdpLine);
                aParser.ConsumeLength(NULL, 2);//go past 'a='
                StrPtrLen aLineType;
                aParser.ConsumeWord(&aLineType);
                if (aLineType.Equal(sControlStr))
                    hasTrackIDs = true;
                break;
            }
            default:
            {
                localSDPFormatter.Put(sdpLine);
                localSDPFormatter.PutEOL();
            }
        }
    }
    
    if ((trackIndex > 0) && (!hasTrackIDs))
    {
        qtss_sprintf(trackIndexBuffer, "a=control:trackID=%ld\r\n",trackIndex);
        localSDPFormatter.Put(trackIndexBuffer, ::strlen(trackIndexBuffer));
    }
    *newSDPLen = (UInt32)localSDPFormatter.GetCurrentOffset();
    
    StrPtrLen theSDPStr(localSDP);
    SDPContainer rawSDPContainer; 
    (void) rawSDPContainer.SetSDPBuffer( &theSDPStr );    
    SDPLineSorter sortedSDP(&rawSDPContainer);

    return sortedSDP.GetSortedSDPCopy(); // return a new copy of the sorted SDP
}


void SDPSourceInfo::Parse(char* sdpData, UInt32 sdpLen)
{
    //
    // There are some situations in which Parse can be called twice.
    // If that happens, just return and don't do anything the second time.
    if (fSDPData.Ptr != NULL)
        return;
        
    Assert(fStreamArray == NULL);
    
    char *sdpDataCopy = NEW char[sdpLen];
    Assert(sdpDataCopy != NULL);
    
    memcpy(sdpDataCopy,sdpData, sdpLen);
    fSDPData.Set(sdpDataCopy, sdpLen);

    // If there is no trackID information in this SDP, we make the track IDs start
    // at 1 -> N
    UInt32 currentTrack = 1;
    
    Bool16 hasGlobalStreamInfo = false;
    StreamInfo theGlobalStreamInfo; //needed if there is one c= header independent of
                                    //individual streams

    StrPtrLen sdpLine;
    StringParser trackCounter(&fSDPData);
    StringParser sdpParser(&fSDPData);
    UInt32 theStreamIndex = 0;

    //walk through the SDP, counting up the number of tracks
    // Repeat until there's no more data in the SDP
    while (trackCounter.GetDataRemaining() > 0)
    {
        //each 'm' line in the SDP file corresponds to another track.
        trackCounter.GetThruEOL(&sdpLine);
        if ((sdpLine.Len > 0) && (sdpLine.Ptr[0] == 'm'))
            fNumStreams++;  
    }

    //We should scale the # of StreamInfos to the # of trax, but we can't because
    //of an annoying compiler bug...
    
    fStreamArray = NEW StreamInfo[fNumStreams];

    // set the default destination as our default IP address and set the default ttl
    theGlobalStreamInfo.fDestIPAddr = INADDR_ANY;
    theGlobalStreamInfo.fTimeToLive = kDefaultTTL;
        
    //Set bufferdelay to default of 3
    theGlobalStreamInfo.fBufferDelay = (Float32) eDefaultBufferDelay;
    
    //Now actually get all the data on all the streams
    while (sdpParser.GetDataRemaining() > 0)
    {
        sdpParser.GetThruEOL(&sdpLine);
        if (sdpLine.Len == 0)
            continue;//skip over any blank lines

        switch (*sdpLine.Ptr)
        {
            case 't':
            {
                StringParser mParser(&sdpLine);
                                
                mParser.ConsumeUntil(NULL, StringParser::sDigitMask);
                UInt32 ntpStart = mParser.ConsumeInteger(NULL);
                
                mParser.ConsumeUntil(NULL, StringParser::sDigitMask);               
                UInt32 ntpEnd = mParser.ConsumeInteger(NULL);
                
                SetActiveNTPTimes(ntpStart,ntpEnd);
            }
            break;
            
            case 'm':
            {
                if (hasGlobalStreamInfo)
                {
                    fStreamArray[theStreamIndex].fDestIPAddr = theGlobalStreamInfo.fDestIPAddr;
                    fStreamArray[theStreamIndex].fTimeToLive = theGlobalStreamInfo.fTimeToLive;
                }
                fStreamArray[theStreamIndex].fTrackID = currentTrack;
                currentTrack++;
                
                StringParser mParser(&sdpLine);
                
                //find out what type of track this is
                mParser.ConsumeLength(NULL, 2);//go past 'm='
                StrPtrLen theStreamType;
                mParser.ConsumeWord(&theStreamType);
                if (theStreamType.Equal(sVideoStr))
                    fStreamArray[theStreamIndex].fPayloadType = qtssVideoPayloadType;
                else if (theStreamType.Equal(sAudioStr))
                    fStreamArray[theStreamIndex].fPayloadType = qtssAudioPayloadType;
                    
                //find the port for this stream
                mParser.ConsumeUntil(NULL, StringParser::sDigitMask);
                SInt32 tempPort = mParser.ConsumeInteger(NULL);
                if ((tempPort > 0) && (tempPort < 65536))
                    fStreamArray[theStreamIndex].fPort = (UInt16) tempPort;
                    
                // find out whether this is TCP or UDP
                mParser.ConsumeWhitespace();
                StrPtrLen transportID;
                mParser.ConsumeWord(&transportID);
                
                static const StrPtrLen kTCPTransportStr("RTP/AVP/TCP");
                if (transportID.Equal(kTCPTransportStr))
                    fStreamArray[theStreamIndex].fIsTCP = true;
                    
                theStreamIndex++;
            }
            break;
            case 'a':
            {
                StringParser aParser(&sdpLine);

                aParser.ConsumeLength(NULL, 2);//go past 'a='

                StrPtrLen aLineType;

                aParser.ConsumeWord(&aLineType);



                if (aLineType.Equal(sBroadcastControlStr))

                {   // found a control line for the broadcast (delete at time or delete at end of broadcast/server startup) 

                    // qtss_printf("found =%s\n",sBroadcastControlStr);

                    aParser.ConsumeUntil(NULL,StringParser::sWordMask);

                    StrPtrLen sessionControlType;

                    aParser.ConsumeWord(&sessionControlType);

                    if (sessionControlType.Equal(sAutoDisconnect))
                    {
                       fSessionControlType = kRTSPSessionControl; 
                    }       
                    else if (sessionControlType.Equal(sAutoDisconnectTime))
                    {
                       fSessionControlType = kSDPTimeControl; 
                    }       
                    

                }

                //if we haven't even hit an 'm' line yet, just ignore all 'a' lines
                if (theStreamIndex == 0)
                    break;
                    
                if (aLineType.Equal(sRtpMapStr))
                {
                    //mark the codec type if this line has a codec name on it. If we already
                    //have a codec type for this track, just ignore this line
                    if ((fStreamArray[theStreamIndex - 1].fPayloadName.Len == 0) &&
                        (aParser.GetThru(NULL, ' ')))
                    {
                        StrPtrLen payloadNameFromParser;
                        (void)aParser.GetThruEOL(&payloadNameFromParser);
                                                char* temp = payloadNameFromParser.GetAsCString();
//                                                qtss_printf("payloadNameFromParser (%x) = %s\n", temp, temp);
                        (fStreamArray[theStreamIndex - 1].fPayloadName).Set(temp, payloadNameFromParser.Len);
//                                                qtss_printf("%s\n", fStreamArray[theStreamIndex - 1].fPayloadName.Ptr);
                    }
                }
                else if (aLineType.Equal(sControlStr))
                {           
                    //mark the trackID if that's what this line has
                    aParser.ConsumeUntil(NULL, StringParser::sDigitMask);
                    fStreamArray[theStreamIndex - 1].fTrackID = aParser.ConsumeInteger(NULL);
                }
                else if (aLineType.Equal(sBufferDelayStr))
                {   // if a BufferDelay is found then set all of the streams to the same buffer delay (it's global)
                    aParser.ConsumeUntil(NULL, StringParser::sDigitMask);
                    theGlobalStreamInfo.fBufferDelay = aParser.ConsumeFloat();
                }

            }
            break;
            case 'c':
            {
                //get the IP address off this header
                StringParser cParser(&sdpLine);
                cParser.ConsumeLength(NULL, 9);//strip off "c=in ip4 "
                UInt32 tempIPAddr = SDPSourceInfo::GetIPAddr(&cParser, '/');
                                
                //grab the ttl
                SInt32 tempTtl = kDefaultTTL;
                if (cParser.GetThru(NULL, '/'))
                {
                    tempTtl = cParser.ConsumeInteger(NULL);
                    Assert(tempTtl >= 0);
                    Assert(tempTtl < 65536);
                }

                if (theStreamIndex > 0)
                {
                    //if this c= line is part of a stream, it overrides the
                    //global stream information
                    fStreamArray[theStreamIndex - 1].fDestIPAddr = tempIPAddr;
                    fStreamArray[theStreamIndex - 1].fTimeToLive = (UInt16) tempTtl;
                } else {
                    theGlobalStreamInfo.fDestIPAddr = tempIPAddr;
                    theGlobalStreamInfo.fTimeToLive = (UInt16) tempTtl;
                    hasGlobalStreamInfo = true;
                }
            }
        }
    }       
    
    // Add the default buffer delay
    Float32 bufferDelay = (Float32) eDefaultBufferDelay;
    if (theGlobalStreamInfo.fBufferDelay != (Float32) eDefaultBufferDelay)
        bufferDelay = theGlobalStreamInfo.fBufferDelay;
    
    UInt32 count = 0;
    while (count < fNumStreams)
    {   fStreamArray[count].fBufferDelay = bufferDelay;
        count ++;
    }
        
}

UInt32 SDPSourceInfo::GetIPAddr(StringParser* inParser, char inStopChar)
{
    StrPtrLen ipAddrStr;

    // Get the IP addr str
    inParser->ConsumeUntil(&ipAddrStr, inStopChar);
    
    if (ipAddrStr.Len == 0)
        return 0;
    
    // NULL terminate it
    char endChar = ipAddrStr.Ptr[ipAddrStr.Len];
    ipAddrStr.Ptr[ipAddrStr.Len] = '\0';
    
    //inet_addr returns numeric IP addr in network byte order, make
    //sure to convert to host order.
    UInt32 ipAddr = SocketUtils::ConvertStringToAddr(ipAddrStr.Ptr);
    
    // Make sure to put the old char back!
    ipAddrStr.Ptr[ipAddrStr.Len] = endChar;

    return ipAddr;
}

