| /********** |
| 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 <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-2015 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 <GroupsockHelper.hh> |
| #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, 50*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, &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() { |
| #ifdef DEBUG |
| char const* streamName |
| = (fOurServerMediaSession == NULL) ? "???" : fOurServerMediaSession->streamName(); |
| fprintf(stderr, "Client session (id \"%08X\", stream name \"%s\"): Liveness indication\n", |
| fOurSessionId, streamName); |
| #endif |
| 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: |
| #ifdef DEBUG |
| 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)); |
| } |