/********** This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. (See .) This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA **********/ // "liveMedia" // Copyright (c) 1996-2016 Live Networks, Inc. All rights reserved. // A RTSP server // Implementation #include "RTSPServer.hh" #include "RTSPCommon.hh" #include "RTSPRegisterSender.hh" #include "ProxyServerMediaSession.hh" #include "Base64.hh" #include #include ////////// RTSPServer implementation ////////// RTSPServer* RTSPServer::createNew(UsageEnvironment& env, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationSeconds) { int ourSocket = setUpOurSocket(env, ourPort); if (ourSocket == -1) return NULL; return new RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationSeconds); } Boolean RTSPServer::lookupByName(UsageEnvironment& env, char const* name, RTSPServer*& resultServer) { resultServer = NULL; // unless we succeed Medium* medium; if (!Medium::lookupByName(env, name, medium)) return False; if (!medium->isRTSPServer()) { env.setResultMsg(name, " is not a RTSP server"); return False; } resultServer = (RTSPServer*) medium; return True; } void rtspRegisterResponseHandler(RTSPClient* rtspClient, int resultCode, char* resultString); // forward // A class that represents the state of a "REGISTER" request in progress: // TODO: comment for strip code //class RegisterRequestRecord: public RTSPRegisterSender { //public: // RegisterRequestRecord(RTSPServer& ourServer, unsigned requestId, // char const* remoteClientNameOrAddress, portNumBits remoteClientPortNum, char const* rtspURLToRegister, // RTSPServer::responseHandlerForREGISTER* responseHandler, Authenticator* authenticator, // Boolean requestStreamingViaTCP, char const* proxyURLSuffix) // : RTSPRegisterSender(ourServer.envir(), remoteClientNameOrAddress, remoteClientPortNum, rtspURLToRegister, // rtspRegisterResponseHandler, authenticator, // requestStreamingViaTCP, proxyURLSuffix, True/*reuseConnection*/, //#ifdef DEBUG // 1/*verbosityLevel*/, //#else // 0/*verbosityLevel*/, //#endif // NULL), // fOurServer(ourServer), fRequestId(requestId), fResponseHandler(responseHandler) { // // Add ourself to our server's 'pending REGISTER requests' table: // ourServer.fPendingRegisterRequests->Add((char const*)this, this); // } // // virtual ~RegisterRequestRecord() { // // Remove ourself from the server's 'pending REGISTER requests' hash table before we go: // fOurServer.fPendingRegisterRequests->Remove((char const*)this); // } // // void handleResponse(int resultCode, char* resultString) { // if (resultCode == 0) { // // The "REGISTER" request succeeded, so use the still-open RTSP socket to await incoming commands from the remote endpoint: // int sock; // struct sockaddr_in remoteAddress; // // grabConnection(sock, remoteAddress); // if (sock >= 0) (void)fOurServer.createNewClientConnection(sock, remoteAddress); // } // // if (fResponseHandler != NULL) { // // Call our (REGISTER-specific) response handler now: // (*fResponseHandler)(&fOurServer, fRequestId, resultCode, resultString); // } else { // // We need to delete[] "resultString" before we leave: // delete[] resultString; // } // // // We're completely done with the REGISTER command now, so delete ourself now: // delete this; // } // //private: // RTSPServer& fOurServer; // unsigned fRequestId; // RTSPServer::responseHandlerForREGISTER* fResponseHandler; //}; //void rtspRegisterResponseHandler(RTSPClient* rtspClient, int resultCode, char* resultString) { // RegisterRequestRecord* registerRequestRecord = (RegisterRequestRecord*)rtspClient; // // registerRequestRecord->handleResponse(resultCode, resultString); //} unsigned RTSPServer::registerStream(ServerMediaSession* serverMediaSession, char const* remoteClientNameOrAddress, portNumBits remoteClientPortNum, responseHandlerForREGISTER* responseHandler, char const* username, char const* password, Boolean receiveOurStreamViaTCP, char const* proxyURLSuffix) { // Create a new "RegisterRequestRecord" that will send the "REGISTER" command. // (This object will automatically get deleted after we get a response to the "REGISTER" command, or if we're deleted.) Authenticator* authenticator = NULL; if (username != NULL) { if (password == NULL) password = ""; authenticator = new Authenticator(username, password); } unsigned requestId = ++fRegisterRequestCounter; // new RegisterRequestRecord(*this, requestId, // remoteClientNameOrAddress, remoteClientPortNum, rtspURL(serverMediaSession), // responseHandler, authenticator, // receiveOurStreamViaTCP, proxyURLSuffix); throw "code is commented, can not run on right way!"; delete authenticator; // we can do this here because it was copied to the "RegisterRequestRecord" return requestId; } char* RTSPServer::rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket) const { char* urlPrefix = rtspURLPrefix(clientSocket); char const* sessionName = serverMediaSession->streamName(); char* resultURL = new char[strlen(urlPrefix) + strlen(sessionName) + 1]; sprintf(resultURL, "%s%s", urlPrefix, sessionName); delete[] urlPrefix; return resultURL; } char* RTSPServer::rtspURLPrefix(int clientSocket) const { struct sockaddr_in ourAddress; if (clientSocket < 0) { // Use our default IP address in the URL: ourAddress.sin_addr.s_addr = ReceivingInterfaceAddr != 0 ? ReceivingInterfaceAddr : ourIPAddress(envir()); // hack } else { SOCKLEN_T namelen = sizeof(ourAddress); getsockname(clientSocket, (struct sockaddr*) &ourAddress, (socklen_t *)&namelen); } char urlBuffer[100]; // more than big enough for "rtsp://:/" portNumBits portNumHostOrder = ntohs(fServerPort.num()); if (portNumHostOrder == 554 /* the default port number */) { sprintf(urlBuffer, "rtsp://%s/", AddressString(ourAddress).val()); } else { sprintf(urlBuffer, "rtsp://%s:%hu/", AddressString(ourAddress).val(), portNumHostOrder); } return strDup(urlBuffer); } UserAuthenticationDatabase* RTSPServer::setAuthenticationDatabase( UserAuthenticationDatabase* newDB) { UserAuthenticationDatabase* oldDB = fAuthDB; fAuthDB = newDB; return oldDB; } Boolean RTSPServer::setUpTunnelingOverHTTP(Port httpPort) { fHTTPServerSocket = setUpOurSocket(envir(), httpPort); if (fHTTPServerSocket >= 0) { fHTTPServerPort = httpPort; envir().taskScheduler().turnOnBackgroundReadHandling(fHTTPServerSocket, incomingConnectionHandlerHTTP, this); return True; } return False; } portNumBits RTSPServer::httpServerPortNum() const { return ntohs(fHTTPServerPort.num()); } char const* RTSPServer::allowedCommandNames() { return "OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER"; } Boolean RTSPServer::weImplementREGISTER(char const* /*proxyURLSuffix*/, char*& responseStr) { // By default, servers do not implement our custom "REGISTER" command: responseStr = NULL; return False; } void RTSPServer::implementCmd_REGISTER(char const* /*url*/, char const* /*urlSuffix*/, int /*socketToRemoteServer*/, Boolean /*deliverViaTCP*/, char const* /*proxyURLSuffix*/) { // By default, this function is a 'noop' } UserAuthenticationDatabase* RTSPServer::getAuthenticationDatabaseForCommand( char const* /*cmdName*/) { // default implementation return fAuthDB; } Boolean RTSPServer::specialClientAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/, char const* /*urlSuffix*/) { // default implementation return True; } Boolean RTSPServer::specialClientUserAccessCheck(int /*clientSocket*/, struct sockaddr_in& /*clientAddr*/, char const* /*urlSuffix*/, char const * /*username*/) { // default implementation; no further access restrictions: return True; } RTSPServer::RTSPServer(UsageEnvironment& env, int ourSocket, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationSeconds) : GenericMediaServer(env, ourSocket, ourPort, reclamationSeconds), fHTTPServerSocket( -1), fHTTPServerPort(0), fClientConnectionsForHTTPTunneling( NULL), // will get created if needed fTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)), fPendingRegisterRequests( HashTable::create(ONE_WORD_HASH_KEYS)), fRegisterRequestCounter( 0), fAuthDB(authDatabase), fAllowStreamingRTPOverTCP(True) { } // A data structure that is used to implement "fTCPStreamingDatabase" // (and the "noteTCPStreamingOnSocket()" and "stopTCPStreamingOnSocket()" member functions): class streamingOverTCPRecord { public: streamingOverTCPRecord(u_int32_t sessionId, unsigned trackNum, streamingOverTCPRecord* next) : fNext(next), fSessionId(sessionId), fTrackNum(trackNum) { } virtual ~streamingOverTCPRecord() { delete fNext; } streamingOverTCPRecord* fNext; u_int32_t fSessionId; unsigned fTrackNum; }; RTSPServer::~RTSPServer() { // Turn off background HTTP read handling (if any): if (fHTTPServerSocket > 0) { envir().taskScheduler().turnOffBackgroundReadHandling( fHTTPServerSocket); ::closeSocket(fHTTPServerSocket); delete fClientConnectionsForHTTPTunneling; } cleanup(); // Removes all "ClientSession" and "ClientConnection" objects, and their tables. // Delete any pending REGISTER requests: //TODO : comment for strip code // RegisterRequestRecord* registerRequest; // while ((registerRequest = (RegisterRequestRecord*)fPendingRegisterRequests->getFirst()) != NULL) { // delete registerRequest; // } delete fPendingRegisterRequests; // Empty out and close "fTCPStreamingDatabase": streamingOverTCPRecord* sotcp; while ((sotcp = (streamingOverTCPRecord*) fTCPStreamingDatabase->getFirst()) != NULL) { delete sotcp; } delete fTCPStreamingDatabase; } Boolean RTSPServer::isRTSPServer() const { return True; } void RTSPServer::incomingConnectionHandlerHTTP(void* instance, int /*mask*/) { RTSPServer* server = (RTSPServer*) instance; server->incomingConnectionHandlerHTTP(); } void RTSPServer::incomingConnectionHandlerHTTP() { incomingConnectionHandlerOnSocket(fHTTPServerSocket); } void RTSPServer::noteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum) { streamingOverTCPRecord* sotcpCur = (streamingOverTCPRecord*) fTCPStreamingDatabase->Lookup( (char const*) socketNum); streamingOverTCPRecord* sotcpNew = new streamingOverTCPRecord( clientSession->fOurSessionId, trackNum, sotcpCur); fTCPStreamingDatabase->Add((char const*) socketNum, sotcpNew); } void RTSPServer::unnoteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum) { if (socketNum < 0) return; streamingOverTCPRecord* sotcpHead = (streamingOverTCPRecord*) fTCPStreamingDatabase->Lookup( (char const*) socketNum); if (sotcpHead == NULL) return; // Look for a record of the (session,track); remove it if found: streamingOverTCPRecord* sotcp = sotcpHead; streamingOverTCPRecord* sotcpPrev = sotcpHead; do { if (sotcp->fSessionId == clientSession->fOurSessionId && sotcp->fTrackNum == trackNum) break; sotcpPrev = sotcp; sotcp = sotcp->fNext; } while (sotcp != NULL); if (sotcp == NULL) return; // not found if (sotcp == sotcpHead) { // We found it at the head of the list. Remove it and reinsert the tail into the hash table: sotcpHead = sotcp->fNext; sotcp->fNext = NULL; delete sotcp; if (sotcpHead == NULL) { // There were no more entries on the list. Remove the original entry from the hash table: fTCPStreamingDatabase->Remove((char const*) socketNum); } else { // Add the rest of the list into the hash table (replacing the original): fTCPStreamingDatabase->Add((char const*) socketNum, sotcpHead); } } else { // We found it on the list, but not at the head. Unlink it: sotcpPrev->fNext = sotcp->fNext; sotcp->fNext = NULL; delete sotcp; } } void RTSPServer::stopTCPStreamingOnSocket(int socketNum) { // Close any stream that is streaming over "socketNum" (using RTP/RTCP-over-TCP streaming): streamingOverTCPRecord* sotcp = (streamingOverTCPRecord*) fTCPStreamingDatabase->Lookup( (char const*) socketNum); if (sotcp != NULL) { do { RTSPClientSession* clientSession = (RTSPServer::RTSPClientSession*) lookupClientSession( sotcp->fSessionId); if (clientSession != NULL) { clientSession->deleteStreamByTrack(sotcp->fTrackNum); } streamingOverTCPRecord* sotcpNext = sotcp->fNext; sotcp->fNext = NULL; delete sotcp; sotcp = sotcpNext; } while (sotcp != NULL); fTCPStreamingDatabase->Remove((char const*) socketNum); } } ////////// RTSPServer::RTSPClientConnection implementation ////////// RTSPServer::RTSPClientConnection::RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr) : GenericMediaServer::ClientConnection(ourServer, clientSocket, clientAddr), fOurRTSPServer(ourServer), fClientInputSocket( fOurSocket), fClientOutputSocket(fOurSocket), fIsActive(True), fRecursionCount( 0), fOurSessionCookie(NULL) { resetRequestBuffer(); } RTSPServer::RTSPClientConnection::~RTSPClientConnection() { if (fOurSessionCookie != NULL) { // We were being used for RTSP-over-HTTP tunneling. Also remove ourselves from the 'session cookie' hash table before we go: fOurRTSPServer.fClientConnectionsForHTTPTunneling->Remove( fOurSessionCookie); delete[] fOurSessionCookie; } closeSocketsRTSP(); } // Special mechanism for handling our custom "REGISTER" command: RTSPServer::RTSPClientConnection::ParamsForREGISTER::ParamsForREGISTER( RTSPServer::RTSPClientConnection* ourConnection, char const* url, char const* urlSuffix, Boolean reuseConnection, Boolean deliverViaTCP, char const* proxyURLSuffix) : fOurConnection(ourConnection), fURL(strDup(url)), fURLSuffix( strDup(urlSuffix)), fReuseConnection(reuseConnection), fDeliverViaTCP( deliverViaTCP), fProxyURLSuffix(strDup(proxyURLSuffix)) { } RTSPServer::RTSPClientConnection::ParamsForREGISTER::~ParamsForREGISTER() { delete[] fURL; delete[] fURLSuffix; delete[] fProxyURLSuffix; } // Handler routines for specific RTSP commands: void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() { snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n", fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames()); } void RTSPServer::RTSPClientConnection::handleCmd_GET_PARAMETER( char const* /*fullRequestStr*/) { // By default, we implement "GET_PARAMETER" (on the entire server) just as a 'no op', and send back a dummy response. // (If you want to handle this type of "GET_PARAMETER" differently, you can do so by defining a subclass of "RTSPServer" // and "RTSPServer::RTSPClientConnection", and then reimplement this virtual function in your subclass.) setRTSPResponse("200 OK", LIVEMEDIA_LIBRARY_VERSION_STRING); } void RTSPServer::RTSPClientConnection::handleCmd_SET_PARAMETER( char const* /*fullRequestStr*/) { // By default, we implement "SET_PARAMETER" (on the entire server) just as a 'no op', and send back an empty response. // (If you want to handle this type of "SET_PARAMETER" differently, you can do so by defining a subclass of "RTSPServer" // and "RTSPServer::RTSPClientConnection", and then reimplement this virtual function in your subclass.) setRTSPResponse("200 OK"); } void RTSPServer::RTSPClientConnection::handleCmd_DESCRIBE( char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) { ServerMediaSession* session = NULL; char* sdpDescription = NULL; char* rtspURL = NULL; do { char urlTotalSuffix[2 * RTSP_PARAM_STRING_MAX]; // enough space for urlPreSuffix/urlSuffix'\0' urlTotalSuffix[0] = '\0'; if (urlPreSuffix[0] != '\0') { strcat(urlTotalSuffix, urlPreSuffix); strcat(urlTotalSuffix, "/"); } strcat(urlTotalSuffix, urlSuffix); if (!authenticationOK("DESCRIBE", urlTotalSuffix, fullRequestStr)) break; // We should really check that the request contains an "Accept:" ##### // for "application/sdp", because that's what we're sending back ##### // Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix": session = fOurServer.lookupServerMediaSession(urlTotalSuffix); if (session == NULL) { handleCmd_notFound(); break; } // Increment the "ServerMediaSession" object's reference count, in case someone removes it // while we're using it: session->incrementReferenceCount(); // Then, assemble a SDP description for this session: sdpDescription = session->generateSDPDescription(); if (sdpDescription == NULL) { // This usually means that a file name that was specified for a // "ServerMediaSubsession" does not exist. setRTSPResponse("404 File Not Found, Or In Incorrect Format"); break; } unsigned sdpDescriptionSize = strlen(sdpDescription); // Also, generate our RTSP URL, for the "Content-Base:" header // (which is necessary to ensure that the correct URL gets used in subsequent "SETUP" requests). rtspURL = fOurRTSPServer.rtspURL(session, fClientInputSocket); snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 200 OK\r\nCSeq: %s\r\n" "%s" "Content-Base: %s/\r\n" "Content-Type: application/sdp\r\n" "Cache-Control: no-cache\r\n" "Content-Length: %d\r\n\r\n" "%s", fCurrentCSeq, dateHeader(), rtspURL, sdpDescriptionSize, sdpDescription); } while (0); if (session != NULL) { // Decrement its reference count, now that we're done using it: session->decrementReferenceCount(); if (session->referenceCount() == 0 && session->deleteWhenUnreferenced()) { fOurServer.removeServerMediaSession(session); } } delete[] sdpDescription; delete[] rtspURL; } static void lookForHeader(char const* headerName, char const* source, unsigned sourceLen, char* resultStr, unsigned resultMaxSize) { resultStr[0] = '\0'; // by default, return an empty string unsigned headerNameLen = strlen(headerName); for (int i = 0; i < (int) (sourceLen - headerNameLen); ++i) { if (strncmp(&source[i], headerName, headerNameLen) == 0 && source[i + headerNameLen] == ':') { // We found the header. Skip over any whitespace, then copy the rest of the line to "resultStr": for (i += headerNameLen + 1; i < (int) sourceLen && (source[i] == ' ' || source[i] == '\t'); ++i) { } for (unsigned j = i; j < sourceLen; ++j) { if (source[j] == '\r' || source[j] == '\n') { // We've found the end of the line. Copy it to the result (if it will fit): if (j - i + 1 > resultMaxSize) break; char const* resultSource = &source[i]; char const* resultSourceEnd = &source[j]; while (resultSource < resultSourceEnd) *resultStr++ = *resultSource++; *resultStr = '\0'; break; } } } } } void RTSPServer::RTSPClientConnection::handleCmd_REGISTER(char const* url, char const* urlSuffix, char const* fullRequestStr, Boolean reuseConnection, Boolean deliverViaTCP, char const* proxyURLSuffix) { char* responseStr; if (fOurRTSPServer.weImplementREGISTER(proxyURLSuffix, responseStr)) { // The "REGISTER" command - if we implement it - may require access control: if (!authenticationOK("REGISTER", urlSuffix, fullRequestStr)) return; // We implement the "REGISTER" command by first replying to it, then actually handling it // (in a separate event-loop task, that will get called after the reply has been done): setRTSPResponse(responseStr == NULL ? "200 OK" : responseStr); delete[] responseStr; ParamsForREGISTER* registerParams = new ParamsForREGISTER(this, url, urlSuffix, reuseConnection, deliverViaTCP, proxyURLSuffix); envir().taskScheduler().scheduleDelayedTask(0, (TaskFunc*) continueHandlingREGISTER, registerParams); } else if (responseStr != NULL) { setRTSPResponse(responseStr); delete[] responseStr; } else { handleCmd_notSupported(); } } void RTSPServer::RTSPClientConnection::handleCmd_bad() { // Don't do anything with "fCurrentCSeq", because it might be nonsense snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 400 Bad Request\r\n%sAllow: %s\r\n\r\n", dateHeader(), fOurRTSPServer.allowedCommandNames()); } void RTSPServer::RTSPClientConnection::handleCmd_notSupported() { snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 405 Method Not Allowed\r\nCSeq: %s\r\n%sAllow: %s\r\n\r\n", fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames()); } void RTSPServer::RTSPClientConnection::handleCmd_notFound() { setRTSPResponse("404 Stream Not Found"); } void RTSPServer::RTSPClientConnection::handleCmd_sessionNotFound() { setRTSPResponse("454 Session Not Found"); } void RTSPServer::RTSPClientConnection::handleCmd_unsupportedTransport() { setRTSPResponse("461 Unsupported Transport"); } Boolean RTSPServer::RTSPClientConnection::parseHTTPRequestString( char* resultCmdName, unsigned resultCmdNameMaxSize, char* urlSuffix, unsigned urlSuffixMaxSize, char* sessionCookie, unsigned sessionCookieMaxSize, char* acceptStr, unsigned acceptStrMaxSize) { // Check for the limited HTTP requests that we expect for specifying RTSP-over-HTTP tunneling. // This parser is currently rather dumb; it should be made smarter ##### char const* reqStr = (char const*) fRequestBuffer; unsigned const reqStrSize = fRequestBytesAlreadySeen; // Read everything up to the first space as the command name: Boolean parseSucceeded = False; unsigned i; for (i = 0; i < resultCmdNameMaxSize - 1 && i < reqStrSize; ++i) { char c = reqStr[i]; if (c == ' ' || c == '\t') { parseSucceeded = True; break; } resultCmdName[i] = c; } resultCmdName[i] = '\0'; if (!parseSucceeded) return False; // Look for the string "HTTP/", before the first \r or \n: parseSucceeded = False; for (; i < reqStrSize - 5 && reqStr[i] != '\r' && reqStr[i] != '\n'; ++i) { if (reqStr[i] == 'H' && reqStr[i + 1] == 'T' && reqStr[i + 2] == 'T' && reqStr[i + 3] == 'P' && reqStr[i + 4] == '/') { i += 5; // to advance past the "HTTP/" parseSucceeded = True; break; } } if (!parseSucceeded) return False; // Get the 'URL suffix' that occurred before this: unsigned k = i - 6; while (k > 0 && reqStr[k] == ' ') --k; // back up over white space unsigned j = k; while (j > 0 && reqStr[j] != ' ' && reqStr[j] != '/') --j; // The URL suffix is in position (j,k]: if (k - j + 1 > urlSuffixMaxSize) return False; // there's no room> unsigned n = 0; while (++j <= k) urlSuffix[n++] = reqStr[j]; urlSuffix[n] = '\0'; // Look for various headers that we're interested in: lookForHeader("x-sessioncookie", &reqStr[i], reqStrSize - i, sessionCookie, sessionCookieMaxSize); lookForHeader("Accept", &reqStr[i], reqStrSize - i, acceptStr, acceptStrMaxSize); return True; } void RTSPServer::RTSPClientConnection::handleHTTPCmd_notSupported() { snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "HTTP/1.1 405 Method Not Allowed\r\n%s\r\n\r\n", dateHeader()); } void RTSPServer::RTSPClientConnection::handleHTTPCmd_notFound() { snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "HTTP/1.1 404 Not Found\r\n%s\r\n\r\n", dateHeader()); } void RTSPServer::RTSPClientConnection::handleHTTPCmd_OPTIONS() { #ifdef DEBUG fprintf(stderr, "Handled HTTP \"OPTIONS\" request\n"); #endif // Construct a response to the "OPTIONS" command that notes that our special headers (for RTSP-over-HTTP tunneling) are allowed: snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "HTTP/1.1 200 OK\r\n" "%s" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Methods: POST, GET, OPTIONS\r\n" "Access-Control-Allow-Headers: x-sessioncookie, Pragma, Cache-Control\r\n" "Access-Control-Max-Age: 1728000\r\n" "\r\n", dateHeader()); } void RTSPServer::RTSPClientConnection::handleHTTPCmd_TunnelingGET( char const* sessionCookie) { // Record ourself as having this 'session cookie', so that a subsequent HTTP "POST" command (with the same 'session cookie') // can find us: if (fOurRTSPServer.fClientConnectionsForHTTPTunneling == NULL) { fOurRTSPServer.fClientConnectionsForHTTPTunneling = HashTable::create( STRING_HASH_KEYS); } delete[] fOurSessionCookie; fOurSessionCookie = strDup(sessionCookie); fOurRTSPServer.fClientConnectionsForHTTPTunneling->Add(sessionCookie, (void*) this); #ifdef DEBUG fprintf(stderr, "Handled HTTP \"GET\" request (client output socket: %d)\n", fClientOutputSocket); #endif // Construct our response: snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "HTTP/1.1 200 OK\r\n" "%s" "Cache-Control: no-cache\r\n" "Pragma: no-cache\r\n" "Content-Type: application/x-rtsp-tunnelled\r\n" "\r\n", dateHeader()); } Boolean RTSPServer::RTSPClientConnection::handleHTTPCmd_TunnelingPOST( char const* sessionCookie, unsigned char const* extraData, unsigned extraDataSize) { // Use the "sessionCookie" string to look up the separate "RTSPClientConnection" object that should have been used to handle // an earlier HTTP "GET" request: if (fOurRTSPServer.fClientConnectionsForHTTPTunneling == NULL) { fOurRTSPServer.fClientConnectionsForHTTPTunneling = HashTable::create( STRING_HASH_KEYS); } RTSPServer::RTSPClientConnection* prevClientConnection = (RTSPServer::RTSPClientConnection*) (fOurRTSPServer.fClientConnectionsForHTTPTunneling->Lookup( sessionCookie)); if (prevClientConnection == NULL) { // There was no previous HTTP "GET" request; treat this "POST" request as bad: handleHTTPCmd_notSupported(); fIsActive = False; // triggers deletion of ourself return False; } #ifdef DEBUG fprintf(stderr, "Handled HTTP \"POST\" request (client input socket: %d)\n", fClientInputSocket); #endif // Change the previous "RTSPClientSession" object's input socket to ours. It will be used for subsequent requests: prevClientConnection->changeClientInputSocket(fClientInputSocket, extraData, extraDataSize); fClientInputSocket = fClientOutputSocket = -1; // so the socket doesn't get closed when we get deleted return True; } void RTSPServer::RTSPClientConnection::handleHTTPCmd_StreamingGET( char const* /*urlSuffix*/, char const* /*fullRequestStr*/) { // By default, we don't support requests to access streams via HTTP: handleHTTPCmd_notSupported(); } void RTSPServer::RTSPClientConnection::resetRequestBuffer() { ClientConnection::resetRequestBuffer(); fLastCRLF = &fRequestBuffer[-3]; // hack: Ensures that we don't think we have end-of-msg if the data starts with fBase64RemainderCount = 0; } void RTSPServer::RTSPClientConnection::closeSocketsRTSP() { // First, tell our server to stop any streaming that it might be doing over our output socket: fOurRTSPServer.stopTCPStreamingOnSocket(fClientOutputSocket); // Turn off background handling on our input socket (and output socket, if different); then close it (or them): if (fClientOutputSocket != fClientInputSocket) { envir().taskScheduler().disableBackgroundHandling(fClientOutputSocket); ::closeSocket(fClientOutputSocket); } fClientOutputSocket = -1; closeSockets(); // closes fClientInputSocket } void RTSPServer::RTSPClientConnection::handleAlternativeRequestByte( void* instance, u_int8_t requestByte) { RTSPClientConnection* connection = (RTSPClientConnection*) instance; connection->handleAlternativeRequestByte1(requestByte); } void RTSPServer::RTSPClientConnection::handleAlternativeRequestByte1( u_int8_t requestByte) { if (requestByte == 0xFF) { // Hack: The new handler of the input TCP socket encountered an error reading it. Indicate this: handleRequestBytes(-1); } else if (requestByte == 0xFE) { // Another hack: The new handler of the input TCP socket no longer needs it, so take back control of it: envir().taskScheduler().setBackgroundHandling(fClientInputSocket, SOCKET_READABLE | SOCKET_EXCEPTION, incomingRequestHandler, this); } else { // Normal case: Add this character to our buffer; then try to handle the data that we have buffered so far: if (fRequestBufferBytesLeft == 0|| fRequestBytesAlreadySeen >= REQUEST_BUFFER_SIZE) return; fRequestBuffer[fRequestBytesAlreadySeen] = requestByte; handleRequestBytes(1); } } // A special version of "parseTransportHeader()", used just for parsing the "Transport:" header in an incoming "REGISTER" command: static void parseTransportHeaderForREGISTER(char const* buf, Boolean &reuseConnection, Boolean& deliverViaTCP, char*& proxyURLSuffix) { // Initialize the result parameters to default values: reuseConnection = False; deliverViaTCP = False; proxyURLSuffix = NULL; // First, find "Transport:" while (1) { if (*buf == '\0') return; // not found if (*buf == '\r' && *(buf + 1) == '\n' && *(buf + 2) == '\r') return; // end of the headers => not found if (_strncasecmp(buf, "Transport:", 10) == 0) break; ++buf; } // Then, run through each of the fields, looking for ones we handle: char const* fields = buf + 10; while (*fields == ' ') ++fields; char* field = strDupSize(fields); while (sscanf(fields, "%[^;\r\n]", field) == 1) { if (strcmp(field, "reuse_connection") == 0) { reuseConnection = True; } else if (_strncasecmp(field, "preferred_delivery_protocol=udp", 31) == 0) { deliverViaTCP = False; } else if (_strncasecmp(field, "preferred_delivery_protocol=interleaved", 39) == 0) { deliverViaTCP = True; } else if (_strncasecmp(field, "proxy_url_suffix=", 17) == 0) { delete[] proxyURLSuffix; proxyURLSuffix = strDup(field + 17); } fields += strlen(field); while (*fields == ';' || *fields == ' ' || *fields == '\t') ++fields; // skip over separating ';' chars or whitespace if (*fields == '\0' || *fields == '\r' || *fields == '\n') break; } delete[] field; } void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) { int numBytesRemaining = 0; ++fRecursionCount; do { RTSPServer::RTSPClientSession* clientSession = NULL; if (newBytesRead < 0 || (unsigned) newBytesRead >= fRequestBufferBytesLeft) { // Either the client socket has died, or the request was too big for us. // Terminate this connection: #ifdef DEBUG fprintf(stderr, "RTSPClientConnection[%p]::handleRequestBytes() read %d new bytes (of %d); terminating connection!\n", this, newBytesRead, fRequestBufferBytesLeft); #endif fIsActive = False; break; } Boolean endOfMsg = False; unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen]; #ifdef DEBUG ptr[newBytesRead] = '\0'; fprintf(stderr, "RTSPClientConnection[%p]::handleRequestBytes() %s %d new bytes:%s\n", this, numBytesRemaining > 0 ? "processing" : "read", newBytesRead, ptr); #endif if (fClientOutputSocket != fClientInputSocket && numBytesRemaining == 0) { // We're doing RTSP-over-HTTP tunneling, and input commands are assumed to have been Base64-encoded. // We therefore Base64-decode as much of this new data as we can (i.e., up to a multiple of 4 bytes). // But first, we remove any whitespace that may be in the input data: unsigned toIndex = 0; for (int fromIndex = 0; fromIndex < newBytesRead; ++fromIndex) { char c = ptr[fromIndex]; if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) { // not 'whitespace': space,tab,CR,NL ptr[toIndex++] = c; } } newBytesRead = toIndex; unsigned numBytesToDecode = fBase64RemainderCount + newBytesRead; unsigned newBase64RemainderCount = numBytesToDecode % 4; numBytesToDecode -= newBase64RemainderCount; if (numBytesToDecode > 0) { ptr[newBytesRead] = '\0'; unsigned decodedSize; unsigned char* decodedBytes = base64Decode( (char const*) (ptr - fBase64RemainderCount), numBytesToDecode, decodedSize); #ifdef DEBUG fprintf(stderr, "Base64-decoded %d input bytes into %d new bytes:", numBytesToDecode, decodedSize); for (unsigned k = 0; k < decodedSize; ++k) fprintf(stderr, "%c", decodedBytes[k]); fprintf(stderr, "\n"); #endif // Copy the new decoded bytes in place of the old ones (we can do this because there are fewer decoded bytes than original): unsigned char* to = ptr - fBase64RemainderCount; for (unsigned i = 0; i < decodedSize; ++i) *to++ = decodedBytes[i]; // Then copy any remaining (undecoded) bytes to the end: for (unsigned j = 0; j < newBase64RemainderCount; ++j) *to++ = (ptr - fBase64RemainderCount + numBytesToDecode)[j]; newBytesRead = decodedSize - fBase64RemainderCount + newBase64RemainderCount; // adjust to allow for the size of the new decoded data (+ remainder) delete[] decodedBytes; } fBase64RemainderCount = newBase64RemainderCount; } unsigned char* tmpPtr = fLastCRLF + 2; if (fBase64RemainderCount == 0) { // no more Base-64 bytes remain to be read/decoded // Look for the end of the message: if (tmpPtr < fRequestBuffer) tmpPtr = fRequestBuffer; while (tmpPtr < &ptr[newBytesRead - 1]) { if (*tmpPtr == '\r' && *(tmpPtr + 1) == '\n') { if (tmpPtr - fLastCRLF == 2) { // This is it: endOfMsg = True; break; } fLastCRLF = tmpPtr; } ++tmpPtr; } } fRequestBufferBytesLeft -= newBytesRead; fRequestBytesAlreadySeen += newBytesRead; if (!endOfMsg) break; // subsequent reads will be needed to complete the request // Parse the request string into command name and 'CSeq', then handle the command: fRequestBuffer[fRequestBytesAlreadySeen] = '\0'; char cmdName[RTSP_PARAM_STRING_MAX]; char urlPreSuffix[RTSP_PARAM_STRING_MAX]; char urlSuffix[RTSP_PARAM_STRING_MAX]; char cseq[RTSP_PARAM_STRING_MAX]; char sessionIdStr[RTSP_PARAM_STRING_MAX]; unsigned contentLength = 0; fLastCRLF[2] = '\0'; // temporarily, for parsing Boolean parseSucceeded = parseRTSPRequestString((char*) fRequestBuffer, fLastCRLF + 2 - fRequestBuffer, cmdName, sizeof cmdName, urlPreSuffix, sizeof urlPreSuffix, urlSuffix, sizeof urlSuffix, cseq, sizeof cseq, sessionIdStr, sizeof sessionIdStr, contentLength); fLastCRLF[2] = '\r'; // restore its value Boolean playAfterSetup = False; if (parseSucceeded) { #ifdef DEBUG fprintf(stderr, "parseRTSPRequestString() succeeded, returning cmdName \"%s\", urlPreSuffix \"%s\", urlSuffix \"%s\", CSeq \"%s\", Content-Length %u, with %d bytes following the message.\n", cmdName, urlPreSuffix, urlSuffix, cseq, contentLength, ptr + newBytesRead - (tmpPtr + 2)); #endif // If there was a "Content-Length:" header, then make sure we've received all of the data that it specified: if (ptr + newBytesRead < tmpPtr + 2 + contentLength) break; // we still need more data; subsequent reads will give it to us // If the request included a "Session:" id, and it refers to a client session that's // current ongoing, then use this command to indicate 'liveness' on that client session: Boolean const requestIncludedSessionId = sessionIdStr[0] != '\0'; // Boolean const requestIncludedSessionId = false; if (requestIncludedSessionId) { clientSession = (RTSPServer::RTSPClientSession*) (fOurRTSPServer.lookupClientSession( sessionIdStr)); if (clientSession != NULL) { clientSession->noteLiveness(); } // else { // clientSession = (RTSPServer::RTSPClientSession*)(fOurRTSPServer.createNewClientSession( // (u_int32_t)strtol(sessionIdStr, NULL, 16))); // } } // We now have a complete RTSP request. // Handle the specified command (beginning with commands that are session-independent): fCurrentCSeq = cseq; if (strcmp(cmdName, "OPTIONS") == 0) { // If the "OPTIONS" command included a "Session:" id for a session that doesn't exist, // then treat this as an error: if (requestIncludedSessionId && clientSession == NULL) { handleCmd_sessionNotFound(); } else { // Normal case: handleCmd_OPTIONS(); } } else if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0') { // The special "*" URL means: an operation on the entire server. This works only for GET_PARAMETER and SET_PARAMETER: if (strcmp(cmdName, "GET_PARAMETER") == 0) { handleCmd_GET_PARAMETER((char const*) fRequestBuffer); } else if (strcmp(cmdName, "SET_PARAMETER") == 0) { handleCmd_SET_PARAMETER((char const*) fRequestBuffer); } else { handleCmd_notSupported(); } } else if (strcmp(cmdName, "DESCRIBE") == 0) { handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const*) fRequestBuffer); } else if (strcmp(cmdName, "SETUP") == 0) { Boolean areAuthenticated = True; if (!requestIncludedSessionId) { // No session id was present in the request. // So create a new "RTSPClientSession" object for this request. // But first, make sure that we're authenticated to perform this command: char urlTotalSuffix[2 * RTSP_PARAM_STRING_MAX]; // enough space for urlPreSuffix/urlSuffix'\0' urlTotalSuffix[0] = '\0'; if (urlPreSuffix[0] != '\0') { strcat(urlTotalSuffix, urlPreSuffix); strcat(urlTotalSuffix, "/"); } strcat(urlTotalSuffix, urlSuffix); if (authenticationOK("SETUP", urlTotalSuffix, (char const*) fRequestBuffer)) { clientSession = (RTSPServer::RTSPClientSession*) fOurRTSPServer.createNewClientSessionWithId(); } else { areAuthenticated = False; } } if (clientSession != NULL) { //test_symbol: clientSession->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const*) fRequestBuffer); playAfterSetup = clientSession->fStreamAfterSETUP; } else if (areAuthenticated) { handleCmd_sessionNotFound(); // clientSession = (RTSPClientSession *)fOurRTSPServer.createNewClientSession((u_int32_t)strtol(sessionIdStr, NULL, 16)); // goto test_symbol; } } else if (strcmp(cmdName, "TEARDOWN") == 0 || strcmp(cmdName, "PLAY") == 0 || strcmp(cmdName, "PAUSE") == 0 || strcmp(cmdName, "GET_PARAMETER") == 0 || strcmp(cmdName, "SET_PARAMETER") == 0) { if (clientSession != NULL) { clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*) fRequestBuffer); } else { handleCmd_sessionNotFound(); } } else if (strcmp(cmdName, "REGISTER") == 0) { // Because - unlike other commands - an implementation of this command needs // the entire URL, we re-parse the command to get it: char* url = strDupSize((char*) fRequestBuffer); if (sscanf((char*) fRequestBuffer, "%*s %s", url) == 1) { // Check for special command-specific parameters in a "Transport:" header: Boolean reuseConnection, deliverViaTCP; char* proxyURLSuffix; parseTransportHeaderForREGISTER( (const char*) fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix); handleCmd_REGISTER(url, urlSuffix, (char const*) fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix); delete[] proxyURLSuffix; } else { handleCmd_bad(); } delete[] url; } else { // The command is one that we don't handle: handleCmd_notSupported(); } } else { #ifdef DEBUG fprintf(stderr, "parseRTSPRequestString() failed; checking now for HTTP commands (for RTSP-over-HTTP tunneling)...\n"); #endif // The request was not (valid) RTSP, but check for a special case: HTTP commands (for setting up RTSP-over-HTTP tunneling): char sessionCookie[RTSP_PARAM_STRING_MAX]; char acceptStr[RTSP_PARAM_STRING_MAX]; *fLastCRLF = '\0'; // temporarily, for parsing parseSucceeded = parseHTTPRequestString(cmdName, sizeof cmdName, urlSuffix, sizeof urlPreSuffix, sessionCookie, sizeof sessionCookie, acceptStr, sizeof acceptStr); *fLastCRLF = '\r'; if (parseSucceeded) { #ifdef DEBUG fprintf(stderr, "parseHTTPRequestString() succeeded, returning cmdName \"%s\", urlSuffix \"%s\", sessionCookie \"%s\", acceptStr \"%s\"\n", cmdName, urlSuffix, sessionCookie, acceptStr); #endif // Check that the HTTP command is valid for RTSP-over-HTTP tunneling: There must be a 'session cookie'. Boolean isValidHTTPCmd = True; if (strcmp(cmdName, "OPTIONS") == 0) { handleHTTPCmd_OPTIONS(); } else if (sessionCookie[0] == '\0') { // There was no "x-sessioncookie:" header. If there was an "Accept: application/x-rtsp-tunnelled" header, // then this is a bad tunneling request. Otherwise, assume that it's an attempt to access the stream via HTTP. if (strcmp(acceptStr, "application/x-rtsp-tunnelled") == 0) { isValidHTTPCmd = False; } else { handleHTTPCmd_StreamingGET(urlSuffix, (char const*) fRequestBuffer); } } else if (strcmp(cmdName, "GET") == 0) { handleHTTPCmd_TunnelingGET(sessionCookie); } else if (strcmp(cmdName, "POST") == 0) { // We might have received additional data following the HTTP "POST" command - i.e., the first Base64-encoded RTSP command. // Check for this, and handle it if it exists: unsigned char const* extraData = fLastCRLF + 4; unsigned extraDataSize = &fRequestBuffer[fRequestBytesAlreadySeen] - extraData; if (handleHTTPCmd_TunnelingPOST(sessionCookie, extraData, extraDataSize)) { // We don't respond to the "POST" command, and we go away: fIsActive = False; break; } } else { isValidHTTPCmd = False; } if (!isValidHTTPCmd) { handleHTTPCmd_notSupported(); } } else { #ifdef DEBUG fprintf(stderr, "parseHTTPRequestString() failed!\n"); #endif handleCmd_bad(); } } #ifdef DEBUG fprintf(stderr, "sending response: %s", fResponseBuffer); #endif send(fClientOutputSocket, (char const*) fResponseBuffer, strlen((char*) fResponseBuffer), 0); if (playAfterSetup) { // The client has asked for streaming to commence now, rather than after a // subsequent "PLAY" command. So, simulate the effect of a "PLAY" command: clientSession->handleCmd_withinSession(this, "PLAY", urlPreSuffix, urlSuffix, (char const*) fRequestBuffer); } // Check whether there are extra bytes remaining in the buffer, after the end of the request (a rare case). // If so, move them to the front of our buffer, and keep processing it, because it might be a following, pipelined request. unsigned requestSize = (fLastCRLF + 4 - fRequestBuffer) + contentLength; numBytesRemaining = fRequestBytesAlreadySeen - requestSize; resetRequestBuffer(); // to prepare for any subsequent request if (numBytesRemaining > 0) { memmove(fRequestBuffer, &fRequestBuffer[requestSize], numBytesRemaining); newBytesRead = numBytesRemaining; } } while (numBytesRemaining > 0); --fRecursionCount; if (!fIsActive) { if (fRecursionCount > 0) closeSockets(); else delete this; // Note: The "fRecursionCount" test is for a pathological situation where we reenter the event loop and get called recursively // while handling a command (e.g., while handling a "DESCRIBE", to get a SDP description). // In such a case we don't want to actually delete ourself until we leave the outermost call. } } void RTSPServer::RTSPClientSession::noteLiveness() { if (!usesTCPTransport()) { GenericMediaServer::ClientSession::noteLiveness(); } else { envir().taskScheduler().unscheduleDelayedTask(fLivenessCheckTask); } } static Boolean parseAuthorizationHeader(char const* buf, char const*& username, char const*& realm, char const*& nonce, char const*& uri, char const*& response) { // Initialize the result parameters to default values: username = realm = nonce = uri = response = NULL; // First, find "Authorization:" while (1) { if (*buf == '\0') return False; // not found if (_strncasecmp(buf, "Authorization: Digest ", 22) == 0) break; ++buf; } // Then, run through each of the fields, looking for ones we handle: char const* fields = buf + 22; while (*fields == ' ') ++fields; char* parameter = strDupSize(fields); char* value = strDupSize(fields); while (1) { value[0] = '\0'; if (sscanf(fields, "%[^=]=\"%[^\"]\"", parameter, value) != 2 && sscanf(fields, "%[^=]=\"\"", parameter) != 1) { break; } if (strcmp(parameter, "username") == 0) { username = strDup(value); } else if (strcmp(parameter, "realm") == 0) { realm = strDup(value); } else if (strcmp(parameter, "nonce") == 0) { nonce = strDup(value); } else if (strcmp(parameter, "uri") == 0) { uri = strDup(value); } else if (strcmp(parameter, "response") == 0) { response = strDup(value); } fields += strlen(parameter) + 2 /*="*/+ strlen(value) + 1 /*"*/; while (*fields == ',' || *fields == ' ') ++fields; // skip over any separating ',' and ' ' chars if (*fields == '\0' || *fields == '\r' || *fields == '\n') break; } delete[] parameter; delete[] value; return True; } Boolean RTSPServer::RTSPClientConnection::authenticationOK(char const* cmdName, char const* urlSuffix, char const* fullRequestStr) { if (!fOurRTSPServer.specialClientAccessCheck(fClientInputSocket, fClientAddr, urlSuffix)) { setRTSPResponse("401 Unauthorized"); return False; } // If we weren't set up with an authentication database, we're OK: UserAuthenticationDatabase* authDB = fOurRTSPServer.getAuthenticationDatabaseForCommand(cmdName); if (authDB == NULL) return True; char const* username = NULL; char const* realm = NULL; char const* nonce = NULL; char const* uri = NULL; char const* response = NULL; Boolean success = False; do { // To authenticate, we first need to have a nonce set up // from a previous attempt: if (fCurrentAuthenticator.nonce() == NULL) break; // Next, the request needs to contain an "Authorization:" header, // containing a username, (our) realm, (our) nonce, uri, // and response string: if (!parseAuthorizationHeader(fullRequestStr, username, realm, nonce, uri, response) || username == NULL || realm == NULL || strcmp(realm, fCurrentAuthenticator.realm()) != 0 || nonce == NULL || strcmp(nonce, fCurrentAuthenticator.nonce()) != 0 || uri == NULL || response == NULL) { break; } // Next, the username has to be known to us: char const* password = authDB->lookupPassword(username); #ifdef DEBUG fprintf(stderr, "lookupPassword(%s) returned password %s\n", username, password); #endif if (password == NULL) break; fCurrentAuthenticator.setUsernameAndPassword(username, password, authDB->passwordsAreMD5()); // Finally, compute a digest response from the information that we have, // and compare it to the one that we were given: char const* ourResponse = fCurrentAuthenticator.computeDigestResponse( cmdName, uri); success = (strcmp(ourResponse, response) == 0); fCurrentAuthenticator.reclaimDigestResponse(ourResponse); } while (0); delete[] (char*) realm; delete[] (char*) nonce; delete[] (char*) uri; delete[] (char*) response; if (success) { // The user has been authenticated. // Now allow subclasses a chance to validate the user against the IP address and/or URL suffix. if (!fOurRTSPServer.specialClientUserAccessCheck(fClientInputSocket, fClientAddr, urlSuffix, username)) { // Note: We don't return a "WWW-Authenticate" header here, because the user is valid, // even though the server has decided that they should not have access. setRTSPResponse("401 Unauthorized"); delete[] (char*) username; return False; } } delete[] (char*) username; if (success) return True; // If we get here, we failed to authenticate the user. // Send back a "401 Unauthorized" response, with a new random nonce: fCurrentAuthenticator.setRealmAndRandomNonce(authDB->realm()); snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 401 Unauthorized\r\n" "CSeq: %s\r\n" "%s" "WWW-Authenticate: Digest realm=\"%s\", nonce=\"%s\"\r\n\r\n", fCurrentCSeq, dateHeader(), fCurrentAuthenticator.realm(), fCurrentAuthenticator.nonce()); return False; } void RTSPServer::RTSPClientConnection::setRTSPResponse( char const* responseStr) { snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 %s\r\n" "CSeq: %s\r\n" "%s\r\n", responseStr, fCurrentCSeq, dateHeader()); } void RTSPServer::RTSPClientConnection::setRTSPResponse(char const* responseStr, u_int32_t sessionId) { snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 %s\r\n" "CSeq: %s\r\n" "%s" "Cache-Control: no-cache\r\n" "Session: %08X\r\n\r\n", responseStr, fCurrentCSeq, dateHeader(), sessionId); } void RTSPServer::RTSPClientConnection::setRTSPResponse(char const* responseStr, char const* contentStr) { if (contentStr == NULL) contentStr = ""; unsigned const contentLen = strlen(contentStr); snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 %s\r\n" "CSeq: %s\r\n" "%s" "Content-Length: %d\r\n\r\n" "%s", responseStr, fCurrentCSeq, dateHeader(), contentLen, contentStr); } void RTSPServer::RTSPClientConnection::setRTSPResponse(char const* responseStr, u_int32_t sessionId, char const* contentStr) { if (contentStr == NULL) contentStr = ""; unsigned const contentLen = strlen(contentStr); snprintf((char*) fResponseBuffer, sizeof fResponseBuffer, "RTSP/1.0 %s\r\n" "CSeq: %s\r\n" "%s" "Session: %08X\r\n" "Cache-Control: no-cache\r\n" "Content-Length: %d\r\n\r\n" "%s", responseStr, fCurrentCSeq, dateHeader(), sessionId, contentLen, contentStr); } void RTSPServer::RTSPClientConnection::changeClientInputSocket(int newSocketNum, unsigned char const* extraData, unsigned extraDataSize) { envir().taskScheduler().disableBackgroundHandling(fClientInputSocket); fClientInputSocket = newSocketNum; envir().taskScheduler().setBackgroundHandling(fClientInputSocket, SOCKET_READABLE | SOCKET_EXCEPTION, incomingRequestHandler, this); // Also write any extra data to our buffer, and handle it: if (extraDataSize > 0 && extraDataSize <= fRequestBufferBytesLeft/*sanity check; should always be true*/) { unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen]; for (unsigned i = 0; i < extraDataSize; ++i) { ptr[i] = extraData[i]; } handleRequestBytes(extraDataSize); } } void RTSPServer::RTSPClientConnection::continueHandlingREGISTER( ParamsForREGISTER* params) { params->fOurConnection->continueHandlingREGISTER1(params); } void RTSPServer::RTSPClientConnection::continueHandlingREGISTER1( ParamsForREGISTER* params) { // Reuse our socket if requested: int socketNumToBackEndServer = params->fReuseConnection ? fClientOutputSocket : -1; RTSPServer* ourServer = &fOurRTSPServer; // copy the pointer now, in case we "delete this" below if (socketNumToBackEndServer >= 0) { // Because our socket will no longer be used by the server to handle incoming requests, we can now delete this // "RTSPClientConnection" object. We do this now, in case the "implementCmd_REGISTER()" call below would also end up // deleting this. fClientInputSocket = fClientOutputSocket = -1; // so the socket doesn't get closed when we get deleted delete this; } ourServer->implementCmd_REGISTER(params->fURL, params->fURLSuffix, socketNumToBackEndServer, params->fDeliverViaTCP, params->fProxyURLSuffix); delete params; } ////////// RTSPServer::RTSPClientSession implementation ////////// RTSPServer::RTSPClientSession::RTSPClientSession(RTSPServer& ourServer, u_int32_t sessionId) : GenericMediaServer::ClientSession(ourServer, sessionId), fOurRTSPServer( ourServer), fIsMulticast(False), fStreamAfterSETUP(False), fTCPStreamIdCount( 0), fNumStreamStates(0), fStreamStates(NULL) { } RTSPServer::RTSPClientSession::~RTSPClientSession() { reclaimStreamStates(); } void RTSPServer::RTSPClientSession::deleteStreamByTrack(unsigned trackNum) { if (trackNum >= fNumStreamStates) return; // sanity check; shouldn't happen if (fStreamStates[trackNum].subsession != NULL) { fStreamStates[trackNum].subsession->deleteStream(fOurSessionId, fStreamStates[trackNum].streamToken); fStreamStates[trackNum].subsession = NULL; } // Optimization: If all subsessions have now been deleted, then we can delete ourself now: Boolean noSubsessionsRemain = True; for (unsigned i = 0; i < fNumStreamStates; ++i) { if (fStreamStates[i].subsession != NULL) { noSubsessionsRemain = False; break; } } if (noSubsessionsRemain) delete this; } void RTSPServer::RTSPClientSession::reclaimStreamStates() { for (unsigned i = 0; i < fNumStreamStates; ++i) { if (fStreamStates[i].subsession != NULL) { fOurRTSPServer.unnoteTCPStreamingOnSocket( fStreamStates[i].tcpSocketNum, this, i); fStreamStates[i].subsession->deleteStream(fOurSessionId, fStreamStates[i].streamToken); } } delete[] fStreamStates; fStreamStates = NULL; fNumStreamStates = 0; } typedef enum StreamingMode { RTP_UDP, RTP_TCP, RAW_UDP } StreamingMode; static void parseTransportHeader(char const* buf, StreamingMode& streamingMode, char*& streamingModeString, char*& destinationAddressStr, u_int8_t& destinationTTL, portNumBits& clientRTPPortNum, // if UDP portNumBits& clientRTCPPortNum, // if UDP unsigned char& rtpChannelId, // if TCP unsigned char& rtcpChannelId // if TCP ) { // Initialize the result parameters to default values: streamingMode = RTP_UDP; streamingModeString = NULL; destinationAddressStr = NULL; destinationTTL = 255; clientRTPPortNum = 0; clientRTCPPortNum = 1; rtpChannelId = rtcpChannelId = 0xFF; portNumBits p1, p2; unsigned ttl, rtpCid, rtcpCid; // First, find "Transport:" while (1) { if (*buf == '\0') return; // not found if (*buf == '\r' && *(buf + 1) == '\n' && *(buf + 2) == '\r') return; // end of the headers => not found if (_strncasecmp(buf, "Transport:", 10) == 0) break; ++buf; } // Then, run through each of the fields, looking for ones we handle: char const* fields = buf + 10; while (*fields == ' ') ++fields; char* field = strDupSize(fields); while (sscanf(fields, "%[^;\r\n]", field) == 1) { if (strcmp(field, "RTP/AVP/TCP") == 0) { streamingMode = RTP_TCP; } else if (strcmp(field, "RAW/RAW/UDP") == 0 || strcmp(field, "MP2T/H2221/UDP") == 0) { streamingMode = RAW_UDP; streamingModeString = strDup(field); } else if (_strncasecmp(field, "destination=", 12) == 0) { delete[] destinationAddressStr; destinationAddressStr = strDup(field + 12); } else if (sscanf(field, "ttl%u", &ttl) == 1) { destinationTTL = (u_int8_t) ttl; } else if (sscanf(field, "client_port=%hu-%hu", &p1, &p2) == 2) { clientRTPPortNum = p1; clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p2; // ignore the second port number if the client asked for raw UDP } else if (sscanf(field, "client_port=%hu", &p1) == 1) { clientRTPPortNum = p1; clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p1 + 1; } else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) { rtpChannelId = (unsigned char) rtpCid; rtcpChannelId = (unsigned char) rtcpCid; } fields += strlen(field); while (*fields == ';' || *fields == ' ' || *fields == '\t') ++fields; // skip over separating ';' chars or whitespace if (*fields == '\0' || *fields == '\r' || *fields == '\n') break; } delete[] field; } static Boolean parsePlayNowHeader(char const* buf) { // Find "x-playNow:" header, if present while (1) { if (*buf == '\0') return False; // not found if (_strncasecmp(buf, "x-playNow:", 10) == 0) break; ++buf; } return True; } void RTSPServer::RTSPClientSession::handleCmd_SETUP( RTSPServer::RTSPClientConnection* ourClientConnection, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) { // Normally, "urlPreSuffix" should be the session (stream) name, and "urlSuffix" should be the subsession (track) name. // However (being "liberal in what we accept"), we also handle 'aggregate' SETUP requests (i.e., without a track name), // in the special case where we have only a single track. I.e., in this case, we also handle: // "urlPreSuffix" is empty and "urlSuffix" is the session (stream) name, or // "urlPreSuffix" concatenated with "urlSuffix" (with "/" inbetween) is the session (stream) name. char const* streamName = urlPreSuffix; // in the normal case char const* trackId = urlSuffix; // in the normal case char* concatenatedStreamName = NULL; // in the normal case do { // First, make sure the specified stream name exists: ServerMediaSession* sms = fOurServer.lookupServerMediaSession( streamName, fOurServerMediaSession == NULL); if (sms == NULL) { // Check for the special case (noted above), before we give up: if (urlPreSuffix[0] == '\0') { streamName = urlSuffix; } else { concatenatedStreamName = new char[strlen(urlPreSuffix) + strlen(urlSuffix) + 2]; // allow for the "/" and the trailing '\0' sprintf(concatenatedStreamName, "%s/%s", urlPreSuffix, urlSuffix); streamName = concatenatedStreamName; } trackId = NULL; // Check again: sms = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL); } if (sms == NULL) { if (fOurServerMediaSession == NULL) { // The client asked for a stream that doesn't exist (and this session descriptor has not been used before): ourClientConnection->handleCmd_notFound(); } else { // The client asked for a stream that doesn't exist, but using a stream id for a stream that does exist. Bad request: ourClientConnection->handleCmd_bad(); } break; } else { if (fOurServerMediaSession == NULL) { // We're accessing the "ServerMediaSession" for the first time. fOurServerMediaSession = sms; fOurServerMediaSession->incrementReferenceCount(); } else if (sms != fOurServerMediaSession) { // The client asked for a stream that's different from the one originally requested for this stream id. Bad request: ourClientConnection->handleCmd_bad(); break; } } if (fStreamStates == NULL) { // This is the first "SETUP" for this session. Set up our array of states for all of this session's subsessions (tracks): ServerMediaSubsessionIterator iter(*fOurServerMediaSession); for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) { } // begin by counting the number of subsessions (tracks) fStreamStates = new struct streamState[fNumStreamStates]; iter.reset(); ServerMediaSubsession* subsession; for (unsigned i = 0; i < fNumStreamStates; ++i) { subsession = iter.next(); fStreamStates[i].subsession = subsession; fStreamStates[i].tcpSocketNum = -1; // for now; may get set for RTP-over-TCP streaming fStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later } } // Look up information for the specified subsession (track): ServerMediaSubsession* subsession = NULL; unsigned trackNum; if (trackId != NULL && trackId[0] != '\0') { // normal case for (trackNum = 0; trackNum < fNumStreamStates; ++trackNum) { subsession = fStreamStates[trackNum].subsession; if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break; } if (trackNum >= fNumStreamStates) { // The specified track id doesn't exist, so this request fails: ourClientConnection->handleCmd_notFound(); break; } } else { // Weird case: there was no track id in the URL. // This works only if we have only one subsession: if (fNumStreamStates != 1 || fStreamStates[0].subsession == NULL) { ourClientConnection->handleCmd_bad(); break; } trackNum = 0; subsession = fStreamStates[trackNum].subsession; } // ASSERT: subsession != NULL void*& token = fStreamStates[trackNum].streamToken; // alias if (token != NULL) { // We already handled a "SETUP" for this track (to the same client), // so stop any existing streaming of it, before we set it up again: subsession->pauseStream(fOurSessionId, token); fOurRTSPServer.unnoteTCPStreamingOnSocket( fStreamStates[trackNum].tcpSocketNum, this, trackNum); subsession->deleteStream(fOurSessionId, token); } // Look for a "Transport:" header in the request string, to extract client parameters: StreamingMode streamingMode; char* streamingModeString = NULL; // set when RAW_UDP streaming is specified char* clientsDestinationAddressStr; u_int8_t clientsDestinationTTL; portNumBits clientRTPPortNum, clientRTCPPortNum; unsigned char rtpChannelId, rtcpChannelId; parseTransportHeader(fullRequestStr, streamingMode, streamingModeString, clientsDestinationAddressStr, clientsDestinationTTL, clientRTPPortNum, clientRTCPPortNum, rtpChannelId, rtcpChannelId); if ((streamingMode == RTP_TCP && rtpChannelId == 0xFF) || (streamingMode != RTP_TCP && ourClientConnection->fClientOutputSocket != ourClientConnection->fClientInputSocket)) { // An anomolous situation, caused by a buggy client. Either: // 1/ TCP streaming was requested, but with no "interleaving=" fields. (QuickTime Player sometimes does this.), or // 2/ TCP streaming was not requested, but we're doing RTSP-over-HTTP tunneling (which implies TCP streaming). // In either case, we assume TCP streaming, and set the RTP and RTCP channel ids to proper values: streamingMode = RTP_TCP; rtpChannelId = fTCPStreamIdCount; rtcpChannelId = fTCPStreamIdCount + 1; } if (streamingMode == RTP_TCP) fTCPStreamIdCount += 2; Port clientRTPPort(clientRTPPortNum); Port clientRTCPPort(clientRTCPPortNum); // Next, check whether a "Range:" or "x-playNow:" header is present in the request. // This isn't legal, but some clients do this to combine "SETUP" and "PLAY": double rangeStart = 0.0, rangeEnd = 0.0; char* absStart = NULL; char* absEnd = NULL; Boolean startTimeIsNow; if (parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow)) { delete[] absStart; delete[] absEnd; fStreamAfterSETUP = True; } else if (parsePlayNowHeader(fullRequestStr)) { fStreamAfterSETUP = True; } else { fStreamAfterSETUP = False; } // Then, get server parameters from the 'subsession': if (streamingMode == RTP_TCP) { // Note that we'll be streaming over the RTSP TCP connection: fStreamStates[trackNum].tcpSocketNum = ourClientConnection->fClientOutputSocket; fOurRTSPServer.noteTCPStreamingOnSocket( fStreamStates[trackNum].tcpSocketNum, this, trackNum); } netAddressBits destinationAddress = 0; u_int8_t destinationTTL = 255; #ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING if (clientsDestinationAddressStr != NULL) { // Use the client-provided "destination" address. // Note: This potentially allows the server to be used in denial-of-service // attacks, so don't enable this code unless you're sure that clients are // trusted. destinationAddress = our_inet_addr(clientsDestinationAddressStr); } // Also use the client-provided TTL. destinationTTL = clientsDestinationTTL; #endif delete[] clientsDestinationAddressStr; Port serverRTPPort(0); Port serverRTCPPort(0); // Make sure that we transmit on the same interface that's used by the client (in case we're a multi-homed server): struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr; getsockname(ourClientConnection->fClientInputSocket, (struct sockaddr*) &sourceAddr, (socklen_t *)&namelen); netAddressBits origSendingInterfaceAddr = SendingInterfaceAddr; netAddressBits origReceivingInterfaceAddr = ReceivingInterfaceAddr; // NOTE: The following might not work properly, so we ifdef it out for now: #ifdef HACK_FOR_MULTIHOMED_SERVERS ReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr; #endif subsession->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr, clientRTPPort, clientRTCPPort, fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId, destinationAddress, destinationTTL, fIsMulticast, serverRTPPort, serverRTCPPort, fStreamStates[trackNum].streamToken); SendingInterfaceAddr = origSendingInterfaceAddr; ReceivingInterfaceAddr = origReceivingInterfaceAddr; AddressString destAddrStr(destinationAddress); AddressString sourceAddrStr(sourceAddr); char timeoutParameterString[100]; if (fOurRTSPServer.fReclamationSeconds > 0) { sprintf(timeoutParameterString, ";timeout=%u", fOurRTSPServer.fReclamationSeconds); } else { timeoutParameterString[0] = '\0'; } if (fIsMulticast) { switch (streamingMode) { case RTP_UDP: { snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n" "Session: %08X%s\r\n" "Cache-Control: no-cache\r\n\r\n", ourClientConnection->fCurrentCSeq, dateHeader(), destAddrStr.val(), sourceAddrStr.val(), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), destinationTTL, fOurSessionId, timeoutParameterString); break; } case RTP_TCP: { // multicast streams can't be sent via TCP ourClientConnection->handleCmd_unsupportedTransport(); break; } case RAW_UDP: { snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: %s;multicast;destination=%s;source=%s;port=%d;ttl=%d\r\n" "Cache-Control: no-cache\r\n" "Session: %08X%s\r\n\r\n", ourClientConnection->fCurrentCSeq, dateHeader(), streamingModeString, destAddrStr.val(), sourceAddrStr.val(), ntohs(serverRTPPort.num()), destinationTTL, fOurSessionId, timeoutParameterString); break; } } } else { switch (streamingMode) { case RTP_UDP: { snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n" "Session: %08X%s\r\n" "Cache-Control: no-cache\r\n\r\n", ourClientConnection->fCurrentCSeq, dateHeader(), destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), fOurSessionId, timeoutParameterString); break; } case RTP_TCP: { if (!fOurRTSPServer.fAllowStreamingRTPOverTCP) { ourClientConnection->handleCmd_unsupportedTransport(); } else { snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n" "Cache-Control: no-cache\r\n" "Session: %08X%s\r\n\r\n", ourClientConnection->fCurrentCSeq, dateHeader(), destAddrStr.val(), sourceAddrStr.val(), rtpChannelId, rtcpChannelId, fOurSessionId, timeoutParameterString); } break; } case RAW_UDP: { snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "Transport: %s;unicast;destination=%s;source=%s;client_port=%d;server_port=%d\r\n" "Cache-Control: no-cache\r\n" "Session: %08X%s\r\n\r\n", ourClientConnection->fCurrentCSeq, dateHeader(), streamingModeString, destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(serverRTPPort.num()), fOurSessionId, timeoutParameterString); break; } } } delete[] streamingModeString; } while (0); delete[] concatenatedStreamName; } void RTSPServer::RTSPClientSession::handleCmd_withinSession( RTSPServer::RTSPClientConnection* ourClientConnection, char const* cmdName, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) { // This will either be: // - a non-aggregated operation, if "urlPreSuffix" is the session (stream) // name and "urlSuffix" is the subsession (track) name, or // - an aggregated operation, if "urlSuffix" is the session (stream) name, // or "urlPreSuffix" is the session (stream) name, and "urlSuffix" is empty, // or "urlPreSuffix" and "urlSuffix" are both nonempty, but when concatenated, (with "/") form the session (stream) name. // Begin by figuring out which of these it is: ServerMediaSubsession* subsession; if (fOurServerMediaSession == NULL) { // There wasn't a previous SETUP! ourClientConnection->handleCmd_notSupported(); return; } else if (urlSuffix[0] != '\0' && strcmp(fOurServerMediaSession->streamName(), urlPreSuffix) == 0) { // Non-aggregated operation. // Look up the media subsession whose track id is "urlSuffix": ServerMediaSubsessionIterator iter(*fOurServerMediaSession); while ((subsession = iter.next()) != NULL) { if (strcmp(subsession->trackId(), urlSuffix) == 0) break; // success } if (subsession == NULL) { // no such track! ourClientConnection->handleCmd_notFound(); return; } } else if (strcmp(fOurServerMediaSession->streamName(), urlSuffix) == 0 || (urlSuffix[0] == '\0' && strcmp(fOurServerMediaSession->streamName(), urlPreSuffix) == 0)) { // Aggregated operation subsession = NULL; } else if (urlPreSuffix[0] != '\0' && urlSuffix[0] != '\0') { // Aggregated operation, if / is the session (stream) name: unsigned const urlPreSuffixLen = strlen(urlPreSuffix); if (strncmp(fOurServerMediaSession->streamName(), urlPreSuffix, urlPreSuffixLen) == 0 && fOurServerMediaSession->streamName()[urlPreSuffixLen] == '/' && strcmp( &(fOurServerMediaSession->streamName())[urlPreSuffixLen + 1], urlSuffix) == 0) { subsession = NULL; } else { ourClientConnection->handleCmd_notFound(); return; } } else { // the request doesn't match a known stream and/or track at all! ourClientConnection->handleCmd_notFound(); return; } if (strcmp(cmdName, "TEARDOWN") == 0) { handleCmd_TEARDOWN(ourClientConnection, subsession); // handleCmd_PAUSE(ourClientConnection, subsession); } else if (strcmp(cmdName, "PLAY") == 0) { handleCmd_PLAY(ourClientConnection, subsession, fullRequestStr); } else if (strcmp(cmdName, "PAUSE") == 0) { handleCmd_PAUSE(ourClientConnection, subsession); } else if (strcmp(cmdName, "GET_PARAMETER") == 0) { handleCmd_GET_PARAMETER(ourClientConnection, subsession, fullRequestStr); } else if (strcmp(cmdName, "SET_PARAMETER") == 0) { handleCmd_SET_PARAMETER(ourClientConnection, subsession, fullRequestStr); } } void RTSPServer::RTSPClientSession::handleCmd_TEARDOWN( RTSPServer::RTSPClientConnection* ourClientConnection, ServerMediaSubsession* subsession) { unsigned i; for (i = 0; i < fNumStreamStates; ++i) { if (subsession == NULL /* means: aggregated operation */ || subsession == fStreamStates[i].subsession) { if (fStreamStates[i].subsession != NULL) { fOurRTSPServer.unnoteTCPStreamingOnSocket( fStreamStates[i].tcpSocketNum, this, i); fStreamStates[i].subsession->deleteStream(fOurSessionId, fStreamStates[i].streamToken); fStreamStates[i].subsession = NULL; } } } setRTSPResponse(ourClientConnection, "200 OK"); // Optimization: If all subsessions have now been torn down, then we know that we can reclaim our object now. // (Without this optimization, however, this object would still get reclaimed later, as a result of a 'liveness' timeout.) Boolean noSubsessionsRemain = True; for (i = 0; i < fNumStreamStates; ++i) { if (fStreamStates[i].subsession != NULL) { noSubsessionsRemain = False; break; } } if (noSubsessionsRemain) delete this; } void RTSPServer::RTSPClientSession::handleCmd_PLAY( RTSPServer::RTSPClientConnection* ourClientConnection, ServerMediaSubsession* subsession, char const* fullRequestStr) { char* rtspURL = fOurRTSPServer.rtspURL(fOurServerMediaSession, ourClientConnection->fClientInputSocket); unsigned rtspURLSize = strlen(rtspURL); // Parse the client's "Scale:" header, if any: float scale; Boolean sawScaleHeader = parseScaleHeader(fullRequestStr, scale); // Try to set the stream's scale factor to this value: if (subsession == NULL /*aggregate op*/) { fOurServerMediaSession->testScaleFactor(scale); } else { subsession->testScaleFactor(scale); } char buf[100]; char* scaleHeader; if (!sawScaleHeader) { buf[0] = '\0'; // Because we didn't see a Scale: header, don't send one back } else { sprintf(buf, "Scale: %f\r\n", scale); } scaleHeader = strDup(buf); // Parse the client's "Range:" header, if any: float duration = 0.0; double rangeStart = 0.0, rangeEnd = 0.0; char* absStart = NULL; char* absEnd = NULL; Boolean startTimeIsNow; Boolean sawRangeHeader = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow); if (sawRangeHeader && absStart == NULL/*not seeking by 'absolute' time*/) { // Use this information, plus the stream's duration (if known), to create our own "Range:" header, for the response: duration = subsession == NULL /*aggregate op*/ ? fOurServerMediaSession->duration() : subsession->duration(); if (duration < 0.0) { // We're an aggregate PLAY, but the subsessions have different durations. // Use the largest of these durations in our header duration = -duration; } // Make sure that "rangeStart" and "rangeEnd" (from the client's "Range:" header) // have sane values, before we send back our own "Range:" header in our response: if (rangeStart < 0.0) rangeStart = 0.0; else if (rangeStart > duration) rangeStart = duration; if (rangeEnd < 0.0) rangeEnd = 0.0; else if (rangeEnd > duration) rangeEnd = duration; if ((scale > 0.0 && rangeStart > rangeEnd && rangeEnd > 0.0) || (scale < 0.0 && rangeStart < rangeEnd)) { // "rangeStart" and "rangeEnd" were the wrong way around; swap them: double tmp = rangeStart; rangeStart = rangeEnd; rangeEnd = tmp; } } // Create a "RTP-Info:" line. It will get filled in from each subsession's state: char const* rtpInfoFmt = "%s" // "RTP-Info:", plus any preceding rtpInfo items "%s"// comma separator, if needed "url=%s/%s" ";seq=%d" ";rtptime=%u"; unsigned rtpInfoFmtSize = strlen(rtpInfoFmt); char* rtpInfo = strDup("RTP-Info: "); unsigned i, numRTPInfoItems = 0; // Do any required seeking/scaling on each subsession, before starting streaming. // (However, we don't do this if the "PLAY" request was for just a single subsession // of a multiple-subsession stream; for such streams, seeking/scaling can be done // only with an aggregate "PLAY".) for (i = 0; i < fNumStreamStates; ++i) { if (subsession == NULL /* means: aggregated operation */ || fNumStreamStates == 1) { if (fStreamStates[i].subsession != NULL) { if (sawScaleHeader) { fStreamStates[i].subsession->setStreamScale(fOurSessionId, fStreamStates[i].streamToken, scale); } if (absStart != NULL) { // Special case handling for seeking by 'absolute' time: fStreamStates[i].subsession->seekStream(fOurSessionId, fStreamStates[i].streamToken, absStart, absEnd); } else { // Seeking by relative (NPT) time: u_int64_t numBytes; if (!sawRangeHeader || startTimeIsNow) { // We're resuming streaming without seeking, so we just do a 'null' seek // (to get our NPT, and to specify when to end streaming): fStreamStates[i].subsession->nullSeekStream( fOurSessionId, fStreamStates[i].streamToken, rangeEnd, numBytes); } else { // We do a real 'seek': double streamDuration = 0.0; // by default; means: stream until the end of the media if (rangeEnd > 0.0 && (rangeEnd + 0.001) < duration) { // the 0.001 is because we limited the values to 3 decimal places // We want the stream to end early. Set the duration we want: streamDuration = rangeEnd - rangeStart; if (streamDuration < 0.0) streamDuration = -streamDuration; // should happen only if scale < 0.0 } fStreamStates[i].subsession->seekStream(fOurSessionId, fStreamStates[i].streamToken, rangeStart, streamDuration, numBytes); } } } } } // Create the "Range:" header that we'll send back in our response. // (Note that we do this after seeking, in case the seeking operation changed the range start time.) if (absStart != NULL) { // We're seeking by 'absolute' time: if (absEnd == NULL) { sprintf(buf, "Range: clock=%s-\r\n", absStart); } else { sprintf(buf, "Range: clock=%s-%s\r\n", absStart, absEnd); } delete[] absStart; delete[] absEnd; } else { // We're seeking by relative (NPT) time: if (!sawRangeHeader || startTimeIsNow) { // We didn't seek, so in our response, begin the range with the current NPT (normal play time): float curNPT = 0.0; for (i = 0; i < fNumStreamStates; ++i) { if (subsession == NULL /* means: aggregated operation */ || subsession == fStreamStates[i].subsession) { if (fStreamStates[i].subsession == NULL) continue; float npt = fStreamStates[i].subsession->getCurrentNPT( fStreamStates[i].streamToken); if (npt > curNPT) curNPT = npt; // Note: If this is an aggregate "PLAY" on a multi-subsession stream, // then it's conceivable that the NPTs of each subsession may differ // (if there has been a previous seek on just one subsession). // In this (unusual) case, we just return the largest NPT; I hope that turns out OK... } } rangeStart = curNPT; } if (rangeEnd == 0.0 && scale >= 0.0) { sprintf(buf, "Range: npt=%.3f-\r\n", rangeStart); } else { sprintf(buf, "Range: npt=%.3f-%.3f\r\n", rangeStart, rangeEnd); } } char* rangeHeader = strDup(buf); // Now, start streaming: for (i = 0; i < fNumStreamStates; ++i) { if (subsession == NULL /* means: aggregated operation */ || subsession == fStreamStates[i].subsession) { unsigned short rtpSeqNum = 0; unsigned rtpTimestamp = 0; if (fStreamStates[i].subsession == NULL) continue; fStreamStates[i].subsession->startStream(fOurSessionId, fStreamStates[i].streamToken, (TaskFunc*) RTSPServer::RTSPClientSession::noteClientLiveness, this, rtpSeqNum, rtpTimestamp, RTSPServer::RTSPClientConnection::handleAlternativeRequestByte, ourClientConnection); const char *urlSuffix = fStreamStates[i].subsession->trackId(); char* prevRTPInfo = rtpInfo; unsigned rtpInfoSize = rtpInfoFmtSize + strlen(prevRTPInfo) + 1 + rtspURLSize + strlen(urlSuffix) + 5 /*max unsigned short len*/ + 10 /*max unsigned (32-bit) len*/ + 2 /*allows for trailing \r\n at final end of string*/; rtpInfo = new char[rtpInfoSize]; sprintf(rtpInfo, rtpInfoFmt, prevRTPInfo, numRTPInfoItems++ == 0 ? "" : ",", rtspURL, urlSuffix, rtpSeqNum, rtpTimestamp); delete[] prevRTPInfo; } } if (numRTPInfoItems == 0) { rtpInfo[0] = '\0'; } else { unsigned rtpInfoLen = strlen(rtpInfo); rtpInfo[rtpInfoLen] = '\r'; rtpInfo[rtpInfoLen + 1] = '\n'; rtpInfo[rtpInfoLen + 2] = '\0'; } // Fill in the response: snprintf((char*) ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer, "RTSP/1.0 200 OK\r\n" "CSeq: %s\r\n" "%s" "%s" "%s" "Session: %08X\r\n" "%s\r\n", ourClientConnection->fCurrentCSeq, dateHeader(), scaleHeader, rangeHeader, fOurSessionId, rtpInfo); delete[] rtpInfo; delete[] rangeHeader; delete[] scaleHeader; delete[] rtspURL; } void RTSPServer::RTSPClientSession::noteClientLiveness(void* clientSession) { RTSPServer::RTSPClientSession *s = (RTSPServer::RTSPClientSession*)clientSession; s->noteLiveness(); } void RTSPServer::RTSPClientSession::handleCmd_PAUSE( RTSPServer::RTSPClientConnection* ourClientConnection, ServerMediaSubsession* subsession) { for (unsigned i = 0; i < fNumStreamStates; ++i) { if (subsession == NULL /* means: aggregated operation */ || subsession == fStreamStates[i].subsession) { if (fStreamStates[i].subsession != NULL) { fStreamStates[i].subsession->pauseStream(fOurSessionId, fStreamStates[i].streamToken); } } } setRTSPResponse(ourClientConnection, "200 OK", fOurSessionId); } void RTSPServer::RTSPClientSession::handleCmd_GET_PARAMETER( RTSPServer::RTSPClientConnection* ourClientConnection, ServerMediaSubsession* /*subsession*/, char const* /*fullRequestStr*/) { // By default, we implement "GET_PARAMETER" just as a 'keep alive', and send back a dummy response. // (If you want to handle "GET_PARAMETER" properly, you can do so by defining a subclass of "RTSPServer" // and "RTSPServer::RTSPClientSession", and then reimplement this virtual function in your subclass.) setRTSPResponse(ourClientConnection, "200 OK", fOurSessionId, LIVEMEDIA_LIBRARY_VERSION_STRING); } void RTSPServer::RTSPClientSession::handleCmd_SET_PARAMETER( RTSPServer::RTSPClientConnection* ourClientConnection, ServerMediaSubsession* /*subsession*/, char const* /*fullRequestStr*/) { // By default, we implement "SET_PARAMETER" just as a 'keep alive', and send back an empty response. // (If you want to handle "SET_PARAMETER" properly, you can do so by defining a subclass of "RTSPServer" // and "RTSPServer::RTSPClientSession", and then reimplement this virtual function in your subclass.) setRTSPResponse(ourClientConnection, "200 OK", fOurSessionId); } GenericMediaServer::ClientConnection* RTSPServer::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) { return new RTSPClientConnection(*this, clientSocket, clientAddr); } GenericMediaServer::ClientSession* RTSPServer::createNewClientSession(u_int32_t sessionId) { return new RTSPClientSession(*this, sessionId); } ///////// RTSPServerWithREGISTERProxying implementation ///////// //TODO : comment for strip code // //RTSPServerWithREGISTERProxying* RTSPServerWithREGISTERProxying //::createNew(UsageEnvironment& env, Port ourPort, // UserAuthenticationDatabase* authDatabase, UserAuthenticationDatabase* authDatabaseForREGISTER, // unsigned reclamationSeconds, // Boolean streamRTPOverTCP, int verbosityLevelForProxying) { // int ourSocket = setUpOurSocket(env, ourPort); // if (ourSocket == -1) return NULL; // // return new RTSPServerWithREGISTERProxying(env, ourSocket, ourPort, authDatabase, authDatabaseForREGISTER, reclamationSeconds, // streamRTPOverTCP, verbosityLevelForProxying); //} // //RTSPServerWithREGISTERProxying //::RTSPServerWithREGISTERProxying(UsageEnvironment& env, int ourSocket, Port ourPort, // UserAuthenticationDatabase* authDatabase, UserAuthenticationDatabase* authDatabaseForREGISTER, // unsigned reclamationSeconds, // Boolean streamRTPOverTCP, int verbosityLevelForProxying) // : RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationSeconds), // fStreamRTPOverTCP(streamRTPOverTCP), fVerbosityLevelForProxying(verbosityLevelForProxying), // fRegisteredProxyCounter(0), fAllowedCommandNames(NULL), fAuthDBForREGISTER(authDatabaseForREGISTER) { //} // //RTSPServerWithREGISTERProxying::~RTSPServerWithREGISTERProxying() { // delete[] fAllowedCommandNames; //} // //char const* RTSPServerWithREGISTERProxying::allowedCommandNames() { // if (fAllowedCommandNames == NULL) { // char const* baseAllowedCommandNames = RTSPServer::allowedCommandNames(); // char const* newAllowedCommandName = ", REGISTER"; // fAllowedCommandNames = new char[strlen(baseAllowedCommandNames) + strlen(newAllowedCommandName) + 1/* for '\0' */]; // sprintf(fAllowedCommandNames, "%s%s", baseAllowedCommandNames, newAllowedCommandName); // } // return fAllowedCommandNames; //} // //Boolean RTSPServerWithREGISTERProxying::weImplementREGISTER(char const* proxyURLSuffix, char*& responseStr) { // // First, check whether we have already proxied a stream as "proxyURLSuffix": // if (proxyURLSuffix != NULL && lookupServerMediaSession(proxyURLSuffix) != NULL) { // responseStr = strDup("451 Invalid parameter"); // return False; // } // // // Otherwise, we will implement it: // responseStr = NULL; // return True; //} // //void RTSPServerWithREGISTERProxying::implementCmd_REGISTER(char const* url, char const* /*urlSuffix*/, int socketToRemoteServer, // Boolean deliverViaTCP, char const* proxyURLSuffix) { // // Continue setting up proxying for the specified URL. // // By default: // // - We use "registeredProxyStream-N" as the (front-end) stream name (ignoring the back-end stream's 'urlSuffix'), // // unless "proxyURLSuffix" is non-NULL (in which case we use that) // // - There is no 'username' and 'password' for the back-end stream. (Thus, access-controlled back-end streams will fail.) // // - If "fStreamRTPOverTCP" is True, then we request delivery over TCP, regardless of the value of "deliverViaTCP". // // (Otherwise, if "fStreamRTPOverTCP" is False, we use the value of "deliverViaTCP" to decide this.) // // To change this default behavior, you will need to subclass "RTSPServerWithREGISTERProxying", and reimplement this function. // // char const* proxyStreamName; // char proxyStreamNameBuf[100]; // if (proxyURLSuffix == NULL) { // sprintf(proxyStreamNameBuf, "registeredProxyStream-%u", ++fRegisteredProxyCounter); // proxyStreamName = proxyStreamNameBuf; // } else { // proxyStreamName = proxyURLSuffix; // } // // if (fStreamRTPOverTCP) deliverViaTCP = True; // portNumBits tunnelOverHTTPPortNum = deliverViaTCP ? (portNumBits)(~0) : 0; // // We don't support streaming from the back-end via RTSP/RTP/RTCP-over-HTTP; only via RTP/RTCP-over-TCP or RTP/RTCP-over-UDP // // ServerMediaSession* sms // = ProxyServerMediaSession::createNew(envir(), this, url, proxyStreamName, NULL, NULL, // tunnelOverHTTPPortNum, fVerbosityLevelForProxying, socketToRemoteServer); // addServerMediaSession(sms); // // // (Regardless of the verbosity level) announce the fact that we're proxying this new stream, and the URL to use to access it: // char* proxyStreamURL = rtspURL(sms); // envir() << "Proxying the registered back-end stream \"" << url << "\".\n"; // envir() << "\tPlay this stream using the URL: " << proxyStreamURL << "\n"; // delete[] proxyStreamURL; //} // //UserAuthenticationDatabase* RTSPServerWithREGISTERProxying::getAuthenticationDatabaseForCommand(char const* cmdName) { // if (strcmp(cmdName, "REGISTER") == 0) return fAuthDBForREGISTER; // // return RTSPServer::getAuthenticationDatabaseForCommand(cmdName); //}