liveMedia/RTSPClient.cpp

Go to the documentation of this file.
00001 /**********
00002 This library is free software; you can redistribute it and/or modify it under
00003 the terms of the GNU Lesser General Public License as published by the
00004 Free Software Foundation; either version 2.1 of the License, or (at your
00005 option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
00006 
00007 This library is distributed in the hope that it will be useful, but WITHOUT
00008 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00009 FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
00010 more details.
00011 
00012 You should have received a copy of the GNU Lesser General Public License
00013 along with this library; if not, write to the Free Software Foundation, Inc.,
00014 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00015 **********/
00016 // "liveMedia"
00017 // Copyright (c) 1996-2008 Live Networks, Inc.  All rights reserved.
00018 // A generic RTSP client
00019 // Implementation
00020 
00021 #include "RTSPClient.hh"
00022 #include "RTSPCommon.hh"
00023 #include "Base64.hh"
00024 #include "Locale.hh"
00025 #include <GroupsockHelper.hh>
00026 #include "our_md5.h"
00027 #ifdef SUPPORT_REAL_RTSP
00028 #include "../RealRTSP/include/RealRTSP.hh"
00029 #endif
00030 
00032 
00033 RTSPClient* RTSPClient::createNew(UsageEnvironment& env,
00034                                   int verbosityLevel,
00035                                   char const* applicationName,
00036                                   portNumBits tunnelOverHTTPPortNum) {
00037   return new RTSPClient(env, verbosityLevel,
00038                         applicationName, tunnelOverHTTPPortNum);
00039 }
00040 
00041 Boolean RTSPClient::lookupByName(UsageEnvironment& env,
00042                                  char const* instanceName,
00043                                  RTSPClient*& resultClient) {
00044   resultClient = NULL; // unless we succeed
00045 
00046   Medium* medium;
00047   if (!Medium::lookupByName(env, instanceName, medium)) return False;
00048 
00049   if (!medium->isRTSPClient()) {
00050     env.setResultMsg(instanceName, " is not a RTSP client");
00051     return False;
00052   }
00053 
00054   resultClient = (RTSPClient*)medium;
00055   return True;
00056 }
00057 
00058 unsigned RTSPClient::fCSeq = 0;
00059 
00060 RTSPClient::RTSPClient(UsageEnvironment& env,
00061                        int verbosityLevel, char const* applicationName,
00062                        portNumBits tunnelOverHTTPPortNum)
00063   : Medium(env),
00064     fVerbosityLevel(verbosityLevel),
00065     fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum),
00066     fInputSocketNum(-1), fOutputSocketNum(-1), fServerAddress(0),
00067     fBaseURL(NULL), fTCPStreamIdCount(0), fLastSessionId(NULL),
00068     fSessionTimeoutParameter(0),
00069 #ifdef SUPPORT_REAL_RTSP
00070     fRealChallengeStr(NULL), fRealETagStr(NULL),
00071 #endif
00072     fServerIsKasenna(False), fKasennaContentType(NULL),
00073     fServerIsMicrosoft(False)
00074 {
00075   fResponseBufferSize = 20000;
00076   fResponseBuffer = new char[fResponseBufferSize+1];
00077 
00078   // Set the "User-Agent:" header to use in each request:
00079   char const* const libName = "LIVE555 Streaming Media v";
00080   char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
00081   char const* libPrefix; char const* libSuffix;
00082   if (applicationName == NULL || applicationName[0] == '\0') {
00083     applicationName = libPrefix = libSuffix = "";
00084   } else {
00085     libPrefix = " (";
00086     libSuffix = ")";
00087   }
00088   char const* const formatStr = "User-Agent: %s%s%s%s%s\r\n";
00089   unsigned headerSize
00090     = strlen(formatStr) + strlen(applicationName) + strlen(libPrefix)
00091     + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix);
00092   fUserAgentHeaderStr = new char[headerSize];
00093   sprintf(fUserAgentHeaderStr, formatStr,
00094           applicationName, libPrefix, libName, libVersionStr, libSuffix);
00095   fUserAgentHeaderStrSize = strlen(fUserAgentHeaderStr);
00096 }
00097 
00098 void RTSPClient::setUserAgentString(char const* userAgentStr) {
00099   if (userAgentStr == NULL) return;
00100 
00101   // Change the existing user agent header string:
00102   char const* const formatStr = "User-Agent: %s\r\n";
00103   unsigned const headerSize = strlen(formatStr) + strlen(userAgentStr) + 1;
00104   delete[] fUserAgentHeaderStr;
00105   fUserAgentHeaderStr = new char[headerSize];
00106   sprintf(fUserAgentHeaderStr, formatStr, userAgentStr);
00107   fUserAgentHeaderStrSize = strlen(fUserAgentHeaderStr);
00108 }
00109 
00110 RTSPClient::~RTSPClient() {
00111   envir().taskScheduler().turnOffBackgroundReadHandling(fInputSocketNum); // must be called before:
00112   reset();
00113 
00114   delete[] fResponseBuffer;
00115   delete[] fUserAgentHeaderStr;
00116 }
00117 
00118 Boolean RTSPClient::isRTSPClient() const {
00119   return True;
00120 }
00121 
00122 void RTSPClient::resetTCPSockets() {
00123   if (fInputSocketNum >= 0) {
00124     ::closeSocket(fInputSocketNum);
00125     if (fOutputSocketNum != fInputSocketNum) ::closeSocket(fOutputSocketNum);
00126   }
00127   fInputSocketNum = fOutputSocketNum = -1;
00128 }
00129 
00130 void RTSPClient::reset() {
00131   resetTCPSockets();
00132   fServerAddress = 0;
00133 
00134   delete[] fBaseURL; fBaseURL = NULL;
00135 
00136   fCurrentAuthenticator.reset();
00137 
00138   delete[] fKasennaContentType; fKasennaContentType = NULL;
00139 #ifdef SUPPORT_REAL_RTSP
00140   delete[] fRealChallengeStr; fRealChallengeStr = NULL;
00141   delete[] fRealETagStr; fRealETagStr = NULL;
00142 #endif
00143   delete[] fLastSessionId; fLastSessionId = NULL;
00144 }
00145 
00146 static char* getLine(char* startOfLine) {
00147   // returns the start of the next line, or NULL if none
00148   for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) {
00149     // Check for the end of line: \r\n (but also accept \r or \n by itself):
00150     if (*ptr == '\r' || *ptr == '\n') {
00151       // We found the end of the line
00152       if (*ptr == '\r') {
00153         *ptr++ = '\0';
00154         if (*ptr == '\n') ++ptr;
00155       } else {
00156         *ptr++ = '\0';
00157       }
00158       return ptr;
00159     }
00160   }
00161 
00162   return NULL;
00163 }
00164 
00165 char* RTSPClient::describeURL(char const* url, Authenticator* authenticator,
00166                               Boolean allowKasennaProtocol) {
00167   char* cmd = NULL;
00168   fDescribeStatusCode = 0;
00169   do {  
00170     // First, check whether "url" contains a username:password to be used:
00171     char* username; char* password;
00172     if (authenticator == NULL
00173         && parseRTSPURLUsernamePassword(url, username, password)) {
00174       char* result = describeWithPassword(url, username, password, allowKasennaProtocol);
00175       delete[] username; delete[] password; // they were dynamically allocated
00176       return result;
00177     }
00178 
00179     if (!openConnectionFromURL(url, authenticator)) break;
00180 
00181     // Send the DESCRIBE command:
00182 
00183     // First, construct an authenticator string:
00184     fCurrentAuthenticator.reset();
00185     char* authenticatorStr
00186       = createAuthenticatorString(authenticator, "DESCRIBE", url);
00187 
00188     char const* acceptStr = allowKasennaProtocol
00189       ? "Accept: application/x-rtsp-mh, application/sdp\r\n"
00190       : "Accept: application/sdp\r\n";
00191 
00192     // (Later implement more, as specified in the RTSP spec, sec D.1 #####)
00193     char* const cmdFmt =
00194       "DESCRIBE %s RTSP/1.0\r\n"
00195       "CSeq: %d\r\n"
00196       "%s"
00197       "%s"
00198       "%s"
00199 #ifdef SUPPORT_REAL_RTSP
00200       REAL_DESCRIBE_HEADERS
00201 #endif
00202       "\r\n";
00203     unsigned cmdSize = strlen(cmdFmt)
00204       + strlen(url)
00205       + 20 /* max int len */
00206       + strlen(acceptStr)
00207       + strlen(authenticatorStr)
00208       + fUserAgentHeaderStrSize;
00209     cmd = new char[cmdSize];
00210     sprintf(cmd, cmdFmt,
00211             url,
00212             ++fCSeq,
00213             acceptStr,
00214             authenticatorStr,
00215             fUserAgentHeaderStr);
00216     delete[] authenticatorStr;
00217 
00218     if (!sendRequest(cmd, "DESCRIBE")) break;
00219 
00220     // Get the response from the server:
00221     unsigned bytesRead; unsigned responseCode;
00222     char* firstLine; char* nextLineStart;
00223     if (!getResponse("DESCRIBE", bytesRead, responseCode, firstLine, nextLineStart,
00224                      False /*don't check for response code 200*/)) break;
00225 
00226     // Inspect the first line to check whether it's a result code that
00227     // we can handle.
00228     Boolean wantRedirection = False;
00229     char* redirectionURL = NULL;
00230 #ifdef SUPPORT_REAL_RTSP
00231     delete[] fRealETagStr; fRealETagStr = new char[fResponseBufferSize];
00232 #endif
00233     if (responseCode == 301 || responseCode == 302) {
00234       wantRedirection = True;
00235       redirectionURL = new char[fResponseBufferSize]; // ensures enough space
00236     } else if (responseCode != 200) {
00237       checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
00238       envir().setResultMsg("cannot handle DESCRIBE response: ", firstLine);
00239       break;
00240     }
00241 
00242     // Skip over subsequent header lines, until we see a blank line.
00243     // The remaining data is assumed to be the SDP descriptor that we want.
00244     // While skipping over the header lines, we also check for certain headers
00245     // that we recognize.
00246     // (We should also check for "Content-type: application/sdp",
00247     // "Content-location", "CSeq", etc.) #####
00248     char* serverType = new char[fResponseBufferSize]; // ensures enough space
00249     int contentLength = -1;
00250     char* lineStart;
00251     while (1) {
00252       lineStart = nextLineStart;
00253       if (lineStart == NULL) break;
00254 
00255       nextLineStart = getLine(lineStart);
00256       if (lineStart[0] == '\0') break; // this is a blank line
00257 
00258       if (sscanf(lineStart, "Content-Length: %d", &contentLength) == 1
00259           || sscanf(lineStart, "Content-length: %d", &contentLength) == 1) {
00260         if (contentLength < 0) {
00261           envir().setResultMsg("Bad \"Content-length:\" header: \"",
00262                                lineStart, "\"");
00263           break;
00264         }
00265       } else if (strncmp(lineStart, "Content-Base:", 13) == 0) {
00266         int cbIndex = 13;
00267 
00268         while (lineStart[cbIndex] == ' ' || lineStart[cbIndex] == '\t') ++cbIndex; 
00269         if (lineStart[cbIndex] != '\0'/*sanity check*/) {
00270           delete[] fBaseURL; fBaseURL = strDup(&lineStart[cbIndex]);
00271         }
00272       } else if (sscanf(lineStart, "Server: %s", serverType) == 1) {
00273         if (strncmp(serverType, "Kasenna", 7) == 0) fServerIsKasenna = True;
00274         if (strncmp(serverType, "WMServer", 8) == 0) fServerIsMicrosoft = True;
00275 #ifdef SUPPORT_REAL_RTSP
00276       } else if (sscanf(lineStart, "ETag: %s", fRealETagStr) == 1) {
00277 #endif
00278       } else if (wantRedirection) { 
00279         if (sscanf(lineStart, "Location: %s", redirectionURL) == 1) {
00280           // Try again with this URL
00281           if (fVerbosityLevel >= 1) {
00282             envir() << "Redirecting to the new URL \""
00283                     << redirectionURL << "\"\n";
00284           }
00285           reset();
00286           char* result = describeURL(redirectionURL, authenticator, allowKasennaProtocol);
00287           delete[] redirectionURL;
00288           delete[] serverType;
00289           delete[] cmd;
00290           return result;
00291         }
00292       }
00293     } 
00294     delete[] serverType;
00295 
00296     // We're now at the end of the response header lines
00297     if (wantRedirection) {
00298       envir().setResultMsg("Saw redirection response code, but not a \"Location:\" header");
00299       delete[] redirectionURL;
00300       break;
00301     }
00302     if (lineStart == NULL) {
00303       envir().setResultMsg("no content following header lines: ", fResponseBuffer);
00304       break;
00305     }
00306 
00307     // Use the remaining data as the SDP descr, but first, check
00308     // the "Content-length:" header (if any) that we saw.  We may need to
00309     // read more data, or we may have extraneous data in the buffer.
00310     char* bodyStart = nextLineStart;
00311     if (contentLength >= 0) {
00312       // We saw a "Content-length:" header
00313       unsigned numBodyBytes = &firstLine[bytesRead] - bodyStart;
00314       if (contentLength > (int)numBodyBytes) {
00315         // We need to read more data.  First, make sure we have enough
00316         // space for it:
00317         unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
00318         unsigned remainingBufferSize
00319           = fResponseBufferSize - (bytesRead + (firstLine - fResponseBuffer));
00320         if (numExtraBytesNeeded > remainingBufferSize) {
00321           char tmpBuf[200];
00322           sprintf(tmpBuf, "Read buffer size (%d) is too small for \"Content-length:\" %d (need a buffer size of >= %d bytes\n",
00323                   fResponseBufferSize, contentLength,
00324                   fResponseBufferSize + numExtraBytesNeeded - remainingBufferSize);
00325           envir().setResultMsg(tmpBuf);
00326           break;
00327         }
00328 
00329         // Keep reading more data until we have enough:
00330         if (fVerbosityLevel >= 1) {
00331           envir() << "Need to read " << numExtraBytesNeeded
00332                   << " extra bytes\n";
00333         }
00334         while (numExtraBytesNeeded > 0) {
00335           struct sockaddr_in fromAddress;
00336           char* ptr = &firstLine[bytesRead];
00337           int bytesRead2 = readSocket(envir(), fInputSocketNum, (unsigned char*)ptr,
00338                                       numExtraBytesNeeded, fromAddress);
00339           if (bytesRead2 < 0) break;
00340           ptr[bytesRead2] = '\0';
00341           if (fVerbosityLevel >= 1) {
00342             envir() << "Read " << bytesRead2 << " extra bytes: "
00343                     << ptr << "\n";
00344           }
00345 
00346           bytesRead += bytesRead2;
00347           numExtraBytesNeeded -= bytesRead2;
00348         }
00349         if (numExtraBytesNeeded > 0) break; // one of the reads failed
00350       }
00351 
00352       // Remove any '\0' characters from inside the SDP description.
00353       // Any such characters would violate the SDP specification, but
00354       // some RTSP servers have been known to include them:
00355       int from, to = 0;
00356       for (from = 0; from < contentLength; ++from) {
00357         if (bodyStart[from] != '\0') {
00358           if (to != from) bodyStart[to] = bodyStart[from];
00359           ++to;
00360         }
00361       }
00362       if (from != to && fVerbosityLevel >= 1) {
00363         envir() << "Warning: " << from-to << " invalid 'NULL' bytes were found in (and removed from) the SDP description.\n";
00364       }
00365       bodyStart[to] = '\0'; // trims any extra data
00366     }
00367 
00369     // If necessary, handle Kasenna's non-standard BS response:
00370     if (fServerIsKasenna && strncmp(bodyStart, "<MediaDescription>", 18) == 0) {
00371       // Translate from x-rtsp-mh to sdp
00372       int videoPid, audioPid;
00373       u_int64_t mh_duration;
00374       char* currentWord = new char[fResponseBufferSize]; // ensures enough space
00375       delete[] fKasennaContentType;
00376       fKasennaContentType = new char[fResponseBufferSize]; // ensures enough space
00377       char* currentPos = bodyStart;
00378       
00379       while (strcmp(currentWord, "</MediaDescription>") != 0) {
00380           sscanf(currentPos, "%s", currentWord);
00381           
00382           if (strcmp(currentWord, "VideoPid") == 0) {
00383             currentPos += strlen(currentWord) + 1;
00384             sscanf(currentPos, "%s", currentWord);
00385             currentPos += strlen(currentWord) + 1;
00386             sscanf(currentPos, "%d", &videoPid);
00387             currentPos += 3;
00388           }
00389           
00390           if (strcmp(currentWord, "AudioPid") == 0) {
00391             currentPos += strlen(currentWord) + 1;
00392             sscanf(currentPos, "%s", currentWord);
00393             currentPos += strlen(currentWord) + 1;
00394             sscanf(currentPos, "%d", &audioPid);
00395             currentPos += 3;
00396           }
00397           
00398           if (strcmp(currentWord, "Duration") == 0) {
00399             currentPos += strlen(currentWord) + 1;
00400             sscanf(currentPos, "%s", currentWord);
00401             currentPos += strlen(currentWord) + 1;
00402             sscanf(currentPos, "%llu", &mh_duration);
00403             currentPos += 3;
00404           }
00405           
00406           if (strcmp(currentWord, "TypeSpecificData") == 0) {
00407             currentPos += strlen(currentWord) + 1;
00408             sscanf(currentPos, "%s", currentWord);
00409             currentPos += strlen(currentWord) + 1;
00410             sscanf(currentPos, "%s", fKasennaContentType);
00411             currentPos += 3;
00412             printf("Kasenna Content Type: %s\n", fKasennaContentType);
00413           }
00414           
00415           currentPos += strlen(currentWord) + 1;
00416         }
00417       
00418       if (fKasennaContentType != NULL
00419           && strcmp(fKasennaContentType, "PARTNER_41_MPEG-4") == 0) {
00420           char* describeSDP = describeURL(url, authenticator, True);
00421           
00422           delete[] currentWord;
00423           delete[] cmd;
00424           return describeSDP;
00425       }
00426       
00427       unsigned char byte1 = fServerAddress & 0x000000ff;
00428       unsigned char byte2 = (fServerAddress & 0x0000ff00) >>  8;
00429       unsigned char byte3 = (fServerAddress & 0x00ff0000) >> 16;
00430       unsigned char byte4 = (fServerAddress & 0xff000000) >> 24;
00431       
00432       char const* sdpFmt =
00433         "v=0\r\n"
00434         "o=NoSpacesAllowed 1 1 IN IP4 %u.%u.%u.%u\r\n"
00435         "s=%s\r\n"
00436         "c=IN IP4 %u.%u.%u.%u\r\n"
00437         "t=0 0\r\n"
00438         "a=control:*\r\n"
00439         "a=range:npt=0-%llu\r\n"
00440         "m=video 1554 RAW/RAW/UDP 33\r\n"
00441         "a=control:trackID=%d\r\n";
00442       unsigned sdpBufSize = strlen(sdpFmt)
00443         + 4*3 // IP address
00444         + strlen(url)
00445         + 20 // max int length
00446         + 20; // max int length
00447       char* sdpBuf = new char[sdpBufSize];
00448       sprintf(sdpBuf, sdpFmt,
00449               byte1, byte2, byte3, byte4,
00450               url,
00451               byte1, byte2, byte3, byte4,
00452               mh_duration/1000000,
00453               videoPid);
00454       
00455       char* result = strDup(sdpBuf);
00456       delete[] sdpBuf; delete[] currentWord;
00457       delete[] cmd;
00458       return result;
00459     }
00461     
00462     delete[] cmd;
00463     return strDup(bodyStart);
00464   } while (0);
00465   
00466   delete[] cmd;
00467   if (fDescribeStatusCode == 0) fDescribeStatusCode = 2;
00468   return NULL;
00469 }
00470 
00471 char* RTSPClient
00472 ::describeWithPassword(char const* url,
00473                        char const* username, char const* password,
00474                        Boolean allowKasennaProtocol) {
00475   Authenticator authenticator;
00476   authenticator.setUsernameAndPassword(username, password);
00477   char* describeResult = describeURL(url, &authenticator, allowKasennaProtocol);
00478   if (describeResult != NULL) {
00479     // We are already authorized
00480     return describeResult;
00481   }
00482 
00483   // The "realm" field should have been filled in:
00484   if (authenticator.realm() == NULL) {
00485     // We haven't been given enough information to try again, so fail:
00486     return NULL;
00487   }
00488 
00489   // Try again:
00490   describeResult = describeURL(url, &authenticator, allowKasennaProtocol);
00491   if (describeResult != NULL) {
00492     // The authenticator worked, so use it in future requests:
00493     fCurrentAuthenticator = authenticator;
00494   }
00495 
00496   return describeResult;
00497 }
00498 
00499 char* RTSPClient::sendOptionsCmd(char const* url,
00500                                  char* username, char* password,
00501                                  Authenticator* authenticator) {
00502   char* result = NULL;
00503   char* cmd = NULL;
00504   Boolean haveAllocatedAuthenticator = False;
00505   do {
00506     if (authenticator == NULL) {
00507       // First, check whether "url" contains a username:password to be used
00508       // (and no username,password pair was supplied separately):
00509       if (username == NULL && password == NULL
00510           && parseRTSPURLUsernamePassword(url, username, password)) {
00511         Authenticator newAuthenticator;
00512         newAuthenticator.setUsernameAndPassword(username, password);
00513         result = sendOptionsCmd(url, username, password, &newAuthenticator);
00514         delete[] username; delete[] password; // they were dynamically allocated
00515         break;
00516       } else if (username != NULL && password != NULL) {
00517         // Use the separately supplied username and password:
00518         authenticator = new Authenticator;
00519         haveAllocatedAuthenticator = True;
00520         authenticator->setUsernameAndPassword(username, password);
00521 
00522         result = sendOptionsCmd(url, username, password, authenticator);
00523         if (result != NULL) break; // We are already authorized
00524 
00525         // The "realm" field should have been filled in:
00526         if (authenticator->realm() == NULL) {
00527           // We haven't been given enough information to try again, so fail:
00528           break;
00529         }
00530         // Try again:
00531       }
00532     }
00533 
00534     if (!openConnectionFromURL(url, authenticator)) break;
00535 
00536     // Send the OPTIONS command:
00537 
00538     // First, construct an authenticator string:
00539     char* authenticatorStr
00540       = createAuthenticatorString(authenticator, "OPTIONS", url);
00541 
00542     char* const cmdFmt =
00543       "OPTIONS %s RTSP/1.0\r\n"
00544       "CSeq: %d\r\n"
00545       "%s"
00546       "%s"
00547 #ifdef SUPPORT_REAL_RTSP
00548       REAL_OPTIONS_HEADERS
00549 #endif
00550       "\r\n";
00551     unsigned cmdSize = strlen(cmdFmt)
00552       + strlen(url)
00553       + 20 /* max int len */
00554       + strlen(authenticatorStr)
00555       + fUserAgentHeaderStrSize;
00556     cmd = new char[cmdSize];
00557     sprintf(cmd, cmdFmt,
00558             url,
00559             ++fCSeq,
00560             authenticatorStr,
00561             fUserAgentHeaderStr);
00562     delete[] authenticatorStr;
00563 
00564     if (!sendRequest(cmd, "OPTIONS")) break;
00565 
00566     // Get the response from the server:
00567     unsigned bytesRead; unsigned responseCode;
00568     char* firstLine; char* nextLineStart;
00569     if (!getResponse("OPTIONS", bytesRead, responseCode, firstLine, nextLineStart,
00570                      False /*don't check for response code 200*/)) break;
00571     if (responseCode != 200) {
00572       checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
00573       envir().setResultMsg("cannot handle OPTIONS response: ", firstLine);
00574       break;
00575     }
00576 
00577     // Look for a "Public:" header (which will contain our result str):
00578     char* lineStart;
00579     while (1) {
00580       lineStart = nextLineStart;
00581       if (lineStart == NULL) break;
00582 
00583       nextLineStart = getLine(lineStart);
00584 
00585       if (_strncasecmp(lineStart, "Public: ", 8) == 0) {
00586         delete[] result; result = strDup(&lineStart[8]);
00587 #ifdef SUPPORT_REAL_RTSP
00588       } else if (_strncasecmp(lineStart, "RealChallenge1: ", 16) == 0) {
00589         delete[] fRealChallengeStr; fRealChallengeStr = strDup(&lineStart[16]);
00590 #endif
00591       }
00592     }
00593   } while (0);
00594 
00595   delete[] cmd;
00596   if (haveAllocatedAuthenticator) delete authenticator;
00597   return result;
00598 }
00599 
00600 static Boolean isAbsoluteURL(char const* url) {
00601   // Assumption: "url" is absolute if it contains a ':', before any
00602   // occurrence of '/'
00603   while (*url != '\0' && *url != '/') {
00604     if (*url == ':') return True;
00605     ++url;
00606   }
00607 
00608   return False;
00609 }
00610 
00611 char const* RTSPClient::sessionURL(MediaSession const& session) const {
00612   char const* url = session.controlPath();
00613   if (url == NULL || strcmp(url, "*") == 0) url = fBaseURL;
00614 
00615   return url;
00616 }
00617 
00618 void RTSPClient::constructSubsessionURL(MediaSubsession const& subsession,
00619                                         char const*& prefix,
00620                                         char const*& separator,
00621                                         char const*& suffix) {
00622   // Figure out what the URL describing "subsession" will look like.
00623   // The URL is returned in three parts: prefix; separator; suffix
00624   //##### NOTE: This code doesn't really do the right thing if "sessionURL()"
00625   // doesn't end with a "/", and "subsession.controlPath()" is relative.
00626   // The right thing would have been to truncate "sessionURL()" back to the
00627   // rightmost "/", and then add "subsession.controlPath()".
00628   // In practice, though, each "DESCRIBE" response typically contains
00629   // a "Content-Base:" header that consists of "sessionURL()" followed by
00630   // a "/", in which case this code ends up giving the correct result.
00631   // However, we should really fix this code to do the right thing, and
00632   // also check for and use the "Content-Base:" header appropriately. #####
00633   prefix = sessionURL(subsession.parentSession());
00634   if (prefix == NULL) prefix = "";
00635 
00636   suffix = subsession.controlPath();
00637   if (suffix == NULL) suffix = "";
00638 
00639   if (isAbsoluteURL(suffix)) {
00640     prefix = separator = "";
00641   } else {
00642     unsigned prefixLen = strlen(prefix);
00643     separator = (prefix[prefixLen-1] == '/' || suffix[0] == '/') ? "" : "/";
00644   }
00645 }
00646 
00647 Boolean RTSPClient::announceSDPDescription(char const* url,
00648                                            char const* sdpDescription,
00649                                            Authenticator* authenticator) {
00650   char* cmd = NULL;
00651   do {
00652     if (!openConnectionFromURL(url, authenticator)) break;
00653 
00654     // Send the ANNOUNCE command:
00655 
00656     // First, construct an authenticator string:
00657     fCurrentAuthenticator.reset();
00658     char* authenticatorStr
00659       = createAuthenticatorString(authenticator, "ANNOUNCE", url);
00660 
00661     char* const cmdFmt =
00662       "ANNOUNCE %s RTSP/1.0\r\n"
00663       "CSeq: %d\r\n"
00664       "Content-Type: application/sdp\r\n"
00665       "%s"
00666       "Content-length: %d\r\n\r\n"
00667       "%s";
00668             // Note: QTSS hangs if an "ANNOUNCE" contains a "User-Agent:" field (go figure), so don't include one here
00669     unsigned sdpSize = strlen(sdpDescription);
00670     unsigned cmdSize = strlen(cmdFmt)
00671       + strlen(url)
00672       + 20 /* max int len */
00673       + strlen(authenticatorStr)
00674       + 20 /* max int len */
00675       + sdpSize;
00676     cmd = new char[cmdSize];
00677     sprintf(cmd, cmdFmt,
00678             url,
00679             ++fCSeq,
00680             authenticatorStr,
00681             sdpSize,
00682             sdpDescription);
00683     delete[] authenticatorStr;
00684 
00685     if (!sendRequest(cmd, "ANNOUNCE")) break;
00686 
00687     // Get the response from the server:
00688     unsigned bytesRead; unsigned responseCode;
00689     char* firstLine; char* nextLineStart;
00690     if (!getResponse("ANNOUNCE", bytesRead, responseCode, firstLine, nextLineStart,
00691                      False /*don't check for response code 200*/)) break;
00692 
00693     // Inspect the first line to check whether it's a result code 200
00694     if (responseCode != 200) {
00695       checkForAuthenticationFailure(responseCode, nextLineStart, authenticator);
00696       envir().setResultMsg("cannot handle ANNOUNCE response: ", firstLine);
00697       break;
00698     }
00699 
00700     delete[] cmd;
00701     return True;
00702   } while (0);
00703 
00704   delete[] cmd;
00705   return False;
00706 }
00707 
00708 Boolean RTSPClient
00709 ::announceWithPassword(char const* url, char const* sdpDescription,
00710                        char const* username, char const* password) {
00711   Authenticator authenticator;
00712   authenticator.setUsernameAndPassword(username, password);
00713   if (announceSDPDescription(url, sdpDescription, &authenticator)) {
00714     // We are already authorized
00715     return True;
00716   }
00717 
00718   // The "realm" field should have been filled in:
00719   if (authenticator.realm() == NULL) {
00720     // We haven't been given enough information to try again, so fail:
00721     return False;
00722   }
00723 
00724   // Try again:
00725   Boolean secondTrySuccess
00726     = announceSDPDescription(url, sdpDescription, &authenticator);
00727 
00728   if (secondTrySuccess) {
00729     // The authenticator worked, so use it in future requests:
00730     fCurrentAuthenticator = authenticator;
00731   }
00732 
00733   return secondTrySuccess;
00734 }
00735 
00736 Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
00737                                          Boolean streamOutgoing,
00738                                          Boolean streamUsingTCP,
00739                                          Boolean forceMulticastOnUnspecified) {
00740   char* cmd = NULL;
00741   char* setupStr = NULL;
00742 
00743   if (fServerIsMicrosoft) {
00744     // Microsoft doesn't send the right endTime on live streams.  Correct this:
00745     char *tmpStr = subsession.parentSession().mediaSessionType();
00746     if (tmpStr != NULL && strncmp(tmpStr, "broadcast", 9) == 0) {
00747       subsession.parentSession().playEndTime() = 0.0;
00748     }
00749   }
00750 
00751   do {
00752     // Construct the SETUP command:
00753 
00754     // First, construct an authenticator string:
00755     char* authenticatorStr
00756       = createAuthenticatorString(&fCurrentAuthenticator,
00757                                   "SETUP", fBaseURL);
00758 
00759     // When sending more than one "SETUP" request, include a "Session:"
00760     // header in the 2nd and later "SETUP"s.
00761     char* sessionStr;
00762     if (fLastSessionId != NULL) {
00763       sessionStr = new char[20+strlen(fLastSessionId)];
00764       sprintf(sessionStr, "Session: %s\r\n", fLastSessionId);
00765     } else {
00766       sessionStr = strDup("");
00767     }
00768 
00769     char* transportStr = NULL;
00770 #ifdef SUPPORT_REAL_RTSP
00771     if (usingRealNetworksChallengeResponse()) {
00772       // Use a special "Transport:" header, and also add a 'challenge response'.
00773       char challenge2[64];
00774       char checksum[34];
00775       RealCalculateChallengeResponse(fRealChallengeStr, challenge2, checksum);
00776 
00777       char const* etag = fRealETagStr == NULL ? "" : fRealETagStr;
00778 
00779       char* transportHeader;
00780       if (subsession.parentSession().isRealNetworksRDT) {
00781         transportHeader = strDup("Transport: x-pn-tng/tcp;mode=play,rtp/avp/unicast;mode=play\r\n");
00782       } else {
00783         // Use a regular "Transport:" header:
00784         char const* transportHeaderFmt
00785           = "Transport: RTP/AVP%s%s=%d-%d\r\n";
00786         char const* transportTypeStr;
00787         char const* portTypeStr;
00788         unsigned short rtpNumber, rtcpNumber;
00789         if (streamUsingTCP) { // streaming over the RTSP connection
00790           transportTypeStr = "/TCP;unicast";
00791           portTypeStr = ";interleaved";
00792           rtpNumber = fTCPStreamIdCount++;
00793           rtcpNumber = fTCPStreamIdCount++;
00794         } else { // normal RTP streaming
00795           unsigned connectionAddress = subsession.connectionEndpointAddress();
00796           Boolean requestMulticastStreaming = IsMulticastAddress(connectionAddress)
00797             || (connectionAddress == 0 && forceMulticastOnUnspecified);
00798           transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast";
00799           portTypeStr = ";client_port";
00800           rtpNumber = subsession.clientPortNum();
00801           if (rtpNumber == 0) {
00802             envir().setResultMsg("Client port number unknown\n");
00803             break;
00804           }
00805           rtcpNumber = rtpNumber + 1;
00806         }
00807 
00808         unsigned transportHeaderSize = strlen(transportHeaderFmt)
00809           + strlen(transportTypeStr) + strlen(portTypeStr) + 2*5 /* max port len */;
00810         transportHeader = new char[transportHeaderSize];
00811         sprintf(transportHeader, transportHeaderFmt,
00812                 transportTypeStr, portTypeStr, rtpNumber, rtcpNumber);
00813       }
00814       char const* transportFmt =
00815         "%s"
00816         "RealChallenge2: %s, sd=%s\r\n"
00817         "If-Match: %s\r\n";
00818       unsigned transportSize = strlen(transportFmt)
00819         + strlen(transportHeader)
00820         + sizeof challenge2 + sizeof checksum
00821         + strlen(etag);
00822       transportStr = new char[transportSize];
00823       sprintf(transportStr, transportFmt,
00824               transportHeader,
00825               challenge2, checksum,
00826               etag);
00827       delete[] transportHeader;
00828 
00829       if (subsession.parentSession().isRealNetworksRDT) {
00830         // Also, tell the RDT source to use the RTSP TCP socket:
00831         RealRDTSource* rdtSource
00832           = (RealRDTSource*)(subsession.readSource());
00833         rdtSource->setInputSocket(fInputSocketNum);
00834       }
00835     }
00836 #endif
00837 
00838     char const *prefix, *separator, *suffix;
00839     constructSubsessionURL(subsession, prefix, separator, suffix);
00840     char* transportFmt;
00841 
00842     if (strcmp(subsession.protocolName(), "UDP") == 0) {
00843       char const* setupFmt = "SETUP %s%s RTSP/1.0\r\n";
00844       unsigned setupSize = strlen(setupFmt)
00845         + strlen(prefix) + strlen (separator);
00846       setupStr = new char[setupSize];
00847       sprintf(setupStr, setupFmt, prefix, separator);
00848 
00849       transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n";
00850     } else {
00851       char const* setupFmt = "SETUP %s%s%s RTSP/1.0\r\n";
00852       unsigned setupSize = strlen(setupFmt)
00853         + strlen(prefix) + strlen (separator) + strlen(suffix);
00854       setupStr = new char[setupSize];
00855       sprintf(setupStr, setupFmt, prefix, separator, suffix);
00856 
00857       transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n";
00858     }
00859 
00860     if (transportStr == NULL) {
00861       // Construct a "Transport:" header.
00862       char const* transportTypeStr;
00863       char const* modeStr = streamOutgoing ? ";mode=receive" : "";
00864           // Note: I think the above is nonstandard, but DSS wants it this way
00865       char const* portTypeStr;
00866       unsigned short rtpNumber, rtcpNumber;
00867       if (streamUsingTCP) { // streaming over the RTSP connection
00868         transportTypeStr = "/TCP;unicast";
00869         portTypeStr = ";interleaved";
00870         rtpNumber = fTCPStreamIdCount++;
00871         rtcpNumber = fTCPStreamIdCount++;
00872       } else { // normal RTP streaming      
00873         unsigned connectionAddress = subsession.connectionEndpointAddress();
00874         Boolean requestMulticastStreaming = IsMulticastAddress(connectionAddress)
00875           || (connectionAddress == 0 && forceMulticastOnUnspecified);
00876         transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast";
00877         portTypeStr = ";client_port";
00878         rtpNumber = subsession.clientPortNum();
00879         if (rtpNumber == 0) {
00880           envir().setResultMsg("Client port number unknown\n");
00881           delete[] authenticatorStr; delete[] sessionStr; delete[] setupStr;
00882           break;
00883         }
00884         rtcpNumber = rtpNumber + 1;
00885       }
00886 
00887       unsigned transportSize = strlen(transportFmt)
00888         + strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */;
00889       transportStr = new char[transportSize];
00890       sprintf(transportStr, transportFmt,
00891               transportTypeStr, modeStr, portTypeStr, rtpNumber, rtcpNumber);
00892     }
00893 
00894     // (Later implement more, as specified in the RTSP spec, sec D.1 #####)
00895     char* const cmdFmt =
00896       "%s"
00897       "CSeq: %d\r\n"
00898       "%s"
00899       "%s"
00900       "%s"
00901       "%s"
00902       "\r\n";
00903 
00904     unsigned cmdSize = strlen(cmdFmt)
00905       + strlen(setupStr)
00906       + 20 /* max int len */
00907       + strlen(transportStr)
00908       + strlen(sessionStr)
00909       + strlen(authenticatorStr)
00910       + fUserAgentHeaderStrSize;
00911     cmd = new char[cmdSize];
00912     sprintf(cmd, cmdFmt,
00913             setupStr,
00914             ++fCSeq,
00915             transportStr,
00916             sessionStr,
00917             authenticatorStr,
00918             fUserAgentHeaderStr);
00919     delete[] authenticatorStr; delete[] sessionStr; delete[] setupStr; delete[] transportStr;
00920 
00921     // And then send it:
00922     if (!sendRequest(cmd, "SETUP")) break;
00923 
00924     // Get the response from the server:
00925     unsigned bytesRead; unsigned responseCode;
00926     char* firstLine; char* nextLineStart;
00927     if (!getResponse("SETUP", bytesRead, responseCode, firstLine, nextLineStart)) break;
00928 
00929     // Look for a "Session:" header (to set our session id), and
00930     // a "Transport: " header (to set the server address/port)
00931     // For now, ignore other headers.
00932     char* lineStart;
00933     char* sessionId = new char[fResponseBufferSize]; // ensures we have enough space
00934     unsigned cLength = 0;
00935     while (1) {
00936       lineStart = nextLineStart;
00937       if (lineStart == NULL) break;
00938 
00939       nextLineStart = getLine(lineStart);
00940 
00941       if (sscanf(lineStart, "Session: %[^;]", sessionId) == 1) {
00942         subsession.sessionId = strDup(sessionId);
00943         delete[] fLastSessionId; fLastSessionId = strDup(sessionId);
00944 
00945         // Also look for an optional "; timeout = " parameter following this:
00946         char* afterSessionId
00947           = lineStart + strlen(sessionId) + strlen ("Session: ");;
00948         int timeoutVal;
00949         if (sscanf(afterSessionId, "; timeout = %d", &timeoutVal) == 1) {
00950           fSessionTimeoutParameter = timeoutVal;
00951         }
00952         continue;
00953       }
00954 
00955       char* serverAddressStr;
00956       portNumBits serverPortNum;
00957       unsigned char rtpChannelId, rtcpChannelId;
00958       if (parseTransportResponse(lineStart,
00959                                  serverAddressStr, serverPortNum,
00960                                  rtpChannelId, rtcpChannelId)) {
00961         delete[] subsession.connectionEndpointName();
00962         subsession.connectionEndpointName() = serverAddressStr;
00963         subsession.serverPortNum = serverPortNum;
00964         subsession.rtpChannelId = rtpChannelId;
00965         subsession.rtcpChannelId = rtcpChannelId;
00966         continue;
00967       }
00968 
00969       // Also check for a "Content-Length:" header.  Some weird servers include this
00970       // in the RTSP "SETUP" response.
00971       if (sscanf(lineStart, "Content-Length: %d", &cLength) == 1) continue;
00972     } 
00973     delete[] sessionId;
00974 
00975     if (subsession.sessionId == NULL) {
00976       envir().setResultMsg("\"Session:\" header is missing in the response");
00977       break;
00978     }
00979 
00980     // If we saw a "Content-Length:" header in the response, then discard whatever
00981     // included data it refers to:
00982     if (cLength > 0) {
00983       char* dummyBuf = new char[cLength];
00984       getResponse1(dummyBuf, cLength);
00985       delete[] dummyBuf;
00986     }
00987 
00988     if (streamUsingTCP) {
00989       // Tell the subsession to receive RTP (and send/receive RTCP)
00990       // over the RTSP stream:
00991       if (subsession.rtpSource() != NULL)
00992         subsession.rtpSource()->setStreamSocket(fInputSocketNum,
00993                                                 subsession.rtpChannelId);
00994       if (subsession.rtcpInstance() != NULL)
00995         subsession.rtcpInstance()->setStreamSocket(fInputSocketNum,
00996                                                    subsession.rtcpChannelId);
00997     } else {
00998       // Normal case.
00999       // Set the RTP and RTCP sockets' destination address and port
01000       // from the information in the SETUP response (if present): 
01001       netAddressBits destAddress = subsession.connectionEndpointAddress();
01002       if (destAddress == 0) destAddress = fServerAddress;
01003       subsession.setDestinations(destAddress);
01004     }
01005 
01006     delete[] cmd;
01007     return True;
01008   } while (0);
01009 
01010   delete[] cmd;
01011   return False;
01012 }
01013 
01014 static char* createScaleString(float scale, float currentScale) {
01015   char buf[100];
01016   if (scale == 1.0f && currentScale == 1.0f) {
01017     // This is the default value; we don't need a "Scale:" header:
01018     buf[0] = '\0';
01019   } else {
01020     Locale("C", LC_NUMERIC);
01021     sprintf(buf, "Scale: %f\r\n", scale);
01022   }
01023 
01024   return strDup(buf);
01025 }
01026       
01027 static char* createRangeString(float start, float end) {
01028   char buf[100];
01029   if (start < 0) {
01030     // We're resuming from a PAUSE; there's no "Range:" header at all
01031     buf[0] = '\0';
01032   } else if (end < 0) {
01033     // There's no end time:
01034     Locale("C", LC_NUMERIC);
01035     sprintf(buf, "Range: npt=%.3f-\r\n", start);
01036   } else {
01037     // There's both a start and an end time; in