blob: a6b1ca475b5017f9015edac446ee3dde895f672b [file] [log] [blame]
/**********
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 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
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-2020 Live Networks, Inc. All rights reserved.
// A RTSP server
// Implementation
#include "RTSPServer.hh"
#include "RTSPCommon.hh"
#include "RTSPRegisterSender.hh"
#include "Base64.hh"
#include <GroupsockHelper.hh>
////////// 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;
}
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, &namelen);
}
char urlBuffer[100]; // more than big enough for "rtsp://<ip-address>:<port>/"
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";
}
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)),
fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),
fRegisterOrDeregisterRequestCounter(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):
envir().taskScheduler().turnOffBackgroundReadHandling(fHTTPServerSocket);
::closeSocket(fHTTPServerSocket);
cleanup(); // Removes all "ClientSession" and "ClientConnection" objects, and their tables.
delete fClientConnectionsForHTTPTunneling;
// Delete any pending REGISTER requests:
RTSPRegisterOrDeregisterSender* r;
while ((r = (RTSPRegisterOrDeregisterSender*)fPendingRegisterOrDeregisterRequests->getFirst()) != NULL) {
delete r;
}
delete fPendingRegisterOrDeregisterRequests;
// 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();
}
// 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"
"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) return; // it wouldn't fit
char const* resultSource = &source[i];
char const* resultSourceEnd = &source[j];
while (resultSource < resultSourceEnd) *resultStr++ = *resultSource++;
*resultStr = '\0';
return;
}
}
}
}
}
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 || prevClientConnection == this) {
// Either there was no previous HTTP "GET" request, or it was on the same connection; 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 <CR><LF>
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);
}
}
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: <CR><LF><CR><LF>
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;
Boolean playAfterSetup = False;
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
// Check first for a bogus "Content-Length" value that would cause a pointer wraparound:
if (tmpPtr + 2 + contentLength < tmpPtr + 2) {
#ifdef DEBUG
fprintf(stderr, "parseRTSPRequestString() returned a bogus \"Content-Length:\" value: 0x%x (%d)\n", contentLength, (int)contentLength);
#endif
contentLength = 0;
parseSucceeded = 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';
if (requestIncludedSessionId) {
clientSession
= (RTSPServer::RTSPClientSession*)(fOurRTSPServer.lookupClientSession(sessionIdStr));
if (clientSession != NULL) clientSession->noteLiveness();
}
// 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) {
#ifdef DEBUG
fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 1)\n");
#endif
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) {
clientSession->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
playAfterSetup = clientSession->fStreamAfterSETUP;
} else if (areAuthenticated) {
#ifdef DEBUG
fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 2)\n");
#endif
handleCmd_sessionNotFound();
}
} 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 {
#ifdef DEBUG
fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 3)\n");
#endif
handleCmd_sessionNotFound();
}
} else if (strcmp(cmdName, "REGISTER") == 0 || strcmp(cmdName, "DEREGISTER") == 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(cmdName, 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.
}
}
#define SKIP_WHITESPACE while (*fields != '\0' && (*fields == ' ' || *fields == '\t')) ++fields
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;
char* parameter = strDupSize(fields);
char* value = strDupSize(fields);
char* p;
Boolean success;
do {
// Parse: <parameter>="<value>"
success = False;
parameter[0] = value[0] = '\0';
SKIP_WHITESPACE;
for (p = parameter; *fields != '\0' && *fields != ' ' && *fields != '\t' && *fields != '='; ) *p++ = *fields++;
SKIP_WHITESPACE;
if (*fields++ != '=') break; // parsing failed
*p = '\0'; // complete parsing <parameter>
SKIP_WHITESPACE;
if (*fields++ != '"') break; // parsing failed
for (p = value; *fields != '\0' && *fields != '"'; ) *p++ = *fields++;
if (*fields++ != '"') break; // parsing failed
*p = '\0'; // complete parsing <value>
SKIP_WHITESPACE;
success = True;
// Copy values for parameters that we understand:
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);
}
// Check for a ',', indicating that more <parameter>="<value>" pairs follow:
} while (*fields++ == ',');
delete[] parameter; delete[] value;
return success;
}
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"
"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"
"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);
}
}
////////// 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):
fNumStreamStates = fOurServerMediaSession->numSubsessions();
fStreamStates = new struct streamState[fNumStreamStates];
ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
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, &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\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"
"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\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"
"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"
"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 <urlPreSuffix>/<urlSuffix> 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);
} 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*)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
::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);
}