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

Generated on Thu May 17 07:11:47 2012 for live by  doxygen 1.5.2