/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ * */ /* File: RTSPSourceInfo.cpp Contains: */ #include "RTSPSourceInfo.h" #include "StringParser.h" #include "SDPSourceInfo.h" #include "OSMemory.h" #include "StringFormatter.h" #include "SocketUtils.h" #include "RelayOutput.h" #include "StringTranslator.h" #include "SDPUtils.h" #include "OSArrayObjectDeleter.h" StrPtrLen RTSPSourceInfo::sKeyString("rtsp_source"); StrPtrLen RTSPSourceInfo::sAnnouncedKeyString("announced_rtsp_source"); // StrPtrLen's for various keywords on the relay_source & relay_destination lines static StrPtrLen sOutAddr("out_addr"); static StrPtrLen sDestAddr("dest_addr"); static StrPtrLen sDestPorts("dest_ports"); static StrPtrLen sTtl("ttl"); char* RTSPOutputInfo::CopyString(const char* srcStr) { char* dstStr = NULL; if(srcStr != NULL) { UInt32 len = ::strlen(srcStr); dstStr = NEW char[len + 1]; ::memcpy(dstStr, srcStr, len); dstStr[len] = '\0'; } return dstStr; } void RTSPOutputInfo::Copy(const RTSPOutputInfo& copy) { fIsAnnounced = copy.fIsAnnounced; fAnnouncePort = copy.fAnnouncePort; if(copy.fDestURl != NULL) fDestURl = RTSPOutputInfo::CopyString(copy.fDestURl); if(copy.fUserName != NULL) fUserName = RTSPOutputInfo::CopyString(copy.fUserName); if(copy.fPassword != NULL) fPassword = RTSPOutputInfo::CopyString(copy.fPassword); } RTSPSourceInfo::RTSPSourceInfo(const RTSPSourceInfo& copy) :RCFSourceInfo(copy), fHostAddr(copy.fHostAddr), fHostPort(copy.fHostPort), fLocalAddr(copy.fLocalAddr), fNumSetupsComplete(copy.fNumSetupsComplete), fDescribeComplete(copy.fDescribeComplete), fAnnounce(copy.fAnnounce), fAnnounceIP(copy.fAnnounceIP), fAnnounceActualIP(copy.fAnnounceIP),fQueueElem() { fQueueElem.SetEnclosingObject(this); fSourceURL = RTSPOutputInfo::CopyString(copy.fSourceURL); fUserName = RTSPOutputInfo::CopyString(copy.fUserName); fPassword = RTSPOutputInfo::CopyString(copy.fPassword); fAnnounceURL = RTSPOutputInfo::CopyString(copy.fAnnounceURL); fRTSPInfoArray = NEW RTSPOutputInfo[fNumOutputs]; for (UInt32 index=0; index < fNumOutputs; index++) fRTSPInfoArray[index].Copy(copy.fRTSPInfoArray[index]); // These aren't set anyway and shouldn't be copied around fClientSocket = NULL; fClient = NULL; fRelaySessionCreatorTask = NULL; fSession = NULL; fSessionQueue = NULL; if ((copy.fLocalSDP).Ptr != NULL) fLocalSDP.Set((copy.fLocalSDP).GetAsCString(), (copy.fLocalSDP).Len); } RTSPSourceInfo::~RTSPSourceInfo() { OSMutexLocker locker(RelayOutput::GetQueueMutex()); if (fRelaySessionCreatorTask != NULL) fRelaySessionCreatorTask->fInfo = NULL; if (fDescribeComplete) { Assert(fClientSocket != NULL); Assert(fClient != NULL); // the task will delete these objects when it is done with the teardown TeardownTask* task = new TeardownTask(fClientSocket, fClient); task->Signal(Task::kStartEvent); } else { if (fClientSocket != NULL) delete fClientSocket; if (fClient != NULL) delete fClient; } if (fQueueElem.IsMemberOfAnyQueue()) fQueueElem.Remove(); if (fLocalSDP.Ptr != NULL) delete fLocalSDP.Ptr; fLocalSDP.Len = 0; if (fSourceURL != NULL) delete fSourceURL; if (fAnnounceURL != NULL) delete fAnnounceURL; if (fRTSPInfoArray != NULL) delete [] fRTSPInfoArray; } void RTSPSourceInfo::InitClient(UInt32 inSocketType) { fClientSocket = NEW TCPClientSocket(inSocketType); fClient = NEW RTSPClient(fClientSocket, false, RelaySession::sRelayUserAgent); } void RTSPSourceInfo::SetClientInfo(UInt32 inAddr, UInt16 inPort, char* inURL, UInt32 inLocalAddr) { if (fClientSocket != NULL) fClientSocket->Set(inAddr, inPort); StrPtrLen inURLPtrLen(inURL); if (fClient != NULL) fClient->Set(inURLPtrLen); if (inLocalAddr != 0) fClientSocket->GetSocket()->Bind(inLocalAddr, 0); } QTSS_Error RTSPSourceInfo::ParsePrefs(XMLTag* relayTag, Bool16 inAnnounce) { XMLTag* prefTag; UInt32 localAddr = 0; UInt32 theHostAddr = 0; UInt16 theHostPort = 554; char* userName = NULL; char* password = NULL; StrPtrLen theURL; fAnnounce = inAnnounce; XMLTag* sourceTag = relayTag->GetEmbeddedTagByNameAndAttr("OBJECT", "CLASS", "source"); if (sourceTag == NULL) return QTSS_ValueNotFound; prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "in_addr"); if (prefTag != NULL) { char* inAddrStr = prefTag->GetValue(); if (inAddrStr != NULL) localAddr = SocketUtils::ConvertStringToAddr(inAddrStr); } prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "source_addr"); if (prefTag != NULL) { char* destAddrStr = prefTag->GetValue(); if (destAddrStr != NULL) theHostAddr = SocketUtils::ConvertStringToAddr(destAddrStr); } prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "rtsp_port"); if (prefTag != NULL) { char* portStr = prefTag->GetValue(); if (portStr != NULL) theHostPort = atoi(portStr); } prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "name"); if (prefTag != NULL) { userName = prefTag->GetValue(); } prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "password"); if (prefTag != NULL) { password = prefTag->GetValue(); } prefTag = sourceTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "url"); char urlBuff[1024]; if (prefTag != NULL) { char* urlString = prefTag->GetValue(); StringTranslator::EncodeURL(urlString, strlen(urlString) + 1, urlBuff, sizeof(urlBuff)); theURL.Set(urlBuff); } if (fAnnounce) { fAnnounceURL = theURL.GetAsCString(); fAnnounceIP = theHostAddr; } else { fHostAddr = theHostAddr; fHostPort = theHostPort; fSourceURL = theURL.GetAsCString(); fUserName = RTSPOutputInfo::CopyString(userName); fPassword = RTSPOutputInfo::CopyString(password); } return QTSS_NoErr; } QTSS_Error RTSPSourceInfo::Describe() { QTSS_Error theErr = QTSS_NoErr; if (!fDescribeComplete) { // Work on the describe theErr = fClient->SendDescribe(); if (theErr != QTSS_NoErr) return theErr; if (fClient->GetStatus() != 200) return QTSS_RequestFailed; // If the above function returns QTSS_NoErr, we've gotten the describe response, // so process it. SDPSourceInfo theSourceInfo(fClient->GetContentBody(), fClient->GetContentLength()); // Copy the Source Info into our local SourceInfo. fNumStreams = theSourceInfo.GetNumStreams(); fStreamArray = NEW StreamInfo[fNumStreams]; for (UInt32 x = 0; x < fNumStreams; x++) { // Copy fPayloadType, fPayloadName, fTrackID, fBufferDelay fStreamArray[x].Copy(*theSourceInfo.GetStreamInfo(x)); // Copy all stream info data. Also set fSrcIPAddr to be the host addr fStreamArray[x].fSrcIPAddr = fClientSocket->GetHostAddr(); fStreamArray[x].fDestIPAddr = fClientSocket->GetLocalAddr(); fStreamArray[x].fPort = 0; fStreamArray[x].fTimeToLive = 0; } } // Ok, describe is complete, copy out the SDP information. fLocalSDP.Ptr = NEW char[fClient->GetContentLength() + 1]; // Look for an "a=range" line in the SDP. If there is one, remove it. static StrPtrLen sRangeStr("a=range:"); StrPtrLen theSDPPtr(fClient->GetContentBody(), fClient->GetContentLength()); StringParser theSDPParser(&theSDPPtr); do { // Loop until we reach the end of the SDP or hit a a=range line. StrPtrLen theSDPLine(theSDPParser.GetCurrentPosition(), theSDPParser.GetDataRemaining()); if ((theSDPLine.Len > sRangeStr.Len) && (theSDPLine.NumEqualIgnoreCase(sRangeStr.Ptr, sRangeStr.Len))) break; } while (theSDPParser.GetThruEOL(NULL)); // Copy what we have so far ::memcpy(fLocalSDP.Ptr, fClient->GetContentBody(), theSDPParser.GetDataParsedLen()); fLocalSDP.Len = theSDPParser.GetDataParsedLen(); // Skip over the range (if it exists) (void)theSDPParser.GetThruEOL(NULL); // Copy the rest of the SDP ::memcpy(fLocalSDP.Ptr + fLocalSDP.Len, theSDPParser.GetCurrentPosition(), theSDPParser.GetDataRemaining()); fLocalSDP.Len += theSDPParser.GetDataRemaining(); #define _WRITE_SDP_ 0 #if _WRITE_SDP_ FILE* outputFile = ::fopen("rtspclient.sdp", "w"); if (outputFile != NULL) { fLocalSDP.Ptr[fLocalSDP.Len] = '\0'; qtss_fprintf(outputFile, "%s", fLocalSDP.Ptr); ::fclose(outputFile); qtss_printf("Wrote sdp to rtspclient.sdp\n"); } else qtss_printf("Failed to write sdp\n"); #endif fDescribeComplete = true; return QTSS_NoErr; } QTSS_Error RTSPSourceInfo::SetupAndPlay() { QTSS_Error theErr = QTSS_NoErr; // Do all the setups. This is async, so when a setup doesn't complete // immediately, return an error, and we'll pick up where we left off. while (fNumSetupsComplete < fNumStreams) { theErr = fClient->SendUDPSetup(fStreamArray[fNumSetupsComplete].fTrackID, fStreamArray[fNumSetupsComplete].fPort); if (theErr != QTSS_NoErr) return theErr; else if (fClient->GetStatus() != 200) return QTSS_RequestFailed; else fNumSetupsComplete++; } // We've done all the setups. Now send a play. theErr = fClient->SendPlay(0); if (theErr != QTSS_NoErr) return theErr; if (fClient->GetStatus() != 200) return QTSS_RequestFailed; return QTSS_NoErr; } QTSS_Error RTSPSourceInfo::Teardown() { return (QTSS_Error)fClient->SendTeardown(); } char* RTSPSourceInfo::GetLocalSDP(UInt32* newSDPLen) { *newSDPLen = fLocalSDP.Len; return fLocalSDP.GetAsCString(); } char* RTSPSourceInfo::GetAnnounceSDP(UInt32 ipAddr, UInt32* newSDPLen) { char *announceSDP = NEW char[fLocalSDP.Len * 2]; StringFormatter announceSDPFormatter(announceSDP, fLocalSDP.Len * 2); StrPtrLen sdpLine; StringParser sdpParser(&fLocalSDP); bool added = 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; // remove any existing c lines case 'm': { if (!added) { added = true; // add a c line before the first m line char ipStr[50]; char buff[50]; StrPtrLen temp(buff); struct in_addr theIPAddr; theIPAddr.s_addr = htonl(ipAddr); SocketUtils::ConvertAddrToString(theIPAddr, &temp); qtss_sprintf(ipStr, "c=IN IP4 %s", buff); StrPtrLen tempLine(ipStr); announceSDPFormatter.Put(tempLine); announceSDPFormatter.PutEOL(); } announceSDPFormatter.Put(sdpLine); announceSDPFormatter.PutEOL(); break;//ignore connection information } default: { announceSDPFormatter.Put(sdpLine); announceSDPFormatter.PutEOL(); } } } *newSDPLen = (UInt32)announceSDPFormatter.GetCurrentOffset(); announceSDP[*newSDPLen] = 0; StrPtrLen theSDPStr(announceSDP); SDPContainer rawSDPContainer; if (!rawSDPContainer.SetSDPBuffer( &theSDPStr )) { return NULL; // it is screwed up do nothing the sdp will be deleted automatically } SDPLineSorter sortedSDP(&rawSDPContainer); return sortedSDP.GetSortedSDPCopy(); // return a new copy of the sorted SDP } void RTSPSourceInfo::ParseAnnouncedDestination(XMLTag* destTag, UInt32 index) { XMLTag* prefTag; fRTSPInfoArray[index].fIsAnnounced = true; prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "out_addr"); if (prefTag != NULL) { char* outAddrStr = prefTag->GetValue(); if (outAddrStr != NULL) fOutputArray[index].fLocalAddr = SocketUtils::ConvertStringToAddr(outAddrStr); } prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "dest_addr"); if (prefTag != NULL) { char* destAddrStr = prefTag->GetValue(); if (destAddrStr != NULL) fOutputArray[index].fDestAddr = SocketUtils::ConvertStringToAddr(destAddrStr); } prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "rtsp_port"); if (prefTag != NULL) { char* portStr = prefTag->GetValue(); if (portStr != NULL) fRTSPInfoArray[index].fAnnouncePort = atoi(portStr); } prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "name"); if (prefTag != NULL) { StrPtrLen userName(prefTag->GetValue()); fRTSPInfoArray[index].fUserName = userName.GetAsCString(); } prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "url"); if (prefTag != NULL) { char urlBuff[1024]; char* urlString = prefTag->GetValue(); StringTranslator::EncodeURL(urlString, strlen(urlString) + 1, urlBuff, sizeof(urlBuff)); StrPtrLen destURL(urlBuff); fRTSPInfoArray[index].fDestURl = destURL.GetAsCString(); } prefTag = destTag->GetEmbeddedTagByNameAndAttr("PREF", "NAME", "password"); if (prefTag != NULL) { StrPtrLen password(prefTag->GetValue()); fRTSPInfoArray[index].fPassword = password.GetAsCString(); } } void RTSPSourceInfo::AllocateOutputArray(UInt32 numOutputs) { // Allocate the proper number of relay outputs RCFSourceInfo::AllocateOutputArray(numOutputs); fRTSPInfoArray = new RTSPOutputInfo[numOutputs]; } Bool16 RTSPSourceInfo::Equal(SourceInfo* inInfo) { if (!inInfo->IsRTSPSourceInfo()) return false; // RTSPSourceInfo* info = dynamic_cast(inInfo); RTSPSourceInfo* info = (RTSPSourceInfo *)(inInfo); // if (info == NULL) // return false; if (!fAnnounce) { StrPtrLen source(fSourceURL); if( source.Equal(info->GetSourceURL()) && (fHostAddr == info->GetHostAddr()) && (fHostPort == info->GetHostPort()) ) return true; } else { StrPtrLen announceURL(fAnnounceURL); if ((fAnnounceIP == info->GetAnnounceIP()) && announceURL.Equal(info->GetAnnounceURL())) return true; } return false; } void RTSPSourceInfo::SetSourceParameters(UInt32 inHostAddr, UInt16 inHostPort, StrPtrLen& inURL) { fHostAddr = inHostAddr; fHostPort = inHostPort; fSourceURL = inURL.GetAsCString(); } void RTSPSourceInfo::StartSessionCreatorTask(OSQueue* inSessionQueue, OSQueue* inSourceQueue) { InitClient(Socket::kNonBlockingSocketType); SetClientInfo(fHostAddr, fHostPort, fSourceURL, fLocalAddr); if (fUserName != NULL) fClient->SetName(fUserName); if (fPassword != NULL) fClient->SetPassword(fPassword); fSessionQueue = inSessionQueue; if(fAnnounce) inSourceQueue->EnQueue(&fQueueElem); fSessionCreationState = kSendingDescribe; fRelaySessionCreatorTask = NEW RelaySessionCreator(this); fRelaySessionCreatorTask->Signal(Task::kStartEvent); } SInt64 RTSPSourceInfo::RelaySessionCreator::Run() { OSMutexLocker locker(RelayOutput::GetQueueMutex()); SInt64 result = -1; if (fInfo != NULL) result = fInfo->RunCreateSession(); return result; } SInt64 RTSPSourceInfo::RunCreateSession() { OS_Error osErr = OS_NoErr; SInt64 result = 500; if (fSessionCreationState == kSendingDescribe) { if (!fDescribeComplete) { osErr = fClient->SendDescribe(); if (osErr == OS_NoErr) { if (fClient->GetStatus() == 200) { // we've gotten the describe response, so process it. SDPSourceInfo theSourceInfo(fClient->GetContentBody(), fClient->GetContentLength()); // Copy the Source Info into our local SourceInfo. fNumStreams = theSourceInfo.GetNumStreams(); fStreamArray = NEW StreamInfo[fNumStreams]; for (UInt32 x = 0; x < fNumStreams; x++) { // Copy fPayloadType, fPayloadName, fTrackID, fBufferDelay fStreamArray[x].Copy(*theSourceInfo.GetStreamInfo(x)); // Copy all stream info data. Also set fSrcIPAddr to be the host addr fStreamArray[x].fSrcIPAddr = fClientSocket->GetHostAddr(); fStreamArray[x].fDestIPAddr = fClientSocket->GetLocalAddr(); fStreamArray[x].fPort = 0; fStreamArray[x].fTimeToLive = 0; } } else osErr = ENOTCONN; } } //describe is complete if(osErr == OS_NoErr) { //copy out the SDP information fLocalSDP.Ptr = NEW char[fClient->GetContentLength() + 1]; // Look for an "a=range" line in the SDP. If there is one, remove it. static StrPtrLen sRangeStr("a=range:"); StrPtrLen theSDPPtr(fClient->GetContentBody(), fClient->GetContentLength()); StringParser theSDPParser(&theSDPPtr); do { // Loop until we reach the end of the SDP or hit a a=range line. StrPtrLen theSDPLine(theSDPParser.GetCurrentPosition(), theSDPParser.GetDataRemaining()); if ((theSDPLine.Len > sRangeStr.Len) && (theSDPLine.NumEqualIgnoreCase(sRangeStr.Ptr, sRangeStr.Len))) break; } while (theSDPParser.GetThruEOL(NULL)); // Copy what we have so far ::memcpy(fLocalSDP.Ptr, fClient->GetContentBody(), theSDPParser.GetDataParsedLen()); fLocalSDP.Len = theSDPParser.GetDataParsedLen(); // Skip over the range (if it exists) (void)theSDPParser.GetThruEOL(NULL); // Copy the rest of the SDP ::memcpy(fLocalSDP.Ptr + fLocalSDP.Len, theSDPParser.GetCurrentPosition(), theSDPParser.GetDataRemaining()); fLocalSDP.Len += theSDPParser.GetDataRemaining(); #define _WRITE_SDP_ 0 #if _WRITE_SDP_ FILE* outputFile = ::fopen("rtspclient.sdp", "w"); if (outputFile != NULL) { fLocalSDP.Ptr[fLocalSDP.Len] = '\0'; qtss_fprintf(outputFile, "%s", fLocalSDP.Ptr); ::fclose(outputFile); qtss_printf("Wrote sdp to rtspclient.sdp\n"); } else qtss_printf("Failed to write sdp\n"); #endif fDescribeComplete = true; fSession = NEW RelaySession(NULL, this); if (fSession->SetupRelaySession(this) == OS_NoErr) { fSessionCreationState = kSendingSetup; } else { osErr = ENOTCONN; } } } while ((fSessionCreationState == kSendingSetup) && (osErr == OS_NoErr)) { osErr = fClient->SendUDPSetup(fStreamArray[fNumSetupsComplete].fTrackID, fStreamArray[fNumSetupsComplete].fPort); if(osErr == OS_NoErr) { if(fClient->GetStatus() == 200) { fNumSetupsComplete++; if (fNumSetupsComplete == fNumStreams) fSessionCreationState = kSendingPlay; } else osErr = ENOTCONN; } } if (fSessionCreationState == kSendingPlay) { osErr = fClient->SendPlay(0); if (osErr == OS_NoErr) { if (fClient->GetStatus() == 200) fSessionCreationState = kDone; else osErr = ENOTCONN; } } if (fSessionCreationState == kDone) { // If session was correctly set up, // add the outputs if(fSession != NULL) { // Format SourceInfo HTML for the stats web page fSession->FormatHTML(fClient->GetURL()); OSMutexLocker locker(RelayOutput::GetQueueMutex()); fSessionQueue->EnQueue(fSession->GetQueueElem()); for (UInt32 x = 0; x < fNumOutputs; x++) { SourceInfo::OutputInfo* theOutputInfo = GetOutputInfo(x); if (theOutputInfo->fAlreadySetup) continue; // shouldn't ever happen RelayOutput* theOutput = NEW RelayOutput(this, x, fSession, true); if (theOutput->IsValid()) fSession->AddOutput(theOutput, false); else delete theOutput; } } fClientSocket->GetSocket()->SetTask(NULL); //detach the task from the socket result = -1; // let the task die fRelaySessionCreatorTask = NULL; } if ((osErr == EINPROGRESS) || (osErr == EAGAIN)) { // Request an async event fClientSocket->GetSocket()->SetTask(fRelaySessionCreatorTask); fClientSocket->GetSocket()->RequestEvent(fClientSocket->GetEventMask() ); } else if (osErr != OS_NoErr) { // We encountered some fatal error with the socket. Record this as a connection failure // delete the session // delete the session if(fSession != NULL) { delete fSession; fSession = NULL; } fClientSocket->GetSocket()->SetTask(NULL); //detach the task from the socket result = -1; // let the task die fRelaySessionCreatorTask = NULL; } return result; } RTSPSourceInfo::TeardownTask::TeardownTask(TCPClientSocket* clientSocket, RTSPClient* client) { this->SetTaskName("RTSPSourceInfo::TeardownTask"); fClientSocket = clientSocket; fClient = client; } RTSPSourceInfo::TeardownTask::~TeardownTask() { delete fClientSocket; delete fClient; } SInt64 RTSPSourceInfo::TeardownTask::Run() { OS_Error err = fClient->SendTeardown(); if ((err == EINPROGRESS) || (err == EAGAIN)) { // Request an async event fClientSocket->GetSocket()->SetTask(this); fClientSocket->GetSocket()->RequestEvent(fClientSocket->GetEventMask() ); return 250; } fClientSocket->GetSocket()->SetTask(NULL); //detach the task from the socket return -1; // we're out of here, this will cause the destructor to be called }