mirror of
				https://github.com/RangeNetworks/openbts.git
				synced 2025-11-03 21:33:15 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			750 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			750 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
* Copyright 2013 Range Networks, Inc.
 | 
						|
*
 | 
						|
* This software is distributed under multiple licenses;
 | 
						|
* see the COPYING file in the main directory for licensing
 | 
						|
* information for this specific distribuion.
 | 
						|
*
 | 
						|
* This use of this software may be subject to additional restrictions.
 | 
						|
* See the LEGAL file in the main directory for details.
 | 
						|
 | 
						|
    This program is distributed in the hope that it will be useful,
 | 
						|
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
						|
 | 
						|
*/
 | 
						|
// Written by Pat Thompson.
 | 
						|
#define LOG_GROUP LogGroup::SIP		// Can set Log.Level.SIP for debugging
 | 
						|
#include <string>
 | 
						|
#include <list>
 | 
						|
#include <string.h>
 | 
						|
#include <Logger.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <ControlTransfer.h>
 | 
						|
#include "SIPParse.h"
 | 
						|
#include "SIPMessage.h"
 | 
						|
#include "SIPBase.h"
 | 
						|
#include "SIPDialog.h"
 | 
						|
 | 
						|
 | 
						|
namespace SIP {
 | 
						|
using namespace std;
 | 
						|
using namespace Control;
 | 
						|
 | 
						|
struct SipParseError : public std::exception {
 | 
						|
	SipParseError() { LOG(DEBUG) << "SipParseError"; }
 | 
						|
	virtual const char *what() const throw() {
 | 
						|
		return "SipParseError";
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
// endpos is the index of one char past the end of the string, eg, of the trailing nul. 
 | 
						|
// Move endpos backward until we find a char that is not one of the trimchars, and leave
 | 
						|
// endpos at the index one past that char, which is the appropriate length to substr everything but that char.
 | 
						|
// Example: string result = input.substr(input.c_str(),input.size());
 | 
						|
size_t trimrightn(const char *startp, size_t endpos, const char *trimchars /*=" \t\r\n"*/)
 | 
						|
{
 | 
						|
	while (endpos > 0 && strchr(trimchars,startp[endpos-1])) { endpos--; }
 | 
						|
	return endpos;
 | 
						|
}
 | 
						|
 | 
						|
// Return the index of the first char not in trimchars, or if none, of the trailing nul in the string.
 | 
						|
// Note that if you trimleft and trimright a string of spaces, the two indicies would cross, so be careful.  See trimboth.
 | 
						|
size_t trimleftn(const char *startp, size_t startpos/*=0*/, const char *trimchars /*=" \t\r\n"*/)
 | 
						|
{
 | 
						|
	while (startp[startpos] && strchr(trimchars,startp[startpos])) { startpos++; }
 | 
						|
	return startpos;
 | 
						|
}
 | 
						|
 | 
						|
// Trim both ends of a string.
 | 
						|
string trimboth(string input, const char *trimchars /*=" \t\r\n"*/)
 | 
						|
{
 | 
						|
	size_t end = trimrightn(input.c_str(),input.size());
 | 
						|
	size_t start = (end==0) ? 0 : trimleftn(input.c_str(),0,trimchars);
 | 
						|
	return input.substr(start,end);
 | 
						|
	//const char *bp = str.c_str();
 | 
						|
	//const char *endp = str.c_str() + str.size(), *ep = endp;		// points to the trailing nul.
 | 
						|
	//while (*bp && strchr(trimchars,*bp)) { bp++; }
 | 
						|
	//while (ep > bp && strchr(trimchars,ep[-1])) { ep--; }
 | 
						|
	//return (bp == str.c_str() && bp == endp) ? str : string(bp,bp-ep);
 | 
						|
}
 | 
						|
 | 
						|
void commaListPushBack(string *cl, string val)
 | 
						|
{
 | 
						|
	val = trimboth(val," ,");	// Make sure there is no errant garbage around the new value.
 | 
						|
	if (! cl->empty()) { cl->append(","); }
 | 
						|
	cl->append(val);
 | 
						|
}
 | 
						|
 | 
						|
void commaListPushFront(string *cl, string val)
 | 
						|
{
 | 
						|
	val = trimboth(val," ,");	// Make sure there is no errant garbage around the new value.
 | 
						|
	if (cl->empty()) {
 | 
						|
		*cl = val;
 | 
						|
	} else {
 | 
						|
		*cl = val + "," + *cl;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
string commaListFront(string cl)
 | 
						|
{
 | 
						|
	size_t cn = cl.find_first_of(',');
 | 
						|
	if (cn == string::npos) { cn = cl.size(); }
 | 
						|
	return cl.substr(0,trimrightn(cl.c_str(),cn));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// Case insenstive string comparison
 | 
						|
// Yes there is a way to do this in C++ by changing the character traits, but who cares.
 | 
						|
bool strcaseeql(const char *a,const char *b) { return 0==strcasecmp((a),(b)); }
 | 
						|
bool strncaseeql(const char *a,const char *b, unsigned n) { return 0==strncasecmp((a),(b),(n)); }
 | 
						|
bool strceql(const string a, const string b) { return strcaseeql(a.c_str(),b.c_str()); }
 | 
						|
 | 
						|
 | 
						|
//static const char *reserved = ";/?:@&=+$,";
 | 
						|
//static const char *mark = "-_.!~*'()";
 | 
						|
//static const char *param_unreserved = "[]/:&+$";
 | 
						|
static const char *token = "-.!%*_+`'~"; 	// or alphanum
 | 
						|
 | 
						|
class SipChar {
 | 
						|
	typedef unsigned char uchar;
 | 
						|
	static char charClassData[256];
 | 
						|
	enum {
 | 
						|
		ccIsToken = 1
 | 
						|
	};
 | 
						|
	public:
 | 
						|
	SipChar() {
 | 
						|
		memset(charClassData,0,256);
 | 
						|
		for (uchar ch = 'a'; ch <= 'z'; ch++) { charClassData[ch] = ccIsToken; }
 | 
						|
		for (uchar ch = 'A'; ch <= 'Z'; ch++) { charClassData[ch] = ccIsToken; }
 | 
						|
		for (uchar ch = '0'; ch <= '9'; ch++) { charClassData[ch] = ccIsToken; }
 | 
						|
		for (uchar *cp = (uchar*)token; *cp; cp++) { charClassData[*cp] |= ccIsToken; }
 | 
						|
	}
 | 
						|
	static bool isToken(const uchar ch) { return charClassData[ch] & ccIsToken; }
 | 
						|
	// unreserved is alphanum + mark.
 | 
						|
} gSipChar;	// Without the global variable the class is never initialized.
 | 
						|
 | 
						|
char SipChar::charClassData[256];
 | 
						|
 | 
						|
 | 
						|
// This is pretty easy to parse.  The major demarcation chars are reserved: @ ; ? &
 | 
						|
// [sip: | sips: ]  user [: password] @ host [: port] [;param=value]* [? hname=havalue [& hname=hvalue]* ]
 | 
						|
// absoluteURI ::= scheme: (hierpart | opaquepart)
 | 
						|
// scheme ::= alphanum|[+-.]
 | 
						|
// hierpart ::= // blah blah | / blah blah
 | 
						|
 | 
						|
 | 
						|
// This can not distinguish between a missing param and one with an empty value.
 | 
						|
string SipParamList::paramFind(const char*name)
 | 
						|
{
 | 
						|
	for (SipParamList::iterator it = this->begin(); it != this->end(); it++) {
 | 
						|
		if (strceql(it->mName.c_str(),name)) { return it->mValue; }
 | 
						|
	}
 | 
						|
	return string("");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
struct SipParseLine {
 | 
						|
	const char *currentLine;	// Start of current line being parsed, used only for error messages.
 | 
						|
	const char *pp;		// Pointer into the parse buffer.	It is const* for this class, but not for SipParseMessage
 | 
						|
 | 
						|
	void spLineInit(const char *buffer) { currentLine = pp = buffer; }
 | 
						|
	SipParseLine(const char* buffer) { spLineInit(buffer); }
 | 
						|
	SipParseLine() {}
 | 
						|
 | 
						|
	virtual void spError(const char *msg) {
 | 
						|
		LOG(ERR) << "SIP Parse error "<<msg<<" in line:"<<currentLine;
 | 
						|
		throw SipParseError();
 | 
						|
	}
 | 
						|
 | 
						|
	void skipSpace()
 | 
						|
	{
 | 
						|
		while (*pp && isspace(*pp)) { pp++; }
 | 
						|
	}
 | 
						|
 | 
						|
	bool scanChar(int ch)
 | 
						|
	{
 | 
						|
		skipSpace();
 | 
						|
		if (*pp != ch) { return false; }
 | 
						|
		pp++;
 | 
						|
		skipSpace();
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	// This is used for URIs, so no spaces allowed!
 | 
						|
	string scanToSet(const char *sepchars) {
 | 
						|
		const char *bp = pp;
 | 
						|
		if ((pp = strpbrk(bp,sepchars)) == NULL) { pp = bp + strlen(bp); }
 | 
						|
		return string(bp,pp-bp);
 | 
						|
	}
 | 
						|
 | 
						|
	void scanUriParam(const char *sepchars, SipParamList ¶ms) {
 | 
						|
		SipParam param;
 | 
						|
		const char *bp = pp;
 | 
						|
		if ((pp = strpbrk(bp,sepchars)) == NULL) { pp = bp + strlen(bp); }
 | 
						|
		if (const char *eqlp = (const char *)memchr(bp,'=',pp-bp)) {
 | 
						|
			param.mName = string(bp,eqlp-bp);
 | 
						|
			param.mValue = string(eqlp+1,pp-eqlp-1);
 | 
						|
		} else {
 | 
						|
			param.mName = string(bp,pp-bp);
 | 
						|
		}
 | 
						|
		if (! param.mName.empty()) params.push_back(param);
 | 
						|
	}
 | 
						|
 | 
						|
	// Currently unsigned string of digits.
 | 
						|
	int scanInt()
 | 
						|
	{
 | 
						|
		skipSpace();
 | 
						|
		const char *bp = pp;
 | 
						|
		while (*pp && isdigit(*pp)) pp++;
 | 
						|
		if (bp == pp) { spError("expected integer"); }
 | 
						|
		return atoi(bp);	// atoi ignores anything following the digits.
 | 
						|
	}
 | 
						|
 | 
						|
	// Discard spaces, then return the next non-space string.
 | 
						|
	// Leave pp pointing at the space char or end of string.
 | 
						|
	string scanNonSpace()
 | 
						|
	{
 | 
						|
		skipSpace();
 | 
						|
		if (!*pp) return string("");
 | 
						|
		const char *bp = pp;
 | 
						|
		while (*pp && ! isspace(*pp)) { pp++; }
 | 
						|
		return string(bp,pp-bp);
 | 
						|
	}
 | 
						|
 | 
						|
	// pp points at the quote starting the string.  Return string without the quotes.
 | 
						|
	string scanQuotedString()
 | 
						|
	{
 | 
						|
		assert(*pp == '"');
 | 
						|
		pp++;
 | 
						|
		char *result = (char*)alloca(strlen(pp)), *rp = result;
 | 
						|
		while (*pp) {
 | 
						|
			if (*pp == '"') { pp++; return string(result,rp-result); }
 | 
						|
			if (*pp == '\\') { pp++; }
 | 
						|
			*rp++ = *pp++;
 | 
						|
		}
 | 
						|
		spError("unterminated quoted string");
 | 
						|
		/*NOTREACHED*/
 | 
						|
		return "";	// Never used.
 | 
						|
	}
 | 
						|
 | 
						|
	string scanToken()
 | 
						|
	{
 | 
						|
		skipSpace();
 | 
						|
		const char *bp = pp;
 | 
						|
		//LOG(DEBUG) "char " << *pp <<"isToken="<<SipChar::isToken(*pp);
 | 
						|
		while (SipChar::isToken(*pp)) { pp++; }
 | 
						|
		return string(bp,pp-bp);
 | 
						|
	}
 | 
						|
 | 
						|
	string scanTokenOrQuotedString()
 | 
						|
	{
 | 
						|
		return (*pp == '"') ?  scanQuotedString() : scanToken();
 | 
						|
	}
 | 
						|
 | 
						|
	// The generic param is token = token or quoted string, with optional space around the '='.
 | 
						|
	// The name is defined as a token, but this is not significant in the grammar; name is terminated by space or '='.
 | 
						|
	// pp may point to space, scan past that first.
 | 
						|
	// Leave pp pointing at the char immediately after the param, which may be a space or separator.
 | 
						|
	bool scanGenericParam(SipParam ¶m)
 | 
						|
	{
 | 
						|
		const char *start = pp;
 | 
						|
		param.mName = scanToken();
 | 
						|
		if (scanChar('=')) {
 | 
						|
			param.mValue = scanTokenOrQuotedString();
 | 
						|
			if (param.mName.empty()) {
 | 
						|
				LOG(NOTICE) << "empty parameter ignored in:"<<currentLine;
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			param.mValue.clear();
 | 
						|
		}
 | 
						|
		// not needed: skipSpace();
 | 
						|
		LOG(DEBUG)<< LOGVAR(param.mName)<<LOGVAR(param.mValue)<<LOGVAR(start);
 | 
						|
		return ! param.mName.empty();
 | 
						|
	}
 | 
						|
 | 
						|
	// The URI may have almost any chars except it may not have embedded space.
 | 
						|
	string parseLineURI(SipParamList ¶ms, SipParamList &headers)
 | 
						|
	{
 | 
						|
		//params.clear(); headers.clear();	// make sure
 | 
						|
 | 
						|
		if (strncaseeql(pp,"sip:",4)) {
 | 
						|
			pp += 4;
 | 
						|
		} else if (strncaseeql(pp,"sips:",5)) {
 | 
						|
			pp += 5;
 | 
						|
		} else {
 | 
						|
			LOG(ERR) << "Unrecognized URI scheme (not sip or sips):"<<pp;
 | 
						|
			return "";
 | 
						|
		}
 | 
						|
 | 
						|
		string address = scanToSet(";?> \t\f");
 | 
						|
		while (*pp == ';') {
 | 
						|
			pp++;
 | 
						|
			scanUriParam(";?> \t\f",params);
 | 
						|
		}
 | 
						|
		while (*pp == '?' || *pp == '&') {
 | 
						|
			pp++;
 | 
						|
			scanUriParam("&> \t\f",headers);
 | 
						|
		}
 | 
						|
		return address;
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
// We are not currently using this because we dont care about them.
 | 
						|
// The uri-parameters appear within the <uri> and are: transport,user,moethod,ttl,lr.
 | 
						|
// The headers appear after '&' with the <uri>
 | 
						|
// In To: and From: there are generic-parameters after the URI outside the <>, including tag.
 | 
						|
string parseURI(const char *buffer, SipParamList &uriparams, SipParamList &headers)
 | 
						|
{
 | 
						|
	try {
 | 
						|
		SipParseLine parser(buffer);
 | 
						|
		return parser.parseLineURI(uriparams,headers);
 | 
						|
	} catch (...) {
 | 
						|
		LOG(DEBUG) << "Caught SIP Parse error";
 | 
						|
		return "";
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Extract a SIP parameter from a string containing a list of parameters.  They look like ;param1=value;param2=value ...
 | 
						|
// The input string need not start exactly at the beginning of the list.
 | 
						|
// The paramid is specified with the semi-colon and =, example: ";tag="
 | 
						|
static string extractParam(const char *pp, const char *paramid)
 | 
						|
{
 | 
						|
	const int taghdrlen = strlen(paramid);	// strlen(";tag=");
 | 
						|
	if (const char *tp = strstr(pp,paramid)) {
 | 
						|
		pp += taghdrlen;
 | 
						|
		const char *ep = strchr(pp,';');
 | 
						|
		return ep ? string(pp,ep-tp) : string(pp);
 | 
						|
	}
 | 
						|
	return string("");	// Not found.
 | 
						|
}
 | 
						|
 | 
						|
void SipPreposition::rebuild()
 | 
						|
{
 | 
						|
	if (mTag.empty()) {
 | 
						|
		mFullHeader = format("%s <%s>",mDisplayName,mUri.uriValue());
 | 
						|
	} else {
 | 
						|
		mFullHeader = format("%s <%s>;tag=%s",mDisplayName,mUri.uriValue(),mTag);
 | 
						|
	}
 | 
						|
	LOG(DEBUG) <<LOGVAR(mDisplayName) <<LOGVAR(mUri.uriValue()) <<LOGVAR(mFullHeader);
 | 
						|
}
 | 
						|
 | 
						|
// Crack a URI or header into component parts:  before <uri>tail
 | 
						|
// The pointers can be NULL.
 | 
						|
// Return false if it is invalid.
 | 
						|
bool crackUri(string header, string *before, string *uri, string *tail)
 | 
						|
{
 | 
						|
	//string junk;
 | 
						|
	//int n = myscanf("%s <%[^>]>%s",before?before:&junk,uri?uri:&junk,tail?tail:&junk);
 | 
						|
	size_t nUriBegin = header.find_first_of('<');
 | 
						|
	if (nUriBegin == string::npos) {
 | 
						|
		// Old format allows the URI without <> but it is not possible to specify the tag parameter that way.
 | 
						|
		// It should begin with "sip:" or "sips:" but we are not checking.
 | 
						|
		if (uri) *uri = header;
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	size_t nUriEnd = header.find_first_of('>',nUriBegin);
 | 
						|
	if (nUriBegin == string::npos) { return false; }
 | 
						|
	// Warning: The display name may be a quoted string or multiple unquoted tokens.
 | 
						|
	if (before) *before = header.substr(0,trimrightn(header.c_str(),nUriBegin));
 | 
						|
	if (uri) *uri = header.substr(nUriBegin+1,nUriEnd-nUriBegin-1);
 | 
						|
	if (tail) *tail = header.substr(nUriEnd+1);
 | 
						|
	return true;	// happiness
 | 
						|
}
 | 
						|
 | 
						|
// Parse immediately, only we're not going to do a full parse on this either.  All we care about is the tag.
 | 
						|
// Note that the tag param is outside the <uri>
 | 
						|
void SipPreposition::prepSet(const string header)
 | 
						|
{
 | 
						|
	mFullHeader = header;
 | 
						|
	mDisplayName.clear();
 | 
						|
	mTag.clear();
 | 
						|
	mUri.clear();
 | 
						|
	if (header.empty()) { return; }
 | 
						|
	try {
 | 
						|
		if (1) {
 | 
						|
			// We dont need the parser for this.  It is trivial.
 | 
						|
			string uri, tail;
 | 
						|
			if (!crackUri(header,&mDisplayName,&uri,&tail)) {
 | 
						|
				LOG(ERR) << "Bad SIP Contact field:"<<header;
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			mUri.uriSet(uri);
 | 
						|
			mTag = extractParam(tail.c_str(),";tag=");
 | 
						|
		} else {
 | 
						|
			// This code works fine, its just overkill.
 | 
						|
			SipParseLine parser(header.c_str());
 | 
						|
			string thing = parser.scanNonSpace();
 | 
						|
			parser.skipSpace();
 | 
						|
			if (*parser.pp == '<') {
 | 
						|
				mDisplayName = thing;	// Warning: this may or may not have quotes.
 | 
						|
				const char *ep = strchr(parser.pp,'>');
 | 
						|
				if (!ep) {
 | 
						|
					LOG(ERR) << "Bad SIP Contact field:"<<header;
 | 
						|
					ep = parser.pp + strlen(parser.pp);	// guess
 | 
						|
				}
 | 
						|
				mUri.uriSet(string(parser.pp+1,ep-parser.pp-1));	// SipUri strips the < > off the ends of the URI.
 | 
						|
				parser.pp = ep;
 | 
						|
				mTag = extractParam(parser.pp,";tag=");
 | 
						|
			} else {
 | 
						|
				mUri.uriSet(thing);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} catch (...) {
 | 
						|
		LOG(DEBUG) << "Caught SIP Parse error";
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void parseToParams(string stuff, SipParamList ¶ms)
 | 
						|
{
 | 
						|
	SipParseLine parser(stuff.c_str());
 | 
						|
	try {
 | 
						|
		do  {
 | 
						|
			SipParam param;
 | 
						|
			if (! parser.scanGenericParam(param)) break;
 | 
						|
			params.push_back(param);
 | 
						|
		} while (parser.scanChar(','));
 | 
						|
	} catch(SipParseError) {
 | 
						|
		// error was already logged.
 | 
						|
		LOG(DEBUG) << "Caught SIP Parse error";
 | 
						|
		return;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// This is only for www-authenticate, not for Authentication-Info
 | 
						|
void parseAuthenticate(string stuff, SipParamList ¶ms)
 | 
						|
{
 | 
						|
	SipParseLine parser(stuff.c_str());
 | 
						|
 | 
						|
	try {
 | 
						|
		string challengeType = parser.scanToken();
 | 
						|
		if (! strceql(challengeType,"Digest")) {
 | 
						|
			LOG(ERR) << format("unrecognized challenge type:%s",challengeType.c_str());
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		do  {
 | 
						|
			SipParam param;
 | 
						|
			if (! parser.scanGenericParam(param)) break;
 | 
						|
			params.push_back(param);
 | 
						|
		} while (parser.scanChar(','));
 | 
						|
	} catch(SipParseError) {
 | 
						|
		// error was already logged.
 | 
						|
		LOG(DEBUG) << "Caught SIP Parse error";
 | 
						|
		return;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// You can pass in the comma-separated list of vias and it will parse just the first.
 | 
						|
void SipVia::viaParse(string vialine)
 | 
						|
{
 | 
						|
	LOG(DEBUG) <<LOGVAR(vialine);
 | 
						|
	this->assign(vialine);
 | 
						|
	// Spaces are allowed anywhere in the via spec even though most people dont insert htem.
 | 
						|
	// Example: "SIP / 2.0 / UDP host : port ; branch = branchstring"
 | 
						|
	// The port may be empty. There may be options after the port.
 | 
						|
	try {
 | 
						|
		SipParseLine parser(vialine.c_str());
 | 
						|
		parser.scanToken();	// protocol-name
 | 
						|
		parser.scanChar('/');
 | 
						|
		parser.scanToken();	// protocol-version
 | 
						|
		parser.scanChar('/');
 | 
						|
		parser.scanToken();	// transport
 | 
						|
		mSentBy = parser.scanToken();	// host
 | 
						|
		if (parser.scanChar(':')) {
 | 
						|
			mSentBy.append(":");
 | 
						|
			mSentBy.append(parser.scanToken());		// port
 | 
						|
		}
 | 
						|
		// Now the list of via-params
 | 
						|
		//SipParam param;
 | 
						|
		//while (parser.scanGenericParam(param)) {
 | 
						|
		//	if (strcaseeql(param.mName.c_str(),"branch")) {		// not sure if this is case insensitive, but be safe.
 | 
						|
		//		mViaBranch = param.mValue;
 | 
						|
		//		break;	// We can break; we dont care about any other parameters.
 | 
						|
		//	}
 | 
						|
		//}
 | 
						|
		while (parser.scanChar(';')) {
 | 
						|
			SipParam param;
 | 
						|
			while (parser.scanGenericParam(param)) {
 | 
						|
				if (strcaseeql(param.mName.c_str(),"branch")) {     // not sure if this is case insensitive, but be safe.
 | 
						|
					mViaBranch = param.mValue;
 | 
						|
					break;  // We can break; we dont care about any other parameters.
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} catch(...) {
 | 
						|
		LOG(ERR) << "Error parsing via:"<<vialine;
 | 
						|
	}
 | 
						|
 | 
						|
	// Another way I didnt use:
 | 
						|
	//int n = myscanf("%[^/ \t] / %[^/ \t] / %s %[^:; \t] : %[^; \t]",
 | 
						|
		//mProtocolName, mProtocolVersion, mTransport, mHost, mPort);
 | 
						|
}
 | 
						|
 | 
						|
class SipParseMessage : SipParseLine {
 | 
						|
	char *start;	// Start of the parse buffer.
 | 
						|
	char *eolp;		// Pointer to end of current line.
 | 
						|
	unsigned linecnt;
 | 
						|
 | 
						|
	void spInit(char *buffer) {
 | 
						|
		start = buffer;
 | 
						|
		linecnt = 0;
 | 
						|
		eolp = NULL;
 | 
						|
		spLineInit(buffer);
 | 
						|
	}
 | 
						|
 | 
						|
	void logError(const char *msg) {
 | 
						|
		LOG(ERR) << "SIP Parse error at line "<<linecnt<<" "<<msg<<". Parsing:'"<<pp<<"'"<< " SIP message="<<start;
 | 
						|
	}
 | 
						|
 | 
						|
	void spError(const char *msg) {
 | 
						|
		if (eolp) { *eolp = '\r'; }		// Make the buffer printable again.
 | 
						|
		logError(msg);
 | 
						|
		throw SipParseError();
 | 
						|
	}
 | 
						|
 | 
						|
	// Return true if there is another line, and nul terminate it; false when the header is complete.
 | 
						|
	// Buffer is modified in place.  Line continuations are removed by substituting spaces for the CR,NL.
 | 
						|
	bool nextLine()
 | 
						|
	{
 | 
						|
		if (eolp) {
 | 
						|
			// Put the CR back so the message is printable in error messages.
 | 
						|
			*eolp = '\r'; 		// Put the CR back in the last line so the message is printable in error messages.
 | 
						|
			spLineInit(eolp+2);	// sets pp to start of next line.
 | 
						|
		}
 | 
						|
		eolp = Unconst(pp);
 | 
						|
		while ((eolp = strstr(eolp,"\r\n"))) {
 | 
						|
			LOG(DEBUG) <<LOGVAR((void*)start)<<LOGVAR((void*)pp)<<LOGVAR((void*)eolp);
 | 
						|
			linecnt++;
 | 
						|
			if (eolp == pp) { return false; }		// Found the terminating blank line.
 | 
						|
			if (eolp[2] == ' ' || eolp[2] == '\t') { *eolp++ = ' '; *eolp++ = ' '; continue; }	// Combine continuation lines.
 | 
						|
			*eolp = 0;								// Terminate.
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		spError("unexpected end of message");
 | 
						|
		/*NOTREACHED*/
 | 
						|
		return "";	// Never used.
 | 
						|
	}
 | 
						|
 | 
						|
	void scanSipVersion()
 | 
						|
	{
 | 
						|
		if (!  (strncaseeql(pp,"SIP",3))) { spError("Expecting 'SIP'"); }
 | 
						|
		pp += 3;	// skip over 'SIP'
 | 
						|
		if (*pp++ != '/') { spError("Invalid SIP-version"); }
 | 
						|
		string version = scanNonSpace(); // Discard the version number.  We dont really care what it is.
 | 
						|
		// We hope it will be "2.0/UDP" or "2.0/TCP";
 | 
						|
		if (strncmp(version.c_str(),"2.0",3)) { LOG(NOTICE) << "unexpected SIP version="<<version; }
 | 
						|
	}
 | 
						|
 | 
						|
	// Buffer is modified in place - lines are terminated at eols.
 | 
						|
	public:
 | 
						|
	SipMessage * sipParse()
 | 
						|
	{
 | 
						|
		//LOG(DEBUG);
 | 
						|
		SipMessage *sipmsg = new SipMessage();
 | 
						|
		// Scan the first line.
 | 
						|
		if (!nextLine()) { spError("Empty SIP message"); }
 | 
						|
		//LOG(DEBUG);
 | 
						|
		// The message is either a request or a response.
 | 
						|
		// Response Status-Line     =  SIP-Version SP Status-Code SP Reason-Phrase CRLF
 | 
						|
		// SIP-Version    =  "SIP" "/" 1*DIGIT "." 1*DIGIT
 | 
						|
		// Request Request-Line = Method SP Request-URI SP SIP-Version CRLF
 | 
						|
		if (strncaseeql(pp,"SIP",3)) {
 | 
						|
			// This is a response.
 | 
						|
			scanSipVersion();
 | 
						|
			sipmsg->msmCode = scanInt();
 | 
						|
			skipSpace();
 | 
						|
			sipmsg->msmReason = string(pp);	// Rest of the line is the reason.
 | 
						|
		} else {
 | 
						|
			// This is a request.
 | 
						|
			sipmsg->msmReqMethod = scanNonSpace();
 | 
						|
			sipmsg->msmReqUri = scanNonSpace();
 | 
						|
			skipSpace();
 | 
						|
			scanSipVersion();
 | 
						|
		}
 | 
						|
		skipSpace();
 | 
						|
		//LOG(DEBUG);
 | 
						|
 | 
						|
		// Get the rest of the header lines.
 | 
						|
		while (nextLine()) {
 | 
						|
			// We have a header line.  The headers names themselves are case insensitive.
 | 
						|
			LOG(DEBUG) << "nextLine="<<pp;
 | 
						|
			string name = scanToken();
 | 
						|
			LOG(DEBUG) << LOGVAR(name);
 | 
						|
			if (name.empty() || !scanChar(':')) { spError("Line without header"); }
 | 
						|
 | 
						|
			if (strceql(name,"to") || strceql(name,"t")) {
 | 
						|
				sipmsg->msmTo.prepSet(string(pp));
 | 
						|
			} else if (strceql(name,"from") || strceql(name,"f")) {
 | 
						|
				sipmsg->msmFrom.prepSet(string(pp));
 | 
						|
			} else if (strceql(name,"contact") || strceql(name,"m")) {
 | 
						|
				sipmsg->msmContactValue = string(pp);
 | 
						|
			} else if (strceql(name,"CSeq")) {
 | 
						|
				sipmsg->msmCSeqNum = scanInt();
 | 
						|
				sipmsg->msmCSeqMethod = scanToken();
 | 
						|
			} else if (strceql(name,"call-id") || strceql(name,"i")) {
 | 
						|
				// The call-id string is defined as word[@word], but unless we are really interested
 | 
						|
				// in validating incoming SIP messages, we can simply scan for non-space.
 | 
						|
				sipmsg->msmCallId = scanNonSpace();
 | 
						|
			} else if (strceql(name,"via") || strceql(name,"v")) {
 | 
						|
				// Multiple vias can appear on separate lines or be comma separated in one line,
 | 
						|
				// so for simplicity we will just keep them all in a comma separated list.
 | 
						|
				commaListPushBack(&sipmsg->msmVias,pp);
 | 
						|
			} else if (strceql(name,"record-route")) {
 | 
						|
				commaListPushBack(&sipmsg->msmRecordRoutes,pp);
 | 
						|
			} else if (strceql(name,"route")) {
 | 
						|
				commaListPushBack(&sipmsg->msmRoutes,pp);
 | 
						|
			// No need to treat this specially here, even though authenticate info does not follow normal SIP parsing rules.
 | 
						|
			//} else if (strceql(name,"www-authenticate")) {
 | 
						|
			//	// authenticate info does not follow normal SIP parsing rules.
 | 
						|
			//	sipmsg->msmAuthenticateValue = string(pp);
 | 
						|
			} else if (strceql(name,"content-type")) {
 | 
						|
				sipmsg->msmContentType = string(pp);
 | 
						|
			} else {
 | 
						|
				SipParam param(name,string(pp));
 | 
						|
				sipmsg->msmHeaders.push_back(param);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		LOG(DEBUG) << "end";
 | 
						|
		if (pp[0] == '\r' && pp[1] == '\n') {
 | 
						|
			pp += 2;
 | 
						|
			sipmsg->msmBody = string(pp);
 | 
						|
		} else {
 | 
						|
			// Dont abort for this.  Just log an error.
 | 
						|
			logError("Missing message body");
 | 
						|
		}
 | 
						|
		return sipmsg;
 | 
						|
	}
 | 
						|
 | 
						|
	SipParseMessage(char *buffer) { spInit(buffer); }
 | 
						|
};
 | 
						|
 | 
						|
SipMessage *sipParseBuffer(const char *buffer)
 | 
						|
{
 | 
						|
	LOG(DEBUG) << "DEBUG";
 | 
						|
	// The SIP Parser modifies buffer, but puts it back the way it found it.
 | 
						|
	// This is OK because no thread contention for the buffer is possible because all callers pass a unique string for parsing.
 | 
						|
	SipParseMessage parser(Unconst(buffer));
 | 
						|
	SipMessage *result;
 | 
						|
	try {
 | 
						|
		result = parser.sipParse();
 | 
						|
	} catch (SipParseError) {
 | 
						|
		// error was already logged.
 | 
						|
		LOG(DEBUG) << "SipParseError caught";
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void codecsToSdp(CodecSet codecs, string *codeclist, string *attrs)
 | 
						|
{
 | 
						|
	attrs->clear();
 | 
						|
	attrs->reserve(80);
 | 
						|
	codeclist->reserve(20);
 | 
						|
	// We are using the same code for offers and answers, so for now only included the one we want:
 | 
						|
	/**
 | 
						|
	if (codecs.isSet(PCMULAW)) {
 | 
						|
		attrs->append("a=rtpmap:0 PCMU/8000\r\n");
 | 
						|
		if (!codeclist->empty()) { codeclist->append(" "); }
 | 
						|
		codeclist->append("0");
 | 
						|
	}
 | 
						|
	***/
 | 
						|
	if (codecs.isSet(GSM_FR) || codecs.isSet(GSM_HR) || codecs.isEmpty()) {
 | 
						|
		attrs->append("a=rtpmap:3 GSM/8000\r\n");
 | 
						|
		if (!codeclist->empty()) { codeclist->append(" "); }
 | 
						|
		codeclist->append("3");
 | 
						|
	}
 | 
						|
	// TODO: Does the half-rate codec need a special RTP format is conversion performed lower down?
 | 
						|
	// RFC5993 7.1 says it is "a=rtpmap:<dynamic-port-number. GSM-HR-08/8000"
 | 
						|
	/**
 | 
						|
	if (codecs.isSet(AMR_FR) || codecs.isSet(AMR_HR)) {
 | 
						|
		// Dynamically allocated SDP starts at 96.
 | 
						|
		attrs->append("a=rtpmap:96 AMR/8000\r\n");
 | 
						|
		if (!codeclist->empty()) { codeclist->append(" "); }
 | 
						|
		codeclist->append("96");
 | 
						|
	}
 | 
						|
	**/
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// We dont fully parse it; just pull out the o,m,c,a lines.
 | 
						|
void SdpInfo::sdpParse(const char *buffer)
 | 
						|
{
 | 
						|
	const char *bp, *eol;
 | 
						|
	for (bp = buffer; bp && *bp; bp = eol ? eol+1 : NULL) {
 | 
						|
		eol = strchr(bp,'\n');
 | 
						|
		switch (*bp) {
 | 
						|
			case 'o':
 | 
						|
				if (myscanf(bp,"o=%s %s %s IN IP4 %s",&sdpUsername,&sdpSessionId, &sdpVersionId, &sdpHost) < 4) {
 | 
						|
					LOG(ERR) << "SDP unrecognized o= line, sdp:"<<buffer;
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			case 'm': {
 | 
						|
				string portTmp;
 | 
						|
				if (myscanf(bp,"m=%*s %s %*s %s",&portTmp, &sdpCodecList) < 2) {
 | 
						|
					LOG(ERR) << "SDP unrecognized m= line, sdp:"<<buffer;
 | 
						|
				}
 | 
						|
				sdpRtpPort = atoi(portTmp.c_str());
 | 
						|
				break;
 | 
						|
				}
 | 
						|
			case 'c':
 | 
						|
				if (myscanf(bp,"c=IN IP4 %s",&sdpHost) < 1) {
 | 
						|
					// If we were paranoid we could check if it matches the o= line.
 | 
						|
					LOG(ERR) << "SDP unrecognized c= line, sdp:"<<buffer;
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			case 'a':
 | 
						|
				// It would crash if eol were null because SDP was truncated, so check.
 | 
						|
				if (eol) { sdpAttrs += string(bp,eol-bp+1); }
 | 
						|
				break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void SdpInfo::sdpInitOffer(const SipBase *dialog)
 | 
						|
{
 | 
						|
	sdpUsername = dialog->sipLocalUsername();
 | 
						|
	sdpRtpPort = dialog->mRTPPort;
 | 
						|
	sdpHost = dialog->localIP();
 | 
						|
	codecsToSdp(dialog->mCodec,&sdpCodecList,&sdpAttrs);
 | 
						|
	static const string zero("0");
 | 
						|
	sdpSessionId = sdpVersionId = zero;
 | 
						|
}
 | 
						|
 | 
						|
void SdpInfo::sdpInitRefer(const SipBase *dialog, int remotePort)
 | 
						|
{
 | 
						|
	sdpInitOffer(dialog);
 | 
						|
	// Same as above, except:
 | 
						|
	sdpRtpPort = remotePort;
 | 
						|
	sdpVersionId = format("%lu",time(NULL));
 | 
						|
}
 | 
						|
 | 
						|
// Note that sdp is not completely order independent.
 | 
						|
string SdpInfo::sdpValue()
 | 
						|
{
 | 
						|
	string result; result.reserve(100);
 | 
						|
	char buf[302];
 | 
						|
	result.append("v=0\r\n");		// SDP protocol version
 | 
						|
	// originator, session id, ip address.
 | 
						|
	snprintf(buf,300,"o=%s %s %s IN IP4 %s\r\n",sdpUsername.c_str(),sdpSessionId.c_str(),sdpVersionId.c_str(),sdpHost.c_str());
 | 
						|
	result.append(buf);
 | 
						|
	// RFC3264 5: And I quote:
 | 
						|
	//	"The SDP 's=' line conveys the subject of the session, which is reasonably defined for multicast,
 | 
						|
	// but ill defined for unicast.  For unicast sessions, it is RECOMMENDED that it consist of a single space
 | 
						|
	// character (0x20) or a dash (-)."
 | 
						|
	// I dont know why we are setting it to 'Talk Time'.
 | 
						|
	result.append("s=Talk Time\r\n");
 | 
						|
	result.append("t=0 0\r\n");		// time session is active; 0 means unbounded.
 | 
						|
	snprintf(buf,300,"m=audio %u RTP/AVP %s\r\n",sdpRtpPort,sdpCodecList.c_str());	// media name and transport address.
 | 
						|
	result.append(buf);	// media name and transport address.
 | 
						|
	// Optional connection information.  Redundant because we included in 'o='.
 | 
						|
	snprintf(buf,300,"c=IN IP4 %s\r\n",sdpHost.c_str());
 | 
						|
	result.append(buf);
 | 
						|
	result.append(sdpAttrs);
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
};	// namespace SIP
 |