📄 sipclient.cpp
字号:
/**********This library is free software; you can redistribute it and/or modify it underthe terms of the GNU Lesser General Public License as published by theFree Software Foundation; either version 2.1 of the License, or (at youroption) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)This library is distributed in the hope that it will be useful, but WITHOUTANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESSFOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License formore details.You should have received a copy of the GNU Lesser General Public Licensealong with this library; if not, write to the Free Software Foundation, Inc.,59 Temple Place, Suite 330, Boston, MA 02111-1307 USA**********/// "liveMedia"// Copyright (c) 1996-2004 Live Networks, Inc. All rights reserved.// A generic SIP client// Implementation#include "SIPClient.hh"#include "GroupsockHelper.hh"#if defined(__WIN32__) || defined(_WIN32) || defined(_QNX4)#define _strncasecmp _strnicmp#else#define _strncasecmp strncasecmp#endif////////// SIPClient //////////SIPClient* SIPClient::createNew(UsageEnvironment& env, unsigned char desiredAudioRTPPayloadFormat, char const* mimeSubtype, int verbosityLevel, char const* applicationName) { return new SIPClient(env, desiredAudioRTPPayloadFormat, mimeSubtype, verbosityLevel, applicationName);}SIPClient::SIPClient(UsageEnvironment& env, unsigned char desiredAudioRTPPayloadFormat, char const* mimeSubtype, int verbosityLevel, char const* applicationName) : Medium(env), fT1(500000 /* 500 ms */), fDesiredAudioRTPPayloadFormat(desiredAudioRTPPayloadFormat), fVerbosityLevel(verbosityLevel), fCSeq(0), fURL(NULL), fURLSize(0), fToTagStr(NULL), fToTagStrSize(0), fUserName(NULL), fUserNameSize(0), fInviteSDPDescription(NULL), fInviteCmd(NULL), fInviteCmdSize(0){ if (mimeSubtype == NULL) mimeSubtype = ""; fMIMESubtype = strDup(mimeSubtype); fMIMESubtypeSize = strlen(fMIMESubtype); if (applicationName == NULL) applicationName = ""; fApplicationName = strDup(applicationName); fApplicationNameSize = strlen(fApplicationName); struct in_addr ourAddress; ourAddress.s_addr = ourSourceAddressForMulticast(env); // hack fOurAddressStr = strDup(our_inet_ntoa(ourAddress)); fOurAddressStrSize = strlen(fOurAddressStr); fOurSocket = new Groupsock(env, ourAddress, 0, 255); if (fOurSocket == NULL) { env << "ERROR: Failed to create socket for addr " << our_inet_ntoa(ourAddress) << ": " << env.getResultMsg() << "\n"; } // Now, find out our source port number. Hack: Do this by first trying to // send a 0-length packet, so that the "getSourcePort()" call will work. fOurSocket->output(envir(), 255, (unsigned char*)"", 0); Port srcPort(0); getSourcePort(env, fOurSocket->socketNum(), srcPort); if (srcPort.num() != 0) { fOurPortNum = ntohs(srcPort.num()); } else { // No luck. Try again using a default port number: fOurPortNum = 5060; delete fOurSocket; fOurSocket = new Groupsock(env, ourAddress, fOurPortNum, 255); if (fOurSocket == NULL) { env << "ERROR: Failed to create socket for addr " << our_inet_ntoa(ourAddress) << ", port " << fOurPortNum << ": " << env.getResultMsg() << "\n"; } } // Set various headers to be used in each request: char const* formatStr; unsigned headerSize; // Set the "User-Agent:" header: char const* const libName = "LIVE.COM Streaming Media v"; char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING; char const* libPrefix; char const* libSuffix; if (applicationName == NULL || applicationName[0] == '\0') { applicationName = libPrefix = libSuffix = ""; } else { libPrefix = " ("; libSuffix = ")"; } formatStr = "User-Agent: %s%s%s%s%s\r\n"; headerSize = strlen(formatStr) + fApplicationNameSize + strlen(libPrefix) + strlen(libName) + strlen(libVersionStr) + strlen(libSuffix); fUserAgentHeaderStr = new char[headerSize]; sprintf(fUserAgentHeaderStr, formatStr, applicationName, libPrefix, libName, libVersionStr, libSuffix); fUserAgentHeaderStrSize = strlen(fUserAgentHeaderStr); reset();}SIPClient::~SIPClient() { reset(); delete[] fUserAgentHeaderStr; delete fOurSocket; delete[] (char*)fOurAddressStr; delete[] (char*)fApplicationName; delete[] (char*)fMIMESubtype;}void SIPClient::reset() { fWorkingAuthenticator = NULL; delete[] fInviteCmd; fInviteCmd = NULL; fInviteCmdSize = 0; delete[] fInviteSDPDescription; fInviteSDPDescription = NULL; delete[] (char*)fUserName; fUserName = strDup(fApplicationName); fUserNameSize = strlen(fUserName); fValidAuthenticator.reset(); delete[] (char*)fToTagStr; fToTagStr = NULL; fToTagStrSize = 0; fServerPortNum = 0; fServerAddress.s_addr = 0; delete[] (char*)fURL; fURL = NULL; fURLSize = 0;}void SIPClient::setProxyServer(unsigned proxyServerAddress, portNumBits proxyServerPortNum) { fServerAddress.s_addr = proxyServerAddress; fServerPortNum = proxyServerPortNum; if (fOurSocket != NULL) { fOurSocket->changeDestinationParameters(fServerAddress, fServerPortNum, 255); }}static char* getLine(char* startOfLine) { // returns the start of the next line, or NULL if none for (char* ptr = startOfLine; *ptr != '\0'; ++ptr) { if (*ptr == '\r' || *ptr == '\n') { // We found the end of the line *ptr++ = '\0'; if (*ptr == '\n') ++ptr; return ptr; } } return NULL;}char* SIPClient::invite(char const* url, Authenticator* authenticator) { // First, check whether "url" contains a username:password to be used: fInviteStatusCode = 0; char* username; char* password; if (authenticator == NULL && parseSIPURLUsernamePassword(url, username, password)) { char* result = inviteWithPassword(url, username, password); delete[] username; delete[] password; // they were dynamically allocated return result; } if (!processURL(url)) return NULL; delete[] (char*)fURL; fURL = strDup(url); fURLSize = strlen(fURL); fCallId = our_random(); fFromTag = our_random(); return invite1(authenticator);}char* SIPClient::invite1(Authenticator* authenticator) { do { // Send the INVITE command: // First, construct an authenticator string: fValidAuthenticator.reset(); fWorkingAuthenticator = authenticator; char* authenticatorStr = createAuthenticatorString(fWorkingAuthenticator, "INVITE", fURL); // Then, construct the SDP description to be sent in the INVITE: char* rtpmapLine; unsigned rtpmapLineSize; if (fMIMESubtypeSize > 0) { char* const rtpmapFmt = "a=rtpmap:%u %s/8000\r\n"; unsigned rtpmapFmtSize = strlen(rtpmapFmt) + 3 /* max char len */ + fMIMESubtypeSize; rtpmapLine = new char[rtpmapFmtSize]; sprintf(rtpmapLine, rtpmapFmt, fDesiredAudioRTPPayloadFormat, fMIMESubtype); rtpmapLineSize = strlen(rtpmapLine); } else { // Static payload type => no "a=rtpmap:" line rtpmapLine = strDup(""); rtpmapLineSize = 0; } char* const inviteSDPFmt = "v=0\r\n" "o=- %u %u IN IP4 %s\r\n" "s=%s session\r\n" "c=IN IP4 %s\r\n" "t=0 0\r\n" "m=audio %u RTP/AVP %u\r\n" "%s"; unsigned inviteSDPFmtSize = strlen(inviteSDPFmt) + 20 /* max int len */ + 20 + fOurAddressStrSize + fApplicationNameSize + fOurAddressStrSize + 5 /* max short len */ + 3 /* max char len */ + rtpmapLineSize; delete[] fInviteSDPDescription; fInviteSDPDescription = new char[inviteSDPFmtSize]; sprintf(fInviteSDPDescription, inviteSDPFmt, fCallId, fCSeq, fOurAddressStr, fApplicationName, fOurAddressStr, fClientStartPortNum, fDesiredAudioRTPPayloadFormat, rtpmapLine); unsigned inviteSDPSize = strlen(fInviteSDPDescription); delete[] rtpmapLine; char* const cmdFmt = "INVITE %s SIP/2.0\r\n" "From: %s <sip:%s@%s>;tag=%u\r\n" "Via: SIP/2.0/UDP %s:%u\r\n" "To: %s\r\n" "Contact: sip:%s@%s:%u\r\n" "Call-ID: %u@%s\r\n" "CSeq: %d INVITE\r\n" "Content-Type: application/sdp\r\n" "%s" /* Proxy-Authorization: line (if any) */ "%s" /* User-Agent: line */ "Content-length: %d\r\n\r\n" "%s"; unsigned inviteCmdSize = strlen(cmdFmt) + fURLSize + 2*fUserNameSize + fOurAddressStrSize + 20 /* max int len */ + fOurAddressStrSize + 5 /* max port len */ + fURLSize + fUserNameSize + fOurAddressStrSize + 5 + 20 + fOurAddressStrSize + 20 + strlen(authenticatorStr) + fUserAgentHeaderStrSize + 20 + inviteSDPSize; delete[] fInviteCmd; fInviteCmd = new char[inviteCmdSize]; sprintf(fInviteCmd, cmdFmt, fURL, fUserName, fUserName, fOurAddressStr, fFromTag, fOurAddressStr, fOurPortNum, fURL, fUserName, fOurAddressStr, fOurPortNum, fCallId, fOurAddressStr, ++fCSeq, authenticatorStr, fUserAgentHeaderStr, inviteSDPSize, fInviteSDPDescription); fInviteCmdSize = strlen(fInviteCmd); delete[] authenticatorStr; // Before sending the "INVITE", arrange to handle any response packets, // and set up timers: fInviteClientState = Calling; fEventLoopStopFlag = 0; TaskScheduler& sched = envir().taskScheduler(); // abbrev. sched.turnOnBackgroundReadHandling(fOurSocket->socketNum(), &inviteResponseHandler, this); fTimerALen = 1*fT1; // initially fTimerACount = 0; // initially fTimerA = sched.scheduleDelayedTask(fTimerALen, timerAHandler, this); fTimerB = sched.scheduleDelayedTask(64*fT1, timerBHandler, this); fTimerD = NULL; // for now if (!sendINVITE()) break; // Enter the event loop, to handle response packets, and timeouts: envir().taskScheduler().doEventLoop(&fEventLoopStopFlag); // We're finished with this "INVITE". // Turn off response handling and timers: sched.turnOffBackgroundReadHandling(fOurSocket->socketNum()); sched.unscheduleDelayedTask(fTimerA); sched.unscheduleDelayedTask(fTimerB); sched.unscheduleDelayedTask(fTimerD); // NOTE: We return the SDP description that we used in the "INVITE", // not the one that we got from the server. // ##### Later: match the codecs in the response (offer, answer) ##### if (fInviteSDPDescription != NULL) { return strDup(fInviteSDPDescription); } } while (0); fInviteStatusCode = 2; return NULL;}void SIPClient::inviteResponseHandler(void* clientData, int /*mask*/) { SIPClient* client = (SIPClient*)clientData; unsigned responseCode = client->getResponseCode(); client->doInviteStateMachine(responseCode);}// Special 'response codes' that represent timers expiring:unsigned const timerAFires = 0xAAAAAAAA;unsigned const timerBFires = 0xBBBBBBBB;unsigned const timerDFires = 0xDDDDDDDD;void SIPClient::timerAHandler(void* clientData) { SIPClient* client = (SIPClient*)clientData; if (client->fVerbosityLevel >= 1) { client->envir() << "RETRANSMISSION " << ++client->fTimerACount << ", after " << client->fTimerALen/1000000.0 << " additional seconds\n"; } client->doInviteStateMachine(timerAFires);}void SIPClient::timerBHandler(void* clientData) { SIPClient* client = (SIPClient*)clientData; if (client->fVerbosityLevel >= 1) { client->envir() << "RETRANSMISSION TIMEOUT, after " << 64*client->fT1/1000000.0 << " seconds\n"; fflush(stderr); } client->doInviteStateMachine(timerBFires);}void SIPClient::timerDHandler(void* clientData) { SIPClient* client = (SIPClient*)clientData; if (client->fVerbosityLevel >= 1) { client->envir() << "TIMER D EXPIRED\n"; } client->doInviteStateMachine(timerDFires);}void SIPClient::doInviteStateMachine(unsigned responseCode) { // Implement the state transition diagram (RFC 3261, Figure 5) TaskScheduler& sched = envir().taskScheduler(); // abbrev. switch (fInviteClientState) { case Calling: { if (responseCode == timerAFires) { // Restart timer A (with double the timeout interval): fTimerALen *= 2; fTimerA = sched.scheduleDelayedTask(fTimerALen, timerAHandler, this); fInviteClientState = Calling; if (!sendINVITE()) doInviteStateTerminated(0); } else { // Turn off timers A & B before moving to a new state: sched.unscheduleDelayedTask(fTimerA); sched.unscheduleDelayedTask(fTimerB); if (responseCode == timerBFires) { envir().setResultMsg("No response from server"); doInviteStateTerminated(0); } else if (responseCode >= 100 && responseCode <= 199) { fInviteClientState = Proceeding; } else if (responseCode >= 200 && responseCode <= 299) { doInviteStateTerminated(responseCode); } else if (responseCode >= 400 && responseCode <= 499) { doInviteStateTerminated(responseCode); // this isn't what the spec says, but it seems right... } else if (responseCode >= 300 && responseCode <= 699) { fInviteClientState = Completed; fTimerD = sched.scheduleDelayedTask(32000000, timerDHandler, this); if (!sendACK()) doInviteStateTerminated(0); } } break; } case Proceeding: { if (responseCode >= 100 && responseCode <= 199) { fInviteClientState = Proceeding; } else if (responseCode >= 200 && responseCode <= 299) { doInviteStateTerminated(responseCode); } else if (responseCode >= 400 && responseCode <= 499) { doInviteStateTerminated(responseCode); // this isn't what the spec says, but it seems right... } else if (responseCode >= 300 && responseCode <= 699) { fInviteClientState = Completed; fTimerD = sched.scheduleDelayedTask(32000000, timerDHandler, this); if (!sendACK()) doInviteStateTerminated(0); } break; } case Completed: { if (responseCode == timerDFires) { envir().setResultMsg("Transaction terminated"); doInviteStateTerminated(0); } else if (responseCode >= 300 && responseCode <= 699) { fInviteClientState = Completed; if (!sendACK()) doInviteStateTerminated(0); } break; } case Terminated: { doInviteStateTerminated(responseCode); break; } }}void SIPClient::doInviteStateTerminated(unsigned responseCode) { fInviteClientState = Terminated; // FWIW... if (responseCode < 200 || responseCode > 299) { // We failed, so return NULL; delete[] fInviteSDPDescription; fInviteSDPDescription = NULL; } // Unblock the event loop: fEventLoopStopFlag = ~0;}Boolean SIPClient::sendINVITE() { if (!sendRequest(fInviteCmd, fInviteCmdSize)) { envir().setResultErrMsg("INVITE send() failed: "); return False; } return True;}unsigned SIPClient::getResponseCode() { unsigned responseCode = 0; do { // Get the response from the server: unsigned const readBufSize = 10000; char readBuffer[readBufSize+1]; char* readBuf = readBuffer; char* firstLine = NULL; char* nextLineStart = NULL; unsigned bytesRead = getResponse(readBuf, readBufSize); if (bytesRead < 0) break; if (fVerbosityLevel >= 1) { envir() << "Received INVITE response: " << readBuf << "\n"; } // Inspect the first line to get the response code: firstLine = readBuf; nextLineStart = getLine(firstLine); if (!parseResponseCode(firstLine, responseCode)) break; if (responseCode != 200) { if (responseCode >= 400 && responseCode <= 499 && fWorkingAuthenticator != NULL) { // We have an authentication failure, so fill in
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -