liveMedia/RTSPClient.cpp

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

Generated on Tue Jun 18 13:16:52 2013 for live by  doxygen 1.5.2