/********** 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 generic media server class, used to implement a RTSP server, and any other server that uses // "ServerMediaSession" objects to describe media to be served. // Implementation #include "GenericMediaServer.hh" #include #include #include #include #if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4) #define snprintf _snprintf #endif ////////// GenericMediaServer implementation ////////// void GenericMediaServer::addServerMediaSession(ServerMediaSession* serverMediaSession) { if (serverMediaSession == NULL) return; char const* sessionName = serverMediaSession->streamName(); if (sessionName == NULL) sessionName = ""; removeServerMediaSession(sessionName); // in case an existing "ServerMediaSession" with this name already exists fServerMediaSessions->Add(sessionName, (void*)serverMediaSession); } ServerMediaSession* GenericMediaServer ::lookupServerMediaSession(char const* streamName, Boolean /*isFirstLookupInSession*/) { // Default implementation: return (ServerMediaSession*)(fServerMediaSessions->Lookup(streamName)); } void GenericMediaServer::removeServerMediaSession(ServerMediaSession* serverMediaSession) { if (serverMediaSession == NULL) return; fServerMediaSessions->Remove(serverMediaSession->streamName()); if (serverMediaSession->referenceCount() == 0) { Medium::close(serverMediaSession); } else { serverMediaSession->deleteWhenUnreferenced() = True; } } void GenericMediaServer::removeServerMediaSession(char const* streamName) { removeServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName))); } void GenericMediaServer::closeAllClientSessionsForServerMediaSession(ServerMediaSession* serverMediaSession) { if (serverMediaSession == NULL) return; HashTable::Iterator* iter = HashTable::Iterator::create(*fClientSessions); GenericMediaServer::ClientSession* clientSession; char const* key; // dummy while ((clientSession = (GenericMediaServer::ClientSession*)(iter->next(key))) != NULL) { if (clientSession->fOurServerMediaSession == serverMediaSession) { delete clientSession; } } delete iter; } void GenericMediaServer::closeAllClientSessionsForServerMediaSession(char const* streamName) { closeAllClientSessionsForServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName))); } void GenericMediaServer::deleteServerMediaSession(ServerMediaSession* serverMediaSession) { if (serverMediaSession == NULL) return; closeAllClientSessionsForServerMediaSession(serverMediaSession); removeServerMediaSession(serverMediaSession); } void GenericMediaServer::deleteServerMediaSession(char const* streamName) { deleteServerMediaSession((ServerMediaSession*)(fServerMediaSessions->Lookup(streamName))); } GenericMediaServer ::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort, unsigned reclamationSeconds) : Medium(env), fServerSocket(ourSocket), fServerPort(ourPort), fReclamationSeconds(reclamationSeconds), fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)), fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)), fClientSessions(HashTable::create(STRING_HASH_KEYS)) { ignoreSigPipeOnSocket(fServerSocket); // so that clients on the same host that are killed don't also kill us // Arrange to handle connections from others: env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this); } GenericMediaServer::~GenericMediaServer() { // Turn off background read handling: envir().taskScheduler().turnOffBackgroundReadHandling(fServerSocket); ::closeSocket(fServerSocket); } void GenericMediaServer::cleanup() { // This member function must be called in the destructor of any subclass of //"GenericMediaServer". (We don't call this in the destructor of "GenericMediaServer" itself, // because by that time, the subclass destructor will already have been called, and this may // affect (break) the destruction of the "ClientSession" and "ClientConnection" objects, which // themselves will have been subclassed.) // Close all client session objects: GenericMediaServer::ClientSession* clientSession; while ((clientSession = (GenericMediaServer::ClientSession*)fClientSessions->getFirst()) != NULL) { delete clientSession; } delete fClientSessions; // Close all client connection objects: GenericMediaServer::ClientConnection* connection; while ((connection = (GenericMediaServer::ClientConnection*)fClientConnections->getFirst()) != NULL) { delete connection; } delete fClientConnections; // Delete all server media sessions ServerMediaSession* serverMediaSession; while ((serverMediaSession = (ServerMediaSession*)fServerMediaSessions->getFirst()) != NULL) { removeServerMediaSession(serverMediaSession); // will delete it, because it no longer has any 'client session' objects using it } delete fServerMediaSessions; } #define LISTEN_BACKLOG_SIZE 20 int GenericMediaServer::setUpOurSocket(UsageEnvironment& env, Port& ourPort) { int ourSocket = -1; do { // The following statement is enabled by default. // Don't disable it (by defining ALLOW_SERVER_PORT_REUSE) unless you know what you're doing. #if !defined(ALLOW_SERVER_PORT_REUSE) && !defined(ALLOW_RTSP_SERVER_PORT_REUSE) // ALLOW_RTSP_SERVER_PORT_REUSE is for backwards-compatibility ##### NoReuse dummy(env); // Don't use this socket if there's already a local server using it #endif ourSocket = setupStreamSocket(env, ourPort); if (ourSocket < 0) break; // Make sure we have a big send buffer: if (!increaseSendBufferTo(env, ourSocket, 625*1024)) break; // Allow multiple simultaneous connections: if (listen(ourSocket, LISTEN_BACKLOG_SIZE) < 0) { env.setResultErrMsg("listen() failed: "); break; } if (ourPort.num() == 0) { // bind() will have chosen a port for us; return it also: if (!getSourcePort(env, ourSocket, ourPort)) break; } return ourSocket; } while (0); if (ourSocket != -1) ::closeSocket(ourSocket); return -1; } void GenericMediaServer::incomingConnectionHandler(void* instance, int /*mask*/) { GenericMediaServer* server = (GenericMediaServer*)instance; server->incomingConnectionHandler(); } void GenericMediaServer::incomingConnectionHandler() { incomingConnectionHandlerOnSocket(fServerSocket); } void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) { struct sockaddr_in clientAddr; SOCKLEN_T clientAddrLen = sizeof clientAddr; int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, (socklen_t*)&clientAddrLen); if (clientSocket < 0) { int err = envir().getErrno(); if (err != EWOULDBLOCK) { envir().setResultErrMsg("accept() failed: "); } return; } ignoreSigPipeOnSocket(clientSocket); // so that clients on the same host that are killed don't also kill us makeSocketNonBlocking(clientSocket); increaseSendBufferTo(envir(), clientSocket, 50*1024); #ifdef DEBUG envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n"; #endif // Create a new object for handling this connection: (void)createNewClientConnection(clientSocket, clientAddr); } ////////// GenericMediaServer::ClientConnection implementation ////////// GenericMediaServer::ClientConnection ::ClientConnection(GenericMediaServer& ourServer, int clientSocket, struct sockaddr_in clientAddr) : fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr) { // Add ourself to our 'client connections' table: fOurServer.fClientConnections->Add((char const*)this, this); // Arrange to handle incoming requests: resetRequestBuffer(); envir().taskScheduler() .setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this); } GenericMediaServer::ClientConnection::~ClientConnection() { // Remove ourself from the server's 'client connections' hash table before we go: fOurServer.fClientConnections->Remove((char const*)this); closeSockets(); } void GenericMediaServer::ClientConnection::closeSockets() { // Turn off background handling on our socket: envir().taskScheduler().disableBackgroundHandling(fOurSocket); ::closeSocket(fOurSocket); fOurSocket = -1; } void GenericMediaServer::ClientConnection::incomingRequestHandler(void* instance, int /*mask*/) { ClientConnection* connection = (ClientConnection*)instance; connection->incomingRequestHandler(); } void GenericMediaServer::ClientConnection::incomingRequestHandler() { struct sockaddr_in dummy; // 'from' address, meaningless in this case int bytesRead = readSocket(envir(), fOurSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy); handleRequestBytes(bytesRead); } void GenericMediaServer::ClientConnection::resetRequestBuffer() { fRequestBytesAlreadySeen = 0; fRequestBufferBytesLeft = sizeof fRequestBuffer; } ////////// GenericMediaServer::ClientSession implementation ////////// GenericMediaServer::ClientSession ::ClientSession(GenericMediaServer& ourServer, u_int32_t sessionId) : fOurServer(ourServer), fOurSessionId(sessionId), fOurServerMediaSession(NULL), fLivenessCheckTask(NULL) { noteLiveness(); } GenericMediaServer::ClientSession::~ClientSession() { // Turn off any liveness checking: envir().taskScheduler().unscheduleDelayedTask(fLivenessCheckTask); // Remove ourself from the server's 'client sessions' hash table before we go: char sessionIdStr[8+1]; sprintf(sessionIdStr, "%08X", fOurSessionId); fOurServer.fClientSessions->Remove(sessionIdStr); if (fOurServerMediaSession != NULL) { fOurServerMediaSession->decrementReferenceCount(); if (fOurServerMediaSession->referenceCount() == 0 && fOurServerMediaSession->deleteWhenUnreferenced()) { fOurServer.removeServerMediaSession(fOurServerMediaSession); fOurServerMediaSession = NULL; } } } void GenericMediaServer::ClientSession::noteLiveness() { #if 0 char const* streamName = (fOurServerMediaSession == NULL) ? "???" : fOurServerMediaSession->streamName(); fprintf(stderr, "Client session (id \"%08X\", stream name \"%s\"): Liveness indication\n", fOurSessionId, streamName); #endif if (fOurServerMediaSession != NULL) fOurServerMediaSession->noteLiveness(); if (fOurServer.fReclamationSeconds > 0) { envir().taskScheduler().rescheduleDelayedTask(fLivenessCheckTask, fOurServer.fReclamationSeconds*1000000, (TaskFunc*)livenessTimeoutTask, this); } } void GenericMediaServer::ClientSession::noteClientLiveness(ClientSession* clientSession) { clientSession->noteLiveness(); } void GenericMediaServer::ClientSession::livenessTimeoutTask(ClientSession* clientSession) { // If this gets called, the client session is assumed to have timed out, so delete it: #if 1 char const* streamName = (clientSession->fOurServerMediaSession == NULL) ? "???" : clientSession->fOurServerMediaSession->streamName(); fprintf(stderr, "Client session (id \"%08X\", stream name \"%s\") has timed out (due to inactivity)\n", clientSession->fOurSessionId, streamName); #endif delete clientSession; } GenericMediaServer::ClientSession* GenericMediaServer::createNewClientSessionWithId() { u_int32_t sessionId; char sessionIdStr[8+1]; // Choose a random (unused) 32-bit integer for the session id // (it will be encoded as a 8-digit hex number). (We avoid choosing session id 0, // because that has a special use by some servers.) do { sessionId = (u_int32_t)our_random32(); snprintf(sessionIdStr, sizeof sessionIdStr, "%08X", sessionId); } while (sessionId == 0 || lookupClientSession(sessionIdStr) != NULL); ClientSession* clientSession = createNewClientSession(sessionId); fClientSessions->Add(sessionIdStr, clientSession); return clientSession; } GenericMediaServer::ClientSession* GenericMediaServer::lookupClientSession(u_int32_t sessionId) { char sessionIdStr[8+1]; snprintf(sessionIdStr, sizeof sessionIdStr, "%08X", sessionId); return lookupClientSession(sessionIdStr); } GenericMediaServer::ClientSession* GenericMediaServer::lookupClientSession(char const* sessionIdStr) { return (GenericMediaServer::ClientSession*)fClientSessions->Lookup(sessionIdStr); } ////////// ServerMediaSessionIterator implementation ////////// GenericMediaServer::ServerMediaSessionIterator ::ServerMediaSessionIterator(GenericMediaServer& server) : fOurIterator((server.fServerMediaSessions == NULL) ? NULL : HashTable::Iterator::create(*server.fServerMediaSessions)) { } GenericMediaServer::ServerMediaSessionIterator::~ServerMediaSessionIterator() { delete fOurIterator; } ServerMediaSession* GenericMediaServer::ServerMediaSessionIterator::next() { if (fOurIterator == NULL) return NULL; char const* key; // dummy return (ServerMediaSession*)(fOurIterator->next(key)); } ////////// UserAuthenticationDatabase implementation ////////// UserAuthenticationDatabase::UserAuthenticationDatabase(char const* realm, Boolean passwordsAreMD5) : fTable(HashTable::create(STRING_HASH_KEYS)), fRealm(strDup(realm == NULL ? "LIVE555 Streaming Media" : realm)), fPasswordsAreMD5(passwordsAreMD5) { } UserAuthenticationDatabase::~UserAuthenticationDatabase() { delete[] fRealm; // Delete the allocated 'password' strings that we stored in the table, and then the table itself: char* password; while ((password = (char*)fTable->RemoveNext()) != NULL) { delete[] password; } delete fTable; } void UserAuthenticationDatabase::addUserRecord(char const* username, char const* password) { fTable->Add(username, (void*)(strDup(password))); } void UserAuthenticationDatabase::removeUserRecord(char const* username) { char* password = (char*)(fTable->Lookup(username)); fTable->Remove(username); delete[] password; } char const* UserAuthenticationDatabase::lookupPassword(char const* username) { return (char const*)(fTable->Lookup(username)); }