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 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
00015 **********/
00016 // "liveMedia"
00017 // Copyright (c) 1996-2013 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 
00029 
00030 RTSPClient* RTSPClient::createNew(UsageEnvironment& env, char const* rtspURL,
00031                                   int verbosityLevel,
00032                                   char const* applicationName,
00033                                   portNumBits tunnelOverHTTPPortNum) {
00034   return new RTSPClient(env, rtspURL,
00035                         verbosityLevel, applicationName, tunnelOverHTTPPortNum);
00036 }
00037 
00038 unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
00039   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00040   return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
00041 }
00042 
00043 unsigned RTSPClient::sendOptionsCommand(responseHandler* responseHandler, Authenticator* authenticator) {
00044   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00045   return sendRequest(new RequestRecord(++fCSeq, "OPTIONS", responseHandler));
00046 }
00047 
00048 unsigned RTSPClient::sendAnnounceCommand(char const* sdpDescription, responseHandler* responseHandler, Authenticator* authenticator) {
00049   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00050   return sendRequest(new RequestRecord(++fCSeq, "ANNOUNCE", responseHandler, NULL, NULL, False, 0.0, 0.0, 0.0, sdpDescription));
00051 }
00052 
00053 unsigned RTSPClient::sendSetupCommand(MediaSubsession& subsession, responseHandler* responseHandler,
00054                                       Boolean streamOutgoing, Boolean streamUsingTCP, Boolean forceMulticastOnUnspecified,
00055                                       Authenticator* authenticator) {
00056   if (fTunnelOverHTTPPortNum != 0) streamUsingTCP = True; // RTSP-over-HTTP tunneling uses TCP (by definition)
00057   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00058 
00059   u_int32_t booleanFlags = 0;
00060   if (streamUsingTCP) booleanFlags |= 0x1;
00061   if (streamOutgoing) booleanFlags |= 0x2;
00062   if (forceMulticastOnUnspecified) booleanFlags |= 0x4;
00063   return sendRequest(new RequestRecord(++fCSeq, "SETUP", responseHandler, NULL, &subsession, booleanFlags));
00064 }
00065 
00066 unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
00067                                      double start, double end, float scale,
00068                                      Authenticator* authenticator) {
00069   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00070   return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, &session, NULL, 0, start, end, scale));
00071 }
00072 
00073 unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
00074                                      double start, double end, float scale,
00075                                      Authenticator* authenticator) {
00076   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00077   return sendRequest(new RequestRecord(++fCSeq, "PLAY", responseHandler, NULL, &subsession, 0, start, end, scale));
00078 }
00079 
00080 unsigned RTSPClient::sendPlayCommand(MediaSession& session, responseHandler* responseHandler,
00081                                      char const* absStartTime, char const* absEndTime, float scale,
00082                                      Authenticator* authenticator) {
00083   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00084   return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, &session, NULL));
00085 }
00086 
00087 unsigned RTSPClient::sendPlayCommand(MediaSubsession& subsession, responseHandler* responseHandler,
00088                                      char const* absStartTime, char const* absEndTime, float scale,
00089                                      Authenticator* authenticator) {
00090   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00091   return sendRequest(new RequestRecord(++fCSeq, responseHandler, absStartTime, absEndTime, scale, NULL, &subsession));
00092 }
00093 
00094 unsigned RTSPClient::sendPauseCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
00095   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00096   return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, &session));
00097 }
00098 
00099 unsigned RTSPClient::sendPauseCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
00100   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00101   return sendRequest(new RequestRecord(++fCSeq, "PAUSE", responseHandler, NULL, &subsession));
00102 }
00103 
00104 unsigned RTSPClient::sendRecordCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
00105   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00106   return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, &session));
00107 }
00108 
00109 unsigned RTSPClient::sendRecordCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
00110   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00111   return sendRequest(new RequestRecord(++fCSeq, "RECORD", responseHandler, NULL, &subsession));
00112 }
00113 
00114 unsigned RTSPClient::sendTeardownCommand(MediaSession& session, responseHandler* responseHandler, Authenticator* authenticator) {
00115   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00116   return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, &session));
00117 }
00118 
00119 unsigned RTSPClient::sendTeardownCommand(MediaSubsession& subsession, responseHandler* responseHandler, Authenticator* authenticator) {
00120   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00121   return sendRequest(new RequestRecord(++fCSeq, "TEARDOWN", responseHandler, NULL, &subsession));
00122 }
00123 
00124 unsigned RTSPClient::sendSetParameterCommand(MediaSession& session, responseHandler* responseHandler,
00125                                              char const* parameterName, char const* parameterValue,
00126                                              Authenticator* authenticator) {
00127   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00128   char* paramString = new char[strlen(parameterName) + strlen(parameterValue) + 10];
00129   sprintf(paramString, "%s: %s\r\n", parameterName, parameterValue);
00130   unsigned result = sendRequest(new RequestRecord(++fCSeq, "SET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
00131   delete[] paramString;
00132   return result;
00133 }
00134 
00135 unsigned RTSPClient::sendGetParameterCommand(MediaSession& session, responseHandler* responseHandler, char const* parameterName,
00136                                              Authenticator* authenticator) {
00137   if (authenticator != NULL) fCurrentAuthenticator = *authenticator;
00138 
00139   // We assume that:
00140   //    parameterName is NULL means: Send no body in the request.
00141   //    parameterName is "" means: Send only \r\n in the request body.  
00142   //    parameterName is non-empty means: Send "<parameterName>\r\n" as the request body.  
00143   unsigned parameterNameLen = parameterName == NULL ? 0 : strlen(parameterName);
00144   char* paramString = new char[parameterNameLen + 3]; // the 3 is for \r\n + the '\0' byte
00145   if (parameterName == NULL) {
00146     paramString[0] = '\0';
00147   } else {
00148     sprintf(paramString, "%s\r\n", parameterName);
00149   }
00150   unsigned result = sendRequest(new RequestRecord(++fCSeq, "GET_PARAMETER", responseHandler, &session, NULL, False, 0.0, 0.0, 0.0, paramString));
00151   delete[] paramString;
00152   return result;
00153 }
00154 
00155 Boolean RTSPClient::changeResponseHandler(unsigned cseq, responseHandler* newResponseHandler) { 
00156   // Look for the matching request record in each of our 'pending requests' queues:
00157   RequestRecord* request;
00158   if ((request = fRequestsAwaitingConnection.findByCSeq(cseq)) != NULL
00159       || (request = fRequestsAwaitingHTTPTunneling.findByCSeq(cseq)) != NULL
00160       || (request = fRequestsAwaitingResponse.findByCSeq(cseq)) != NULL) {
00161     request->handler() = newResponseHandler;
00162     return True;
00163   }
00164 
00165   return False;
00166 }
00167 
00168 Boolean RTSPClient::lookupByName(UsageEnvironment& env,
00169                                  char const* instanceName,
00170                                  RTSPClient*& resultClient) {
00171   resultClient = NULL; // unless we succeed
00172 
00173   Medium* medium;
00174   if (!Medium::lookupByName(env, instanceName, medium)) return False;
00175 
00176   if (!medium->isRTSPClient()) {
00177     env.setResultMsg(instanceName, " is not a RTSP client");
00178     return False;
00179   }
00180 
00181   resultClient = (RTSPClient*)medium;
00182   return True;
00183 }
00184 
00185 Boolean RTSPClient::parseRTSPURL(UsageEnvironment& env, char const* url,
00186                                  char*& username, char*& password,
00187                                  NetAddress& address,
00188                                  portNumBits& portNum,
00189                                  char const** urlSuffix) {
00190   do {
00191     // Parse the URL as "rtsp://[<username>[:<password>]@]<server-address-or-name>[:<port>][/<stream-name>]"
00192     char const* prefix = "rtsp://";
00193     unsigned const prefixLength = 7;
00194     if (_strncasecmp(url, prefix, prefixLength) != 0) {
00195       env.setResultMsg("URL is not of the form \"", prefix, "\"");
00196       break;
00197     }
00198 
00199     unsigned const parseBufferSize = 100;
00200     char parseBuffer[parseBufferSize];
00201     char const* from = &url[prefixLength];
00202 
00203     // Check whether "<username>[:<password>]@" occurs next.
00204     // We do this by checking whether '@' appears before the end of the URL, or before the first '/'.
00205     username = password = NULL; // default return values
00206     char const* colonPasswordStart = NULL;
00207     char const* p;
00208     for (p = from; *p != '\0' && *p != '/'; ++p) {
00209       if (*p == ':' && colonPasswordStart == NULL) {
00210         colonPasswordStart = p;
00211       } else if (*p == '@') {
00212         // We found <username> (and perhaps <password>).  Copy them into newly-allocated result strings:
00213         if (colonPasswordStart == NULL) colonPasswordStart = p;
00214 
00215         char const* usernameStart = from;
00216         unsigned usernameLen = colonPasswordStart - usernameStart;
00217         username = new char[usernameLen + 1] ; // allow for the trailing '\0'
00218         for (unsigned i = 0; i < usernameLen; ++i) username[i] = usernameStart[i];
00219         username[usernameLen] = '\0';
00220 
00221         char const* passwordStart = colonPasswordStart;
00222         if (passwordStart < p) ++passwordStart; // skip over the ':'
00223         unsigned passwordLen = p - passwordStart;
00224         password = new char[passwordLen + 1]; // allow for the trailing '\0'
00225         for (unsigned j = 0; j < passwordLen; ++j) password[j] = passwordStart[j];
00226         password[passwordLen] = '\0';
00227 
00228         from = p + 1; // skip over the '@'
00229         break;
00230       }
00231     }
00232 
00233     // Next, parse <server-address-or-name>
00234     char* to = &parseBuffer[0];
00235     unsigned i;
00236     for (i = 0; i < parseBufferSize; ++i) {
00237       if (*from == '\0' || *from == ':' || *from == '/') {
00238         // We've completed parsing the address
00239         *to = '\0';
00240         break;
00241       }
00242       *to++ = *from++;
00243     }
00244     if (i == parseBufferSize) {
00245       env.setResultMsg("URL is too long");
00246       break;
00247     }
00248 
00249     NetAddressList addresses(parseBuffer);
00250     if (addresses.numAddresses() == 0) {
00251       env.setResultMsg("Failed to find network address for \"",
00252                        parseBuffer, "\"");
00253       break;
00254     }
00255     address = *(addresses.firstAddress());
00256 
00257     portNum = 554; // default value
00258     char nextChar = *from;
00259     if (nextChar == ':') {
00260       int portNumInt;
00261       if (sscanf(++from, "%d", &portNumInt) != 1) {
00262         env.setResultMsg("No port number follows ':'");
00263         break;
00264       }
00265       if (portNumInt < 1 || portNumInt > 65535) {
00266         env.setResultMsg("Bad port number");
00267         break;
00268       }
00269       portNum = (portNumBits)portNumInt;
00270       while (*from >= '0' && *from <= '9') ++from; // skip over port number
00271     }
00272 
00273     // The remainder of the URL is the suffix:
00274     if (urlSuffix != NULL) *urlSuffix = from;
00275 
00276     return True;
00277   } while (0);
00278 
00279   return False;
00280 }
00281 
00282 void RTSPClient::setUserAgentString(char const* userAgentName) {
00283   if (userAgentName == NULL) return;
00284 
00285   // Change the existing user agent header string:
00286   char const* const formatStr = "User-Agent: %s\r\n";
00287   unsigned const headerSize = strlen(formatStr) + strlen(userAgentName);
00288   delete[] fUserAgentHeaderStr;
00289   fUserAgentHeaderStr = new char[headerSize];
00290   sprintf(fUserAgentHeaderStr, formatStr, userAgentName);
00291   fUserAgentHeaderStrLen = strlen(fUserAgentHeaderStr);
00292 }
00293 
00294 unsigned RTSPClient::responseBufferSize = 20000; // default value; you can reassign this in your application if you need to
00295 
00296 RTSPClient::RTSPClient(UsageEnvironment& env, char const* rtspURL,
00297                        int verbosityLevel, char const* applicationName,
00298                        portNumBits tunnelOverHTTPPortNum)
00299   : Medium(env),
00300     fVerbosityLevel(verbosityLevel), fCSeq(1),
00301     fTunnelOverHTTPPortNum(tunnelOverHTTPPortNum), fUserAgentHeaderStr(NULL), fUserAgentHeaderStrLen(0),
00302     fInputSocketNum(-1), fOutputSocketNum(-1), fServerAddress(0), fBaseURL(NULL), fTCPStreamIdCount(0),
00303     fLastSessionId(NULL), fSessionTimeoutParameter(0), fSessionCookieCounter(0), fHTTPTunnelingConnectionIsPending(False) {
00304   setBaseURL(rtspURL);
00305 
00306   fResponseBuffer = new char[responseBufferSize+1];
00307   resetResponseBuffer();
00308 
00309   // Set the "User-Agent:" header to use in each request:
00310   char const* const libName = "LIVE555 Streaming Media v";
00311   char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;
00312   char const* libPrefix; char const* libSuffix;
00313   if (applicationName == NULL || applicationName[0] == '\0') {
00314     applicationName = libPrefix = libSuffix = "";
00315   } else {
00316     libPrefix = " (";
00317     libSuffix = ")";
00318   }
00319   unsigned userAgentNameSize
00320     = strlen(applicationName) + strlen(libPrefix) + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix) + 1;
00321   char* userAgentName = new char[userAgentNameSize];
00322   sprintf(userAgentName, "%s%s%s%s%s", applicationName, libPrefix, libName, libVersionStr, libSuffix);
00323   setUserAgentString(userAgentName);
00324   delete[] userAgentName;
00325 }
00326 
00327 RTSPClient::~RTSPClient() {
00328   reset();
00329 
00330   delete[] fResponseBuffer;
00331   delete[] fUserAgentHeaderStr;
00332 }
00333 
00334 Boolean RTSPClient::isRTSPClient() const {
00335   return True;
00336 }
00337 
00338 void RTSPClient::reset() {
00339   resetTCPSockets();
00340   resetResponseBuffer();
00341   fServerAddress = 0;
00342 
00343   setBaseURL(NULL);
00344 
00345   fCurrentAuthenticator.reset();
00346 
00347   delete[] fLastSessionId; fLastSessionId = NULL;
00348 }
00349 
00350 void RTSPClient::resetTCPSockets() {
00351   if (fInputSocketNum >= 0) {
00352     envir().taskScheduler().disableBackgroundHandling(fInputSocketNum);
00353     ::closeSocket(fInputSocketNum);
00354     if (fOutputSocketNum != fInputSocketNum) {
00355       envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
00356       ::closeSocket(fOutputSocketNum);
00357     }
00358   }
00359   fInputSocketNum = fOutputSocketNum = -1;
00360 }
00361 
00362 void RTSPClient::resetResponseBuffer() {
00363   fResponseBytesAlreadySeen = 0;
00364   fResponseBufferBytesLeft = responseBufferSize;
00365 }
00366 
00367 void RTSPClient::setBaseURL(char const* url) {
00368   delete[] fBaseURL; fBaseURL = strDup(url);
00369 }
00370 
00371 int RTSPClient::openConnection() {
00372   do {
00373     // Set up a connection to the server.  Begin by parsing the URL:
00374 
00375     char* username;
00376     char* password;
00377     NetAddress destAddress;
00378     portNumBits urlPortNum;
00379     char const* urlSuffix;
00380     if (!parseRTSPURL(envir(), fBaseURL, username, password, destAddress, urlPortNum, &urlSuffix)) break;
00381     portNumBits destPortNum = fTunnelOverHTTPPortNum == 0 ? urlPortNum : fTunnelOverHTTPPortNum;
00382     if (username != NULL || password != NULL) {
00383       fCurrentAuthenticator.setUsernameAndPassword(username, password);
00384       delete[] username;
00385       delete[] password;
00386     }
00387 
00388     // We don't yet have a TCP socket (or we used to have one, but it got closed).  Set it up now.
00389     fInputSocketNum = fOutputSocketNum = setupStreamSocket(envir(), 0);
00390     if (fInputSocketNum < 0) break;
00391     ignoreSigPipeOnSocket(fInputSocketNum); // so that servers on the same host that killed don't also kill us
00392       
00393     // Connect to the remote endpoint:
00394     fServerAddress = *(netAddressBits*)(destAddress.data());
00395     int connectResult = connectToServer(fInputSocketNum, destPortNum);
00396     if (connectResult < 0) break;
00397     else if (connectResult > 0) {
00398       // The connection succeeded.  Arrange to handle responses to requests sent on it:
00399       envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
00400                                                     (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
00401     }
00402     return connectResult;
00403   } while (0);
00404   
00405   resetTCPSockets();
00406   return -1;
00407 }
00408 
00409 int RTSPClient::connectToServer(int socketNum, portNumBits remotePortNum) {
00410   MAKE_SOCKADDR_IN(remoteName, fServerAddress, htons(remotePortNum));
00411   if (fVerbosityLevel >= 1) {
00412     envir() << "Opening connection to " << AddressString(remoteName).val() << ", port " << remotePortNum << "...\n";
00413   }
00414   if (connect(socketNum, (struct sockaddr*) &remoteName, sizeof remoteName) != 0) {
00415     int const err = envir().getErrno();
00416     if (err == EINPROGRESS || err == EWOULDBLOCK) {
00417       // The connection is pending; we'll need to handle it later.  Wait for our socket to be 'writable', or have an exception.
00418       envir().taskScheduler().setBackgroundHandling(socketNum, SOCKET_WRITABLE|SOCKET_EXCEPTION,
00419                                                     (TaskScheduler::BackgroundHandlerProc*)&connectionHandler, this);
00420       return 0;
00421     }
00422     envir().setResultErrMsg("connect() failed: ");
00423     if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n";
00424     return -1;
00425   }
00426   if (fVerbosityLevel >= 1) envir() << "...local connection opened\n";
00427 
00428   return 1;
00429 }
00430 
00431 char*
00432 RTSPClient::createAuthenticatorString(char const* cmd, char const* url) {
00433   Authenticator& auth = fCurrentAuthenticator; // alias, for brevity
00434   if (auth.realm() != NULL && auth.username() != NULL && auth.password() != NULL) {
00435     // We have a filled-in authenticator, so use it:
00436     char* authenticatorStr;
00437     if (auth.nonce() != NULL) { // Digest authentication
00438       char const* const authFmt =
00439         "Authorization: Digest username=\"%s\", realm=\"%s\", "
00440         "nonce=\"%s\", uri=\"%s\", response=\"%s\"\r\n";
00441       char const* response = auth.computeDigestResponse(cmd, url);
00442       unsigned authBufSize = strlen(authFmt)
00443         + strlen(auth.username()) + strlen(auth.realm())
00444         + strlen(auth.nonce()) + strlen(url) + strlen(response);
00445       authenticatorStr = new char[authBufSize];
00446       sprintf(authenticatorStr, authFmt,
00447               auth.username(), auth.realm(),
00448               auth.nonce(), url, response);
00449       auth.reclaimDigestResponse(response);
00450     } else { // Basic authentication
00451       char const* const authFmt = "Authorization: Basic %s\r\n";
00452 
00453       unsigned usernamePasswordLength = strlen(auth.username()) + 1 + strlen(auth.password());
00454       char* usernamePassword = new char[usernamePasswordLength+1];
00455       sprintf(usernamePassword, "%s:%s", auth.username(), auth.password());
00456 
00457       char* response = base64Encode(usernamePassword, usernamePasswordLength);
00458       unsigned const authBufSize = strlen(authFmt) + strlen(response) + 1;
00459       authenticatorStr = new char[authBufSize];
00460       sprintf(authenticatorStr, authFmt, response);
00461       delete[] response; delete[] usernamePassword;
00462     }
00463 
00464     return authenticatorStr;
00465   }
00466 
00467   // We don't have a (filled-in) authenticator.
00468   return strDup("");
00469 }
00470 
00471 static char* createSessionString(char const* sessionId) {
00472   char* sessionStr;
00473   if (sessionId != NULL) {
00474     sessionStr = new char[20+strlen(sessionId)];
00475     sprintf(sessionStr, "Session: %s\r\n", sessionId);
00476   } else {
00477     sessionStr = strDup("");
00478   }
00479   return sessionStr;
00480 }
00481 
00482 static char* createScaleString(float scale, float currentScale) {
00483   char buf[100];
00484   if (scale == 1.0f && currentScale == 1.0f) {
00485     // This is the default value; we don't need a "Scale:" header:
00486     buf[0] = '\0';
00487   } else {
00488     Locale l("C", Numeric);
00489     sprintf(buf, "Scale: %f\r\n", scale);
00490   }
00491 
00492   return strDup(buf);
00493 }
00494 
00495 static char* createRangeString(double start, double end, char const* absStartTime, char const* absEndTime) {
00496   char buf[100];
00497 
00498   if (absStartTime != NULL) {
00499     // Create a "Range:" header that specifies 'absolute' time values:
00500 
00501     if (absEndTime == NULL) {
00502       // There's no end time:
00503       snprintf(buf, sizeof buf, "Range: clock=%s-\r\n", absStartTime);
00504     } else {
00505       // There's both a start and an end time; include them both in the "Range:" hdr
00506       snprintf(buf, sizeof buf, "Range: clock=%s-%s\r\n", absStartTime, absEndTime);
00507     }
00508   } else {
00509     // Create a "Range:" header that specifies relative (i.e., NPT) time values:
00510 
00511     if (start < 0) {
00512       // We're resuming from a PAUSE; there's no "Range:" header at all
00513       buf[0] = '\0';
00514     } else if (end < 0) {
00515       // There's no end time:
00516       Locale l("C", Numeric);
00517       sprintf(buf, "Range: npt=%.3f-\r\n", start);
00518     } else {
00519       // There's both a start and an end time; include them both in the "Range:" hdr
00520       Locale l("C", Numeric);
00521       sprintf(buf, "Range: npt=%.3f-%.3f\r\n", start, end);
00522     }
00523   }
00524 
00525   return strDup(buf);
00526 }
00527 
00528 unsigned RTSPClient::sendRequest(RequestRecord* request) {
00529   char* cmd = NULL;
00530   do {
00531     Boolean connectionIsPending = False;
00532     if (!fRequestsAwaitingConnection.isEmpty()) {
00533       // A connection is currently pending (with at least one enqueued request).  Enqueue this request also:
00534       connectionIsPending = True;
00535     } else if (fInputSocketNum < 0) { // we need to open a connection
00536       int connectResult = openConnection();
00537       if (connectResult < 0) break; // an error occurred
00538       else if (connectResult == 0) {
00539         // A connection is pending
00540         connectionIsPending = True;
00541       } // else the connection succeeded.  Continue sending the command.u
00542     }
00543     if (connectionIsPending) {
00544       fRequestsAwaitingConnection.enqueue(request);
00545       return request->cseq();
00546     }
00547 
00548     // If requested (and we're not already doing it, or have done it), set up the special protocol for tunneling RTSP-over-HTTP:
00549     if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && fOutputSocketNum == fInputSocketNum) {
00550       if (!setupHTTPTunneling1()) break;
00551       fRequestsAwaitingHTTPTunneling.enqueue(request);
00552       return request->cseq();
00553     }
00554 
00555     // Construct and send the command:
00556 
00557     // First, construct command-specific headers that we need:
00558 
00559     char* cmdURL = fBaseURL; // by default
00560     Boolean cmdURLWasAllocated = False;
00561 
00562     char const* protocolStr = "RTSP/1.0"; // by default
00563 
00564     char* extraHeaders = (char*)""; // by default
00565     Boolean extraHeadersWereAllocated = False; 
00566 
00567     char* contentLengthHeader = (char*)""; // by default
00568     Boolean contentLengthHeaderWasAllocated = False;
00569 
00570     char const* contentStr = request->contentStr(); // by default
00571     if (contentStr == NULL) contentStr = "";
00572     unsigned contentStrLen = strlen(contentStr);
00573     if (contentStrLen > 0) {
00574       char const* contentLengthHeaderFmt =
00575         "Content-Length: %d\r\n";
00576       unsigned contentLengthHeaderSize = strlen(contentLengthHeaderFmt)
00577         + 20 /* max int len */;
00578       contentLengthHeader = new char[contentLengthHeaderSize];
00579       sprintf(contentLengthHeader, contentLengthHeaderFmt, contentStrLen);
00580       contentLengthHeaderWasAllocated = True;
00581     }
00582 
00583     if (strcmp(request->commandName(), "DESCRIBE") == 0) {
00584       extraHeaders = (char*)"Accept: application/sdp\r\n";
00585     } else if (strcmp(request->commandName(), "OPTIONS") == 0) {
00586     } else if (strcmp(request->commandName(), "ANNOUNCE") == 0) {
00587       extraHeaders = (char*)"Content-Type: application/sdp\r\n";
00588     } else if (strcmp(request->commandName(), "SETUP") == 0) {
00589       MediaSubsession& subsession = *request->subsession();
00590       Boolean streamUsingTCP = (request->booleanFlags()&0x1) != 0;
00591       Boolean streamOutgoing = (request->booleanFlags()&0x2) != 0;
00592       Boolean forceMulticastOnUnspecified = (request->booleanFlags()&0x4) != 0;
00593 
00594       char const *prefix, *separator, *suffix;
00595       constructSubsessionURL(subsession, prefix, separator, suffix);
00596 
00597       char const* transportFmt;
00598       if (strcmp(subsession.protocolName(), "UDP") == 0) {
00599         suffix = "";
00600         transportFmt = "Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n";
00601       } else {
00602         transportFmt = "Transport: RTP/AVP%s%s%s=%d-%d\r\n";
00603       }
00604 
00605       cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];
00606       cmdURLWasAllocated = True;
00607       sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);
00608 
00609       // Construct a "Transport:" header.
00610       char const* transportTypeStr;
00611       char const* modeStr = streamOutgoing ? ";mode=receive" : "";
00612           // Note: I think the above is nonstandard, but DSS wants it this way
00613       char const* portTypeStr;
00614       portNumBits rtpNumber, rtcpNumber;
00615       if (streamUsingTCP) { // streaming over the RTSP connection
00616         transportTypeStr = "/TCP;unicast";
00617         portTypeStr = ";interleaved";
00618         rtpNumber = fTCPStreamIdCount++;
00619         rtcpNumber = fTCPStreamIdCount++;
00620       } else { // normal RTP streaming
00621         unsigned connectionAddress = subsession.connectionEndpointAddress();
00622         Boolean requestMulticastStreaming
00623           = IsMulticastAddress(connectionAddress) || (connectionAddress == 0 && forceMulticastOnUnspecified);
00624         transportTypeStr = requestMulticastStreaming ? ";multicast" : ";unicast";
00625         portTypeStr = ";client_port";
00626         rtpNumber = subsession.clientPortNum();
00627         if (rtpNumber == 0) {
00628           envir().setResultMsg("Client port number unknown\n");
00629           delete[] cmdURL;
00630           break;
00631         }
00632         rtcpNumber = rtpNumber + 1;
00633       }
00634       unsigned transportSize = strlen(transportFmt)
00635         + strlen(transportTypeStr) + strlen(modeStr) + strlen(portTypeStr) + 2*5 /* max port len */;
00636       char* transportStr = new char[transportSize];
00637       sprintf(transportStr, transportFmt,
00638               transportTypeStr, modeStr, portTypeStr, rtpNumber, rtcpNumber);
00639 
00640       // When sending more than one "SETUP" request, include a "Session:" header in the 2nd and later commands:
00641       char* sessionStr = createSessionString(fLastSessionId);
00642 
00643       // The "Transport:" and "Session:" (if present) headers make up the 'extra headers':
00644       extraHeaders = new char[transportSize + strlen(sessionStr)];
00645       extraHeadersWereAllocated = True;
00646       sprintf(extraHeaders, "%s%s", transportStr, sessionStr);
00647       delete[] transportStr; delete[] sessionStr;
00648     } else if (strcmp(request->commandName(), "GET") == 0 || strcmp(request->commandName(), "POST") == 0) {
00649       // We will be sending a HTTP (not a RTSP) request.
00650       // Begin by re-parsing our RTSP URL, just to get the stream name, which we'll use as our 'cmdURL' in the subsequent request:
00651       char* username;
00652       char* password;
00653       NetAddress destAddress;
00654       portNumBits urlPortNum;
00655       if (!parseRTSPURL(envir(), fBaseURL, username, password, destAddress, urlPortNum, (char const**)&cmdURL)) break;
00656       if (cmdURL[0] == '\0') cmdURL = (char*)"/";
00657       delete[] username;
00658       delete[] password;
00659 
00660       protocolStr = "HTTP/1.0";
00661 
00662       if (strcmp(request->commandName(), "GET") == 0) {
00663         // Create a 'session cookie' string, using MD5:
00664         struct {
00665           struct timeval timestamp;
00666           unsigned counter;
00667         } seedData;
00668         gettimeofday(&seedData.timestamp, NULL);
00669         seedData.counter = ++fSessionCookieCounter;
00670         our_MD5Data((unsigned char*)(&seedData), sizeof seedData, fSessionCookie);
00671         // DSS seems to require that the 'session cookie' string be 22 bytes long:
00672         fSessionCookie[23] = '\0';
00673         
00674         char const* const extraHeadersFmt =
00675           "x-sessioncookie: %s\r\n"
00676           "Accept: application/x-rtsp-tunnelled\r\n"
00677           "Pragma: no-cache\r\n"
00678           "Cache-Control: no-cache\r\n";
00679         unsigned extraHeadersSize = strlen(extraHeadersFmt)
00680           + strlen(fSessionCookie);
00681         extraHeaders = new char[extraHeadersSize];
00682         extraHeadersWereAllocated = True;
00683         sprintf(extraHeaders, extraHeadersFmt,
00684         fSessionCookie);
00685       } else { // "POST"
00686         char const* const extraHeadersFmt =
00687           "x-sessioncookie: %s\r\n"
00688           "Content-Type: application/x-rtsp-tunnelled\r\n"
00689           "Pragma: no-cache\r\n"
00690           "Cache-Control: no-cache\r\n"
00691           "Content-Length: 32767\r\n"
00692           "Expires: Sun, 9 Jan 1972 00:00:00 GMT\r\n";
00693         unsigned extraHeadersSize = strlen(extraHeadersFmt)
00694           + strlen(fSessionCookie);
00695         extraHeaders = new char[extraHeadersSize];
00696         extraHeadersWereAllocated = True;
00697         sprintf(extraHeaders, extraHeadersFmt,
00698                 fSessionCookie);
00699       }
00700     } else { // "PLAY", "PAUSE", "TEARDOWN", "RECORD", "SET_PARAMETER", "GET_PARAMETER"
00701       // First, make sure that we have a RTSP session in progress
00702       if (fLastSessionId == NULL) {
00703         envir().setResultMsg("No RTSP session is currently in progress\n");
00704         break;
00705       }
00706 
00707       char const* sessionId;
00708       float originalScale;
00709       if (request->session() != NULL) {
00710         // Session-level operation
00711         cmdURL = (char*)sessionURL(*request->session());
00712 
00713         sessionId = fLastSessionId;
00714         originalScale = request->session()->scale();
00715       } else {
00716         // Media-level operation
00717         char const *prefix, *separator, *suffix;
00718         constructSubsessionURL(*request->subsession(), prefix, separator, suffix);
00719         cmdURL = new char[strlen(prefix) + strlen(separator) + strlen(suffix) + 1];
00720         cmdURLWasAllocated = True;
00721         sprintf(cmdURL, "%s%s%s", prefix, separator, suffix);
00722         
00723         sessionId = request->subsession()->sessionId();
00724         originalScale = request->subsession()->scale();
00725       }
00726 
00727       if (strcmp(request->commandName(), "PLAY") == 0) {
00728         // Create "Session:", "Scale:", and "Range:" headers; these make up the 'extra headers':
00729         char* sessionStr = createSessionString(sessionId);
00730         char* scaleStr = createScaleString(request->scale(), originalScale);
00731         char* rangeStr = createRangeString(request->start(), request->end(), request->absStartTime(), request->absEndTime());
00732         extraHeaders = new char[strlen(sessionStr) + strlen(scaleStr) + strlen(rangeStr) + 1];
00733         extraHeadersWereAllocated = True;
00734         sprintf(extraHeaders, "%s%s%s", sessionStr, scaleStr, rangeStr);
00735         delete[] sessionStr; delete[] scaleStr; delete[] rangeStr;
00736       } else {
00737         // Create a "Session:" header; this makes up our 'extra headers':
00738         extraHeaders = createSessionString(sessionId);
00739         extraHeadersWereAllocated = True;
00740       }
00741     }
00742 
00743     char* authenticatorStr = createAuthenticatorString(request->commandName(), fBaseURL);
00744 
00745     char const* const cmdFmt =
00746       "%s %s %s\r\n"
00747       "CSeq: %d\r\n"
00748       "%s"
00749       "%s"
00750       "%s"
00751       "%s"
00752       "\r\n"
00753       "%s";
00754     unsigned cmdSize = strlen(cmdFmt)
00755       + strlen(request->commandName()) + strlen(cmdURL) + strlen(protocolStr)
00756       + 20 /* max int len */
00757       + strlen(authenticatorStr)
00758       + fUserAgentHeaderStrLen
00759       + strlen(extraHeaders)
00760       + strlen(contentLengthHeader)
00761       + contentStrLen;
00762     cmd = new char[cmdSize];
00763     sprintf(cmd, cmdFmt,
00764             request->commandName(), cmdURL, protocolStr,
00765             request->cseq(),
00766             authenticatorStr,
00767             fUserAgentHeaderStr,
00768             extraHeaders,
00769             contentLengthHeader,
00770             contentStr);
00771     delete[] authenticatorStr;
00772     if (cmdURLWasAllocated) delete[] cmdURL;
00773     if (extraHeadersWereAllocated) delete[] extraHeaders;
00774     if (contentLengthHeaderWasAllocated) delete[] contentLengthHeader;
00775 
00776     if (fVerbosityLevel >= 1) envir() << "Sending request: " << cmd << "\n";
00777 
00778     if (fTunnelOverHTTPPortNum != 0 && strcmp(request->commandName(), "GET") != 0 && strcmp(request->commandName(), "POST") != 0) {
00779       // When we're tunneling RTSP-over-HTTP, we Base-64-encode the request before we send it.
00780       // (However, we don't do this for the HTTP "GET" and "POST" commands that we use to set up the tunnel.)
00781       char* origCmd = cmd;
00782       cmd = base64Encode(origCmd, strlen(cmd));
00783       if (fVerbosityLevel >= 1) envir() << "\tThe request was base-64 encoded to: " << cmd << "\n\n";
00784       delete[] origCmd;
00785     }
00786 
00787     if (send(fOutputSocketNum, cmd, strlen(cmd), 0) < 0) {
00788       char const* errFmt = "%s send() failed: ";
00789       unsigned const errLength = strlen(errFmt) + strlen(request->commandName());
00790       char* err = new char[errLength];
00791       sprintf(err, errFmt, request->commandName());
00792       envir().setResultErrMsg(err);
00793       delete[] err;
00794       break;
00795     }
00796 
00797     // The command send succeeded, so enqueue the request record, so that its response (when it comes) can be handled.
00798     // However, note that we do not expect a response to a POST command with RTSP-over-HTTP, so don't enqueue that.
00799     int cseq = request->cseq();
00800 
00801     if (fTunnelOverHTTPPortNum == 0 || strcmp(request->commandName(), "POST") != 0) {
00802       fRequestsAwaitingResponse.enqueue(request);
00803     } else {
00804       delete request;
00805     }
00806 
00807     delete[] cmd;
00808     return cseq;
00809   } while (0);
00810 
00811   // An error occurred, so call the response handler immediately (indicating the error):
00812   delete[] cmd;
00813   handleRequestError(request);
00814   delete request;
00815   return 0;
00816 }
00817 
00818 void RTSPClient::handleRequestError(RequestRecord* request) {
00819   int resultCode = -envir().getErrno();
00820   if (resultCode == 0) {
00821     // Choose some generic error code instead:
00822 #if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)
00823     resultCode = -WSAENOTCONN;
00824 #else
00825     resultCode = -ENOTCONN;
00826 #endif
00827   }
00828   if (request->handler() != NULL) (*request->handler())(this, resultCode, strDup(envir().getResultMsg()));
00829 }
00830 
00831 Boolean RTSPClient
00832 ::parseResponseCode(char const* line, unsigned& responseCode, char const*& responseString) {
00833   if (sscanf(line, "RTSP/%*s%u", &responseCode) != 1 &&
00834       sscanf(line, "HTTP/%*s%u", &responseCode) != 1) return False;
00835   // Note: We check for HTTP responses as well as RTSP responses, both in order to setup RTSP-over-HTTP tunneling,
00836   // and so that we get back a meaningful error if the client tried to mistakenly send a RTSP command to a HTTP-only server.
00837 
00838   // Use everything after the RTSP/* (or HTTP/*) as the response string:
00839   responseString = line;
00840   while (responseString[0] != '\0' && responseString[0] != ' '  && responseString[0] != '\t') ++responseString;
00841   while (responseString[0] != '\0' && (responseString[0] == ' '  || responseString[0] == '\t')) ++responseString; // skip whitespace
00842 
00843   return True;
00844 }
00845 
00846 void RTSPClient::handleIncomingRequest() {
00847   // Parse the request string into command name and 'CSeq', then 'handle' the command (by responding that we don't support it):
00848   char cmdName[RTSP_PARAM_STRING_MAX];
00849   char urlPreSuffix[RTSP_PARAM_STRING_MAX];
00850   char urlSuffix[RTSP_PARAM_STRING_MAX];
00851   char cseq[RTSP_PARAM_STRING_MAX];
00852   char sessionId[RTSP_PARAM_STRING_MAX];
00853   unsigned contentLength;
00854   if (!parseRTSPRequestString(fResponseBuffer, fResponseBytesAlreadySeen,
00855                               cmdName, sizeof cmdName,
00856                               urlPreSuffix, sizeof urlPreSuffix,
00857                               urlSuffix, sizeof urlSuffix,
00858                               cseq, sizeof cseq,
00859                               sessionId, sizeof sessionId,
00860                               contentLength)) {
00861     return;
00862   } else {
00863     if (fVerbosityLevel >= 1) {
00864       envir() << "Received incoming RTSP request: " << fResponseBuffer << "\n";
00865     }
00866     char tmpBuf[2*RTSP_PARAM_STRING_MAX];
00867     snprintf((char*)tmpBuf, sizeof tmpBuf,
00868              "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n\r\n", cseq);
00869     send(fOutputSocketNum, tmpBuf, strlen(tmpBuf), 0);
00870   }
00871 }
00872 
00873 Boolean RTSPClient::checkForHeader(char const* line, char const* headerName, unsigned headerNameLength, char const*& headerParams) {
00874   if (_strncasecmp(line, headerName, headerNameLength) != 0) return False;
00875 
00876   // The line begins with the desired header name.  Trim off any whitespace, and return the header parameters:
00877   unsigned paramIndex = headerNameLength;
00878   while (line[paramIndex] != '\0' && (line[paramIndex] == ' ' || line[paramIndex] == '\t')) ++paramIndex;
00879   if (&line[paramIndex] == '\0') return False; // the header is assumed to be bad if it has no parameters
00880 
00881   headerParams = &line[paramIndex];
00882   return True;
00883 }
00884 
00885 Boolean RTSPClient::parseTransportParams(char const* paramsStr,
00886                                          char*& serverAddressStr, portNumBits& serverPortNum,
00887                                          unsigned char& rtpChannelId, unsigned char& rtcpChannelId) {
00888   // Initialize the return parameters to 'not found' values:
00889   serverAddressStr = NULL;
00890   serverPortNum = 0;
00891   rtpChannelId = rtcpChannelId = 0xFF;
00892   if (paramsStr == NULL) return False;  
00893 
00894   char* foundServerAddressStr = NULL;
00895   Boolean foundServerPortNum = False;
00896   portNumBits clientPortNum = 0;
00897   Boolean foundClientPortNum = False;
00898   Boolean foundChannelIds = False;
00899   unsigned rtpCid, rtcpCid;
00900   Boolean isMulticast = True; // by default
00901   char* foundDestinationStr = NULL;
00902   portNumBits multicastPortNumRTP, multicastPortNumRTCP;
00903   Boolean foundMulticastPortNum = False;
00904 
00905   // Run through each of the parameters, looking for ones that we handle:
00906   char const* fields = paramsStr;
00907   char* field = strDupSize(fields);
00908   while (sscanf(fields, "%[^;]", field) == 1) {
00909     if (sscanf(field, "server_port=%hu", &serverPortNum) == 1) {
00910       foundServerPortNum = True;
00911     } else if (sscanf(field, "client_port=%hu", &clientPortNum) == 1) {
00912       foundClientPortNum = True;
00913     } else if (_strncasecmp(field, "source=", 7) == 0) {
00914       delete[] foundServerAddressStr;
00915       foundServerAddressStr = strDup(field+7);
00916     } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {
00917       rtpChannelId = (unsigned char)rtpCid;
00918       rtcpChannelId = (unsigned char)rtcpCid;
00919       foundChannelIds = True;
00920     } else if (strcmp(field, "unicast") == 0) {
00921       isMulticast = False;
00922     } else if (_strncasecmp(field, "destination=", 12) == 0) {
00923       delete[] foundDestinationStr;
00924       foundDestinationStr = strDup(field+12);
00925     } else if (sscanf(field, "port=%hu-%hu", &multicastPortNumRTP, &multicastPortNumRTCP) == 2 ||
00926                sscanf(field, "port=%hu", &multicastPortNumRTP) == 1) {
00927       foundMulticastPortNum = True;
00928     }
00929 
00930     fields += strlen(field);
00931     while (fields[0] == ';') ++fields; // skip over all leading ';' chars
00932     if (fields[0] == '\0') break;
00933   }
00934   delete[] field;
00935 
00936   // If we're multicast, and have a "destination=" (multicast) address, then use this
00937   // as the 'server' address (because some weird servers don't specify the multicast
00938   // address earlier, in the "DESCRIBE" response's SDP:
00939   if (isMulticast && foundDestinationStr != NULL && foundMulticastPortNum) {
00940     delete[] foundServerAddressStr;
00941     serverAddressStr = foundDestinationStr;
00942     serverPortNum = multicastPortNumRTP;
00943     return True;
00944   }
00945   delete[] foundDestinationStr;
00946 
00947   // We have a valid "Transport:" header if any of the following are true:
00948   //   - We saw a "interleaved=" field, indicating RTP/RTCP-over-TCP streaming, or
00949   //   - We saw a "server_port=" field, or
00950   //   - We saw a "client_port=" field.
00951   //     If we didn't also see a "server_port=" field, then the server port is assumed to be the same as the client port.
00952   if (foundChannelIds || foundServerPortNum || foundClientPortNum) {
00953     if (foundClientPortNum && !foundServerPortNum) {
00954       serverPortNum = clientPortNum;
00955     }
00956     serverAddressStr = foundServerAddressStr;
00957     return True;
00958   }
00959 
00960   delete[] foundServerAddressStr;
00961   return False;
00962 }
00963 
00964 Boolean RTSPClient::parseScaleParam(char const* paramStr, float& scale) {
00965   Locale l("C", Numeric);
00966   return sscanf(paramStr, "%f", &scale) == 1;
00967 }
00968 
00969 Boolean RTSPClient::parseRTPInfoParams(char const*& paramsStr, u_int16_t& seqNum, u_int32_t& timestamp) {
00970   if (paramsStr == NULL || paramsStr[0] == '\0') return False;
00971   while (paramsStr[0] == ',') ++paramsStr;
00972 
00973   // "paramsStr" now consists of a ';'-separated list of parameters, ending with ',' or '\0'.
00974   char* field = strDupSize(paramsStr);
00975 
00976   Boolean sawSeq = False, sawRtptime = False;
00977   while (sscanf(paramsStr, "%[^;,]", field) == 1) {
00978     if (sscanf(field, "seq=%hu", &seqNum) == 1) {
00979       sawSeq = True;
00980     } else if (sscanf(field, "rtptime=%u", &timestamp) == 1) {
00981       sawRtptime = True;
00982     }
00983 
00984     paramsStr += strlen(field);
00985     if (paramsStr[0] == '\0' || paramsStr[0] == ',') break;
00986     // ASSERT: paramsStr[0] == ';'
00987     ++paramsStr; // skip over the ';'
00988   }
00989 
00990   delete[] field;
00991   // For the "RTP-Info:" parameters to be useful to us, we need to have seen both the "seq=" and "rtptime=" parameters:
00992   return sawSeq && sawRtptime;
00993 }
00994 
00995 Boolean RTSPClient::handleSETUPResponse(MediaSubsession& subsession, char const* sessionParamsStr, char const* transportParamsStr,
00996                                         Boolean streamUsingTCP) {
00997   char* sessionId = new char[responseBufferSize]; // ensures we have enough space
00998   Boolean success = False;
00999   do {
01000     // Check for a session id:
01001     if (sessionParamsStr == NULL || sscanf(sessionParamsStr, "%[^;]", sessionId) != 1) {
01002       envir().setResultMsg("Missing or bad \"Session:\" header");
01003       break;
01004     }
01005     subsession.setSessionId(sessionId);
01006     delete[] fLastSessionId; fLastSessionId = strDup(sessionId);
01007 
01008     // Also look for an optional "; timeout = " parameter following this:
01009     char const* afterSessionId = sessionParamsStr + strlen(sessionId);
01010     int timeoutVal;
01011     if (sscanf(afterSessionId, "; timeout = %d", &timeoutVal) == 1) {
01012       fSessionTimeoutParameter = timeoutVal;
01013     }
01014 
01015     // Parse the "Transport:" header parameters:
01016     char* serverAddressStr;
01017     portNumBits serverPortNum;
01018     unsigned char rtpChannelId, rtcpChannelId;
01019     if (!parseTransportParams(transportParamsStr, serverAddressStr, serverPortNum, rtpChannelId, rtcpChannelId)) {
01020       envir().setResultMsg("Missing or bad \"Transport:\" header");
01021       break;
01022     }
01023     delete[] subsession.connectionEndpointName();
01024     subsession.connectionEndpointName() = serverAddressStr;
01025     subsession.serverPortNum = serverPortNum;
01026     subsession.rtpChannelId = rtpChannelId;
01027     subsession.rtcpChannelId = rtcpChannelId;
01028 
01029     if (streamUsingTCP) {
01030       // Tell the subsession to receive RTP (and send/receive RTCP) over the RTSP stream:
01031       if (subsession.rtpSource() != NULL) {
01032         subsession.rtpSource()->setStreamSocket(fInputSocketNum, subsession.rtpChannelId);
01033         subsession.rtpSource()->setServerRequestAlternativeByteHandler(fInputSocketNum, handleAlternativeRequestByte, this);
01034         subsession.rtpSource()->enableRTCPReports() = False;
01035           // To avoid confusing the server (which won't start handling RTP/RTCP-over-TCP until "PLAY"), don't send RTCP "RR"s yet
01036       }
01037       if (subsession.rtcpInstance() != NULL) subsession.rtcpInstance()->setStreamSocket(fInputSocketNum, subsession.rtcpChannelId);
01038     } else {
01039       // Normal case.
01040       // Set the RTP and RTCP sockets' destination address and port from the information in the SETUP response (if present):
01041       netAddressBits destAddress = subsession.connectionEndpointAddress();
01042       if (destAddress == 0) destAddress = fServerAddress;
01043       subsession.setDestinations(destAddress);
01044 
01045       // Hack: To increase the likelihood of UDP packets from the server reaching us, if we're behind a NAT, send a few 'dummy'
01046       // UDP packets to the server now.  (We do this on both our RTP port and our RTCP port.)
01047       Groupsock* gs1 = NULL; Groupsock* gs2 = NULL;
01048       if (subsession.rtpSource() != NULL) gs1 = subsession.rtpSource()->RTPgs();
01049       if (subsession.rtcpInstance() != NULL) gs2 = subsession.rtcpInstance()->RTCPgs();
01050       u_int32_t const dummy = 0xFEEDFACE;
01051       unsigned const numDummyPackets = 2;
01052       for (unsigned i = 0; i < numDummyPackets; ++i) {
01053         if (gs1 != NULL) gs1->output(envir(), 255, (unsigned char*)&dummy, sizeof dummy);
01054         if (gs2 != NULL) gs2->output(envir(), 255, (unsigned char*)&dummy, sizeof dummy);
01055       }
01056     }
01057 
01058     success = True;
01059   } while (0);
01060 
01061   delete[] sessionId;
01062   return success;
01063 }
01064 
01065 Boolean RTSPClient::handlePLAYResponse(MediaSession& session, MediaSubsession& subsession,
01066                                        char const* scaleParamsStr, char const* rangeParamsStr, char const* rtpInfoParamsStr) {
01067   Boolean scaleOK = False, rangeOK = False;
01068   do {
01069     if (&session != NULL) {
01070       // The command was on the whole session
01071       if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, session.scale())) break;
01072       scaleOK = True;
01073       if (rangeParamsStr != NULL && !parseRangeParam(rangeParamsStr, session.playStartTime(), session.playEndTime(),
01074                                                      session._absStartTime(), session._absEndTime())) break;
01075       rangeOK = True;
01076 
01077       MediaSubsessionIterator iter(session);
01078       MediaSubsession* subsession;
01079       while ((subsession = iter.next()) != NULL) {
01080         u_int16_t seqNum; u_int32_t timestamp;
01081         subsession->rtpInfo.infoIsNew = False;
01082         if (parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) {
01083           subsession->rtpInfo.seqNum = seqNum;
01084           subsession->rtpInfo.timestamp = timestamp;
01085           subsession->rtpInfo.infoIsNew = True;
01086         }
01087 
01088         if (subsession->rtpSource() != NULL) subsession->rtpSource()->enableRTCPReports() = True; // start sending RTCP "RR"s now
01089       }
01090     } else {
01091       // The command was on a subsession
01092       if (scaleParamsStr != NULL && !parseScaleParam(scaleParamsStr, subsession.scale())) break;
01093       scaleOK = True;
01094       if (rangeParamsStr != NULL && !parseRangeParam(rangeParamsStr, subsession._playStartTime(), subsession._playEndTime(),
01095                                                      subsession._absStartTime(), subsession._absEndTime())) break;
01096       rangeOK = True;
01097 
01098       u_int16_t seqNum; u_int32_t timestamp;
01099       subsession.rtpInfo.infoIsNew = False;
01100       if (parseRTPInfoParams(rtpInfoParamsStr, seqNum, timestamp)) {
01101         subsession.rtpInfo.seqNum = seqNum;
01102         subsession.rtpInfo.timestamp = timestamp;
01103         subsession.rtpInfo.infoIsNew = True;
01104       }
01105 
01106       if (subsession.rtpSource() != NULL) subsession.rtpSource()->enableRTCPReports() = True; // start sending RTCP "RR"s now
01107     }
01108 
01109     return True;
01110   } while (0);
01111 
01112   // An error occurred:
01113   if (!scaleOK) {
01114     envir().setResultMsg("Bad \"Scale:\" header");
01115   } else if (!rangeOK) {
01116     envir().setResultMsg("Bad \"Range:\" header");
01117   } else {
01118     envir().setResultMsg("Bad \"RTP-Info:\" header");
01119   }
01120   return False;
01121 }
01122 
01123 Boolean RTSPClient::handleTEARDOWNResponse(MediaSession& /*session*/, MediaSubsession& /*subsession*/) {
01124   // Because we don't expect to always get a response to "TEARDOWN", we don't need to do anything if we do get one:
01125   return True;
01126 }
01127 
01128 Boolean RTSPClient::handleGET_PARAMETERResponse(char const* parameterName, char*& resultValueString) {
01129   do {
01130     // If "parameterName" is non-empty, it may be (possibly followed by ':' and whitespace) at the start of the result string:
01131     if (parameterName != NULL && parameterName[0] != '\0') {
01132       if (parameterName[1] == '\0') break; // sanity check; there should have been \r\n at the end of "parameterName"
01133 
01134       unsigned parameterNameLen = strlen(parameterName);
01135       // ASSERT: parameterNameLen >= 2;
01136       parameterNameLen -= 2; // because of the trailing \r\n
01137       if (_strncasecmp(resultValueString, parameterName, parameterNameLen) == 0) {
01138         resultValueString += parameterNameLen;
01139         if (resultValueString[0] == ':') ++resultValueString;
01140         while (resultValueString[0] == ' ' || resultValueString[0] == '\t') ++resultValueString;
01141       }
01142     }
01143 
01144     // The rest of "resultValueStr" should be our desired result, but first trim off any \r and/or \n characters at the end:
01145     unsigned resultLen = strlen(resultValueString);
01146     while (resultLen > 0 && (resultValueString[resultLen-1] == '\r' || resultValueString[resultLen-1] == '\n')) --resultLen;
01147     resultValueString[resultLen] = '\0';
01148 
01149     return True;
01150   } while (0);
01151 
01152   // An error occurred:
01153   envir().setResultMsg("Bad \"GET_PARAMETER\" response");
01154   return False;
01155 }
01156 
01157 Boolean RTSPClient::handleAuthenticationFailure(char const* paramsStr) {
01158   if (paramsStr == NULL) return False; // There was no "WWW-Authenticate:" header; we can't proceed.
01159 
01160   // Fill in "fCurrentAuthenticator" with the information from the "WWW-Authenticate:" header:
01161   Boolean alreadyHadRealm = fCurrentAuthenticator.realm() != NULL;
01162   char* realm = strDupSize(paramsStr);
01163   char* nonce = strDupSize(paramsStr);
01164   Boolean success = True;
01165   if (sscanf(paramsStr, "Digest realm=\"%[^\"]\", nonce=\"%[^\"]\"", realm, nonce) == 2) {
01166     fCurrentAuthenticator.setRealmAndNonce(realm, nonce);
01167   } else if (sscanf(paramsStr, "Basic realm=\"%[^\"]\"", realm) == 1) {
01168     fCurrentAuthenticator.setRealmAndNonce(realm, NULL); // Basic authentication
01169   } else {
01170     success = False; // bad "WWW-Authenticate:" header
01171   }
01172   delete[] realm; delete[] nonce;
01173 
01174   if (alreadyHadRealm || fCurrentAuthenticator.username() == NULL || fCurrentAuthenticator.password() == NULL) {
01175     // We already had a 'realm', or don't have a username and/or password,
01176     // so the new "WWW-Authenticate:" header information won't help us.  We remain unauthenticated.
01177     success = False;
01178   }
01179 
01180   return success;
01181 }
01182 
01183 Boolean RTSPClient::resendCommand(RequestRecord* request) {
01184   if (fVerbosityLevel >= 1) envir() << "Resending...\n";
01185   if (request != NULL && strcmp(request->commandName(), "GET") != 0) request->cseq() = ++fCSeq;
01186   return sendRequest(request) != 0;
01187 }
01188 
01189 char const* RTSPClient::sessionURL(MediaSession const& session) const {
01190   char const* url = session.controlPath();
01191   if (url == NULL || strcmp(url, "*") == 0) url = fBaseURL;
01192 
01193   return url;
01194 }
01195 
01196 void RTSPClient::handleAlternativeRequestByte(void* rtspClient, u_int8_t requestByte) {
01197   ((RTSPClient*)rtspClient)->handleAlternativeRequestByte1(requestByte);
01198 }
01199 
01200 void RTSPClient::handleAlternativeRequestByte1(u_int8_t requestByte) {
01201   if (requestByte == 0xFF) {
01202     // Hack: The new handler of the input TCP socket encountered an error reading it.  Indicate this:
01203     handleResponseBytes(-1);
01204   } else if (requestByte == 0xFE) {
01205     // Another hack: The new handler of the input TCP socket no longer needs it, so take back control:
01206     envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
01207                                                   (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
01208   } else {
01209     // Normal case:
01210     fResponseBuffer[fResponseBytesAlreadySeen] = requestByte;
01211     handleResponseBytes(1);
01212   }
01213 }
01214 
01215 static Boolean isAbsoluteURL(char const* url) {
01216   // Assumption: "url" is absolute if it contains a ':', before any
01217   // occurrence of '/'
01218   while (*url != '\0' && *url != '/') {
01219     if (*url == ':') return True;
01220     ++url;
01221   }
01222 
01223   return False;
01224 }
01225 
01226 void RTSPClient::constructSubsessionURL(MediaSubsession const& subsession,
01227                                         char const*& prefix,
01228                                         char const*& separator,
01229                                         char const*& suffix) {
01230   // Figure out what the URL describing "subsession" will look like.
01231   // The URL is returned in three parts: prefix; separator; suffix
01232   //##### NOTE: This code doesn't really do the right thing if "sessionURL()"
01233   // doesn't end with a "/", and "subsession.controlPath()" is relative.
01234   // The right thing would have been to truncate "sessionURL()" back to the
01235   // rightmost "/", and then add "subsession.controlPath()".
01236   // In practice, though, each "DESCRIBE" response typically contains
01237   // a "Content-Base:" header that consists of "sessionURL()" followed by
01238   // a "/", in which case this code ends up giving the correct result.
01239   // However, we should really fix this code to do the right thing, and
01240   // also check for and use the "Content-Base:" header appropriately. #####
01241   prefix = sessionURL(subsession.parentSession());
01242   if (prefix == NULL) prefix = "";
01243 
01244   suffix = subsession.controlPath();
01245   if (suffix == NULL) suffix = "";
01246 
01247   if (isAbsoluteURL(suffix)) {
01248     prefix = separator = "";
01249   } else {
01250     unsigned prefixLen = strlen(prefix);
01251     separator = (prefixLen == 0 || prefix[prefixLen-1] == '/' || suffix[0] == '/') ? "" : "/";
01252   }
01253 }
01254 
01255 Boolean RTSPClient::setupHTTPTunneling1() {
01256   // Set up RTSP-over-HTTP tunneling, as described in
01257   //     http://developer.apple.com/quicktime/icefloe/dispatch028.html and http://images.apple.com/br/quicktime/pdf/QTSS_Modules.pdf
01258   if (fVerbosityLevel >= 1) {
01259     envir() << "Requesting RTSP-over-HTTP tunneling (on port " << fTunnelOverHTTPPortNum << ")\n\n";
01260   }
01261 
01262   // Begin by sending a HTTP "GET", to set up the server->client link.  Continue when we handle the response:
01263   return sendRequest(new RequestRecord(1, "GET", responseHandlerForHTTP_GET)) != 0;
01264 }
01265 
01266 void RTSPClient::responseHandlerForHTTP_GET(RTSPClient* rtspClient, int responseCode, char* responseString) {
01267   if (rtspClient != NULL) rtspClient->responseHandlerForHTTP_GET1(responseCode, responseString);
01268 }
01269 
01270 void RTSPClient::responseHandlerForHTTP_GET1(int responseCode, char* responseString) {
01271   RequestRecord* request;
01272   do {
01273     if (responseCode != 0) break; // The HTTP "GET" failed.
01274 
01275     // Having successfully set up (using the HTTP "GET" command) the server->client link, set up a second TCP connection
01276     // (to the same server & port as before) for the client->server link.  All future output will be to this new socket.
01277     fOutputSocketNum = setupStreamSocket(envir(), 0);
01278     if (fOutputSocketNum < 0) break;
01279     ignoreSigPipeOnSocket(fOutputSocketNum); // so that servers on the same host that killed don't also kill us
01280 
01281     fHTTPTunnelingConnectionIsPending = True;
01282     int connectResult = connectToServer(fOutputSocketNum, fTunnelOverHTTPPortNum);
01283     if (connectResult < 0) break; // an error occurred
01284     else if (connectResult == 0) {
01285       // A connection is pending.  Continue setting up RTSP-over-HTTP when the connection completes.
01286       // First, move the pending requests to the 'awaiting connection' queue:
01287       while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
01288         fRequestsAwaitingConnection.enqueue(request);
01289       }
01290       return;
01291     }
01292 
01293     // The connection succeeded.  Continue setting up RTSP-over-HTTP:
01294     if (!setupHTTPTunneling2()) break;
01295 
01296     // RTSP-over-HTTP tunneling succeeded.  Resume the pending request(s):
01297     while ((request = fRequestsAwaitingHTTPTunneling.dequeue()) != NULL) {
01298       sendRequest(request);
01299     }
01300     return;
01301   } while (0);
01302 
01303   // An error occurred.  Dequeue the pending request(s), and tell them about the error:
01304   fHTTPTunnelingConnectionIsPending = False;
01305   resetTCPSockets(); // do this now, in case an error handler deletes "this"
01306   RequestQueue requestQueue(fRequestsAwaitingHTTPTunneling);
01307   while ((request = requestQueue.dequeue()) != NULL) {
01308     handleRequestError(request);
01309     delete request;
01310   }
01311 }
01312 
01313 Boolean RTSPClient::setupHTTPTunneling2() {
01314   fHTTPTunnelingConnectionIsPending = False;
01315 
01316   // Send a HTTP "POST", to set up the client->server link.  (Note that we won't see a reply to the "POST".)
01317   return sendRequest(new RequestRecord(1, "POST", NULL)) != 0;
01318 }
01319 
01320 void RTSPClient::connectionHandler(void* instance, int /*mask*/) {
01321   RTSPClient* client = (RTSPClient*)instance;
01322   client->connectionHandler1();
01323 }
01324 
01325 void RTSPClient::connectionHandler1() {
01326   // Restore normal handling on our sockets:
01327   envir().taskScheduler().disableBackgroundHandling(fOutputSocketNum);
01328   envir().taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
01329                                                 (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
01330 
01331   // Move all requests awaiting connection into a new, temporary queue, to clear "fRequestsAwaitingConnection"
01332   // (so that "sendRequest()" doesn't get confused by "fRequestsAwaitingConnection" being nonempty, and enqueue them all over again).
01333   RequestQueue tmpRequestQueue(fRequestsAwaitingConnection);
01334   RequestRecord* request;
01335 
01336   // Find out whether the connection succeeded or failed:
01337   do {
01338     int err = 0;
01339     SOCKLEN_T len = sizeof err;
01340     if (getsockopt(fInputSocketNum, SOL_SOCKET, SO_ERROR, (char*)&err, &len) < 0 || err != 0) {
01341       envir().setResultErrMsg("Connection to server failed: ", err);
01342       if (fVerbosityLevel >= 1) envir() << "..." << envir().getResultMsg() << "\n";
01343       break;
01344     }
01345 
01346     // The connection succeeded.  If the connection came about from an attempt to set up RTSP-over-HTTP, finish this now:
01347     if (fVerbosityLevel >= 1) envir() << "...remote connection opened\n";
01348     if (fHTTPTunnelingConnectionIsPending && !setupHTTPTunneling2()) break;
01349 
01350     // Resume sending all pending requests:
01351     while ((request = tmpRequestQueue.dequeue()) != NULL) {
01352       sendRequest(request);
01353     }
01354     return;
01355   } while (0);
01356 
01357   // An error occurred.  Tell all pending requests about the error:
01358   resetTCPSockets(); // do this now, in case an error handler deletes "this"
01359   while ((request = tmpRequestQueue.dequeue()) != NULL) {
01360     handleRequestError(request);
01361     delete request;
01362   }
01363 }
01364 
01365 void RTSPClient::incomingDataHandler(void* instance, int /*mask*/) {
01366   RTSPClient* client = (RTSPClient*)instance;
01367   client->incomingDataHandler1();
01368 }
01369 
01370 void RTSPClient::incomingDataHandler1() {
01371   struct sockaddr_in dummy; // 'from' address - not used
01372 
01373   int bytesRead = readSocket(envir(), fInputSocketNum, (unsigned char*)&fResponseBuffer[fResponseBytesAlreadySeen], fResponseBufferBytesLeft, dummy);
01374   handleResponseBytes(bytesRead);
01375 }
01376 
01377 static char* getLine(char* startOfLine) {
01378   // returns the start of the next line, or NULL if none.  Note that this modifies the input string to add '\0' characters.
01379   for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) {
01380     // Check for the end of line: \r\n (but also accept \r or \n by itself):
01381     if (*ptr == '\r' || *ptr == '\n') {
01382       // We found the end of the line
01383       if (*ptr == '\r') {
01384         *ptr++ = '\0';
01385         if (*ptr == '\n') ++ptr;
01386       } else {
01387         *ptr++ = '\0';
01388       }
01389       return ptr;
01390     }
01391   }
01392 
01393   return NULL;
01394 }
01395 
01396 void RTSPClient::handleResponseBytes(int newBytesRead) {
01397   do {
01398     if (newBytesRead >= 0 && (unsigned)newBytesRead < fResponseBufferBytesLeft) break; // data was read OK; process it below
01399 
01400     if (newBytesRead >= (int)fResponseBufferBytesLeft) {
01401       // We filled up our response buffer.  Treat this as an error (for the first response handler):
01402       envir().setResultMsg("RTSP response was truncated. Increase \"RTSPClient::responseBufferSize\"");
01403     }
01404 
01405     // An error occurred while reading our TCP socket.  Call all pending response handlers, indicating this error.
01406     // (However, the "RTSP response was truncated" error is applied to the first response handler only.)
01407     resetResponseBuffer();
01408     RequestRecord* request;
01409     if (newBytesRead > 0) { // The "RTSP response was truncated" error
01410       if ((request = fRequestsAwaitingResponse.dequeue()) != NULL) {
01411         handleRequestError(request);
01412         delete request;
01413       }
01414     } else {
01415       RequestQueue requestQueue(fRequestsAwaitingResponse);
01416       resetTCPSockets(); // do this now, in case an error handler deletes "this"
01417 
01418       while ((request = requestQueue.dequeue()) != NULL) {
01419         handleRequestError(request);
01420         delete request;
01421       }
01422     }
01423     return;
01424   } while (0);
01425 
01426   fResponseBufferBytesLeft -= newBytesRead;
01427   fResponseBytesAlreadySeen += newBytesRead;
01428   fResponseBuffer[fResponseBytesAlreadySeen] = '\0';
01429   if (fVerbosityLevel >= 1 && newBytesRead > 1) envir() << "Received " << newBytesRead << " new bytes of response data.\n";
01430   
01431   unsigned numExtraBytesAfterResponse = 0;
01432   Boolean responseSuccess = False; // by default
01433   do {
01434     // Data was read OK.  Look through the data that we've read so far, to see if it contains <CR><LF><CR><LF>.
01435     // (If not, wait for more data to arrive.)
01436     Boolean endOfHeaders = False;
01437     char const* ptr = fResponseBuffer;
01438     if (fResponseBytesAlreadySeen > 3) {
01439       char const* const ptrEnd = &fResponseBuffer[fResponseBytesAlreadySeen-3];
01440       while (ptr < ptrEnd) {
01441         if (*ptr++ == '\r' && *ptr++ == '\n' && *ptr++ == '\r' && *ptr++ == '\n') {
01442           // This is it
01443           endOfHeaders = True;
01444           break;
01445         }
01446       }
01447     }
01448     
01449     if (!endOfHeaders) return; // subsequent reads will be needed to get the complete response
01450     
01451     // Now that we have the complete response headers (ending with <CR><LF><CR><LF>), parse them to get the response code, CSeq,
01452     // and various other header parameters.  To do this, we first make a copy of the received header data, because we'll be
01453     // modifying it by adding '\0' bytes.
01454     char* headerDataCopy;
01455     unsigned responseCode = 200;
01456     char const* responseStr = NULL;
01457     RequestRecord* foundRequest = NULL;
01458     char const* sessionParamsStr = NULL;
01459     char const* transportParamsStr = NULL;
01460     char const* scaleParamsStr = NULL;
01461     char const* rangeParamsStr = NULL;
01462     char const* rtpInfoParamsStr = NULL;
01463     char const* wwwAuthenticateParamsStr = NULL;
01464     char const* publicParamsStr = NULL;
01465     char* bodyStart = NULL;
01466     unsigned numBodyBytes = 0;
01467     responseSuccess = False;
01468     do {
01469       headerDataCopy = new char[responseBufferSize];
01470       strncpy(headerDataCopy, fResponseBuffer, fResponseBytesAlreadySeen);
01471       headerDataCopy[fResponseBytesAlreadySeen] = '\0';
01472       
01473       char* lineStart = headerDataCopy;
01474       char* nextLineStart = getLine(lineStart);
01475       if (!parseResponseCode(lineStart, responseCode, responseStr)) {
01476         // This does not appear to be a RTSP response; perhaps it's a RTSP request instead?
01477         handleIncomingRequest();
01478         break; // we're done with this data
01479       }
01480       
01481       // Scan through the headers, handling the ones that we're interested in:
01482       Boolean reachedEndOfHeaders;
01483       unsigned cseq = 0;
01484       unsigned contentLength = 0;
01485       
01486       while (1) {
01487         reachedEndOfHeaders = True; // by default; may get changed below
01488         lineStart = nextLineStart;
01489         if (lineStart == NULL) break;
01490         
01491         nextLineStart = getLine(lineStart);
01492         if (lineStart[0] == '\0') break; // this is a blank line
01493         reachedEndOfHeaders = False;
01494         
01495         char const* headerParamsStr; 
01496         if (checkForHeader(lineStart, "CSeq:", 5, headerParamsStr)) {
01497           if (sscanf(headerParamsStr, "%u", &cseq) != 1 || cseq <= 0) {
01498             envir().setResultMsg("Bad \"CSeq:\" header: \"", lineStart, "\"");
01499             break;
01500           }
01501           // Find the handler function for "cseq":
01502           RequestRecord* request;
01503           while ((request = fRequestsAwaitingResponse.dequeue()) != NULL) {
01504             if (request->cseq() < cseq) { // assumes that the CSeq counter will never wrap around
01505               // We never received (and will never receive) a response for this handler, so delete it:
01506               if (fVerbosityLevel >= 1 && strcmp(request->commandName(), "POST") != 0) {
01507                 envir() << "WARNING: The server did not respond to our \"" << request->commandName() << "\" request (CSeq: "
01508                         << request->cseq() << ").  The server appears to be buggy (perhaps not handling pipelined requests properly).\n";
01509               }
01510               delete request;
01511             } else if (request->cseq() == cseq) {
01512               // This is the handler that we want. Remove its record, but remember it, so that we can later call its handler:
01513               foundRequest = request;
01514               break;
01515             } else { // request->cseq() > cseq
01516               // No handler was registered for this response, so ignore it.
01517               break;
01518             }
01519           }
01520         } else if (checkForHeader(lineStart, "Content-Length:", 15, headerParamsStr)) {
01521           if (sscanf(headerParamsStr, "%u", &contentLength) != 1) {
01522             envir().setResultMsg("Bad \"Content-Length:\" header: \"", lineStart, "\"");
01523             break;
01524           }
01525         } else if (checkForHeader(lineStart, "Content-Base:", 13, headerParamsStr)) {
01526           setBaseURL(headerParamsStr);
01527         } else if (checkForHeader(lineStart, "Session:", 8, sessionParamsStr)) {
01528         } else if (checkForHeader(lineStart, "Transport:", 10, transportParamsStr)) {
01529         } else if (checkForHeader(lineStart, "Scale:", 6, scaleParamsStr)) {
01530         } else if (checkForHeader(lineStart, "Range:", 6, rangeParamsStr)) {
01531         } else if (checkForHeader(lineStart, "RTP-Info:", 9, rtpInfoParamsStr)) {
01532         } else if (checkForHeader(lineStart, "WWW-Authenticate:", 17, headerParamsStr)) {
01533           // If we've already seen a "WWW-Authenticate:" header, then we replace it with this new one only if
01534           // the new one specifies "Digest" authentication:
01535           if (wwwAuthenticateParamsStr == NULL || _strncasecmp(headerParamsStr, "Digest", 6) == 0) {
01536             wwwAuthenticateParamsStr = headerParamsStr;
01537           }
01538         } else if (checkForHeader(lineStart, "Public:", 7, publicParamsStr)) {
01539         } else if (checkForHeader(lineStart, "Allow:", 6, publicParamsStr)) {
01540           // Note: we accept "Allow:" instead of "Public:", so that "OPTIONS" requests made to HTTP servers will work.
01541         } else if (checkForHeader(lineStart, "Location:", 9, headerParamsStr)) {
01542           setBaseURL(headerParamsStr);
01543         }
01544       }
01545       if (!reachedEndOfHeaders) break; // an error occurred
01546       
01547       if (foundRequest == NULL) {
01548         // Hack: The response didn't have a "CSeq:" header; assume it's for our most recent request:
01549         foundRequest = fRequestsAwaitingResponse.dequeue();
01550       }
01551       
01552       // If we saw a "Content-Length:" header, then make sure that we have the amount of data that it specified:
01553       unsigned bodyOffset = nextLineStart == NULL ? fResponseBytesAlreadySeen : nextLineStart - headerDataCopy;
01554       bodyStart = &fResponseBuffer[bodyOffset];
01555       numBodyBytes = fResponseBytesAlreadySeen - bodyOffset;
01556       if (contentLength > numBodyBytes) {
01557         // We need to read more data.  First, make sure we have enough space for it:
01558         unsigned numExtraBytesNeeded = contentLength - numBodyBytes;
01559         unsigned remainingBufferSize = responseBufferSize - fResponseBytesAlreadySeen;
01560         if (numExtraBytesNeeded > remainingBufferSize) {
01561           char tmpBuf[200];
01562           sprintf(tmpBuf, "Response buffer size (%d) is too small for \"Content-Length:\" %d (need a buffer size of >= %d bytes\n",
01563                   responseBufferSize, contentLength, fResponseBytesAlreadySeen + numExtraBytesNeeded);
01564           envir().setResultMsg(tmpBuf);
01565           break;
01566         }
01567         
01568         if (fVerbosityLevel >= 1) {
01569           envir() << "Have received " << fResponseBytesAlreadySeen << " total bytes of a "
01570                   << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)")
01571                   << " RTSP response; awaiting " << numExtraBytesNeeded << " bytes more.\n";
01572         }
01573         delete[] headerDataCopy;
01574         if (foundRequest != NULL) fRequestsAwaitingResponse.putAtHead(foundRequest);// put our request record back; we need it again
01575         return; // We need to read more data
01576       }
01577       
01578       // We now have a complete response (including all bytes specified by the "Content-Length:" header, if any).
01579       char* responseEnd = bodyStart + contentLength;
01580       numExtraBytesAfterResponse = &fResponseBuffer[fResponseBytesAlreadySeen] - responseEnd;
01581 
01582       if (fVerbosityLevel >= 1) {
01583         char saved = *responseEnd;
01584         *responseEnd = '\0';
01585         envir() << "Received a complete "
01586                 << (foundRequest != NULL ? foundRequest->commandName() : "(unknown)")
01587                 << " response:\n" << fResponseBuffer << "\n";
01588         if (numExtraBytesAfterResponse > 0) envir() << "\t(plus " << numExtraBytesAfterResponse << " additional bytes)\n";
01589         *responseEnd = saved;
01590       }
01591       
01592       if (foundRequest != NULL) {
01593         Boolean needToResendCommand = False; // by default...
01594         if (responseCode == 200) {
01595           // Do special-case response handling for some commands:
01596           if (strcmp(foundRequest->commandName(), "SETUP") == 0) {
01597             if (!handleSETUPResponse(*foundRequest->subsession(), sessionParamsStr, transportParamsStr, foundRequest->booleanFlags()&0x1)) break;
01598           } else if (strcmp(foundRequest->commandName(), "PLAY") == 0) {
01599             if (!handlePLAYResponse(*foundRequest->session(), *foundRequest->subsession(), scaleParamsStr, rangeParamsStr, rtpInfoParamsStr)) break;
01600           } else if (strcmp(foundRequest->commandName(), "TEARDOWN") == 0) {
01601             if (!handleTEARDOWNResponse(*foundRequest->session(), *foundRequest->subsession())) break;
01602           } else if (strcmp(foundRequest->commandName(), "GET_PARAMETER") == 0) {
01603             if (!handleGET_PARAMETERResponse(foundRequest->contentStr(), bodyStart)) break;
01604           }
01605         } else if (responseCode == 401 && handleAuthenticationFailure(wwwAuthenticateParamsStr)) {
01606           // We need to resend the command, with an "Authorization:" header:
01607           needToResendCommand = True;
01608           
01609           if (strcmp(foundRequest->commandName(), "GET") == 0) {
01610             // Note: If a HTTP "GET" command (for RTSP-over-HTTP tunneling) returns "401 Unauthorized", then we resend it
01611             // (with an "Authorization:" header), just as we would for a RTSP command.  However, we do so using a new TCP connection,
01612             // because some servers close the original connection after returning the "401 Unauthorized".
01613             resetTCPSockets(); // forces the opening of a new connection for the resent command
01614           }
01615         } else if (responseCode == 301 || responseCode == 302) { // redirection
01616           resetTCPSockets(); // because we need to connect somewhere else next
01617           needToResendCommand = True;
01618         }
01619         
01620         if (needToResendCommand) {
01621           resetResponseBuffer();
01622           if (!resendCommand(foundRequest)) break;
01623           delete[] headerDataCopy;
01624           return; // without calling our response handler; the response to the resent command will do that
01625         }
01626       }
01627       
01628       responseSuccess = True;
01629     } while (0);
01630     
01631     // If we have a handler function for this response, call it.
01632     // But first, reset our response buffer, in case the handler goes to the event loop, and we end up getting called recursively:
01633     if (numExtraBytesAfterResponse > 0) {
01634       // An unusual case; usually due to having received pipelined responses.  Move the extra bytes to the front of the buffer:
01635       char* responseEnd = &fResponseBuffer[fResponseBytesAlreadySeen - numExtraBytesAfterResponse];
01636       
01637       // But first: A hack to save a copy of the response 'body', in case it's needed below for "resultString":
01638       numBodyBytes -= numExtraBytesAfterResponse;
01639       if (numBodyBytes > 0) {
01640         char saved = *responseEnd;
01641         *responseEnd = '\0';
01642         bodyStart = strDup(bodyStart);
01643         *responseEnd = saved;
01644       }
01645       
01646       memmove(fResponseBuffer, responseEnd, numExtraBytesAfterResponse);
01647       fResponseBytesAlreadySeen = numExtraBytesAfterResponse;
01648       fResponseBufferBytesLeft = responseBufferSize - numExtraBytesAfterResponse;
01649       fResponseBuffer[numExtraBytesAfterResponse] = '\0';
01650     } else {
01651       resetResponseBuffer();
01652     }
01653     if (foundRequest != NULL && foundRequest->handler() != NULL) {
01654       int resultCode;
01655       char* resultString;
01656       if (responseSuccess) {
01657         if (responseCode == 200) {
01658           resultCode = 0;
01659           resultString = numBodyBytes > 0 ? strDup(bodyStart) : strDup(publicParamsStr);
01660           // Note: The "strDup(bodyStart)" call assumes that the body is encoded without interior '\0' bytes
01661         } else {
01662           resultCode = responseCode;
01663           resultString = strDup(responseStr);
01664           envir().setResultMsg(responseStr);
01665         }
01666         (*foundRequest->handler())(this, resultCode, resultString);
01667       } else {
01668         // An error occurred parsing the response, so call the handler, indicating an error:
01669         handleRequestError(foundRequest);
01670       }
01671     }
01672     delete foundRequest;
01673     delete[] headerDataCopy;
01674     if (numExtraBytesAfterResponse > 0 && numBodyBytes > 0) delete[] bodyStart;
01675   } while (numExtraBytesAfterResponse > 0 && responseSuccess);
01676 }
01677 
01678 
01680 
01681 RTSPClient::RequestRecord::RequestRecord(unsigned cseq, char const* commandName, responseHandler* handler,
01682                                          MediaSession* session, MediaSubsession* subsession, u_int32_t booleanFlags,
01683                                          double start, double end, float scale, char const* contentStr)
01684   : fNext(NULL), fCSeq(cseq), fCommandName(commandName), fSession(session), fSubsession(subsession), fBooleanFlags(booleanFlags),
01685     fStart(start), fEnd(end), fAbsStartTime(NULL), fAbsEndTime(NULL), fScale(scale), fContentStr(strDup(contentStr)), fHandler(handler) {
01686 }
01687 
01688 RTSPClient::RequestRecord::RequestRecord(unsigned cseq, responseHandler* handler,
01689                                          char const* absStartTime, char const* absEndTime, float scale,
01690                                          MediaSession* session, MediaSubsession* subsession)
01691   : fNext(NULL), fCSeq(cseq), fCommandName("PLAY"), fSession(session), fSubsession(subsession), fBooleanFlags(0),
01692     fStart(0.0f), fEnd(-1.0f), fAbsStartTime(strDup(absStartTime)), fAbsEndTime(strDup(absEndTime)), fScale(scale),
01693     fContentStr(NULL), fHandler(handler) {
01694 }
01695 
01696 RTSPClient::RequestRecord::~RequestRecord() {
01697   // Delete the rest of the list first:
01698   delete fNext;
01699 
01700   delete[] fAbsStartTime; delete[] fAbsEndTime;
01701   delete[] fContentStr;
01702 }
01703 
01704 
01706 
01707 RTSPClient::RequestQueue::RequestQueue()
01708   : fHead(NULL), fTail(NULL) {
01709 }
01710 
01711 RTSPClient::RequestQueue::RequestQueue(RequestQueue& origQueue)
01712   : fHead(NULL), fTail(NULL) {
01713   RequestRecord* request;
01714   while ((request = origQueue.dequeue()) != NULL) {
01715     enqueue(request);
01716   }
01717 }
01718 
01719 RTSPClient::RequestQueue::~RequestQueue() {
01720   delete fHead;
01721 }
01722 
01723 void RTSPClient::RequestQueue::enqueue(RequestRecord* request) {
01724   if (fTail == NULL) {
01725     fHead = request;
01726   } else {
01727     fTail->next() = request;
01728   }
01729   fTail = request;
01730 }
01731 
01732 RTSPClient::RequestRecord* RTSPClient::RequestQueue::dequeue() {
01733   RequestRecord* request = fHead;
01734   if (fHead == fTail) {
01735     fHead = NULL;
01736     fTail = NULL;
01737   } else {
01738     fHead = fHead->next();
01739   }
01740   if (request != NULL) request->next() = NULL;
01741   return request;
01742 }
01743 
01744 void RTSPClient::RequestQueue::putAtHead(RequestRecord* request) {
01745   request->next() = fHead;
01746   fHead = request;
01747   if (fTail == NULL) {
01748     fTail = request;
01749   }
01750 }
01751 
01752 RTSPClient::RequestRecord* RTSPClient::RequestQueue::findByCSeq(unsigned cseq) {
01753   RequestRecord* request;
01754   for (request = fHead; request != NULL; request = request->next()) {
01755     if (request->cseq() == cseq) return request;
01756   }
01757   return NULL;
01758 }
01759 
01760 
01761 #ifdef RTSPCLIENT_SYNCHRONOUS_INTERFACE
01762 // Implementation of the old (synchronous) "RTSPClient" interface, using the new (asynchronous) interface:
01763 RTSPClient* RTSPClient::createNew(UsageEnvironment& env,
01764                                   int verbosityLevel,
01765                                   char const* applicationName,
01766                                   portNumBits tunnelOverHTTPPortNum) {
01767   return new RTSPClient(env, NULL,
01768                         verbosityLevel, applicationName, tunnelOverHTTPPortNum);
01769 }
01770 
01771 char* RTSPClient::describeURL(char const* url, Authenticator* authenticator,
01772                               Boolean allowKasennaProtocol, int timeout) {
01773   // Sorry 'Kasenna', but the party's over.  You've had 6 years to make your servers compliant with the standard RTSP protocol.
01774   // We're not going to support your non-standard hacked version of the protocol any more.  Starting now, the "allowKasennaProtocol"
01775   // parameter is a noop (and eventually, when the synchronous interface goes away completely, then so will this parameter).
01776 
01777   // First, check whether "url" contains a username:password to be used.  If so, handle this using "describeWithPassword()" instead:
01778   char* username; char* password;
01779   if (authenticator == NULL
01780       && parseRTSPURLUsernamePassword(url, username, password)) {
01781     char* result = describeWithPassword(url, username, password, allowKasennaProtocol, timeout);
01782     delete[] username; delete[] password; // they were dynamically allocated
01783     return result;
01784   }
01785 
01786   setBaseURL(url);
01787   fWatchVariableForSyncInterface = 0;
01788   fTimeoutTask = NULL;  // by default, unless:
01789   if (timeout > 0) {
01790     // Schedule a task to be called when the specified timeout interval expires.
01791     // Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
01792     // command response handler - because we want the response handler to unschedule any pending timeout handler.
01793     fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
01794   }
01795   (void)sendDescribeCommand(responseHandlerForSyncInterface, authenticator);
01796 
01797   // Now block (but handling events) until we get a response (or a timeout):
01798   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01799   if (fResultCode == 0) return fResultString; // success
01800   delete[] fResultString;
01801   return NULL;
01802 }
01803 
01804 char* RTSPClient::describeWithPassword(char const* url,
01805                                        char const* username, char const* password,
01806                                        Boolean allowKasennaProtocol, int timeout) {
01807   Authenticator authenticator(username, password);
01808   return describeURL(url, &authenticator, allowKasennaProtocol, timeout);
01809 }
01810 
01811 char* RTSPClient::sendOptionsCmd(char const* url,
01812                                  char* username, char* password,
01813                                  Authenticator* authenticator,
01814                                  int timeout) {
01815   char* result = NULL;
01816   Boolean haveAllocatedAuthenticator = False;
01817   if (authenticator == NULL) {
01818     // First, check whether "url" contains a username:password to be used
01819     // (and no username,password pair was supplied separately):
01820     if (username == NULL && password == NULL
01821         && parseRTSPURLUsernamePassword(url, username, password)) {
01822       Authenticator newAuthenticator(username,password);
01823       result = sendOptionsCmd(url, username, password, &newAuthenticator, timeout);
01824       delete[] username; delete[] password; // they were dynamically allocated
01825       return result;
01826     } else if (username != NULL && password != NULL) {
01827       // Use the separately supplied username and password:
01828       authenticator = new Authenticator(username,password);
01829       haveAllocatedAuthenticator = True;
01830       
01831       result = sendOptionsCmd(url, username, password, authenticator, timeout);
01832       if (result != NULL) { // We are already authorized
01833         delete authenticator;
01834         return result;
01835       }
01836 
01837       // The "realm" field should have been filled in:
01838       if (authenticator->realm() == NULL) {
01839         // We haven't been given enough information to try again, so fail:
01840         delete authenticator;
01841         return NULL;
01842       }
01843     }
01844   }
01845 
01846   setBaseURL(url);
01847   fWatchVariableForSyncInterface = 0;
01848   fTimeoutTask = NULL;  // by default, unless:
01849   if (timeout > 0) {
01850     // Schedule a task to be called when the specified timeout interval expires.
01851     // Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
01852     // command response handler - because we want the response handler to unschedule any pending timeout handler.
01853     fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
01854   }
01855   (void)sendOptionsCommand(responseHandlerForSyncInterface, authenticator);
01856   if (haveAllocatedAuthenticator) delete authenticator;
01857 
01858   // Now block (but handling events) until we get a response (or a timeout):
01859   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01860   if (fResultCode == 0) return fResultString; // success
01861   delete[] fResultString;
01862   return NULL;
01863 }
01864 
01865 Boolean RTSPClient::announceSDPDescription(char const* url,
01866                                            char const* sdpDescription,
01867                                            Authenticator* authenticator,
01868                                            int timeout) {
01869   setBaseURL(url);
01870   fWatchVariableForSyncInterface = 0;
01871   fTimeoutTask = NULL;  // by default, unless:
01872   if (timeout > 0) {
01873     // Schedule a task to be called when the specified timeout interval expires.
01874     // Note that we do this *before* attempting to send the RTSP command, in case this attempt fails immediately, calling the
01875     // command response handler - because we want the response handler to unschedule any pending timeout handler.
01876     fTimeoutTask = envir().taskScheduler().scheduleDelayedTask(timeout*1000000, timeoutHandlerForSyncInterface, this);
01877   }
01878   (void)sendAnnounceCommand(sdpDescription, responseHandlerForSyncInterface, authenticator);
01879 
01880   // Now block (but handling events) until we get a response (or a timeout):
01881   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01882   delete[] fResultString;
01883   return fResultCode == 0;
01884 }
01885 
01886 Boolean RTSPClient
01887 ::announceWithPassword(char const* url, char const* sdpDescription,
01888                        char const* username, char const* password, int timeout) {
01889   Authenticator authenticator(username,password);
01890   return announceSDPDescription(url, sdpDescription, &authenticator, timeout);
01891 }
01892 
01893 Boolean RTSPClient::setupMediaSubsession(MediaSubsession& subsession,
01894                                          Boolean streamOutgoing,
01895                                          Boolean streamUsingTCP,
01896                                          Boolean forceMulticastOnUnspecified) {
01897   fWatchVariableForSyncInterface = 0;
01898   fTimeoutTask = NULL;
01899   (void)sendSetupCommand(subsession, responseHandlerForSyncInterface, streamOutgoing, streamUsingTCP, forceMulticastOnUnspecified);
01900 
01901   // Now block (but handling events) until we get a response (or a timeout):
01902   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01903   delete[] fResultString;
01904   return fResultCode == 0;
01905 }
01906 
01907 Boolean RTSPClient::playMediaSession(MediaSession& session,
01908                                      double start, double end, float scale) {
01909   fWatchVariableForSyncInterface = 0;
01910   fTimeoutTask = NULL;
01911   (void)sendPlayCommand(session, responseHandlerForSyncInterface, start, end, scale);
01912 
01913   // Now block (but handling events) until we get a response (or a timeout):
01914   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01915   delete[] fResultString;
01916   return fResultCode == 0;
01917 }
01918 
01919 Boolean RTSPClient::playMediaSubsession(MediaSubsession& subsession,
01920                                         double start, double end, float scale,
01921                                         Boolean /*hackForDSS*/) {
01922   // NOTE: The "hackForDSS" flag is no longer supported.  (However, we will consider resupporting it
01923   // if we get reports that it is still needed.)
01924   fWatchVariableForSyncInterface = 0;
01925   fTimeoutTask = NULL;
01926   (void)sendPlayCommand(subsession, responseHandlerForSyncInterface, start, end, scale);
01927 
01928   // Now block (but handling events) until we get a response (or a timeout):
01929   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01930   delete[] fResultString;
01931   return fResultCode == 0;
01932 }
01933 
01934 Boolean RTSPClient::pauseMediaSession(MediaSession& session) {
01935   fWatchVariableForSyncInterface = 0;
01936   fTimeoutTask = NULL;
01937   (void)sendPauseCommand(session, responseHandlerForSyncInterface);
01938 
01939   // Now block (but handling events) until we get a response (or a timeout):
01940   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01941   delete[] fResultString;
01942   return fResultCode == 0;
01943 }
01944 
01945 Boolean RTSPClient::pauseMediaSubsession(MediaSubsession& subsession) {
01946   fWatchVariableForSyncInterface = 0;
01947   fTimeoutTask = NULL;
01948   (void)sendPauseCommand(subsession, responseHandlerForSyncInterface);
01949 
01950   // Now block (but handling events) until we get a response (or a timeout):
01951   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01952   delete[] fResultString;
01953   return fResultCode == 0;
01954 }
01955 
01956 Boolean RTSPClient::recordMediaSubsession(MediaSubsession& subsession) {
01957   fWatchVariableForSyncInterface = 0;
01958   fTimeoutTask = NULL;
01959   (void)sendRecordCommand(subsession, responseHandlerForSyncInterface);
01960 
01961   // Now block (but handling events) until we get a response (or a timeout):
01962   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01963   delete[] fResultString;
01964   return fResultCode == 0;
01965 }
01966 
01967 Boolean RTSPClient::setMediaSessionParameter(MediaSession& session,
01968                                              char const* parameterName,
01969                                              char const* parameterValue) {
01970   fWatchVariableForSyncInterface = 0;
01971   fTimeoutTask = NULL;
01972   (void)sendSetParameterCommand(session, responseHandlerForSyncInterface, parameterName, parameterValue);
01973 
01974   // Now block (but handling events) until we get a response (or a timeout):
01975   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01976   delete[] fResultString;
01977   return fResultCode == 0;
01978 }
01979 
01980 Boolean RTSPClient::getMediaSessionParameter(MediaSession& session,
01981                                              char const* parameterName,
01982                                              char*& parameterValue) {
01983   fWatchVariableForSyncInterface = 0;
01984   fTimeoutTask = NULL;
01985   (void)sendGetParameterCommand(session, responseHandlerForSyncInterface, parameterName);
01986 
01987   // Now block (but handling events) until we get a response (or a timeout):
01988   envir().taskScheduler().doEventLoop(&fWatchVariableForSyncInterface);
01989   parameterValue = fResultString;
01990   return fResultCode == 0;
01991 }
01992 
01993 Boolean RTSPClient::teardownMediaSession(MediaSession& session) {
01994   fWatchVariableForSyncInterface = 0;
01995   fTimeoutTask = NULL;
01996   (void)sendTeardownCommand(session, NULL);
01997   return True; // we don't wait for a response to the "TEARDOWN"
01998 }
01999 
02000 Boolean RTSPClient::teardownMediaSubsession(MediaSubsession& subsession) {
02001   fWatchVariableForSyncInterface = 0;
02002   fTimeoutTask = NULL;
02003   (void)sendTeardownCommand(subsession, NULL);
02004   return True; // we don't wait for a response to the "TEARDOWN"
02005 }
02006 
02007 Boolean RTSPClient::parseRTSPURLUsernamePassword(char const* url,
02008                                                  char*& username,
02009                                                  char*& password) {
02010   username = password = NULL; // by default
02011   do {
02012     // Parse the URL as "rtsp://<username>[:<password>]@<whatever>"
02013     char const* prefix = "rtsp://";
02014     unsigned const prefixLength = 7;
02015     if (_strncasecmp(url, prefix, prefixLength) != 0) break;
02016 
02017     // Look for the ':' and '@':
02018     unsigned usernameIndex = prefixLength;
02019     unsigned colonIndex = 0, atIndex = 0;
02020     for (unsigned i = usernameIndex; url[i] != '\0' && url[i] != '/'; ++i) {
02021       if (url[i] == ':' && colonIndex == 0) {
02022         colonIndex = i;
02023       } else if (url[i] == '@') {
02024         atIndex = i;
02025         break; // we're done
02026       }
02027     }
02028     if (atIndex == 0) break; // no '@' found
02029 
02030     char* urlCopy = strDup(url);
02031     urlCopy[atIndex] = '\0';
02032     if (colonIndex > 0) {
02033       urlCopy[colonIndex] = '\0';
02034       password = strDup(&urlCopy[colonIndex+1]);
02035     } else {
02036       password = strDup("");
02037     }
02038     username = strDup(&urlCopy[usernameIndex]);
02039     delete[] urlCopy;
02040 
02041     return True;
02042   } while (0);
02043 
02044   return False;
02045 }
02046 
02047 void RTSPClient::responseHandlerForSyncInterface(RTSPClient* rtspClient, int responseCode, char* responseString) {
02048   if (rtspClient != NULL) rtspClient->responseHandlerForSyncInterface1(responseCode, responseString);
02049 }
02050 
02051 void RTSPClient::responseHandlerForSyncInterface1(int responseCode, char* responseString) {
02052   // If we have a 'timeout task' pending, then unschedule it:
02053   if (fTimeoutTask != NULL) envir().taskScheduler().unscheduleDelayedTask(fTimeoutTask);
02054 
02055   // Set result values:
02056   fResultCode = responseCode;
02057   fResultString = responseString;
02058 
02059   // Signal a break from the event loop (thereby returning from the blocking command):
02060   fWatchVariableForSyncInterface = ~0;
02061 }
02062 
02063 void RTSPClient::timeoutHandlerForSyncInterface(void* rtspClient) {
02064   if (rtspClient != NULL) ((RTSPClient*)rtspClient)->timeoutHandlerForSyncInterface1();
02065 }
02066 
02067 void RTSPClient::timeoutHandlerForSyncInterface1() {
02068   // A RTSP command has timed out, so we should have a queued request record.  Disable it by setting its response handler to NULL.
02069   // (Because this is a synchronous interface, there should be exactly one pending response handler - for "fCSeq".)
02070   // all of them.)
02071   changeResponseHandler(fCSeq, NULL);
02072   fTimeoutTask = NULL;
02073 
02074   // Fill in 'negative' return values:
02075   fResultCode = ~0;
02076   fResultString = NULL;
02077 
02078   // Signal a break from the event loop (thereby returning from the blocking command):
02079   fWatchVariableForSyncInterface = ~0;
02080 }
02081 
02082 #endif

Generated on Mon Apr 29 13:28:03 2013 for live by  doxygen 1.5.2