Darwin-Streaming-Server/APIModules/QTSSMP3StreamingModule/QTSSMP3StreamingModule.cpp
Darren VanBuren 849723c9cf Add even more of the source
This should be about everything needed to build so far?
2017-03-07 17:14:16 -08:00

2890 lines
91 KiB
C++
Executable file

/*
*
* @APPLE_LICENSE_HEADER_START@
*
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*
*/
/*
File: QTSSMP3StreamingModule.cpp
Contains: This module will reflect a ShoutCast/IceCast-style MP3 broadcast
out to multiple clients.
Written by: Steve Ussery
*/
#ifndef kVersionString
#include "../../revision.h"
#endif
#include "QTSSMP3StreamingModule.h"
#include "QTSSModuleUtils.h"
#include "QTSSRollingLog.h"
#include "StringParser.h"
#include "OSMemory.h"
#include "OS.h"
#define DEBUG_MP3STREAMING_MODULE 0
#if DEBUG_MP3STREAMING_MODULE
#define DTRACE(s) qtss_printf(s)
#define DTRACE1(s,n1) qtss_printf(s,n1)
#define DTRACE2(s,n1,n2) qtss_printf(s,n1,n2)
#else
#define DTRACE(s)
#define DTRACE1(s,n1)
#define DTRACE2(s,n1,n2)
#endif
class QTSSMP3AccessLog;
//**************************************************
#define kOKHeader "OK\r\n"
#ifdef __MacOSX__
#define kClientAcceptHeader "HTTP/1.1 200 OK\r\nServer: QuickTime Streaming Server %s/%s\r\nContent-Type: audio/mpeg\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\n"
#define kM3UReplyHeader "HTTP/1.1 200 OK\r\nServer: QuickTime Streaming Server %s/%s\r\nContent-Type: audio/mpegurl\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\nContent-Length:"
#else
#define kClientAcceptHeader "HTTP/1.1 200 OK\r\nServer: Darwin Streaming Server %s/%s\r\nContent-Type: audio/mpeg\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\n"
#define kM3UReplyHeader "HTTP/1.1 200 OK\r\nServer: Darwin Streaming Server %s/%s\r\nContent-Type: audio/mpegurl\r\nCache-Control: no-cache\r\nPragma: no-cache\r\nConnection: close\r\nContent-Length:"
#endif
#define kSourceReject "Error - Mount Point Taken or Invalid\r\n"
#define kSourceBadPassword "Error - Bad Password\r\n"
#define kClientCLHeader "Content-Length: 54000000\r\n\r\n"
#define kRemoteAddressSize 20
#define kBandwidthToAddEstimate 16000
// STATIC DATA
static QTSS_ModulePrefsObject sPrefs = NULL;
static QTSS_PrefsObject sServerPrefs = NULL;
static OSBufferPool* sOSBufferPoolPtr = NULL;
static QTSSMP3AccessLog* sMP3AccessLog = NULL;
static QTSS_ServerObject sServer = NULL;
static OSMutex* sLogMutex = NULL; // MP3 access log isn't reentrant
static OSMutex* sAtomicMutex = NULL;
static Bool16 sServerIdle = true;
// Server global MP3 Session management classes.
static MP3BroadcasterQueue sMP3BroadcasterQueue;
static MP3SessionTable sMP3SessionTable;
// PREFERENCE VALUES
static UInt32 sBroadcastBufferSize = 8192;
static UInt32 sDefaultBroadcastBufferSize = 8192;
static char* sBroadcastPassword = NULL;
static char* sDefaultBroadcastPassword = " ";
static Bool16 sMP3StreamingEnabled = true;
static Bool16 sDefaultMP3StreamingEnabled = true;
static SInt32 sMaximumConnections = 0;
static SInt32 sMaximumBandwidth = 0;
static UInt32 sDefaultRollInterval = 7;
static Bool16 sDefaultLogTimeInGMT = true;
static UInt32 sDefaultMaxLogBytes = 10240000;
static SInt32 sDefaultFlowControlTimeInMSec = 10000;
static SInt32 sMaxFlowControlTimeInMSec = 10000;
static Bool16 sDefaultLogEnabled = true;
static char* sDefaultLogName = "mp3_access";
static char* sDefaultLogDir = NULL;
static char* sLogName = NULL;
static char* sLogDir = NULL;
static Bool16 sLogEnabled = true;
static UInt32 sMaxLogBytes = 10240000;
static UInt32 sRollInterval = 7;
static Bool16 sLogTimeInGMT = true;
static char* sLogHeader = "#Software: %s\n"
"#Version: %s\n" //%s == version
"#Date: %s\n" //%s == date/time
"#Remark: All date values are in %s.\n" //%s == "GMT" or "local time"
"#Remark: c-duration is in seconds.\n"
"#Fields: c-ip c-user-agent [date time] cs-uri c-status c-bytes c-duration\n";
static char gClientAcceptHeader[1024];
static char gM3UReplyHeader[1024];
// FUNCTION PROTOTYPES
static QTSS_Error QTSSMP3StreamingModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams);
static QTSS_Error Register(QTSS_Register_Params* inParams);
static QTSS_Error Initialize(QTSS_Initialize_Params* inParams);
static QTSS_Error FilterRequest(QTSS_Filter_Params* inParams);
static QTSS_Error RereadPrefs();
static QTSS_Error SessionClosing(QTSS_RTSPSession_Params* inParams);
static QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams);
static QTSS_Error Shutdown();
static void IncrementMP3SessionCount();
static void DecrementMP3SessionCount();
static void IncrementTotalMP3Bytes(UInt32 bytes);
static Bool16 CheckBandwidth(SInt32 bandwidth);
#if DEBUG_MP3STREAMING_MODULE
static void PrintStringBuffer(StrPtrLen& stringBuffer);
#else
#define PrintStringBuffer(stringBuffer) ;
#endif
static void KeepSession(QTSS_RTSPRequestObject theRequest,Bool16 keep);
static UInt32 GetRTSPSessionID(QTSS_RTSPSessionObject session);
static Bool16 IsActiveBroadcastSession(QTSS_RTSPSessionObject session);
static MP3BroadcasterSession* FindBroadcastSession(QTSS_RTSPSessionObject session);
static Bool16 IsBroadcastPassword(StrPtrLen& theRequest);
static Bool16 IsShoutcastPassword(StrPtrLen& theRequest);
static Bool16 IsHTTPGet(StrPtrLen& theRequest);
static Bool16 IsA_m3u_URL(char* theURL);
static Bool16 IsMetaDataURL(char* theURL);
static void ParseMetaDataURL(char* theURL);
static Bool16 CheckPassword(QTSS_Filter_Params* inParams, StrPtrLen& theRequest, StrPtrLen& mountpoint);
static Bool16 NeedsMetaData(StrPtrLen& theRequest);
static Bool16 ParseURL(StrPtrLen& theRequest, char* outURL, UInt16 maxlen);
static QTSS_Error LogRequest(QTSS_RTSPSessionObject inRTSPSession, MP3ClientSession* client);
static void WriteStartupMessage();
static void WriteShutdownMessage();
static void url_strcpy(char* dest, const char* src);
static QTSS_Error ReEnterFilterRequest(QTSS_Filter_Params* inParams, MP3Session* mp3Session);
static Bool16 IsHTTP(StrPtrLen& theRequest);
static Bool16 IsRTSP(StrPtrLen& theRequest);
//**************************************************
// CLASS DECLARATIONS
//**************************************************
class QTSSMP3AccessLog : public QTSSRollingLog
{
public:
QTSSMP3AccessLog();
virtual ~QTSSMP3AccessLog() {}
virtual char* GetLogName() { return QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_request_logfile_name", sDefaultLogName); }
virtual char* GetLogDir() { return QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_request_logfile_dir", sDefaultLogDir); }
virtual UInt32 GetRollIntervalInDays() { return sRollInterval; }
virtual UInt32 GetMaxLogBytes() { return sMaxLogBytes; }
virtual time_t WriteLogHeader(FILE *inFile);
};
// ---------------------------------------------------------------------------
// CLASS IMPLEMENTATIONS
// ---------------------------------------------------------------------------
// ****************************************************************************
// QTSSMP3AccessLog -- subclass of QTSSRollingLog
// ****************************************************************************
QTSSMP3AccessLog::QTSSMP3AccessLog() : QTSSRollingLog()
{
this->SetTaskName("QTSSMP3AccessLog");
}
time_t QTSSMP3AccessLog::WriteLogHeader(FILE *inFile)
{
//The point of this header is to record the exact time the log file was created,
//in a format that is easy to parse through whenever we open the file again.
//This is necessary to support log rolling based on a time interval, and POSIX doesn't
//support a create date in files.
time_t calendarTime = ::time(NULL);
Assert(-1 != calendarTime);
if (-1 == calendarTime)
return -1;
struct tm timeResult;
struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult);
Assert(NULL != theLocalTime);
if (NULL == theLocalTime)
return -1;
char tempBuffer[1024] = { 0 };
qtss_strftime(tempBuffer, sizeof(tempBuffer), "#Log File Created On: %m/%d/%Y %H:%M:%S\n", theLocalTime);
this->WriteToLog(tempBuffer, !kAllowLogToRoll);
tempBuffer[0] = '\0';
//format a date for the startup time
char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes] = { 0 };
Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false);
if (result)
{
StrPtrLen serverName;
(void)QTSS_GetValuePtr(sServer, qtssSvrServerName, 0, (void**)&serverName.Ptr, &serverName.Len);
StrPtrLen serverVersion;
(void)QTSS_GetValuePtr(sServer, qtssSvrServerVersion, 0, (void**)&serverVersion.Ptr, &serverVersion.Len);
qtss_sprintf(tempBuffer, sLogHeader, serverName.Ptr , serverVersion.Ptr,
theDateBuffer, sLogTimeInGMT ? "GMT" : "local time");
this->WriteToLog(tempBuffer, !kAllowLogToRoll);
}
return calendarTime;
}
// ****************************************************************************
// MP3Session -- This is a base class to hold all the MP3 related
// session state info.
// ****************************************************************************
// initialize static value of class.
SInt32 MP3Session::sTotalNumMP3Sessions = 0L;
MP3Session::MP3Session(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream) :
fState(0),
fResult(200),
fStream(stream),
fSession(sess),
fSessID(0)
{
sTotalNumMP3Sessions++;
sMP3SessionTable.RegisterSession(this);
}
MP3Session::MP3Session() :
fState(0),
fResult(0),
fStream(NULL),
fSession(NULL),
fSessID(0)
{
// this should never be called
Assert(0);
}
MP3Session::~MP3Session()
{
--sTotalNumMP3Sessions;
sMP3SessionTable.UnRegisterSession(this);
fStream = NULL;
fSession = NULL;
fSessID = 0;
}
UInt8 MP3Session::IsA() const
{
return kMP3UndefinedSessionType;
}
// ****************************************************************************
// MP3BroadcasterSession -- This is a class to hold all the MP3 Broadcaster
// session state info.
// ****************************************************************************
MP3BroadcasterSession::MP3BroadcasterSession()
{
// this should never be called.
Assert(0);
}
MP3BroadcasterSession::MP3BroadcasterSession(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream) :
MP3Session(sess, stream),
fMP3ClientQueue(NULL),
fDataBufferLen(0),
fDataBufferSize(sBroadcastBufferSize),
fNewSongName(false)
{
fBuffer = (char*) sOSBufferPoolPtr->Get();
InitBroadcastSessionState();
fHeader[0] = '\0';
fSongName[0] = '\0';
}
MP3BroadcasterSession::~MP3BroadcasterSession()
{
SetState(MP3BroadcasterSession::kBroadcasterShutDownState);
delete fMP3ClientQueue;
fMP3ClientQueue = NULL;
sOSBufferPoolPtr->Put(fBuffer);
fBuffer = NULL;
}
UInt8 MP3BroadcasterSession::IsA() const
{
return kMP3BroadcasterSessionType;
}
void MP3BroadcasterSession::InitBroadcastSessionState()
{
::memset(fMountpoint, 0, kURLBufferSize);
::memset(fHeader, 0, kHeaderBufferSize);
::memset(fSongName, 0, kSongNameBufferSize);
::memset(fBuffer, 0, fDataBufferSize);
SetMountpoint("/");
fDataBufferLen = 0;
SetSessionID(0);
if (fMP3ClientQueue == NULL)
fMP3ClientQueue = NEW MP3ClientQueue();
SetState(MP3BroadcasterSession::kBroadcasterInitState);
}
// Run this broadcaster session's state machine.
// This is called each time through the QTSS_FilterRequest mode which
// is highly re-entrant.
QTSS_Error MP3BroadcasterSession::ExecuteState()
{
QTSS_Error err = QTSS_NoErr;
// The only states we handle here are kBroadcasterGetHeaderState and
// the kBroadcasterRecvDataState. All others are ignored.
if (GetState() == MP3BroadcasterSession::kBroadcasterGetHeaderState)
{
err = GetBroadcastHeaders();
}
else if (GetState() == MP3BroadcasterSession::kBroadcasterRecvDataState)
{
err = GetBroadcastData();
}
return err;
}
// Put this broadcast session in a state where it will accept incoming
// broadcaster connection request.
void MP3BroadcasterSession::AcceptBroadcastSessionState()
{
SetState(MP3BroadcasterSession::kBroadcasterValidatePasswordState);
}
// Put this broadcast session in a state where it has accepted
// the broadcaster connection request.
void MP3BroadcasterSession::AcceptPasswordState()
{
SetState(MP3BroadcasterSession::kBroadcasterGetHeaderState);
}
// Put this broadcast session in a state where it will reflect
// MP3 data to connected clients.
void MP3BroadcasterSession::AcceptDataStreamState()
{
SetState(MP3BroadcasterSession::kBroadcasterRecvDataState);
}
// Put this broadcast session in a state where it will no
// longer accept connections or reflect MP3 data.
void MP3BroadcasterSession::ShutDownState()
{
SetState(MP3BroadcasterSession::kBroadcasterShutDownState);
}
// Set the mount point URL that clients will associate
// with this broadcaster session.
void MP3BroadcasterSession::SetMountpoint(char* mp)
{
StrPtrLen mpStr(mp);
SetMountpoint(mpStr);
}
// Set the mount point URL that clients will associate
// with this broadcaster session.
void MP3BroadcasterSession::SetMountpoint(StrPtrLen& mp)
{
::memset(fMountpoint, 0, kURLBufferSize);
if (mp.Ptr != NULL && mp.Len > 0 && mp.Len < kURLBufferSize)
::memcpy(fMountpoint, mp.Ptr, mp.Len);
}
// Set the song name of the currently broadcasting session.
void MP3BroadcasterSession::SetSongName(char* sn)
{
if (sn != NULL && *sn != '\0')
{
// This mutex is to guard against updating a
// song name string while we're updating our queued
// clients.
OSMutexLocker locker(&fSongNameMutex);
url_strcpy(fSongName, sn);
fNewSongName = true;
}
}
// See if a given mount point URL matches.
Bool16 MP3BroadcasterSession::MountpointEqual(char* mp)
{
StrPtrLen mpStr(mp);
return MountpointEqual(mpStr);
}
// See if a given mount point URL matches.
Bool16 MP3BroadcasterSession::MountpointEqual(StrPtrLen& mp)
{
if (0 == *fMountpoint)
return false;
return mp.Equal(fMountpoint);
}
// Add a MP3 client session ref to our broadcaster
QTSS_Error MP3BroadcasterSession::AddClient(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream)
{
if (fMP3ClientQueue == NULL)
{
Assert(0);
return QTSS_RequestFailed;
}
return fMP3ClientQueue->AddClient(sess, stream, this);
}
// Is this RTSP session one of my clients?
Bool16 MP3BroadcasterSession::IsMyClient(QTSS_RTSPSessionObject sess)
{
if (fMP3ClientQueue == NULL)
{
return false;
}
return fMP3ClientQueue->InQueue(sess);
}
// Remove a MP3 client session ref from our broadcaster
QTSS_Error MP3BroadcasterSession::RemoveClient(QTSS_RTSPSessionObject sess)
{
if (fMP3ClientQueue == 0)
{
Assert(0);
return QTSS_BadArgument;
}
return fMP3ClientQueue->RemoveClient(sess);
}
//Send acknowledgement response to broacaster
QTSS_Error MP3BroadcasterSession::SendOKResponse()
{
QTSS_Error theErr = QTSS_NoErr;
if (GetStreamRef() == 0)
{
Assert(0);
return theErr;
}
theErr = QTSS_Write(GetStreamRef(), kOKHeader, ::strlen(kOKHeader), NULL, qtssWriteFlagsBufferData);
if (theErr != QTSS_NoErr)
{
return theErr;
}
theErr = QTSS_Flush(GetStreamRef());
Assert(theErr == QTSS_NoErr);
return theErr;
}
// Read the broadcast headers
QTSS_Error MP3BroadcasterSession::GetBroadcastHeaders()
{
QTSS_Error theErr = QTSS_NoErr;
UInt32 theLen = 0;
Assert(GetStreamRef() != 0);
::memset(fHeader, 0, kHeaderBufferSize);
theErr = QTSS_Read(GetStreamRef(), fHeader, kHeaderBufferSize, &theLen);
if (theErr == QTSS_WouldBlock)
{
// we're blocked from reading the broadcast headers so we will
// schedule a read so we can try again later.
theErr = QTSS_RequestEvent(GetStreamRef(), QTSS_ReadableEvent);
Assert(theErr == QTSS_NoErr);
}
else if (theErr == QTSS_NoErr)
{
// The read of the headers succeeded
TerminateHeaders();
// parse headers here...
if (strncmp(fHeader, "icy-name", 8) != 0)
AcceptDataStreamState();
// Send another OK response.
SendOKResponse();
// schedule the first read of the data stream.
theErr = QTSS_RequestEvent(GetStreamRef(), QTSS_ReadableEvent);
Assert(theErr == QTSS_NoErr);
}
else
{
// The read failed with an error other than QTSS_WouldBlock
DTRACE1("MP3BroadcasterSession::GetBroadcastHeaders() - read failed with err = %"_S32BITARG_"\n", theErr);
Assert(0);
}
return theErr;
}
// Read the broadcast data stream
QTSS_Error MP3BroadcasterSession::GetBroadcastData()
{
QTSS_Error theErr = QTSS_NoErr;
if (GetStreamRef() == 0)
{
Assert(0);
return theErr;
}
theErr = QTSS_Read(GetStreamRef(), fBuffer, fDataBufferSize, &fDataBufferLen);
if (theErr == QTSS_WouldBlock)
{
// we're blocked from reading the broadcast headers so we will
// schedule a read so we can try again later.
theErr = QTSS_RequestEvent(GetStreamRef(), QTSS_ReadableEvent);
Assert(theErr == QTSS_NoErr);
}
else if (theErr == QTSS_NoErr)
{
// The read of the broadcast stream succeeded
// Send all our clients a copy of the data
theErr = SendClientsData();
// Set up to read the next chunk of incoming broadcast data
// If we didn't do this we'd exit the kFilterRequest state after returning.
// By doing this we get called back when more data arrives.
theErr = QTSS_RequestEvent(GetStreamRef(), QTSS_ReadableEvent);
Assert(theErr == QTSS_NoErr);
}
else if (theErr == QTSS_NotConnected)
{
DTRACE("MP3BroadcasterSession::GetBroadcastData() - read failed with err = QTSS_NotConnected\n");
ShutDownState();
}
else
{
DTRACE1("MP3BroadcasterSession::GetBroadcastData() - read failed with err = %"_S32BITARG_"\n", theErr);
Assert(0);
ShutDownState();
}
return theErr;
}
// Send the broadcast data stream to all our clients
QTSS_Error MP3BroadcasterSession::SendClientsData()
{
QTSS_Error theErr = QTSS_NoErr;
if (fMP3ClientQueue == 0)
{
return theErr;
}
if (fDataBufferLen > 0)
{
// Make sure all queued client sessions have the
// current song name
PreflightClients();
// Send MP3 data to everything in our client queue.
theErr = fMP3ClientQueue->SendToAllClients(fBuffer, fDataBufferLen);
}
return theErr;
}
// Update all of our queued clients with the currently
// active song name
void MP3BroadcasterSession::PreflightClients()
{
if (fMP3ClientQueue == 0)
{
return;
}
if (fNewSongName)
{
OSMutexLocker locker(&fSongNameMutex);
// Copy new song name to all queue MP3ClientSessions.
// and attempt to resend data that was previously blocked.
fMP3ClientQueue->PreflightClients(GetSongName());
fNewSongName = false;
}
}
// Search through the audiocast headers and terminate them with a '\0'
// to mark the end.
void MP3BroadcasterSession::TerminateHeaders()
{
char *bp = fHeader;
char *end = fHeader + kHeaderBufferSize;
while (bp < end)
{
// scan for newlines until end of buffer.
if (*bp++ == '\n')
{
// if the next line after newline starts with
// 'x' or 'i' we assume it's an 'x-audiocast-xxx' line
// or an 'icy-xxxx' line, otherwise we're done.
if (*bp != 'x' && *bp != 'i')
break;
}
}
// make sure we didn't go past the end of buffer.
if (bp > end)
bp = end;
// terminate the buffer.
*bp = '\0';
}
// ****************************************************************************
// MP3ClientSession -- This is a class to hold all the MP3Client state info.
// ****************************************************************************
MP3ClientSession::MP3ClientSession()
{
Assert(0);
// this should never be called.
}
MP3ClientSession::MP3ClientSession( QTSS_RTSPSessionObject sess,
QTSS_StreamRef stream,
MP3BroadcasterSession* owner) :
MP3Session(sess, stream),
fBytesSent(0),
fCurrentBitRate(0),
fLastBitRateBytes(0),
fLastBitRateUpdateTime(0),
fConnectTime(0),
fSendCount(0),
fRetryCount(0),
fOwner(owner),
fNewSongName(false),
fWasBlocked(false),
fBlockTime(0),
fNeedsContentLength(false),
fWantsMetaData(true)
{
QTSS_Error err = QTSS_NoErr;
const char* kType = "MP3 Client";
::memset(fSongName, 0, kSongNameBufferSize);
::memset(fHeader, 0, kHeaderBufferSize);
::memset(fHostName, 0, kHostNameBufferSize);
::memset(fUserAgent, 0, kUserAgentBufferSize);
::memset(fRequestBuffer, 0, kRequestBufferSize);
ParseRequestParams(stream);
QTSS_LockObject(sServer);
UInt32 index;
UInt32 tempInt;
char tempBuffer[1024];
UInt32 len;
QTSS_CreateObjectValue(sServer, qtssSvrConnectedUsers, qtssConnectedUserObjectType, &index, &fQTSSObject);
QTSS_SetValue(fQTSSObject, qtssConnectionType, 0, kType, strlen(kType) + 1);
if (fHostName[0] != '\0')
QTSS_SetValue(fQTSSObject, qtssConnectionHostName, 0, fHostName, ::strlen(fHostName) + 1);
QTSS_SetValuePtr(fQTSSObject, qtssConnectionBytesSent, &fBytesSent, sizeof(fBytesSent));
QTSS_SetValuePtr(fQTSSObject, qtssConnectionCurrentBitRate, &fCurrentBitRate, sizeof(fBytesSent));
tempInt = 0;
QTSS_SetValue(fQTSSObject, qtssConnectionPacketLossPercent, 0, &tempInt, sizeof(tempInt));
fConnectTime = OS::Milliseconds();
QTSS_SetValue(fQTSSObject, qtssConnectionCreateTimeInMsec, 0, &fConnectTime, sizeof(fConnectTime));
len = sizeof(tempBuffer);
err = QTSS_GetValue(sess, qtssRTSPSesLocalAddrStr, 0, tempBuffer, &len);
if (err == QTSS_NoErr)
(void)QTSS_SetValue(fQTSSObject, qtssConnectionSessLocalAddrStr, 0, tempBuffer, len);
len = sizeof(tempBuffer);
err = QTSS_GetValue(sess, qtssRTSPSesRemoteAddrStr, 0, tempBuffer, &len);
if (err == QTSS_NoErr)
(void)QTSS_SetValue(fQTSSObject, qtssConnectionSessRemoteAddrStr, 0, tempBuffer, len);
char* mountpoint = owner->GetMountpoint();
(void)QTSS_SetValue(fQTSSObject, qtssConnectionMountPoint, 0, mountpoint, strlen(mountpoint) + 1);
QTSS_UnlockObject(sServer);
// Increment the server's MP3 session count.
IncrementMP3SessionCount();
}
MP3ClientSession::~MP3ClientSession()
{
SetState(MP3ClientSession::kClientShutDownState);
QTSS_Object sessionObject;
UInt32 len = sizeof(QTSS_Object);
QTSS_LockObject(sServer);
for (int x = 0; QTSS_GetValue(sServer, qtssSvrConnectedUsers, x, &sessionObject, &len) == QTSS_NoErr; x++)
{
Assert(sessionObject != NULL);
Assert(len == sizeof(QTSS_Object));
if (sessionObject == fQTSSObject)
{
(void)QTSS_RemoveValue(sServer, qtssSvrConnectedUsers, x);
break;
}
}
QTSS_UnlockObject(sServer);
// Decrease the server's count of MP3 sessions by one.
DecrementMP3SessionCount();
}
UInt8 MP3ClientSession::IsA() const
{
return kMP3ClientSessionType;
}
// Send the client it HTTP response
QTSS_Error MP3ClientSession::SendResponse()
{
QTSS_Error theErr = QTSS_NoErr;
UInt32 len = 0;
char buffer[512];
if (GetState() == MP3ClientSession::kClientInitState)
{
//
// Note: We don't Parse any client request HTTP parms here...
//
SetState(MP3ClientSession::kClientSendResponse);
theErr = QTSS_Write(GetStreamRef(), gClientAcceptHeader, ::strlen(gClientAcceptHeader), NULL, qtssWriteFlagsBufferData);
if (theErr != QTSS_NoErr)
{
SetState(MP3ClientSession::kClientShutDownState);
return theErr;
}
// x-audiocast header parameters are added and sent here
if (fWantsMetaData)
{
// add the "icy-metaint:xxxx" parameter to the header.
qtss_sprintf(buffer, "icy-metaint:%d\r\n", kClientMetaInt);
::strcat(fHeader, buffer);
}
len = ::strlen(fHeader);
if (len > 0)
{
theErr = QTSS_Write(GetStreamRef(), fHeader, len, NULL, qtssWriteFlagsBufferData);
if (theErr != QTSS_NoErr)
{
SetState(MP3ClientSession::kClientShutDownState);
return theErr;
}
}
// ...and send the phony content length or just a newline to mark end of header
if (WantsContentLength())
theErr = QTSS_Write(GetStreamRef(), kClientCLHeader, ::strlen(kClientCLHeader), NULL, qtssWriteFlagsBufferData);
else
theErr = QTSS_Write(GetStreamRef(), "\r\n", 2, NULL, qtssWriteFlagsBufferData);
if (theErr != QTSS_NoErr)
{
Assert(0);
SetState(MP3ClientSession::kClientShutDownState);
return theErr;
}
else
{
SetState(MP3ClientSession::kClientSendDataState);
}
//
// Make sure the client session remains in FilterRequest server role.
// We do this by scheduling ourself to be called back in
// kClientPollInterval milliseconds. When called back again we will
// continue to schedule ourself until we get into another
//
KeepSession((QTSS_RTSPRequestObject) GetStreamRef(), true);
theErr = QTSS_SetIdleTimer(kClientPollInterval);
if (theErr != QTSS_NoErr)
{
Assert(0);
SetState(MP3ClientSession::kClientShutDownState);
}
}
return theErr;
}
// Send the broadcast data stream to our client including meta-data
// if needed.
QTSS_Error MP3ClientSession::SendMP3Data(char* buffer, UInt32 bufferlen)
{
QTSS_Error theErr = QTSS_NoErr;
UInt32 sendcount;
UInt32 sendlen = 0L;
UInt32 remaininglen = 0L;
// get the broadcast sendcount into our local copy.
sendcount = fSendCount;
Assert(buffer != NULL);
// We only send data if we are int the kClientSendDataState.
if (GetState() != MP3ClientSession::kClientSendDataState)
{
return theErr;
}
// If we are using meta data then check and see if we hit our
// meta-data send interval.
// if so calculate the second write size.
if (fWantsMetaData && ((sendcount + bufferlen) > kClientMetaInt))
{
// sendlen is the amount of buffered data to send before meta data
// is interposed.
sendlen = (UInt32) kClientMetaInt - sendcount;
// remainglen is the reaming amount of buffered data to send
// after sending the meta-data.
remaininglen = bufferlen - sendlen;
}
else
{
// either it's not time to send metadata or we are not
// doing meta-data for this client.
sendlen = bufferlen;
remaininglen = 0L;
}
// Send up to sendlen bytes of data from the buffer.
QTSS_StreamRef stream = GetStreamRef();
Assert(stream != NULL);
Assert(sendlen <= bufferlen);
theErr = QTSS_Write(stream, buffer, sendlen, NULL, qtssWriteFlagsBufferData);
sendcount += sendlen;
if (theErr != QTSS_NoErr)
{
SetState(MP3ClientSession::kClientShutDownState);
return theErr;
}
// If there is meta-data this time then send it followed by remaininglen bytes
// from the buffer.
if (remaininglen != 0L)
{
// send the meta-data
theErr = SendMetaData();
if (theErr != QTSS_NoErr)
{
SetState(MP3ClientSession::kClientShutDownState);
return theErr;
}
// sendcount must be zero after sending meta-data.
sendcount = 0;
// send the remainder of the data buffer.
theErr = QTSS_Write(GetStreamRef(), buffer+sendlen, remaininglen, NULL, qtssWriteFlagsBufferData);
sendcount += remaininglen;
if (theErr != QTSS_NoErr)
{
SetState(MP3ClientSession::kClientShutDownState);
return theErr;
}
}
fSendCount = sendcount;
// Flush to perform the actuall send to the client.
theErr = QTSS_Flush(GetStreamRef());
if (theErr == QTSS_WouldBlock)
{
// the client is flow controlled.
SInt64 curTime = OS::Milliseconds();
fWasBlocked = true;
QTSS_RequestEvent(GetStreamRef(), QTSS_WriteableEvent);
DTRACE2("Got blocked at time %qd, numRetries = %"_S32BITARG_"\n", curTime, fRetryCount);
if (fBlockTime == 0)
fBlockTime = curTime;
fRetryCount++;
if (curTime - fBlockTime > sMaxFlowControlTimeInMSec)
{
SetResult(453);
DTRACE1("MP3ClientSession::SendMP3Data - too many tries. Terminating client session = %"_S32BITARG_"\n", GetSessionID());
SetState(MP3ClientSession::kClientShutDownState);
}
}
else if (theErr != QTSS_NoErr)
{
DTRACE1("MP3ClientSession::SendMP3Data - Terminating client session = %"_S32BITARG_"\n", GetSessionID());
SetState(MP3ClientSession::kClientShutDownState);
}
else
{
// we successfully sent data to the client.
// fSendCount only gets incremented if the QTSS_Flush() was successful
SetResult(200);
fWasBlocked = false;
fBlockTime = 0;
fRetryCount = 0;
}
fBytesSent += bufferlen;
// Increase the server's total MP3 byte count attribute
IncrementTotalMP3Bytes(bufferlen);
UpdateBitRateInternal(OS::Milliseconds());
return theErr;
}
// Retry to send previously blocked data.
QTSS_Error MP3ClientSession::RetrySendData()
{
QTSS_Error theErr = QTSS_NoErr;
// Flush to perform the actuall send to the client.
theErr = QTSS_Flush(GetStreamRef());
if (theErr == QTSS_WouldBlock)
{
// the client is still flow controlled.
fWasBlocked = true;
QTSS_RequestEvent(GetStreamRef(), QTSS_WriteableEvent);
return theErr;
}
else if (theErr != QTSS_NoErr)
{
// some error other than QTSS_WouldBlock occured.
SetResult(454);
SetState(MP3ClientSession::kClientShutDownState);
return theErr;
}
else
{
// we successfully sent data to the client.
// cancel future retries
fWasBlocked = false;
SetResult(200);
fRetryCount = 0;
}
return QTSS_NoErr;
}
// Send the broadcast meta data stream to our client
QTSS_Error MP3ClientSession::SendMetaData()
{
QTSS_Error theErr = QTSS_NoErr;
char buffer[1024];
UInt16 bufferlen;
// Don't allow an update of the song name while we're sending
// it to the client.
OSMutexLocker locker(&fSongNameMutex);
// format the meta-data
::memset(buffer, 0, 1024);
// Make sure we have something to send
if (!fNewSongName || fSongName[0] == '\0')
{
// setup to write a single NULL byte.
bufferlen = 1;
}
else
{
// Setup to write meta-data + pad NULL bytes.
// first value in the buffer is the number of 16 byte chunks
// to send.
char tmp[512];
qtss_sprintf(tmp, "StreamTitle='%s';StreamUrl='';", fSongName);
bufferlen = ::strlen(tmp);
buffer[0] = (unsigned char)((bufferlen/16) + 1);
::strcat(buffer, tmp);
bufferlen = (buffer[0]*16) + 1;
}
// send the meta-data
theErr = QTSS_Write(GetStreamRef(), buffer, bufferlen, NULL, qtssWriteFlagsBufferData);
if (theErr == QTSS_NoErr)
{
// meta data was written successfully
fNewSongName = false;
}
fBytesSent += bufferlen;
return theErr;
}
// Set the x-audiocast headers of the currently broadcasting client.
// Lines that end in '\n' will have a '\r' prepended.
void MP3ClientSession::SetHeader(char* header)
{
char *bp = header;
char *cp = fHeader;
char *end = fHeader + kHeaderBufferSize;
while (cp < end)
{
if (*bp == '\0')
{
// end of header buffer string
break;
}
// scan for newlines .
if (*bp == '\n')
{
// add a CR before the newline
*cp = '\r';
cp++;
*cp = '\n';
}
else
{
// otherwise just copy the byte
*cp = *bp;
}
cp++;
bp++;
}
// make sure we didn't go past the end of buffer.
if (cp > end)
cp = end;
// terminate the buffer.
*cp = '\0';
}
// Set the song name of the currently broadcasting client.
void MP3ClientSession::SetSongName(char* sn)
{
if (sn != NULL && *sn != '\0')
{
// This mutex is to guard against updating a
// song name string while we're trying to send it
// to a remote client.
OSMutexLocker locker(&fSongNameMutex);
::strcpy(fSongName, sn);
fNewSongName = true;
}
}
void MP3ClientSession::UpdateBitRateInternal(const SInt64& curTime)
{
if (curTime < fLastBitRateUpdateTime + 10000)
return; // update the bitrate every 10 seconds
UInt32 bitsInInterval = (fBytesSent - fLastBitRateBytes) * 8;
SInt64 updateTime = (curTime - fLastBitRateUpdateTime) / 1000;
if (updateTime > 0) // leave Bit Rate the same if updateTime is 0 also don't divide by 0.
fCurrentBitRate = (UInt32) ( bitsInInterval / updateTime );
fLastBitRateBytes = fBytesSent;
fLastBitRateUpdateTime = curTime;
}
void MP3ClientSession::ParseRequestParams(QTSS_StreamRef stream)
{
QTSS_Error err = QTSS_NoErr;
char *bp = NULL;
unsigned int idx;
// get a copy of the first line of the HTTP request
QTSS_RTSPRequestObject theRequest = (QTSS_RTSPRequestObject) stream;
StrPtrLen theFullRequest;
if (theRequest != NULL)
err = QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len);
else
err = QTSS_BadArgument;
if (err == QTSS_NoErr)
{
// make sure we don't overflow our buffer
if (theFullRequest.Len >= kRequestBufferSize)
theFullRequest.Len = kRequestBufferSize-1;
// copy the request into our private class buffer.
::memcpy(fRequestBuffer, theFullRequest.Ptr, theFullRequest.Len);
// first see if the client wants to recieve meta-data
fWantsMetaData = NeedsMetaData(theFullRequest);
// Do we need to send a phony HTTP content length field?
if (::strstr(fRequestBuffer, "MSIE") != NULL)
fNeedsContentLength = true;
else if (::strstr(fRequestBuffer, "RMA/1.0") != NULL)
fNeedsContentLength = true;
else if (::strstr(fRequestBuffer, "NSPlayer") != NULL)
fNeedsContentLength = true;
else
fNeedsContentLength = false;
// Get a copy ot the requestor's host name if it exists
bp = ::strstr(fRequestBuffer, "Host:");
if (bp != NULL)
{
bp += ::strlen("Host:");
while (*bp == ' ')
bp++;
idx = 0;
while (bp[idx])
{
if (idx >= kHostNameBufferSize-1)
break;
if (bp[idx] == '\n' || bp[idx] == '\r')
break;
fHostName[idx] = bp[idx];
idx++;
}
}
// Get a copy ot the requesting User Agent's name if it exists
bp = ::strstr(fRequestBuffer, "User-Agent:");
if (bp != NULL)
{
bp += ::strlen("User-Agent:");
while (*bp == ' ')
bp++;
idx = 0;
while (bp[idx])
{
if (idx >= kUserAgentBufferSize-1)
break;
if (bp[idx] == '\n' || bp[idx] == '\r')
break;
fUserAgent[idx] = bp[idx];
idx++;
}
}
// Now that we're done parsing the request we will
// terminate first line of the request with '\0'.
for (idx=0; idx < theFullRequest.Len; idx++)
{
if (fRequestBuffer[idx] == '\r' || fRequestBuffer[idx] == '\n')
{
fRequestBuffer[idx] = '\0';
break;
}
}
}
else
{
// dummy value so that we have something to log
::strcpy(fRequestBuffer, "GET /* HTTP/1.0");
}
}
// ****************************************************************************
// MP3SessionRef -- This class is just a wrapper class for handling the
// mapping of RTSP Session refs to the corresponding MP3 Session class refs.
// It will be the an element of our lookup hash table.
// ****************************************************************************
MP3SessionRef::MP3SessionRef(MP3Session* mp3Session) :
fNextHashEntry(NULL),
fHashValue(0),
fMP3Session(mp3Session)
{
}
MP3SessionRef::~MP3SessionRef()
{
}
// ****************************************************************************
// MP3SessionRefKey -- This class is used to generate hash keys for looking
// up values in our MP3SessionTable. The Hash key is just the RTSP Session
// reference value.
// ****************************************************************************
MP3SessionRefKey::MP3SessionRefKey(MP3SessionRef* mp3SessRef) :
fKeyValue(mp3SessRef)
{
fHashValue = 0L;
if (fKeyValue != NULL)
{
fMP3Session = fKeyValue->GetMP3Session();
if (fMP3Session != NULL)
fHashValue = (PointerSizedInt)fMP3Session->GetSession();
}
else
{
fMP3Session = NULL;
}
}
MP3SessionRefKey::MP3SessionRefKey(QTSS_RTSPSessionObject rtspSessRef) :
fKeyValue(NULL),
fHashValue((PointerSizedInt)rtspSessRef),
fMP3Session(NULL)
{
}
MP3SessionRefKey::~MP3SessionRefKey()
{
}
// ****************************************************************************
// MP3SessionTable -- This class provides a way to map RTSP Session references
// into the corresponding MP3Session class instances if any.
// ****************************************************************************
MP3SessionTable::MP3SessionTable(UInt32 tableSize) :
fTable(tableSize),
fMutex()
{
}
MP3SessionTable::~MP3SessionTable()
{
}
// Attempt to add a new MP3Session ref to the table's map.
// returns true on success or false if it fails.
Bool16 MP3SessionTable::RegisterSession(MP3Session* session)
{
// sanity check
if (session == NULL)
{
Assert(0);
return false;
}
// construct a new entry for the table.
MP3SessionRef* newEntry = NEW MP3SessionRef(session);
if (newEntry == NULL)
{
return false;
}
// generate it's hash key.
MP3SessionRefKey key(newEntry);
// Lock the table while we try and add the new entry.
OSMutexLocker locker(&fMutex);
// check for a duplicate entry;
MP3SessionRef* duplicateRef = fTable.Map(&key);
if (duplicateRef != NULL)
{
delete newEntry;
Assert(0);
return false;
}
// add the new entry to the table.
fTable.Add(newEntry);
return true;
}
// Given an QTSS_RTSPSessionObject resolve it into a MP3Session class
// reference. Returns NULL if there's none in out map.
MP3Session* MP3SessionTable::Resolve(QTSS_RTSPSessionObject rtspSession)
{
// sanity check
if (rtspSession == NULL)
{
Assert(0);
return NULL;
}
// generate it's hash key.
MP3SessionRefKey key(rtspSession);
// Lock the table while we do our lookup.
OSMutexLocker locker(&fMutex);
// Look for the entry in the table's map;
MP3SessionRef* entry = fTable.Map(&key);
if (entry != NULL)
{
// We found it. return the entry's MP3Session ref.
return entry->GetMP3Session();
}
// it's not in the table.
return NULL;
}
// Attempt to remove a MP3Session ref from the table's map.
// returns true on success or false if it fails.
Bool16 MP3SessionTable::UnRegisterSession(MP3Session* session)
{
// sanity check
if (session == NULL)
{
Assert(0);
return false;
}
// generate it's hash key.
MP3SessionRef entryRef(session);
MP3SessionRefKey key(&entryRef);
// Lock the table while we try and remove the entry.
OSMutexLocker locker(&fMutex);
// look for the entry in the table's map;
MP3SessionRef* entry = fTable.Map(&key);
if (entry != NULL)
{
// We found it. Remove it from the table and delete the
// reference instance created by RegisterSession().
fTable.Remove(entry);
delete entry;
return true;
}
// it's not in the table.
return false;
}
// ****************************************************************************
// MP3BroadcasterQueue -- This is a class manages a queue of MP3Broadcaster
// objects.
// ****************************************************************************
MP3BroadcasterQueue::MP3BroadcasterQueue()
{
}
MP3BroadcasterQueue::~MP3BroadcasterQueue()
{
}
// Create a new MP3BroadcasterSession object and queue it.
QTSS_Error MP3BroadcasterQueue::CreateBroadcaster(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream, StrPtrLen& mountpt)
{
QTSS_Error theErr = QTSS_NoErr;
UInt32 uSessID = 0L;
if (sess == 0 || stream == 0)
{
return QTSS_BadArgument;
}
if ((fQueue.GetLength() > 0) && (FindByMountPoint(mountpt) != NULL))
{
theErr = QTSS_Write(stream, kSourceReject, ::strlen(kSourceReject), NULL, qtssWriteFlagsBufferData);
if (theErr != QTSS_NoErr)
{
return theErr;
}
theErr = QTSS_Flush(stream);
DTRACE("MP3BroadcasterQueue::CreateBroadcaster - duplicate mountpoint\n");
return QTSS_BadArgument;
}
if ( (fQueue.GetLength() > 0) && InQueue(sess) )
{
theErr = QTSS_Write(stream, kSourceBadPassword, ::strlen(kSourceBadPassword), NULL, qtssWriteFlagsBufferData);
if (theErr != QTSS_NoErr)
{
return theErr;
}
theErr = QTSS_Flush(stream);
DTRACE("MP3BroadcasterQueue::CreateBroadcaster - duplicate session\n");
return QTSS_BadArgument;
}
MP3BroadcasterSession* broadcaster = NEW MP3BroadcasterSession(sess, stream);
if (broadcaster == NULL)
{
return QTSS_NotEnoughSpace;
}
uSessID = GetRTSPSessionID(sess);
broadcaster->SetSessionID(uSessID);
broadcaster->SetMountpoint(mountpt);
broadcaster->AcceptPasswordState();
OSQueueElem* elem = NEW OSQueueElem(broadcaster);
if (elem != 0)
{
OSMutexLocker locker(&fMutex);
fQueue.EnQueue(elem);
DTRACE1("MP3BroadcasterQueue::CreateBroadcaster() Session(%"_S32BITARG_") broadcaster added!\n", uSessID);
// send the "OK" back to the broadcaster
theErr = broadcaster->SendOKResponse();
// If we failed to send the "OK" probably becuase of flow control
// the only thing to do is to fail the attempt.
if (theErr != QTSS_NoErr)
broadcaster->ShutDownState();
}
else
{
delete broadcaster;
broadcaster = NULL;
theErr = QTSS_NotEnoughSpace;
}
return theErr;
}
// Dequeue a MP3BroadcasterSession object and delete it.
QTSS_Error MP3BroadcasterQueue::RemoveBroadcaster(QTSS_RTSPSessionObject sess)
{
UInt32 uSessID = 0L;
if (sess == 0)
{
return QTSS_BadArgument;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject();
if ((current != 0) && (current->GetSession() == sess))
{
elem->Remove();
uSessID = current->GetSessionID();
DTRACE1("MP3BroadcasterQueue::RemoveBroadcaster() Session(%"_S32BITARG_") broadcaster removed!\n", uSessID);
delete elem;
delete current;
return QTSS_NoErr;
}
iter.Next();
}
return QTSS_BadArgument;
}
// Find and dequeue a MP3ClientSession object and delete it.
QTSS_Error MP3BroadcasterQueue::RemoveClient(QTSS_RTSPSessionObject sess)
{
if (sess == 0)
{
return QTSS_BadArgument;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject();
if ((current != 0) && (current->IsMyClient(sess)))
{
return current->RemoveClient(sess);
}
iter.Next();
}
return QTSS_BadArgument;
}
// See if a particular Broadcaster RTSP session is in our queue.
Bool16 MP3BroadcasterQueue::InQueue(QTSS_RTSPSessionObject sess)
{
if (sess == 0)
{
return false;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject();
if ((current != 0) && (current->GetSession() == sess))
{
return true;
}
iter.Next();
}
return false;
}
// See if a particular Client RTSP session is in any of our broadcasters' queues.
Bool16 MP3BroadcasterQueue::IsActiveClient(QTSS_RTSPSessionObject sess)
{
if (sess == 0)
{
return false;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject();
if ((current != 0) && (current->IsMyClient(sess)))
{
return true;
}
iter.Next();
}
return false;
}
// Find a MP3BroadcasterSession in the queue by it's mountpoint.
MP3BroadcasterSession* MP3BroadcasterQueue::FindByMountPoint(char* mountpoint)
{
if (mountpoint == NULL || mountpoint[0] == '\0')
{
return NULL;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject();
if ((current != 0) && (current->MountpointEqual(mountpoint)))
{
return current;
}
iter.Next();
}
return NULL;
}
// Find a MP3BroadcasterSession in the queue by it's mountpoint.
MP3BroadcasterSession* MP3BroadcasterQueue::FindByMountPoint(StrPtrLen& mountpoint)
{
if (mountpoint.Ptr == NULL || mountpoint.Len == 0)
{
return NULL;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject();
if ((current != 0) && (current->MountpointEqual(mountpoint)))
{
return current;
}
iter.Next();
}
return NULL;
}
// Find a MP3BroadcasterSession in the queue by it's RTSP Session.
MP3BroadcasterSession* MP3BroadcasterQueue::FindBySession(QTSS_RTSPSessionObject sess)
{
if (sess == 0)
{
return NULL;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject();
if ((current != 0) && (current->GetSession() == sess))
{
return current;
}
iter.Next();
}
return NULL;
}
// Mark all broadcast sessions to be terminated when they are next called.
void MP3BroadcasterQueue::TerminateAllBroadcastSessions()
{
OSMutexLocker locker(&fMutex);
OSQueueElem* elem = NULL;
OSQueueIter iter(&fQueue);
while ((elem = iter.GetCurrent()) != NULL)
{
MP3BroadcasterSession* current = (MP3BroadcasterSession*) elem->GetEnclosingObject();
if (current != NULL)
{
current->SetState(MP3BroadcasterSession::kBroadcasterShutDownState);
}
iter.Next();
}
}
// ****************************************************************************
// MP3ClientQueue -- This is a class maintains a queue of MP3Client objects.
// ****************************************************************************
MP3ClientQueue::MP3ClientQueue()
{
}
MP3ClientQueue::~MP3ClientQueue()
{
// Make sure all queued client sessions are marked for termination since
// since they are now orphaned and no longer have an owner.
TerminateClients();
}
// Create a new MP3ClientSession object and queue it.
QTSS_Error MP3ClientQueue::AddClient(QTSS_RTSPSessionObject sess, QTSS_StreamRef stream,
MP3BroadcasterSession* owner)
{
QTSS_Error theErr = QTSS_NoErr;
UInt32 uSessID = 0L;
if (sess == 0 || stream == 0)
{
return QTSS_BadArgument;
}
if ( InQueue (sess) )
{
return QTSS_NoErr;
}
MP3ClientSession* client = NEW MP3ClientSession(sess, stream, owner);
if (client == 0)
{
return QTSS_NotEnoughSpace;
}
uSessID = GetRTSPSessionID(sess);
client->SetSessionID(uSessID);
if (owner != NULL)
{
// Get a copy of the x-audiocast headers from the
// broadcaster's cached copy.
client->SetHeader(owner->GetHeader());
// Make sure we have a song name also
client->SetSongName(owner->GetSongName());
}
OSQueueElem* elem = NEW OSQueueElem(client);
if (elem != 0)
{
OSMutexLocker locker(&fMutex);
fQueue.EnQueue(elem);
DTRACE1("MP3ClientQueue::AddClient() Session(%"_S32BITARG_") client added!\n", uSessID);
// send the "OK" back to the broadcaster
theErr = client->SendResponse();
}
else
{
delete client;
theErr = QTSS_NotEnoughSpace;
}
return theErr;
}
// Dequeue a MP3ClientSession object and delete it.
QTSS_Error MP3ClientQueue::RemoveClient(QTSS_RTSPSessionObject sess)
{
UInt32 uSessID = 0L;
if (sess == 0)
{
return QTSS_BadArgument;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject();
if ((curClient != 0) && (curClient->GetSession() == sess))
{
elem->Remove();
uSessID = curClient->GetSessionID();
DTRACE1("MP3ClientQueue::RemoveClient() Session(%"_S32BITARG_") client removed!\n", uSessID);
delete elem;
delete curClient;
return QTSS_NoErr;
}
iter.Next();
}
return QTSS_BadArgument;
}
// See if a particular Client RTSP session is in our queue.
Bool16 MP3ClientQueue::InQueue(QTSS_RTSPSessionObject sess)
{
if (sess == 0)
{
return false;
}
OSMutexLocker locker(&fMutex);
OSQueueIter iter(&fQueue);
OSQueueElem* elem = NULL;
while ((elem = iter.GetCurrent()) != 0)
{
MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject();
if ((curClient != 0) && (curClient->GetSession() == sess))
{
return true;
}
iter.Next();
}
return false;
}
// Send MP3 data to all queued MP3ClientSession objects.
QTSS_Error MP3ClientQueue::SendToAllClients(char* buffer, UInt32 bufferlen)
{
QTSS_Error theErr = QTSS_NoErr;
OSMutexLocker locker(&fMutex);
OSQueueElem* elem = NULL;
OSQueueIter iter(&fQueue);
while ((elem = iter.GetCurrent()) != NULL)
{
MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject();
if (curClient != NULL)
{
QTSS_Error err;
err = curClient->SendMP3Data(buffer, bufferlen);
if (err != QTSS_NoErr)
theErr = err;
}
iter.Next();
}
return theErr;
}
// Update the current song name of queued MP3ClientSession objects.
// and attempt to resend data thatwas previously blocked.
void MP3ClientQueue::PreflightClients(char* sn)
{
QTSS_Error theErr = QTSS_NoErr;
OSMutexLocker locker(&fMutex);
OSQueueElem* elem = NULL;
OSQueueIter iter(&fQueue);
while ((elem = iter.GetCurrent()) != NULL)
{
MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject();
if (curClient != NULL)
{
if (curClient->WasBlocked())
{
theErr = curClient->RetrySendData();
}
curClient->SetSongName(sn);
}
iter.Next();
}
}
// Mark all clients to be terminated when they are next polled.
// This should be called by the MP3BroadcasterSession whenever it dies
// with active client else we'll have a nasty memory leak and clients
// will pause until they timeout.
void MP3ClientQueue::TerminateClients()
{
OSMutexLocker locker(&fMutex);
OSQueueElem* elem = NULL;
OSQueueIter iter(&fQueue);
while ((elem = iter.GetCurrent()) != NULL)
{
MP3ClientSession* curClient = (MP3ClientSession*) elem->GetEnclosingObject();
if (curClient != NULL)
{
curClient->SetOwner(NULL);
curClient->SetState(MP3ClientSession::kClientShutDownState);
}
iter.Next();
}
}
// ****************************************************************************
// MODULE FUNCTION IMPLEMENTATIONS
// ****************************************************************************
#if DEBUG_MP3STREAMING_MODULE
void PrintStringBuffer(StrPtrLen& stringBuffer)
{
int len = (int)stringBuffer.Len;
char localBuffer[256];
if (len > 255)
len = 255;
::memcpy(localBuffer, stringBuffer.Ptr, len);
localBuffer[len] = '\0';
qtss_printf("%s", localBuffer);
}
#endif
inline void KeepSession(QTSS_RTSPRequestObject theRequest,Bool16 keep)
{
(void)QTSS_SetValue(theRequest, qtssRTSPReqRespKeepAlive, 0, &keep, sizeof(keep));
}
QTSS_Error QTSSMP3StreamingModule_Main(void* inPrivateArgs)
{
return _stublibrary_main(inPrivateArgs, QTSSMP3StreamingModuleDispatch);
}
// QTSSMP3StreamingModuleDispatch - Dispatch all the API roles that this module handles
QTSS_Error QTSSMP3StreamingModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams)
{
switch (inRole)
{
case QTSS_Register_Role:
return Register(&inParams->regParams);
case QTSS_Initialize_Role:
return Initialize(&inParams->initParams);
case QTSS_RTSPFilter_Role:
return FilterRequest(&inParams->rtspFilterParams);
case QTSS_RereadPrefs_Role:
return RereadPrefs();
case QTSS_RTSPSessionClosing_Role:
return SessionClosing(&inParams->rtspSessionClosingParams);
case QTSS_StateChange_Role:
return StateChange(&inParams->stateChangeParams);
case QTSS_Shutdown_Role:
return Shutdown();
}
return QTSS_NoErr;
}
// Register - register all the API roles that this module handles
QTSS_Error Register(QTSS_Register_Params* inParams)
{
// Do role setup
(void)QTSS_AddRole(QTSS_Initialize_Role);
(void)QTSS_AddRole(QTSS_RereadPrefs_Role);
(void)QTSS_AddRole(QTSS_RTSPFilter_Role);
(void)QTSS_AddRole(QTSS_RTSPSessionClosing_Role);
(void)QTSS_AddRole(QTSS_StateChange_Role);
(void)QTSS_AddRole(QTSS_Shutdown_Role);
// Tell the server our name!
static char* sModuleName = "QTSSMP3StreamingModule";
::strcpy(inParams->outModuleName, sModuleName);
return QTSS_NoErr;
}
QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
{
// Setup module utils
QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);
sServer = inParams->inServer;
sServerPrefs = inParams->inPrefs;
sPrefs = QTSSModuleUtils::GetModulePrefsObject(inParams->inModule);
RereadPrefs();
sOSBufferPoolPtr = NEW OSBufferPool(sBroadcastBufferSize);
sLogMutex = NEW OSMutex();
sAtomicMutex = NEW OSMutex();
// pre-format the standard HTTP reply headers
qtss_sprintf(gClientAcceptHeader, kClientAcceptHeader, kVersionString, kBuildString);
qtss_sprintf(gM3UReplyHeader, kM3UReplyHeader, kVersionString, kBuildString);
sMP3AccessLog = NEW QTSSMP3AccessLog();
if (sMP3AccessLog != NULL && sLogEnabled)
sMP3AccessLog->EnableLog();
WriteStartupMessage();
return QTSS_NoErr;
}
// RereadPrefs - refread our prefs from the prefs file.
QTSS_Error RereadPrefs()
{
delete [] sDefaultLogDir;
(void)QTSS_GetValueAsString(sServerPrefs, qtssPrefsErrorLogDir, 0, &sDefaultLogDir);
delete [] sLogName;
sLogName = QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_request_logfile_name", sDefaultLogName);
delete [] sLogDir;
sLogDir = QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_request_logfile_dir", sDefaultLogDir);
QTSSModuleUtils::GetAttribute(sPrefs, "mp3_streaming_enabled", qtssAttrDataTypeBool16,
&sMP3StreamingEnabled, &sDefaultMP3StreamingEnabled, sizeof(sMP3StreamingEnabled));
delete [] sBroadcastPassword;
sBroadcastPassword = QTSSModuleUtils::GetStringAttribute(sPrefs, "mp3_broadcast_password", sDefaultBroadcastPassword);
// if broadcast password is blank or empty don't allow MP3 streaming.
if (sBroadcastPassword == NULL || *sBroadcastPassword == '\0' || *sBroadcastPassword == ' ')
{
sMP3StreamingEnabled = false;
}
QTSSModuleUtils::GetAttribute(sPrefs, "mp3_broadcast_buffer_size", qtssAttrDataTypeUInt32,
&sBroadcastBufferSize, &sDefaultBroadcastBufferSize, sizeof(sBroadcastBufferSize));
QTSSModuleUtils::GetAttribute(sPrefs, "mp3_max_flow_control_time", qtssAttrDataTypeSInt32,
&sMaxFlowControlTimeInMSec, &sDefaultFlowControlTimeInMSec, sizeof(sMaxFlowControlTimeInMSec));
QTSSModuleUtils::GetAttribute(sPrefs, "mp3_request_logging", qtssAttrDataTypeBool16,
&sLogEnabled, &sDefaultLogEnabled, sizeof(sLogEnabled));
QTSSModuleUtils::GetAttribute(sPrefs, "mp3_request_logfile_size", qtssAttrDataTypeUInt32,
&sMaxLogBytes, &sDefaultMaxLogBytes, sizeof(sMaxLogBytes));
QTSSModuleUtils::GetAttribute(sPrefs, "mp3_request_logfile_interval", qtssAttrDataTypeUInt32,
&sRollInterval, &sDefaultRollInterval, sizeof(sRollInterval));
QTSSModuleUtils::GetAttribute(sPrefs, "mp3_request_logtime_in_gmt", qtssAttrDataTypeBool16,
&sLogTimeInGMT, &sDefaultLogTimeInGMT, sizeof(sLogTimeInGMT));
UInt32 len = sizeof(SInt32);
(void) QTSS_GetValue(sServerPrefs, qtssPrefsMaximumConnections, 0, (void*)&sMaximumConnections, &len);
(void) QTSS_GetValue(sServerPrefs, qtssPrefsMaximumBandwidth, 0, (void*)&sMaximumBandwidth, &len);
// handle changing the sLogEnabled state of the access log.
if (sMP3AccessLog != NULL)
{
if (sLogEnabled)
{
if (!sMP3AccessLog->IsLogEnabled())
sMP3AccessLog->EnableLog();
}
else
{
if (sMP3AccessLog->IsLogEnabled())
sMP3AccessLog->CloseLog();
}
}
return QTSS_NoErr;
}
// SessionClosing - clean up broadcaster/client connections.
QTSS_Error SessionClosing(QTSS_RTSPSession_Params* inParams)
{
Assert(inParams != NULL);
#if DEBUG_MP3STREAMING_MODULE
UInt32 uSessID = 0L;
uSessID = GetRTSPSessionID(inParams->inRTSPSession);
DTRACE1("Closing SessionID = %"_S32BITARG_"\n", uSessID);
#endif
MP3Session* mp3sess = sMP3SessionTable.Resolve(inParams->inRTSPSession);
// If Resolve() returns a non-NULL value then we are closing
// the RTSP session related to either a reflected MP3 broadcast
// session or we are closing an RTSP session related to an
// MP3 client session.
//
// If Resolve() returns NULL this RTSP session does not belong to
// this module and we just ignore it by returning QTSS_NoErr.
if (mp3sess != NULL)
{
if (mp3sess->IsA() == kMP3BroadcasterSessionType)
{
// Remove the MP3BroadcasterSession from the global
// server queue and delete its instance.
return sMP3BroadcasterQueue.RemoveBroadcaster(inParams->inRTSPSession);
}
else if (mp3sess->IsA() == kMP3ClientSessionType)
{
MP3ClientSession* client = (MP3ClientSession*)mp3sess;
MP3BroadcasterSession* owner = client->GetOwner();
if (sLogEnabled)
LogRequest(inParams->inRTSPSession, client);
if (owner != NULL)
{
// Let the the owner, a MP3BroadcasterSession,
// remove the client from its queue and delete it.
return owner->RemoveClient(inParams->inRTSPSession);
}
else
{
// otherwise, we no longer have an owner for this
// client session and we just delete the instance.
delete client;
return QTSS_NoErr;
}
}
}
return QTSS_NoErr;
}
QTSS_Error Shutdown()
{
WriteShutdownMessage();
return QTSS_NoErr;
}
QTSS_Error StateChange(QTSS_StateChange_Params* stateChangeParams)
{
if (stateChangeParams->inNewState == qtssIdleState)
{
WriteShutdownMessage();
sMP3BroadcasterQueue.TerminateAllBroadcastSessions();
}
else
{
WriteStartupMessage();
}
return QTSS_NoErr;
}
// IncrementMP3SessionCount - This increments the MP3 session counter in the server's attributes
void IncrementMP3SessionCount()
{
QTSS_Error err = QTSS_NoErr;
// Increment the server's MP3 session count.
UInt32* ptrValue = NULL;
UInt32 valueLen = sizeof(ptrValue);
OSMutexLocker locker(sAtomicMutex);
err = QTSS_GetValuePtr(sServer, qtssMP3SvrCurConn, 0, (void**)&ptrValue, &valueLen);
if ((err == QTSS_NoErr) && (ptrValue != NULL))
{
*ptrValue += 1;
}
else
{
DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrCurConn failed with err = %"_S32BITARG_"\n", (SInt32)err);
}
// bump the total sessions count up also
err = QTSS_GetValuePtr(sServer, qtssMP3SvrTotalConn, 0, (void**)&ptrValue, &valueLen);
if ((err == QTSS_NoErr) && (ptrValue != NULL))
{
*ptrValue += 1;
}
else
{
DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrTotalConn failed with err = %"_S32BITARG_"\n", (SInt32)err);
}
}
// DecrementMP3SessionCount - This decrements the MP3 session counter in the server's attributes
void DecrementMP3SessionCount()
{
QTSS_Error err = QTSS_NoErr;
// Decrement the server's MP3 session count.
UInt32* ptrValue = NULL;
UInt32 valueLen = sizeof(ptrValue);
err = QTSS_GetValuePtr(sServer, qtssMP3SvrCurConn, 0, (void**)&ptrValue, &valueLen);
if ((err == QTSS_NoErr) && (ptrValue != NULL))
{
OSMutexLocker locker(sAtomicMutex);
*ptrValue -= 1;
}
else
{
DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrCurConn failed with err = %"_S32BITARG_"\n", (SInt32)err);
}
}
// IncrementTotalMP3Bytes - This increments the MP3 byte counter in the server's attributes
void IncrementTotalMP3Bytes(UInt32 bytes)
{
QTSS_Error err = QTSS_NoErr;
// Increment the server's MP3 total byte count.
UInt64* ptrValue = NULL;
UInt32 valueLen = sizeof(ptrValue);
err = QTSS_GetValuePtr(sServer, qtssMP3SvrTotalBytes, 0, (void**)&ptrValue, &valueLen);
if ((err == QTSS_NoErr) && (ptrValue != NULL))
{
OSMutexLocker locker(sAtomicMutex);
*ptrValue += bytes;
}
else
{
DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrTotalBytes failed with err = %"_S32BITARG_"\n", (SInt32)err);
}
}
// CheckBandwidth - Check and see if we can handle any more bandwidth.
Bool16 CheckBandwidth(SInt32 bandwidth)
{
QTSS_Error err = QTSS_NoErr;
// Get our current bandwidth and check against server's maximum.
SInt32* ptrValue = NULL;
UInt32 valueLen = sizeof(ptrValue);
err = QTSS_GetValuePtr(sServer, qtssMP3SvrCurBandwidth, 0, (void**)&ptrValue, &valueLen);
if ((err == QTSS_NoErr) && (ptrValue != NULL))
{
OSMutexLocker locker(sAtomicMutex);
if (((*ptrValue + bandwidth)/1000) <= sMaximumBandwidth)
return true;
}
else
{
DTRACE1("QTSS_GetValuePtr() for qtssMP3SvrCurBandwidth failed with err = %"_S32BITARG_"\n", (SInt32)err);
}
return false;
}
// GetRTSPSessionID - returns the RTSP Session ID for the given RTSPSession object.
// return 0 on failure.
UInt32 GetRTSPSessionID(QTSS_RTSPSessionObject session)
{
UInt32 uSessID = 0L;
UInt32 paramLen = sizeof(uSessID);
QTSS_Error err = QTSS_GetValue(session, qtssRTSPSesID, 0, (void*)&uSessID, &paramLen);
if (err != QTSS_NoErr)
{
uSessID = 0L;
}
return uSessID;
}
// IsActiveBroadcastSession - This is true when the specified session belongs to
// a current broadcast session. We use this to let us know if we were called back
// in our FilterRequest() role because a read or write was blocked previously.
inline Bool16 IsActiveBroadcastSession(QTSS_RTSPSessionObject session)
{
return (FindBroadcastSession(session) != NULL);
}
// FindBroadcastSession - Given a RTSP session find the MP3BroadcasterSession
// object that handles this session. returns NULL pointer if none.
MP3BroadcasterSession* FindBroadcastSession(QTSS_RTSPSessionObject session)
{
MP3Session* mp3sess = sMP3SessionTable.Resolve(session);
if (mp3sess != NULL && mp3sess->IsA() == kMP3BroadcasterSessionType)
return (MP3BroadcasterSession*) mp3sess;
else
return NULL;
}
// This determines if an incoming request is an broadcaster sending
// us his password. It will be in the format of:
// "SOURCE <password> <mountpoint>\n".
Bool16 IsBroadcastPassword(StrPtrLen& theRequest)
{
if (IsShoutcastPassword(theRequest))
return true;
StrPtrLen token = theRequest;
token.Len = 6;
return token.Equal(StrPtrLen("SOURCE"));
}
// This determines if an incoming request is a Shoutcast broadcaster sending
// us his password. It will be in the format of:
// <password>\n".
Bool16 IsShoutcastPassword(StrPtrLen& theRequest)
{
char* bp = (char*)theRequest.Ptr;
int i;
int n = (int)theRequest.Len;
for (i=0; i<n; i++)
{
// any request with an embeded whitespace cannot be a
// Shoutcast password request.
if (*bp == ' ' || *bp == '\t')
return false;
}
// otherwise we assume it is.
return true;
}
Bool16 IsProtocolString(StrPtrLen& source, char *protocol)
{
UInt32 protocolLen = ::strlen(protocol);
if (protocolLen <= source.Len) // must be smaller or same
{
source.Len = protocolLen; // trim off the /version from protocol/version
if (source.EqualIgnoreCase(protocol))
return true;
}
return false;
}
Bool16 IsProtocol(StrPtrLen& theRequest, char* protocol)
{
StringParser reqParse(&theRequest);
StrPtrLen line;
reqParse.GetThruEOL(&line);
StringParser lineParse(&line);
StrPtrLen lastWord;
StrPtrLen currentWord;
lineParse.ConsumeUntilWhitespace(&currentWord);
lineParse.ConsumeWhitespace();
if (IsProtocolString(currentWord, protocol))//test for line starting with protocol/vers in a response from the client
return true;
if (line.Len > 0) do
{
lastWord = currentWord;
lineParse.ConsumeUntilWhitespace(&currentWord);
lineParse.ConsumeWhitespace();
} while (currentWord.Len > 0);
if (IsProtocolString(lastWord, protocol))// test for line ending with protocol/version as in a request from the client
return true;
return false;
}
Bool16 IsHTTP(StrPtrLen& theRequest)
{
return IsProtocol(theRequest, "HTTP");
}
Bool16 IsRTSP(StrPtrLen& theRequest)
{
return IsProtocol(theRequest, "RTSP");
}
// This determines if an incoming request is an HTTP GET
// request.
Bool16 IsHTTPGet(StrPtrLen& theRequest)
{
StrPtrLen token = theRequest;
token.Len = 3;
Bool16 found = false;
if (token.EqualIgnoreCase(StrPtrLen("GET")) && IsHTTP(theRequest) )
found = true;
return found;
}
// Is this URL a *.m3u file request.
Bool16 IsA_m3u_URL(char* theURL)
{
if (::strstr(theURL, ".m3u") != 0)
return true;
return false;
}
// Is this URL a metdata xfer.
Bool16 IsMetaDataURL(char* theURL)
{
if (::strncmp(theURL, "/admin.cgi?mode=updinfo", 23) == 0)
return true;
return false;
}
// Parse this URL and extract metdata.
void ParseMetaDataURL(char* theURL)
{
char URLBuffer[kURLBufferSize];
if (theURL == NULL)
{
Assert(0);
return;
}
else if (::strlen(theURL) == 0)
{
Assert(0);
return;
}
DTRACE1("##ParseMetaDataURL() - meta-data URL <%s>\n", theURL);
StrPtrLen urlPtr(theURL);
StrPtrLen token;
StringParser urlParser(&urlPtr);
urlParser.ConsumeUntil(&token, '&');
if ( !token.Equal(StrPtrLen("/admin.cgi?mode=updinfo")) )
{
// expected '/admin.cgi?mode=updinfo' in URL!
Assert(0);
return;
}
if ( !urlParser.Expect('&') )
{
// expected '&' in URL after '/admin.cgi?mode=updinfo'
Assert(0);
return;
}
urlParser.ConsumeUntil(&token, '=');
if ( !token.Equal(StrPtrLen("pass")) )
{
// expected 'pass=' in URL!
Assert(0);
return;
}
if ( !urlParser.Expect('=') )
{
// expected '=' in URL after 'pass'!
Assert(0);
return;
}
urlParser.ConsumeUntil(&token, '&');
if ( !token.Equal((const char*)sBroadcastPassword) )
{
DTRACE1("##ParseMetaDataURL() - expected Password of '%s' in URL!\n", sBroadcastPassword);
return;
}
else
{
if ( !urlParser.Expect('&') )
{
// expected '&' in URL after 'pass=xxx'!
Assert(0);
return;
}
}
urlParser.ConsumeUntil(&token, '=');
if ( !token.Equal(StrPtrLen("mount")) )
{
// expected 'mount=' in URL!
Assert(0);
return;
}
if ( !urlParser.Expect('=') )
{
// expected '=' in URL after 'mount'!
Assert(0);
return;
}
urlParser.ConsumeUntil(&token, '&');
if ( token.Len < 1 )
{
// expected mountpoint in URL!
Assert(0);
return;
}
else
{
if ( !urlParser.Expect('&') )
{
// expected '&' in URL after 'mount=xxx'!
Assert(0);
return;
}
}
::memset(URLBuffer, 0, kURLBufferSize);
::memcpy(URLBuffer, token.Ptr, token.Len);
DTRACE1("##ParseMetaDataURL() - got mountpoint of '%s' in URL.\n", URLBuffer);
MP3BroadcasterSession* xs = sMP3BroadcasterQueue.FindByMountPoint(token);
if (xs == NULL)
{
DTRACE("##ParseMetaDataURL() - no matching mountpoint found!\n");
return;
}
urlParser.ConsumeUntil(&token, '=');
if ( !token.Equal(StrPtrLen("song")) )
{
// expected 'song=' in URL!
Assert(0);
return;
}
if ( !urlParser.Expect('=') )
{
// expected '=' in URL after 'song'!
Assert(0);
return;
}
xs->SetSongName(urlParser.GetCurrentPosition());
}
void SendBroadcastAuthErr(QTSS_Filter_Params* inParams, char * errormessage)
{
if (errormessage != NULL)
{
UInt32 len = ::strlen(errormessage);
(void)QTSS_Write(inParams->inRTSPRequest,errormessage ,len, NULL, 0);
(void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespMsg, 0, errormessage, len);
}
QTSS_SessionStatusCode inStatusCode = qtssClientUnAuthorized;
(void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqStatusCode, 0, &inStatusCode, sizeof(inStatusCode));
const Bool16 sFalse = false;
(void)QTSS_SetValue(inParams->inRTSPRequest, qtssRTSPReqRespKeepAlive, 0, &sFalse, sizeof(sFalse));
}
// This parses and checks the password in the broadcaster's
// incoming request.
Bool16 CheckPassword(QTSS_Filter_Params* inParams, StrPtrLen& theRequest, StrPtrLen& mountpoint)
{
Bool16 passwordOK = false;
StrPtrLen strPtr;
mountpoint.Ptr = NULL;
mountpoint.Len = 0;
StringParser reqParse(&theRequest);
reqParse.ConsumeUntilWhitespace(&strPtr);
char* tmp;
if ( strPtr.Equal(StrPtrLen("SOURCE")) )
{
//it's an x-audio broadcast request
// PARSE X-AUDIO HERE!!!!
reqParse.ConsumeWhitespace();
reqParse.ConsumeUntilWhitespace(&strPtr);
tmp = strPtr.GetAsCString();
// Let's see if the password matches...
DTRACE1("CheckPassword() - #Got X-AUDIO password: %s\n", tmp);
if (strcmp(tmp, sBroadcastPassword) != 0)
{
SendBroadcastAuthErr(inParams, "ERROR - Bad Password\r\n"); //ice cast style error
delete[] tmp;
return false;
}
delete[] tmp;
// get the mount point
reqParse.ConsumeWhitespace();
reqParse.ConsumeUntilWhitespace(&strPtr);
mountpoint.Ptr = strPtr.Ptr;
mountpoint.Len = strPtr.Len;
tmp = strPtr.GetAsCString();
DTRACE1("CheckPassword() - #Got X-AUDIO mountpoint: %s\n", tmp);
delete[] tmp;
passwordOK = true;
}
else if ( IsShoutcastPassword(strPtr) )
{
tmp = strPtr.GetAsCString();
if (strPtr.Len > 1)
tmp[strPtr.Len] = '\0';
// Let's see if the password matches...
DTRACE1("CheckPassword() - #Got Shoutcast password: %s\n", tmp);
if (strcmp(tmp, sBroadcastPassword) != 0)
{
SendBroadcastAuthErr(inParams, "invalid password\r\n"); // shoutcast style error
delete[] tmp;
return false;
}
delete[] tmp;
mountpoint.Ptr = "/";
mountpoint.Len = 1;
passwordOK = true;
}
return passwordOK;
}
Bool16 GetHeaderAndValueFromeLine(StrPtrLen *line, StrPtrLen* header, char separator, StrPtrLen* value)
{
StringParser lineParser(line);
lineParser.ConsumeWhitespace();
Bool16 foundHeader = lineParser.GetThru(header, separator);
lineParser.ConsumeWhitespace();
lineParser.GetThruEOL(value);
return foundHeader;
}
// Find out if the HTTP header wants meta-data.
Bool16 NeedsMetaData(StrPtrLen& theRequest)
{
StrPtrLen line;
StrPtrLen header;
StrPtrLen value;
StringParser requestParser(&theRequest);
requestParser.GetThruEOL(NULL); // strip off command line
while ( requestParser.GetThruEOL(&line) || line.Len > 0 ) // pull out each line
{
if (!GetHeaderAndValueFromeLine(&line, &header, ':', &value))
return false; // no separator found
if (header.EqualIgnoreCase("icy-metadata") && (StringParser(&value).ConsumeInteger() > 0) )
return true; // icy-metadata: (non zero value)
}
return false;
}
/* NeedsMetaData Test cases
StrPtrLen temp;
temp.Set("\r\nIcy-metadatA: 1\r");
if ( NeedsMetaData(temp) )
printf("icy-metadata: 1-1\n");
temp.Set("\ricy-metadata: 1\r");
if ( NeedsMetaData(temp) )
printf("icy-metadata: 1-2\n");
temp.Set("\nicy-metadata: 1\r");
if ( NeedsMetaData(temp) )
printf("icy-metadata: 1 -3\n");
temp.Set("\r\nicy-metadata:1\r");
if ( NeedsMetaData(temp) )
printf("icy-metadata:1-4\n");
temp.Set("\r\nicy-metadata:\r");
if ( NeedsMetaData(temp) )
printf("icy-metadata:-5\n");
temp.Set("\r\nicy-metadata 1\n");
if ( NeedsMetaData(temp) )
printf("icy-metadata 1-6\n");
temp.Set("\r\nicy-metadata: 0\r");
if ( NeedsMetaData(temp) )
printf("icy-metadata: 0-7\n");
temp.Set("\r\nicy-metadata: 3\r");
if ( NeedsMetaData(temp) )
printf("icy-metadata: 3-8\n");
temp.Set("\r\nIcy-metadatA: 1");
if ( NeedsMetaData(temp) )
printf("icy-metadata: 1-0\n");
temp.Set("\nIcy-metadatA: 1");
if ( NeedsMetaData(temp) )
printf("icy-metadata: 1-10\n");
*/
// Parse out the URL from the HTTP GET line.
Bool16 ParseURL(StrPtrLen& theRequest, char* outURL, UInt16 maxlen)
{
StringParser reqParse(&theRequest);
StrPtrLen strPtr;
::memset(outURL, 0, maxlen);
reqParse.ConsumeWord(&strPtr);
if ( !strPtr.Equal(StrPtrLen("GET")) )
{
return false;
}
reqParse.ConsumeWhitespace();
reqParse.ConsumeUntilWhitespace(&strPtr);
if (strPtr.Len == 0)
return false;
else if ((UInt16)strPtr.Len > maxlen-1)
strPtr.Len = maxlen-1;
::memcpy(outURL, strPtr.Ptr, strPtr.Len);
return true;
}
void WriteStartupMessage()
{
if (!sServerIdle)
return;
sServerIdle = false;
//format a date for the startup time
char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes];
Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false);
char tempBuffer[1024];
if (result)
qtss_sprintf(tempBuffer, "#Remark: Streaming beginning STARTUP %s\n", theDateBuffer);
// log startup message to error log as well.
if ((result) && (sMP3AccessLog != NULL))
sMP3AccessLog->WriteToLog(tempBuffer, kAllowLogToRoll);
}
void WriteShutdownMessage()
{
if (sServerIdle)
return;
sServerIdle = true;
//log shutdown message
//format a date for the shutdown time
char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes];
Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, false);
char tempBuffer[1024];
if (result)
qtss_sprintf(tempBuffer, "#Remark: Streaming beginning SHUTDOWN %s\n", theDateBuffer);
if ( result && sMP3AccessLog != NULL )
sMP3AccessLog->WriteToLog(tempBuffer, kAllowLogToRoll);
}
QTSS_Error LogRequest(QTSS_RTSPSessionObject inRTSPSession, MP3ClientSession* client)
{
char logbuffer[1024];
char reqBuffer[kRequestBufferSize];
char userAgent[kUserAgentBufferSize];
char theDateBuffer[QTSSRollingLog::kMaxDateBufferSizeInBytes];
UInt16 reqResult = 200;
UInt32 byteCount = 54000000;
UInt32 duration = 0;
QTSS_Error theError = QTSS_NoErr;
// Sanity check...
if (sMP3AccessLog == NULL)
return QTSS_NoErr;
// LogRequest() is not re-entrant. Hold a mutex until done.
OSMutexLocker locker(sLogMutex);
// Construct the timestamp for the entry
Bool16 result = QTSSRollingLog::FormatDate(theDateBuffer, sLogTimeInGMT);
// this should never happen, but just in case...
if (!result)
theDateBuffer[0] = '\0';
// Get the IP address of the client who made the request
char remoteAddress[kRemoteAddressSize] = {0};
StrPtrLen theClientIPAddressStr;
theClientIPAddressStr.Set(remoteAddress,kRemoteAddressSize);
QTSS_Error err = QTSS_GetValue( inRTSPSession,
qtssRTSPSesRemoteAddrStr,
0,
(void*)theClientIPAddressStr.Ptr,
&theClientIPAddressStr.Len);
if (err != QTSS_NoErr)
::strcpy(remoteAddress, "127.0.0.1");
// Pull the actual HTTP request line from the MP3 client session.
::strcpy(reqBuffer, client->GetRequest());
// Get the user agent's name or "unknown user agent" if none was specified
// in the original HTTP request.
userAgent[0] = '\0';
::strcpy(userAgent, client->GetUserAgent());
if (userAgent[0] == '\0')
::strcpy(userAgent, "unknown user agent");
// Determine how may bytes this client was streamed.
byteCount = client->GetTotalCount();
// Calculate the total connect duration in seconds.
duration = (UInt32)((OS::Milliseconds() - client->GetConnectTime())/1000);
// Finally, get the HTTP result code.
reqResult = client->GetResult();
// Format the access log entry parameters here...
qtss_sprintf(logbuffer, "%s \"%s\" [%s] \"%s\" %d %"_S32BITARG_" %"_S32BITARG_"\n",
remoteAddress,
userAgent,
theDateBuffer,
reqBuffer,
reqResult,
byteCount,
duration
);
// Commit it to disk...
sMP3AccessLog->WriteToLog(logbuffer, kAllowLogToRoll);
return theError;
}
// url_strcpy - works like strcpy except that it handles URL escape
// conversions as it copies the string.
void url_strcpy(char* dest, const char* src)
{
int c1, c2;
while(*src)
{
if (*src == '%')
{
src++;
c1 = *src++;
if (c1 >= '0' && c1 <= '9')
c1 -= '0';
else if (c1 >= 'A' && c1 <= 'F')
c1 -= 'A' + 10;
else if (c1 >= 'a' && c1 <= 'f')
c1 -= 'a' + 10;
else
c1 = 0;
c2 = *src;
if (c2 >= '0' && c2 <= '9')
c2 -= '0';
else if (c2 >= 'A' && c2 <= 'F')
c2 -= 'A' + 10;
else if (c2 >= 'a' && c2 <= 'f')
c2 -= 'a' + 10;
else
c2 = 32;
*dest = (c1 * 16) + c2;
}
else
{
*dest = *src;
}
// we need to replace single quotes with
// a tick so that the client can parse meta-data.
if (*dest == '\'')
{
*dest = '`';
}
dest++;
src++;
}
*dest = '\0';
}
// ReEnterFilterRequest - This function is called whenever we
// have called back our module in the QTSS_FilterRequest role
// after calling it the first time.
// This will happen in one of two ways:
// 1. A MP3BroadcasterSession posted a read request event and data
// became available on its stream.
// 2. The Idle timer called us back to poll a MP3ClientSession to
// see if it needs to die.
QTSS_Error ReEnterFilterRequest(QTSS_Filter_Params* inParams, MP3Session* mp3Session)
{
QTSS_Error err = QTSS_NoErr;
QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest;
if (mp3Session != NULL)
{
UInt8 sessTyp = mp3Session->IsA();
switch(sessTyp)
{
case kMP3BroadcasterSessionType:
// this is a MP3 broadcaster session. Invoke it's state machine.
return ((MP3BroadcasterSession*)mp3Session)->ExecuteState();
case kMP3ClientSessionType:
// this is a MP3 client session. If we are active schedule ourself
// to be called back again in kClientPollInterval milliseconds.
if (mp3Session->GetState() == MP3ClientSession::kClientSendDataState)
{
MP3ClientSession* client = (MP3ClientSession*)mp3Session;
if (client->WasBlocked())
client->RetrySendData();
KeepSession(theRequest, true);
err = QTSS_SetIdleTimer(kClientPollInterval);
return err;
}
else
{
KeepSession(theRequest, false);
}
return QTSS_NoErr;
default:
Assert(0);
return QTSS_NoErr;
}
}
return err;
}
// FilterRequest - filter the incoming HTTP/Broadcast request.
QTSS_Error FilterRequest(QTSS_Filter_Params* inParams)
{
QTSS_Error err = QTSS_NoErr;
QTSS_RTSPRequestObject theRequest = inParams->inRTSPRequest;
// Pull the actual request data from this RTSP session's attributes.
StrPtrLen theFullRequest;
err = QTSS_GetValuePtr(theRequest, qtssRTSPReqFullRequest, 0, (void**)&theFullRequest.Ptr, &theFullRequest.Len);
if (err != QTSS_NoErr)
{
return QTSS_NoErr;
}
if (IsRTSP(theFullRequest)) // don't process rtsp requests
return QTSS_NoErr;
if (!sMP3StreamingEnabled || sServerIdle)
{
// This allows MP3 streaming to be disabled through a pref setting
// or ignore requests when the server is in the idle state.
if (NeedsMetaData(theFullRequest)) // this is an HTTP icy request
return QTSSModuleUtils::SendHTTPErrorResponse(theRequest,qtssServerUnavailable,true, NULL);
return QTSS_NoErr;
}
// See if this RTSP session is an existing MP3 Session.
// If it is then we have re-entered FilterRequest() from a previous
// invokation and we will call ReEnterFilterRequest() to do its thing.
MP3Session* mp3Session = sMP3SessionTable.Resolve(inParams->inRTSPSession);
if (mp3Session != NULL)
{
err = ReEnterFilterRequest(inParams, mp3Session);
return err;
}
// Check and see if this request is a broadcaster sending it's password or
// an HTTP GET. If it's not, it's assumed to be a RTSP request and we ignore
// it. In that case, some other module must handle this request.
#if DEBUG_MP3STREAMING_MODULE
DTRACE("####\n");
PrintStringBuffer(theFullRequest);
#endif
if (!IsBroadcastPassword(theFullRequest) && !IsHTTPGet(theFullRequest))
{
return QTSS_NoErr;
}
MP3BroadcasterSession* xs = NULL;
#if DEBUG_MP3STREAMING_MODULE
// Debugging info - Retrieve the RTSP session ID associated with this session.
UInt32 uSessID = 0L;
uSessID = GetRTSPSessionID(inParams->inRTSPSession);
DTRACE1("####\n###Filtering SessionID = %"_S32BITARG_"\n", uSessID);
#endif
// See if we have exceeded our maximum number of client connections
if (MP3Session::GetTotalNumMP3Sessions() >= sMaximumConnections)
{
// we are currently at our connection limit. return a 400 error for compatibility with icecast style error.
return QTSSModuleUtils::SendHTTPErrorResponse(theRequest,qtssClientBadRequest,true, NULL);
}
else if (!CheckBandwidth(kBandwidthToAddEstimate))
{
// we are currently at our bandwidth limit. return a 400 error for compatibility with icecast style error.
return QTSSModuleUtils::SendHTTPErrorResponse(theRequest,qtssClientBadRequest,true, NULL);
}
StrPtrLen mountpoint;
if (!IsHTTP(theFullRequest) && IsBroadcastPassword(theFullRequest) && CheckPassword(inParams, theFullRequest, mountpoint))
{
err = sMP3BroadcasterQueue.CreateBroadcaster(inParams->inRTSPSession, inParams->inRTSPRequest, mountpoint);
if (err != QTSS_NoErr)
{
// CreateBroadcaster returned an error but it has already sent the reject reply
// so there's nothing left to do.
return QTSS_NoErr;
}
// If we're here this is a new connection request from a broadcaster.
// Read the broadcast headers from the broadcaster
xs = FindBroadcastSession(inParams->inRTSPSession);
if (xs != NULL)
err = xs->GetBroadcastHeaders();
else
err = QTSS_NoErr;
return err;
}
else if ( IsHTTPGet(theFullRequest) )
{
char theURL[kURLBufferSize];
MP3BroadcasterSession* xs = NULL;
if (!ParseURL(theFullRequest, theURL, kURLBufferSize))
{
return QTSS_NoErr;
}
// if this was a '*.m3u' file URL then handle it here
// sendind back a reply containing the URL.
// This effectively accomplishes a redirect.
if ( IsA_m3u_URL(theURL) )
{
char tmpbuf[1024];
tmpbuf[sizeof(tmpbuf) -1] = 0;
char tmp[1024];
tmp[sizeof(tmp) -1] = 0;
// remove the '.m3u' prefix from the URL
UInt32 ulen = ::strlen(theURL) - 4;
UInt32 serverIPAddr;
UInt8 x1, x2, x3, x4;
theURL[ulen] = '\0';
// Get the server IP address for building the reply playlist path
ulen = sizeof(serverIPAddr);
err = QTSS_GetValue(sServer, qtssSvrDefaultIPAddr, 0, &serverIPAddr, &ulen);
if (err == QTSS_NoErr)
{
x1 = (UInt8)((serverIPAddr >> 24) & 0xff);
x2 = (UInt8)((serverIPAddr >> 16) & 0xff);
x3 = (UInt8)((serverIPAddr >> 8) & 0xff);
x4 = (UInt8)((serverIPAddr) & 0xff);
}
else
{
x1 = 127; x2 = x3 = 0; x4 = 1;
}
// construct the reply string for the client.
qtss_snprintf(tmp,sizeof(tmp) -1, "http://%d.%d.%d.%d:8000%s", x1,x2,x3,x4, theURL);
ulen = ::strlen(tmp);
qtss_snprintf(tmpbuf,sizeof(tmpbuf) -1, "%s %"_U32BITARG_"\r\n\r\n%s\r\n", gM3UReplyHeader, ulen, tmp);
ulen = ::strlen(tmpbuf);
// send the reply to the client.
err = QTSS_Write(inParams->inRTSPRequest, tmpbuf, ulen, NULL, qtssWriteFlagsBufferData);
return QTSS_NoErr;
}
// if this was a meta-data transfer URL then handle it here
if ( IsMetaDataURL(theURL) )
{
// Parse meta-data request here
ParseMetaDataURL(theURL);
// tell the broadcaster we got it.
err = QTSS_Write(inParams->inRTSPRequest, kOKHeader, ::strlen(kOKHeader), NULL, qtssWriteFlagsBufferData);
return QTSS_NoErr;
}
// Since this URL was not a metadata transfer let's assume it is
// a broadcast mount point and search for it.
xs = sMP3BroadcasterQueue.FindByMountPoint(theURL);
if (xs == NULL)
{
if (NeedsMetaData(theFullRequest)) // this is an HTTP icy request
return QTSSModuleUtils::SendHTTPErrorResponse(theRequest,qtssClientNotFound,true, NULL);
// Let some other module handle this URL.
return QTSS_NoErr;
}
// if we're here this is a new MP3 connection request from a client.
// Start the clients stream up...
err = xs->AddClient(inParams->inRTSPSession, inParams->inRTSPRequest);
return err;
}
return QTSS_NoErr;
}