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

Generated on Fri Sep 3 02:35:41 2010 for live by  doxygen 1.5.2