diff --git a/configure.in b/configure.in index 82e6ab84..038efced 100644 --- a/configure.in +++ b/configure.in @@ -1023,6 +1023,7 @@ AC_CONFIG_FILES([packing/rpm/yate.spec libs/ilbc/Makefile libs/ysip/Makefile libs/yrtp/Makefile + libs/ysdp/Makefile libs/yiax/Makefile libs/yxml/Makefile libs/yjingle/Makefile diff --git a/libs/ysdp/.cvsignore b/libs/ysdp/.cvsignore new file mode 100644 index 00000000..efbd82da --- /dev/null +++ b/libs/ysdp/.cvsignore @@ -0,0 +1,8 @@ +Makefile +YateLocal* +core* +*.o +*.a +*.orig +*~ +.*.swp diff --git a/libs/ysdp/Makefile.in b/libs/ysdp/Makefile.in new file mode 100644 index 00000000..484f9cf4 --- /dev/null +++ b/libs/ysdp/Makefile.in @@ -0,0 +1,56 @@ +# Makefile +# This file holds the make rules for the libyatesip + +DEBUG := + +CXX := @CXX@ -Wall +AR := ar +DEFS := +INCLUDES := -I@top_srcdir@ -I../.. -I@srcdir@ +CFLAGS := @CFLAGS@ @MODULE_CPPFLAGS@ @INLINE_FLAGS@ +LDFLAGS:= @LDFLAGS@ -L../.. -lyate +INCFILES := @top_srcdir@/yatemime.h @top_srcdir@/yatephone.h @srcdir@/yatesdp.h + +PROGS= +LIBS = libyatesdp.a +OBJS = media.o session.o parser.o + +LOCALFLAGS = +LOCALLIBS = +COMPILE = $(CXX) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS) +LINK = $(CXX) $(LDFLAGS) + +prefix = @prefix@ +exec_prefix = @exec_prefix@ + +# include optional local make rules +-include YateLocal.mak + +.PHONY: all debug ddebug xdebug +all: $(LIBS) $(PROGS) + +debug: + $(MAKE) all DEBUG=-g3 MODSTRIP= + +ddebug: + $(MAKE) all DEBUG='-g3 -DDEBUG' MODSTRIP= + +xdebug: + $(MAKE) all DEBUG='-g3 -DXDEBUG' MODSTRIP= + +.PHONY: strip +strip: all + strip --strip-debug --discard-locals $(PROGS) + +.PHONY: clean +clean: + @-$(RM) $(PROGS) $(LIBS) $(OBJS) core 2>/dev/null + +%.o: @srcdir@/%.cpp $(INCFILES) + $(COMPILE) -c $< + +Makefile: @srcdir@/Makefile.in ../../config.status + cd ../.. && ./config.status + +libyatesdp.a: $(OBJS) + $(AR) rcs $@ $^ diff --git a/libs/ysdp/media.cpp b/libs/ysdp/media.cpp new file mode 100644 index 00000000..c0ebd9b6 --- /dev/null +++ b/libs/ysdp/media.cpp @@ -0,0 +1,189 @@ +/** + * media.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * SDP media handling + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2004-2009 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +namespace TelEngine { + +/* + * SDPMedia + */ +SDPMedia::SDPMedia(const char* media, const char* transport, const char* formats, + int rport, int lport) + : NamedList(media), + m_audio(true), m_modified(false), m_securable(true), m_localChanged(false), + m_transport(transport), m_formats(formats), + m_rfc2833(String::boolText(false)) +{ + DDebug(DebugAll,"SDPMedia::SDPMedia('%s','%s','%s',%d,%d) [%p]", + media,transport,formats,rport,lport,this); + if (String::operator!=("audio")) { + m_audio = false; + m_suffix << "_" << media; + } + int q = m_formats.find(','); + m_format = m_formats.substr(0,q); + if (rport >= 0) + m_rPort = rport; + if (lport >= 0) + m_lPort = lport; +} + +SDPMedia::~SDPMedia() +{ + DDebug(DebugAll,"SDPMedia::~SDPMedia() '%s' [%p]",c_str(),this); +} + +const char* SDPMedia::fmtList() const +{ + if (m_formats) + return m_formats.c_str(); + if (m_format) + return m_format.c_str(); + // unspecified audio assumed to support G711 + if (m_audio) + return "alaw,mulaw"; + return 0; +} + +// Update members with data taken from a SDP, return true if something changed +bool SDPMedia::update(const char* formats, int rport, int lport) +{ + DDebug(DebugAll,"SDPMedia::update('%s',%d,%d) [%p]",formats,rport,lport,this); + bool chg = false; + String tmp(formats); + if (m_formats != tmp) { + if ((tmp.find(',') < 0) && m_formats && m_formats.find(tmp) < 0) + Debug(DebugInfo,"Not changing to '%s' from '%s' [%p]", + formats,m_formats.c_str(),this); + else { + chg = true; + m_formats = tmp; + int q = m_formats.find(','); + m_format = m_formats.substr(0,q); + } + } + if (rport >= 0) { + tmp = rport; + if (m_rPort != tmp) { + chg = true; + m_rPort = tmp; + } + } + if (lport >= 0) { + tmp = lport; + if (m_lPort != tmp) { + m_localChanged = true; + chg = true; + m_lPort = tmp; + } + } + return chg; +} + +// Update members from a dispatched "chan.rtp" message +void SDPMedia::update(const NamedList& msg, bool pickFormat) +{ + DDebug(DebugAll,"SDPMedia::update('%s',%s) [%p]", + msg.c_str(),String::boolText(pickFormat),this); + m_id = msg.getValue("rtpid",m_id); + m_lPort = msg.getValue("localport",m_lPort); + if (pickFormat) { + const char* format = msg.getValue("format"); + if (format) { + m_format = format; + if ((m_formats != m_format) && (msg.getIntValue("remoteport") > 0)) { + Debug(DebugNote,"Choosing started '%s' format '%s' [%p]", + c_str(),format,this); + m_formats = m_format; + } + } + } +} + +// Add or replace a parameter by name and value, set the modified flag +void SDPMedia::parameter(const char* name, const char* value, bool append) +{ + if (!name) + return; + m_modified = true; + if (append) + addParam(name,value); + else + setParam(name,value); +} + +// Add or replace a parameter, set the modified flag +void SDPMedia::parameter(NamedString* param, bool append) +{ + if (!param) + return; + m_modified = true; + if (append) + addParam(param); + else + setParam(param); +} + +void SDPMedia::crypto(const char* desc, bool remote) +{ + String& sdes = remote ? m_rCrypto : m_lCrypto; + if (sdes != desc) { + sdes = desc; + m_modified = true; + } + if (remote && !desc) + m_securable = false; +} + +// Put the list of net media in a parameter list +void SDPMedia::putMedia(NamedList& msg, bool putPort) +{ + msg.addParam("media" + suffix(),"yes"); + msg.addParam("formats" + suffix(),formats()); + msg.addParam("transport" + suffix(),transport()); + if (mappings()) + msg.addParam("rtp_mapping" + suffix(),mappings()); + if (isAudio()) + msg.addParam("rtp_rfc2833",rfc2833()); + if (putPort) + msg.addParam("rtp_port" + suffix(),remotePort()); + if (remoteCrypto()) + msg.addParam("crypto" + suffix(),remoteCrypto()); + // must handle encryption differently + const char* enc = getValue("encryption"); + if (enc) + msg.addParam("encryption" + suffix(),enc); + clearParam("encryption"); + unsigned int n = length(); + for (unsigned int i = 0; i < n; i++) { + const NamedString* param = getParam(i); + if (param) + msg.addParam("sdp" + suffix() + "_" + param->name(),*param); + } +} + +}; // namespace TelEngine + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/ysdp/parser.cpp b/libs/ysdp/parser.cpp new file mode 100644 index 00000000..51d16907 --- /dev/null +++ b/libs/ysdp/parser.cpp @@ -0,0 +1,331 @@ +/** + * parser.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * SDP media handling + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2004-2009 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +namespace TelEngine { + +/* + * SDPParser + */ +// Yate Payloads for the AV profile +const TokenDict SDPParser::s_payloads[] = { + { "mulaw", 0 }, + { "alaw", 8 }, + { "gsm", 3 }, + { "lpc10", 7 }, + { "slin", 11 }, + { "g726", 2 }, + { "g722", 9 }, + { "g723", 4 }, + { "g728", 15 }, + { "g729", 18 }, + { "ilbc", 98 }, + { "ilbc20", 98 }, + { "ilbc30", 98 }, + { "amr", 96 }, + { "amr-o", 96 }, + { "amr/16000", 99 }, + { "amr-o/16000", 99 }, + { "speex", 102 }, + { "speex/16000", 103 }, + { "speex/32000", 104 }, + { "h261", 31 }, + { "h263", 34 }, + { "mpv", 32 }, + { 0, 0 }, +}; + +// SDP Payloads for the AV profile +const TokenDict SDPParser::s_rtpmap[] = { + { "PCMU/8000", 0 }, + { "PCMA/8000", 8 }, + { "GSM/8000", 3 }, + { "LPC/8000", 7 }, + { "L16/8000", 11 }, + { "G726-32/8000", 2 }, + { "G722/8000", 9 }, + { "G723/8000", 4 }, + { "G728/8000", 15 }, + { "G729/8000", 18 }, + { "G729A/8000", 18 }, + { "ILBC/8000", 98 }, + { "AMR/8000", 96 }, + { "AMR-WB/16000", 99 }, + { "SPEEX/8000", 102 }, + { "SPEEX/16000", 103 }, + { "SPEEX/32000", 104 }, + { "H261/90000", 31 }, + { "H263/90000", 34 }, + { "MPV/90000", 32 }, + { 0, 0 }, +}; + +// Parse a received SDP body +ObjList* SDPParser::parse(const MimeSdpBody& sdp, String& addr, ObjList* oldMedia, + const String& media) +{ + DDebug(DebugAll,"SDPParser::parse(%p,%s,%p,'%s')",&sdp,addr.c_str(),oldMedia,media.safe()); + const NamedString* c = sdp.getLine("c"); + if (c) { + String tmp(*c); + if (tmp.startSkip("IN IP4")) { + tmp.trimBlanks(); + // Handle the case media is muted + if (tmp == "0.0.0.0") + tmp.clear(); + addr = tmp; + } + } + Lock lock(this); + ObjList* lst = 0; + bool defcodecs = m_codecs.getBoolValue("default",true); + c = sdp.getLine("m"); + for (; c; c = sdp.getNextLine(c)) { + String tmp(*c); + int sep = tmp.find(' '); + if (sep < 1) + continue; + String type = tmp.substr(0,sep); + tmp >> " "; + if (media && (type != media)) + continue; + int port = 0; + tmp >> port >> " "; + sep = tmp.find(' '); + if (sep < 1) + continue; + bool rtp = true; + String trans(tmp,sep); + tmp = tmp.c_str() + sep; + if ((trans &= "RTP/AVP") || (trans &= "RTP/SAVP") || + (trans &= "RTP/AVPF") || (trans &= "RTP/SAVPF")) + trans.toUpper(); + else if ((trans &= "udptl") || (trans &= "tcp")) { + trans.toLower(); + rtp = false; + } + else { + Debug(this,DebugWarn,"Unknown SDP transport '%s' for media '%s'", + trans.c_str(),type.c_str()); + continue; + } + String fmt; + String aux; + String mappings; + String crypto; + ObjList params; + bool first = true; + int ptime = 0; + int rfc2833 = -1; + while (tmp[0] == ' ') { + int var = -1; + tmp >> " " >> var; + if (var < 0) { + if (rtp || fmt || aux || tmp.null()) + continue; + // brutal but effective + for (char* p = const_cast(tmp.c_str()); *p; p++) { + if (*p == ' ') + *p = ','; + } + Debug(this,DebugInfo,"Assuming format list '%s' for media '%s'", + tmp.c_str(),type.c_str()); + fmt = tmp; + tmp.clear(); + } + int mode = 0; + bool annexB = m_codecs.getBoolValue("g729_annexb",false); + bool amrOctet = m_codecs.getBoolValue("amr_octet",false); + int defmap = -1; + String payload(lookup(var,s_payloads)); + + const ObjList* l = sdp.lines().find(c); + while (l && (l = l->skipNext())) { + const NamedString* s = static_cast(l->get()); + if (s->name() == "m") + break; + if (s->name() != "a") + continue; + String line(*s); + if (line.startSkip("ptime:",false)) + line >> ptime; + else if (line.startSkip("rtpmap:",false)) { + int num = var - 1; + line >> num >> " "; + if (num == var) { + line.trimBlanks().toUpper(); + if (line.startsWith("G729B/")) { + // some devices add a second map for same payload + annexB = true; + continue; + } + if (line.startsWith("TELEPHONE-EVENT/")) { + rfc2833 = var; + continue; + } + const char* pload = 0; + for (const TokenDict* map = s_rtpmap; map->token; map++) { + if (line.startsWith(map->token,false,true)) { + defmap = map->value; + pload = lookup(defmap,s_payloads); + break; + } + } + payload = pload; + } + } + else if (line.startSkip("fmtp:",false)) { + int num = var - 1; + line >> num >> " "; + if (num == var) { + if (line.startSkip("mode=",false)) + line >> mode; + else if (line.startSkip("annexb=",false)) + line >> annexB; + else if (line.startSkip("octet-align=",false)) + amrOctet = (0 != line.toInteger(0)); + } + } + else if (first) { + if (line.startSkip("crypto:",false)) { + if (crypto.null()) + crypto = line; + else + Debug(this,DebugMild,"Ignoring SDES: '%s'",line.c_str()); + } + else { + int pos = line.find(':'); + if (pos >= 0) + params.append(new NamedString(line.substr(0,pos),line.substr(pos+1))); + else + params.append(new NamedString(line)); + } + } + } + if (var < 0) + break; + first = false; + + if (payload == "ilbc") { + const char* forced = m_hacks.getValue("ilbc_forced"); + if (forced) + payload = forced; + else if ((mode == 20) || (ptime == 20)) + payload = "ilbc20"; + else if ((mode == 30) || (ptime == 30)) + payload = "ilbc30"; + else + payload = m_hacks.getValue("ilbc_default","ilbc30"); + } + + if (amrOctet && payload == "amr") + payload = "amr-o"; + + XDebug(this,DebugAll,"Payload %d format '%s'",var,payload.c_str()); + if (payload && m_codecs.getBoolValue(payload,defcodecs && DataTranslator::canConvert(payload))) { + if (fmt) + fmt << ","; + fmt << payload; + if (var != defmap) { + if (mappings) + mappings << ","; + mappings << payload << "=" << var; + } + if ((payload == "g729") && m_hacks.getBoolValue("g729_annexb",annexB)) + aux << ",g729b"; + } + } + fmt += aux; + DDebug(this,DebugAll,"Formats '%s' mappings '%s'",fmt.c_str(),mappings.c_str()); + SDPMedia* net = 0; + // try to take the media descriptor from the old list + if (oldMedia) { + ObjList* om = oldMedia->find(type); + if (om) + net = static_cast(om->remove(false)); + } + bool append = false; + if (net) + net->update(fmt,port); + else { + net = new SDPMedia(type,trans,fmt,port); + append = true; + } + while (NamedString* par = static_cast(params.remove(false))) + net->parameter(par,append); + net->setModified(false); + net->mappings(mappings); + net->rfc2833(rfc2833); + net->crypto(crypto,true); + if (!lst) + lst = new ObjList; + lst->append(net); + // found media - get out + if (media) + break; + } + return lst; +} + +// Update configuration +void SDPParser::initialize(const NamedList* codecs, const NamedList* hacks, const NamedList* general) +{ + Lock lock(this); + m_codecs.clear(); + m_hacks.clear(); + if (codecs) + m_codecs.copyParams(*codecs); + if (hacks) + m_hacks.copyParams(*hacks); + bool defcodecs = m_codecs.getBoolValue("default",true); + m_audioFormats = ""; + String audio = "audio"; + for (const TokenDict* dict = s_payloads; dict->token; dict++) { + DataFormat fmt(dict->token); + const FormatInfo* info = fmt.getInfo(); + if (info && (audio == info->type)) { + if (m_codecs.getBoolValue(fmt,defcodecs && DataTranslator::canConvert(fmt))) + m_audioFormats.append(fmt,","); + } + } + if (!m_audioFormats) { + Debug(this,DebugWarn,"No default audio codecs, using defaults"); + m_audioFormats = "alaw,mulaw"; + } + DDebug(this,DebugNote,"Default audio codecs: %s",m_audioFormats.c_str()); + m_ignorePort = m_hacks.getBoolValue("ignore_sdp_port",false); + m_rfc2833 = true; + m_secure = false; + m_sdpForward = false; + if (general) { + m_rfc2833 = general->getBoolValue("rfc2833",m_rfc2833); + m_secure = general->getBoolValue("secure",m_secure); + m_sdpForward = general->getBoolValue("forward_sdp",m_sdpForward); + } +} + +}; // namespace TelEngine + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/ysdp/session.cpp b/libs/ysdp/session.cpp new file mode 100644 index 00000000..c4fbbbed --- /dev/null +++ b/libs/ysdp/session.cpp @@ -0,0 +1,741 @@ +/** + * session.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * SDP media handling + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2004-2009 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +namespace TelEngine { + +/* + * SDPSession + */ +SDPSession::SDPSession(SDPParser* parser) + : m_parser(parser), m_mediaStatus(MediaMissing), + m_rtpForward(false), m_sdpForward(false), m_rtpMedia(0), + m_sdpSession(0), m_sdpVersion(0), + m_secure(m_parser->m_secure), m_rfc2833(m_parser->m_rfc2833) +{ +} + +SDPSession::SDPSession(SDPParser* parser, NamedList& params) + : m_parser(parser), m_mediaStatus(MediaMissing), + m_rtpForward(false), m_sdpForward(false), m_rtpMedia(0), + m_sdpSession(0), m_sdpVersion(0) +{ + m_rtpForward = params.getBoolValue("rtp_forward"); + m_secure = params.getBoolValue("secure",parser->m_secure); + m_rfc2833 = params.getBoolValue("rfc2833",parser->m_rfc2833); +} + +SDPSession::~SDPSession() +{ + resetSdp(); +} + +// Set new media list. Return true if changed +bool SDPSession::setMedia(ObjList* media) +{ + if (media == m_rtpMedia) + return false; + DDebug(m_parser,DebugAll,"SDPSession::setMedia(%p) [%p]",media,this); + ObjList* tmp = m_rtpMedia; + m_rtpMedia = media; + bool chg = m_rtpMedia != 0; + if (tmp) { + chg = false; + for (ObjList* o = tmp->skipNull(); o; o = o->skipNext()) { + SDPMedia* m = static_cast(o->get()); + if (media && m->sameAs(static_cast((*media)[*m]),m_parser->ignorePort())) + continue; + chg = true; + mediaChanged(*m); + } + TelEngine::destruct(tmp); + } + return chg; +} + +// Put the list of net media in a parameter list +void SDPSession::putMedia(NamedList& msg, ObjList* mList, bool putPort) +{ + if (!mList) + return; + for (mList = mList->skipNull(); mList; mList = mList->skipNext()) { + SDPMedia* m = static_cast(mList->get()); + m->putMedia(msg,putPort); + } +} + +// Build and dispatch a chan.rtp message for a given media. Update media on success +bool SDPSession::dispatchRtp(SDPMedia* media, const char* addr, bool start, + bool pick, RefObject* context) +{ + DDebug(m_parser,DebugAll,"SDPSession::dispatchRtp(%p,%s,%u,%u,%p) [%p]", + media,addr,start,pick,context,this); + Message* m = buildChanRtp(media,addr,start,context); + if (!(m && Engine::dispatch(m))) { + TelEngine::destruct(m); + return false; + } + media->update(*m,start); + if (!pick) { + TelEngine::destruct(m); + return true; + } + m_rtpForward = false; + m_rtpLocalAddr = m->getValue("localip",m_rtpLocalAddr); + m_mediaStatus = MediaStarted; + const char* sdpPrefix = m->getValue("osdp-prefix","osdp"); + if (sdpPrefix) { + unsigned int n = m->length(); + for (unsigned int j = 0; j < n; j++) { + const NamedString* param = m->getParam(j); + if (!param) + continue; + String tmp = param->name(); + if (tmp.startSkip(sdpPrefix,false) && tmp.startSkip("_",false) && tmp) + media->parameter(tmp,*param,false); + } + } + if (m_secure) { + int tag = m->getIntValue("crypto_tag",1); + tag = m->getIntValue("ocrypto_tag",tag); + const String* suite = m->getParam("ocrypto_suite"); + const String* key = m->getParam("ocrypto_key"); + const String* params = m->getParam("ocrypto_params"); + if (suite && key && (tag > 0)) { + String sdes(tag); + sdes << " " << *suite << " " << *key; + if (params) + sdes << " " << *params; + media->crypto(sdes,false); + } + } + TelEngine::destruct(m); + return true; +} + +// Repeatedly calls dispatchRtp() for each media in the list +// Update it on success. Remove it on failure +bool SDPSession::dispatchRtp(const char* addr, bool start, RefObject* context) +{ + if (!m_rtpMedia) + return false; + DDebug(m_parser,DebugAll,"SDPSession::dispatchRtp(%s,%u,%p) [%p]", + addr,start,context,this); + bool ok = false; + ObjList* o = m_rtpMedia->skipNull(); + while (o) { + SDPMedia* m = static_cast(o->get()); + if (dispatchRtp(m,addr,start,true,context)) { + ok = true; + o = o->skipNext(); + } + else { + Debug(m_parser,DebugMild, + "Removing failed SDP media '%s' format '%s' from offer [%p]", + m->c_str(),m->format().safe(),this); + o->remove(); + o = o->skipNull(); + } + } + return ok; +} + +// Try to start RTP for all media +bool SDPSession::startRtp(RefObject* context) +{ + if (m_rtpForward || !m_rtpMedia || (m_mediaStatus != MediaStarted)) + return false; + DDebug(m_parser,DebugAll,"SDPSession::startRtp(%p) [%p]",context,this); + bool ok = false; + for (ObjList* o = m_rtpMedia->skipNull(); o; o = o->skipNext()) { + SDPMedia* m = static_cast(o->get()); + ok = dispatchRtp(m,m_rtpAddr,true,false,context) || ok; + } + return ok; +} + +// Update from parameters. Build a default SDP if no media is found in params +bool SDPSession::updateSDP(const NamedList& params) +{ + DDebug(m_parser,DebugAll,"SDPSession::updateSdp('%s') [%p]",params.c_str(),this); + bool defaults = true; + const char* sdpPrefix = params.getValue("osdp-prefix","osdp"); + ObjList* lst = 0; + unsigned int n = params.length(); + String defFormats; + m_parser->getAudioFormats(defFormats); + for (unsigned int i = 0; i < n; i++) { + const NamedString* p = params.getParam(i); + if (!p) + continue; + // search for rtp_port or rtp_port_MEDIANAME parameters + String tmp(p->name()); + if (!tmp.startSkip("media",false)) + continue; + if (tmp && (tmp[0] != '_')) + continue; + // since we found at least one media declaration disable defaults + defaults = false; + // now tmp holds the suffix for the media, null for audio + bool audio = tmp.null(); + // check if media is supported, default only for audio + if (!p->toBoolean(audio)) + continue; + String fmts = params.getValue("formats" + tmp); + if (audio && fmts.null()) + fmts = defFormats; + if (fmts.null()) + continue; + String trans = params.getValue("transport" + tmp,"RTP/AVP"); + String crypto; + if (m_secure) + crypto = params.getValue("crypto" + tmp); + if (audio) + tmp = "audio"; + else + tmp >> "_"; + SDPMedia* rtp = 0; + // try to take the media descriptor from the old list + if (m_rtpMedia) { + ObjList* om = m_rtpMedia->find(tmp); + if (om) + rtp = static_cast(om->remove(false)); + } + bool append = false; + if (rtp) + rtp->update(fmts); + else { + rtp = new SDPMedia(tmp,trans,fmts); + append = true; + } + rtp->crypto(crypto,false); + if (sdpPrefix) { + for (unsigned int j = 0; j < n; j++) { + const NamedString* param = params.getParam(j); + if (!param) + continue; + tmp = param->name(); + if (tmp.startSkip(sdpPrefix + rtp->suffix() + "_",false) && (tmp.find('_') < 0)) + rtp->parameter(tmp,*param,append); + } + } + if (!lst) + lst = new ObjList; + lst->append(rtp); + } + if (defaults && !lst) { + lst = new ObjList; + lst->append(new SDPMedia("audio","RTP/AVP",params.getValue("formats",defFormats))); + } + return setMedia(lst); +} + + +// Update RTP/SDP data from parameters +// Return true if media changed +bool SDPSession::updateRtpSDP(const NamedList& params) +{ + DDebug(m_parser,DebugAll,"SDPSession::updateRtpSDP(%s) [%p]",params.c_str(),this); + String addr; + ObjList* tmp = updateRtpSDP(params,addr,m_rtpMedia); + if (tmp) { + bool chg = (m_rtpLocalAddr != addr); + m_rtpLocalAddr = addr; + return setMedia(tmp) || chg; + } + return false; +} + +// Creates a SDP body from transport address and list of media descriptors +// Use own list if given media list is 0 +MimeSdpBody* SDPSession::createSDP(const char* addr, ObjList* mediaList) +{ + DDebug(m_parser,DebugAll,"SDPSession::createSDP('%s',%p) [%p]",addr,mediaList,this); + if (!mediaList) + mediaList = m_rtpMedia; + // if we got no media descriptors we simply create no SDP + if (!mediaList) + return 0; + if (m_sdpSession) + ++m_sdpVersion; + else + m_sdpVersion = m_sdpSession = Time::secNow(); + + // no address means on hold or muted + String origin; + origin << "yate " << m_sdpSession << " " << m_sdpVersion; + origin << " IN IP4 " << (addr ? addr : m_host.safe()); + String conn; + conn << "IN IP4 " << (addr ? addr : "0.0.0.0"); + + MimeSdpBody* sdp = new MimeSdpBody; + sdp->addLine("v","0"); + sdp->addLine("o",origin); + sdp->addLine("s",m_parser->m_sessionName); + sdp->addLine("c",conn); + sdp->addLine("t","0 0"); + + Lock lock(m_parser); + bool defcodecs = m_parser->m_codecs.getBoolValue("default",true); + for (ObjList* ml = mediaList->skipNull(); ml; ml = ml->skipNext()) { + SDPMedia* m = static_cast(ml->get()); + String mline(m->fmtList()); + ObjList* l = mline.split(',',false); + mline = *m; + mline << " " << (m->localPort() ? m->localPort().c_str() : "0") << " " << m->transport(); + ObjList* map = m->mappings().split(',',false); + ObjList rtpmap; + String frm; + int ptime = 0; + ObjList* f = l; + for (; f; f = f->next()) { + String* s = static_cast(f->get()); + if (s) { + int mode = 0; + if (*s == "ilbc20") + ptime = mode = 20; + else if (*s == "ilbc30") + ptime = mode = 30; + else if (*s == "g729b") + continue; + int payload = s->toInteger(SDPParser::s_payloads,-1); + int defcode = payload; + String tmp = *s; + tmp << "="; + for (ObjList* pl = map; pl; pl = pl->next()) { + String* mapping = static_cast(pl->get()); + if (!mapping) + continue; + if (mapping->startsWith(tmp)) { + payload = -1; + tmp = *mapping; + tmp >> "=" >> payload; + XDebug(m_parser,DebugAll,"RTP mapped payload %d for '%s' [%p]", + payload,s->c_str(),this); + break; + } + } + if (payload >= 0) { + if (defcode < 0) + defcode = payload; + const char* map = lookup(defcode,SDPParser::s_rtpmap); + if (map && m_parser->m_codecs.getBoolValue(*s,defcodecs && DataTranslator::canConvert(*s))) { + frm << " " << payload; + String* temp = new String("rtpmap:"); + *temp << payload << " " << map; + rtpmap.append(temp); + if (mode) { + temp = new String("fmtp:"); + *temp << payload << " mode=" << mode; + rtpmap.append(temp); + } + if (*s == "g729") { + temp = new String("fmtp:"); + *temp << payload << " annexb=" << + ((0 != l->find("g729b")) ? "yes" : "no"); + rtpmap.append(temp); + } + else if (*s == "amr") { + temp = new String("fmtp:"); + *temp << payload << " octet-align=0"; + rtpmap.append(temp); + } + else if (*s == "amr-o") { + temp = new String("fmtp:"); + *temp << payload << " octet-align=1"; + rtpmap.append(temp); + } + } + } + } + } + TelEngine::destruct(l); + TelEngine::destruct(map); + + if (m_rfc2833 && frm && m->isAudio()) { + int rfc2833 = m->rfc2833().toInteger(-1); + if (rfc2833 < 0) + rfc2833 = 101; + // claim to support telephone events + frm << " " << rfc2833; + String* s = new String; + *s << "rtpmap:" << rfc2833 << " telephone-event/8000"; + rtpmap.append(s); + } + + if (frm.null()) { + if (m->isAudio() || !m->fmtList()) { + Debug(m_parser,DebugMild,"No formats for '%s', excluding from SDP [%p]", + m->c_str(),this); + continue; + } + Debug(m_parser,DebugInfo,"Assuming formats '%s' for media '%s' [%p]", + m->fmtList(),m->c_str(),this); + frm << " " << m->fmtList(); + // brutal but effective + for (char* p = const_cast(frm.c_str()); *p; p++) { + if (*p == ',') + *p = ' '; + } + } + + if (ptime) { + String* temp = new String("ptime:"); + *temp << ptime; + rtpmap.append(temp); + } + + sdp->addLine("m",mline + frm); + bool enc = false; + if (m->isModified()) { + unsigned int n = m->length(); + for (unsigned int i = 0; i < n; i++) { + const NamedString* param = m->getParam(i); + if (param) { + String tmp = param->name(); + if (*param) + tmp << ":" << *param; + sdp->addLine("a",tmp); + enc = enc || (param->name() == "encryption"); + } + } + } + for (f = rtpmap.skipNull(); f; f = f->skipNext()) { + String* s = static_cast(f->get()); + if (s) + sdp->addLine("a",*s); + } + if (addr && m->localCrypto()) { + sdp->addLine("a","crypto:" + m->localCrypto()); + if (!enc) + sdp->addLine("a","encryption:optional"); + } + } + + return sdp; +} + +// Creates a SDP body for the current media status +MimeSdpBody* SDPSession::createSDP() +{ + switch (m_mediaStatus) { + case MediaStarted: + return createSDP(getRtpAddr()); + case MediaMuted: + return createSDP(0); + default: + return 0; + } +} + +// Creates a SDP from RTP address data present in message +MimeSdpBody* SDPSession::createPasstroughSDP(NamedList& msg, bool update) +{ + String tmp = msg.getValue("rtp_forward"); + msg.clearParam("rtp_forward"); + if (!(m_rtpForward && tmp.toBoolean())) + return 0; + String* raw = msg.getParam("sdp_raw"); + if (raw) { + m_sdpForward = m_sdpForward || m_parser->sdpForward(); + if (m_sdpForward) { + msg.setParam("rtp_forward","accepted"); + return new MimeSdpBody("application/sdp",raw->safe(),raw->length()); + } + } + String addr; + ObjList* lst = updateRtpSDP(msg,addr,update ? m_rtpMedia : 0); + if (!lst) + return 0; + MimeSdpBody* sdp = createSDP(addr,lst); + if (update) { + m_rtpLocalAddr = addr; + setMedia(lst); + } + else + TelEngine::destruct(lst); + if (sdp) + msg.setParam("rtp_forward","accepted"); + return sdp; +} + +// Update media format lists from parameters +void SDPSession::updateFormats(const NamedList& msg) +{ + if (!m_rtpMedia) + return; + unsigned int n = msg.length(); + for (unsigned int i = 0; i < n; i++) { + const NamedString* p = msg.getParam(i); + if (!p) + continue; + // search for formats_MEDIANAME parameters + String tmp = p->name(); + if (!tmp.startSkip("formats",false)) + continue; + if (tmp && (tmp[0] != '_')) + continue; + if (tmp.null()) + tmp = "audio"; + else + tmp = tmp.substr(1); + SDPMedia* rtp = static_cast(m_rtpMedia->operator[](tmp)); + if (rtp && rtp->update(*p)) + Debug(m_parser,DebugNote,"Formats for '%s' changed to '%s' [%p]", + tmp.c_str(),p->c_str(),this); + } +} + +// Add raw SDP forwarding parameter +bool SDPSession::addSdpParams(NamedList& msg, const MimeBody* body) +{ + if (!(m_sdpForward && body)) + return false; + const MimeSdpBody* sdp = + static_cast(body->isSDP() ? body : body->getFirst("application/sdp")); + if (!sdp) + return false; + const DataBlock& raw = sdp->getBody(); + String tmp((const char*)raw.data(),raw.length()); + return addSdpParams(msg,tmp); +} + +// Add raw SDP forwarding parameter +bool SDPSession::addSdpParams(NamedList& msg, const String& rawSdp) +{ + if (!m_sdpForward) + return false; + msg.setParam("rtp_forward","yes"); + msg.addParam("sdp_raw",rawSdp); + return true; +} + +// Add RTP forwarding parameters to a message +bool SDPSession::addRtpParams(NamedList& msg, const String& natAddr, + const MimeBody* body, bool force) +{ + if (!(m_rtpMedia && m_rtpAddr)) + return false; + putMedia(msg,false); + if (force || (!startRtp() && m_rtpForward)) { + if (natAddr) + msg.addParam("rtp_nat_addr",natAddr); + msg.addParam("rtp_forward","yes"); + msg.addParam("rtp_addr",m_rtpAddr); + for (ObjList* o = m_rtpMedia->skipNull(); o; o = o->skipNext()) { + SDPMedia* m = static_cast(o->get()); + msg.addParam("rtp_port" + m->suffix(),m->remotePort()); + if (m->isAudio()) + msg.addParam("rtp_rfc2833",m->rfc2833()); + } + addSdpParams(msg,body); + return true; + } + return false; +} + +// Reset this object to default values +void SDPSession::resetSdp() +{ + m_mediaStatus = MediaMissing; + TelEngine::destruct(m_rtpMedia); + m_rtpForward = false; + m_sdpForward = false; + m_externalAddr.clear(); + m_rtpAddr.clear(); + m_rtpLocalAddr.clear(); + m_sdpSession = 0; + m_sdpVersion = 0; + m_host.clear(); + m_secure = m_parser->secure(); + m_rfc2833 = m_parser->rfc2833(); +} + +// Build a populated chan.rtp message +Message* SDPSession::buildChanRtp(SDPMedia* media, const char* addr, bool start, RefObject* context) +{ + if (!(media && addr)) + return 0; + Message* m = buildChanRtp(context); + if (!m) + return 0; + m->addParam("media",*media); + m->addParam("transport",media->transport()); + m->addParam("direction","bidir"); + if (m_rtpLocalAddr) + m->addParam("localip",m_rtpLocalAddr); + m->addParam("remoteip",addr); + if (start) { + m->addParam("remoteport",media->remotePort()); + m->addParam("format",media->format()); + String tmp = media->format(); + tmp << "="; + ObjList* mappings = media->mappings().split(',',false); + for (ObjList* pl = mappings; pl; pl = pl->next()) { + String* mapping = static_cast(pl->get()); + if (!mapping) + continue; + if (mapping->startsWith(tmp)) { + tmp = *mapping; + tmp >> "="; + m->addParam("payload",tmp); + break; + } + } + m->addParam("evpayload",media->rfc2833()); + TelEngine::destruct(mappings); + } + if (m_secure) { + if (media->remoteCrypto()) { + String sdes = media->remoteCrypto(); + Regexp r("^\\([0-9]\\+\\) \\+\\([^ ]\\+\\) \\+\\([^ ]\\+\\) *\\(.*\\)$"); + if (sdes.matches(r)) { + m->addParam("secure",String::boolText(true)); + m->addParam("crypto_tag",sdes.matchString(1)); + m->addParam("crypto_suite",sdes.matchString(2)); + m->addParam("crypto_key",sdes.matchString(3)); + if (sdes.matchLength(4)) + m->addParam("crypto_params",sdes.matchString(4)); + } + else + Debug(m_parser,DebugWarn,"Invalid SDES: '%s' [%p]",sdes.c_str(),this); + } + else if (media->securable()) + m->addParam("secure",String::boolText(true)); + } + else + media->crypto(0,true); + unsigned int n = media->length(); + for (unsigned int i = 0; i < n; i++) { + const NamedString* param = media->getParam(i); + if (!param) + continue; + m->addParam("sdp_" + param->name(),*param); + } + return m; +} + +// Check if local RTP data changed for at least one media +bool SDPSession::localRtpChanged() const +{ + if (!m_rtpMedia) + return false; + for (ObjList* o = m_rtpMedia->skipNull(); o; o = o->skipNext()) { + SDPMedia* m = static_cast(o->get()); + if (m->localChanged()) + return true; + } + return false; +} + +// Set or reset the local RTP data changed flag for all media +void SDPSession::setLocalRtpChanged(bool chg) +{ + if (!m_rtpMedia) + return; + for (ObjList* o = m_rtpMedia->skipNull(); o; o = o->skipNext()) + (static_cast(o->get()))->setLocalChanged(chg); +} + +// Update RTP/SDP data from parameters +ObjList* SDPSession::updateRtpSDP(const NamedList& params, String& rtpAddr, ObjList* oldList) +{ + DDebug(DebugAll,"SDPSession::updateRtpSDP(%s,%s,%p)",params.c_str(),rtpAddr.c_str(),oldList); + rtpAddr = params.getValue("rtp_addr"); + if (!rtpAddr) + return 0; + const char* sdpPrefix = params.getValue("osdp-prefix","osdp"); + ObjList* lst = 0; + unsigned int n = params.length(); + for (unsigned int i = 0; i < n; i++) { + const NamedString* p = params.getParam(i); + if (!p) + continue; + // search for rtp_port or rtp_port_MEDIANAME parameters + String tmp = p->name(); + if (!tmp.startSkip("rtp_port",false)) + continue; + if (tmp && (tmp[0] != '_')) + continue; + // now tmp holds the suffix for the media, null for audio + bool audio = tmp.null(); + // check if media is supported, default only for audio + if (!params.getBoolValue("media" + tmp,audio)) + continue; + int port = p->toInteger(); + if (!port) + continue; + const char* fmts = params.getValue("formats" + tmp); + if (!fmts) + continue; + String trans = params.getValue("transport" + tmp,"RTP/AVP"); + if (audio) + tmp = "audio"; + else + tmp >> "_"; + SDPMedia* rtp = 0; + // try to take the media descriptor from the old list + if (oldList) { + ObjList* om = oldList->find(tmp); + if (om) + rtp = static_cast(om->remove(false)); + } + bool append = false; + if (rtp) + rtp->update(fmts,-1,port); + else { + rtp = new SDPMedia(tmp,trans,fmts,-1,port); + append = true; + } + if (sdpPrefix) { + for (unsigned int j = 0; j < n; j++) { + const NamedString* param = params.getParam(j); + if (!param) + continue; + tmp = param->name(); + if (tmp.startSkip(sdpPrefix + rtp->suffix() + "_",false) && (tmp.find('_') < 0)) + rtp->parameter(tmp,*param,append); + } + } + rtp->mappings(params.getValue("rtp_mapping" + rtp->suffix())); + if (audio) + rtp->rfc2833(params.getIntValue("rtp_rfc2833",-1)); + rtp->crypto(params.getValue("crypto" + rtp->suffix()),false); + if (!lst) + lst = new ObjList; + lst->append(rtp); + } + return lst; +} + +// Media changed notification. +void SDPSession::mediaChanged(const String& name) +{ + XDebug(m_parser,DebugAll,"SDPSession::mediaChanged(%s) [%p]",name.c_str(),this); +} + +}; // namespace TelEngine + +/* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/libs/ysdp/yatesdp.h b/libs/ysdp/yatesdp.h new file mode 100644 index 00000000..7b832944 --- /dev/null +++ b/libs/ysdp/yatesdp.h @@ -0,0 +1,711 @@ +/* + * yatesdp.h + * This file is part of the YATE Project http://YATE.null.ro + * + * SDP media handling + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2004-2009 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __YATESDP_H +#define __YATESDP_H + +#ifndef __cplusplus +#error C++ is required +#endif + +#include +#include + +#ifdef _WINDOWS + +#ifdef LIBYSDP_EXPORTS +#define YSDP_API __declspec(dllexport) +#else +#ifndef LIBYSDP_STATIC +#define YSDP_API __declspec(dllimport) +#endif +#endif + +#endif /* _WINDOWS */ + +#ifndef YSDP_API +#define YSDP_API +#endif + +/** + * Holds all Telephony Engine related classes. + */ +namespace TelEngine { + +class SDPMedia; +class SDPSession; +class SDPParser; + +/** + * This class holds a single SDP media description + * @short SDP media description + */ +class YSDP_API SDPMedia : public NamedList +{ +public: + /** + * Constructor + * @param media Media type name + * @param transport Transport name + * @param formats Comma separated list of formats + * @param rport Optional remote media port + * @param lport Optional local media port + */ + SDPMedia(const char* media, const char* transport, const char* formats, + int rport = -1, int lport = -1); + + /** + * Destructor + */ + virtual ~SDPMedia(); + + /** + * Check if this media type is audio + * @return True if this media describe an audio one + */ + inline bool isAudio() const + { return m_audio; } + + /** + * Check if a media parameter changed + * @return True if a media changed + */ + inline bool isModified() const + { return m_modified; } + + /** + * Set or reset media parameter changed flag + * @param modified The new value of the media parameter changed flag + */ + inline void setModified(bool modified = true) + { m_modified = modified; } + + /** + * Retrieve the media suffix (built from type) + * @return Media suffix + */ + inline const String& suffix() const + { return m_suffix; } + + /** + * Retrieve the media transport name + * @return The media transport name + */ + inline const String& transport() const + { return m_transport; } + + /** + * Retrieve the media id + * @return The media id + */ + inline const String& id() const + { return m_id; } + + /** + * Retrieve the current media format + * @return The current media format + */ + inline const String& format() const + { return m_format; } + + /** + * Retrieve the formats set for this media + * @return Comma separated list of media formats + */ + inline const String& formats() const + { return m_formats; } + + /** + * Retrieve the remote media port + * @return The remote media port + */ + inline const String& remotePort() const + { return m_rPort; } + + /** + * Retrieve the local media port + * @return The local media port + */ + inline const String& localPort() const + { return m_lPort; } + + /** + * Retrieve rtp payload mappings + * @return Rtp payload mappings + */ + inline const String& mappings() const + { return m_mappings; } + + /** + * Set rtp payload mappings for this media + * @param newMap New rtp payload mappings + */ + inline void mappings(const char* newMap) + { if (newMap) m_mappings = newMap; } + + /** + * Retrieve RFC2833 status or payload of this media + * @return RFC2833 status or payload of this media + */ + inline const String& rfc2833() const + { return m_rfc2833; } + + /** + * Set RFC2833 status or payload of this media + * @param payload SDP numeric payload to set. + * Set it to a negative value to reset RFC2833 + */ + inline void rfc2833(int payload) + { + if (payload >= 0) + m_rfc2833 = payload; + else + m_rfc2833 = String::boolText(false); + } + + /** + * Retrieve remote crypto description + * @return Remote crypto description + */ + inline const String& remoteCrypto() const + { return m_rCrypto; } + + /** + * Retrieve local crypto description + * @return Local crypto description + */ + inline const String& localCrypto() const + { return m_lCrypto; } + + /** + * Check if this media is securable + * @return True if this media is securable + */ + inline bool securable() const + { return m_securable; } + + /** + * Compare this media with another one + * @param other The media to compare with + * @param ignorePort Ignore differences caused only by port number + * @return True if both media have the same formats, transport and remote port + */ + inline bool sameAs(const SDPMedia* other, bool ignorePort = false) const + { return other && (other->formats() == m_formats) && + (other->transport() == m_transport) && + ((ignorePort && other->remotePort() && m_rPort) || + (other->remotePort() == m_rPort)); } + + /** + * Check if local part of this media changed + * @return True if local part of this media changed + */ + inline bool localChanged() const + { return m_localChanged; } + + /** + * Set or reset local media changed flag + * @param chg The new value for local media changed flag + */ + inline void setLocalChanged(bool chg = false) + { m_localChanged = chg; } + + /** + * Retrieve a formats list from this media + * @return Comma separated list of media formats (from formats list, + * current format or a default G711, 'alaw,mulaw', list + */ + const char* fmtList() const; + + /** + * Update this media from formats and ports + * @param formats New media formats + * @param rport Optional remote media port + * @param lport Optional local media port + * @return True if media changed + */ + bool update(const char* formats, int rport = -1, int lport = -1); + + /** + * Update from a chan.rtp message (rtp id and local port) + * @param msg The list of parameters + * @param pickFormat True to update media format(s) from the list + */ + void update(const NamedList& msg, bool pickFormat); + + /** + * Add or replace a parameter by name and value, set the modified flag + * @param name Parameter name + * @param value Parameter value + * @param append True to append, false to replace + */ + void parameter(const char* name, const char* value, bool append); + + /** + * Add or replace a parameter, set the modified flag + * @param param The parameter + * @param append True to append, false to replace + */ + void parameter(NamedString* param, bool append); + + /** + * Set a new crypto description, set the modified flag if changed. + * Reset the media securable flag if the remote crypto is empty + * @param desc The new crypto description + * @param remote True to set the remote crypto, false to set the local one + */ + void crypto(const char* desc, bool remote); + + /** + * Put this net media in a parameter list + * @param msg Destination list + * @param putPort True to add remote media port + */ + void putMedia(NamedList& msg, bool putPort = true); + +private: + bool m_audio; + bool m_modified; + bool m_securable; + // local rtp data changed flag + bool m_localChanged; + // suffix used for this type + String m_suffix; + // transport protocol + String m_transport; + // list of supported format names + String m_formats; + // format used for sending data + String m_format; + // id of the local media channel + String m_id; + // remote media port + String m_rPort; + // mappings of RTP payloads + String m_mappings; + // local media port + String m_lPort; + // payload for telephone/event + String m_rfc2833; + // remote security descriptor + String m_rCrypto; + // local security descriptor + String m_lCrypto; +}; + + +/** + * This class holds RTP/SDP data for multiple media types + * NOTE: The SDPParser pointer held by this class is assumed to be non NULL + * @short A holder for a SDP session + */ +class YSDP_API SDPSession +{ +public: + /** + * RTP media status enumeration + */ + enum { + MediaMissing, + MediaStarted, + MediaMuted + }; + + /** + * Constructor + * @param parser The SDP parser whose data this object will use + */ + SDPSession(SDPParser* parser); + + /** + * Constructor + * @param parser The SDP parser whose data this object will use + * @param params SDP session parameters + */ + SDPSession(SDPParser* parser, NamedList& params); + + /** + * Destructor. Reset the object + */ + virtual ~SDPSession(); + + /** + * Get RTP local host + * @return RTP local host + */ + inline const String& getHost() const + { return m_host; } + + /** + * Get local RTP address + * @return Local RTP address (external or local) + */ + inline const String& getRtpAddr() const + { return m_externalAddr ? m_externalAddr : m_rtpLocalAddr; } + + /** + * Set a new media list + * @param media New media list + * @return True if media changed + */ + bool setMedia(ObjList* media); + + /** + * Put specified media parameters into a list of parameters + * @param msg Destination list + * @param media List of SDP media information + * @param putPort True to add the media port + */ + static void putMedia(NamedList& msg, ObjList* media, bool putPort = true); + + /** + * Put session media parameters into a list of parameters + * @param msg Destination list + * @param putPort True to add the media port + */ + inline void putMedia(NamedList& msg, bool putPort = true) + { putMedia(msg,m_rtpMedia,putPort); } + + /** + * Build and dispatch a chan.rtp message for a given media. Update media on success + * @param media The media to use + * @param addr Remote RTP address + * @param start True to request RTP start + * @param pick True to update local parameters (other then media) from returned message + * @param context Pointer to user provided context, optional + * @return True if the message was succesfully handled + */ + bool dispatchRtp(SDPMedia* media, const char* addr, bool start, bool pick, RefObject* context = 0); + + /** + * Calls dispatchRtp() for each media in the list + * Update it on success. Remove it on failure + * @param addr Remote RTP address + * @param start True to request RTP start + * @param context Pointer to user provided context, optional + * @return True if the message was succesfully handled for at least one media + */ + bool dispatchRtp(const char* addr, bool start, RefObject* context = 0); + + /** + * Try to start RTP (calls dispatchRtp()) for each media in the list + * @param context Pointer to user provided context, optional + * @return True if at least one media was started + */ + bool startRtp(RefObject* context = 0); + + /** + * Update from parameters. Build a default SDP from parser formats if no media is found in params + * @param params List of parameters to update from + * @return True if media changed + */ + bool updateSDP(const NamedList& params); + + /** + * Update RTP/SDP data from parameters + * @param params List of parameters to update from + * @return True if media or local address changed + */ + bool updateRtpSDP(const NamedList& params); + + /** + * Creates a SDP body from transport address and list of media descriptors + * @param addr The address to set. Use own host if empty + * @param mediaList Optional media list. Use own list if the given one is 0 + * @return MimeSdpBody pointer or 0 if there is no media to set + */ + MimeSdpBody* createSDP(const char* addr, ObjList* mediaList = 0); + + /** + * Creates a SDP body for current media status + * @return MimeSdpBody pointer or 0 if media is missing + */ + MimeSdpBody* createSDP(); + + /** + * Creates a SDP from RTP address data present in message. + * Use the raw SDP if present. + * @param msg The list of parameters + * @param update True to update RTP/SDP data if raw SDP is not found in the list + * @return MimeSdpBody pointer or 0 + */ + MimeSdpBody* createPasstroughSDP(NamedList& msg, bool update = true); + + /** + * Creates a set of unstarted external RTP channels from remote addr and + * builds SDP from them + * @param addr Remote RTP address used when dispatching the chan.rtp message + * @param msg List of parameters used to update data + * @return MimeSdpBody pointer or 0 + */ + inline MimeSdpBody* createRtpSDP(const char* addr, const NamedList& msg) + { updateSDP(msg); return createRtpSDP(addr,false); } + + /** + * Creates a set of RTP channels from address and media info and builds SDP from them + * @param addr Remote RTP address used when dispatching the chan.rtp message + * @param start True to create a started RTP + * @return MimeSdpBody pointer or 0 + */ + inline MimeSdpBody* createRtpSDP(const char* addr, bool start) + { return dispatchRtp(addr,start) ? createSDP(getRtpAddr()) : 0; } + + /** + * Creates a set of started external RTP channels from remote addr and + * builds SDP from them + * @param start True to create a started RTP + * @return MimeSdpBody pointer or 0 + */ + inline MimeSdpBody* createRtpSDP(bool start) + { + if (m_rtpAddr.null()) { + m_mediaStatus = MediaMuted; + return createSDP(0); + } + return createRtpSDP(m_rtpAddr,start); + } + + /** + * Update media format lists from parameters + * @param msg Parameter list + */ + void updateFormats(const NamedList& msg); + + /** + * Add raw SDP forwarding parameter from body if SDP forward is enabled + * @param msg Destination list + * @param body Mime body to process + * @return True if the parameter was added + */ + bool addSdpParams(NamedList& msg, const MimeBody* body); + + /** + * Add raw SDP forwarding parameter if SDP forward is enabled + * @param msg Destination list + * @param rawSdp The raw sdp content + * @return True if the parameter was added + */ + bool addSdpParams(NamedList& msg, const String& rawSdp); + + /** + * Add RTP forwarding parameters to a message (media and address) + * @param msg Destination list + * @param natAddr Optional NAT address if detected + * @param body Pointer to the body to extract raw SDP from + * @param force True to override RTP forward flag + * @return True if RTP data was added. Media is always added if present and + * remote address is not empty + */ + bool addRtpParams(NamedList& msg, const String& natAddr = String::empty(), + const MimeBody* body = 0, bool force = false); + + /** + * Reset this object to default values + */ + virtual void resetSdp(); + + /** + * Build a chan.rtp message and populate with media information + * @param media The media list + * @param addr Remote RTP address + * @param start True to request RTP start + * @param context Pointer to reference counted user provided context + * @return The message with media information, NULL if media or addr are missing + */ + virtual Message* buildChanRtp(SDPMedia* media, const char* addr, bool start, RefObject* context); + + /** + * Build a chan.rtp message without media information + * @param context Pointer to reference counted user provided context + * @return The message with user data set but no media information + */ + virtual Message* buildChanRtp(RefObject* context) = 0; + + /** + * Check if local RTP data changed for at least one media + * @return True if local RTP data changed for at least one media + */ + bool localRtpChanged() const; + + /** + * Set or reset the local RTP data changed flag for all media + * @param chg The new value for local RTP data changed flag of all media + */ + void setLocalRtpChanged(bool chg = false); + + /** + * Update RTP/SDP data from parameters + * @param params Parameter list + * @param rtpAddr String to be filled with rtp address from the list + * @param oldList Optional existing media list (found media will be removed + * from it and added to the returned list + * @return List of media or 0 if not found or rtpAddr is empty + */ + static ObjList* updateRtpSDP(const NamedList& params, String& rtpAddr, + ObjList* oldList = 0); + + SDPParser* m_parser; + int m_mediaStatus; + bool m_rtpForward; // Forward RTP flag + bool m_sdpForward; // Forward SDP (only if RTP is forwarded) + String m_externalAddr; // Our external IP address, possibly outside of a NAT + String m_rtpAddr; // Remote RTP address + String m_rtpLocalAddr; // Local RTP address + ObjList* m_rtpMedia; // List of media descriptors + int m_sdpSession; // Unique SDP session number + int m_sdpVersion; // SDP version number, incremented each time we generate a new SDP + String m_host; + bool m_secure; + bool m_rfc2833; // Offer RFC 2833 to remote party + +protected: + /** + * Media changed notification. + * This method is called when setting new media and an old one changed + * @param name Changed media name + */ + virtual void mediaChanged(const String& name); +}; + +/** + * This class holds a SDP parser and additional data used by SDP objects + * @short A SDP parser + */ +class YSDP_API SDPParser : public DebugEnabler, public Mutex +{ + friend class SDPSession; + +public: + /** + * Constructor + * @param dbgName Debug name of this parser + * @param sessName Name of the session in SDP + * @param fmts Default media formats + */ + inline SDPParser(const char* dbgName, const char* sessName, const char* fmts = "alaw,mulaw") + : Mutex(true,"SDPParser"), m_sdpForward(false), + m_rfc2833(true), m_secure(false), m_ignorePort(false), + m_sessionName(sessName), m_audioFormats(fmts), + m_codecs(""), m_hacks("") + { debugName(dbgName); } + + /** + * Get the formats list + * This method is thread safe + * @param buf String to be filled with comma separated list of formats + */ + inline void getAudioFormats(String& buf) + { Lock lock(this); buf = m_audioFormats; } + + /** + * Get the RFC 2833 offer flag + * @return True if RFC 2883 telephony events will be offered + */ + inline bool rfc2833() const + { return m_rfc2833; } + + /** + * Get the secure offer flag + * @return True if SDES descriptors for SRTP will be offered + */ + inline bool secure() const + { return m_secure; } + + /** + * Get the SDP forward flag + * @return True if raw SDP should be added to RTP forward offer + */ + inline bool sdpForward() const + { return m_sdpForward; } + + /** + * Get the RTP port change ignore flag + * @return True if a port change should not cause an offer change + */ + inline bool ignorePort() const + { return m_ignorePort; } + + /** + * Parse a received SDP body + * This method is thread safe + * @param sdp Received SDP body + * @param addr Remote address + * @param oldMedia Optional list of existing media (an already existing media + * will be moved to returned list) + * @param media Optional expected media type. If not empty this will be the + * only media type returned (if found) + * @return List of SDPMedia objects, may be NULL + */ + ObjList* parse(const MimeSdpBody& sdp, String& addr, ObjList* oldMedia = 0, + const String& media = String::empty()); + + /** + * Parse a received SDP body, returns NULL if SDP is not present + * This method is thread safe + * @param sdp Pointer to received SDP body + * @param addr Remote address + * @param oldMedia Optional list of existing media (an already existing media + * will be moved to returned list) + * @param media Optional expected media type. If not empty this will be the + * only media type returned (if found) + * @return List of SDPMedia objects, may be NULL + */ + inline ObjList* parse(const MimeSdpBody* sdp, String& addr, ObjList* oldMedia = 0, + const String& media = String::empty()) + { return sdp ? parse(*sdp,addr,oldMedia,media) : 0; } + + /** + * Update configuration. This method should be called after a configuration file is loaded + * @param codecs List of supported codecs + * @param hacks List of hacks + * @param general List of general settings + */ + void initialize(const NamedList* codecs, const NamedList* hacks, const NamedList* general = 0); + + /** + * Yate Payloads for the AV profile + */ + static const TokenDict s_payloads[]; + + /** + * SDP Payloads for the AV profile + */ + static const TokenDict s_rtpmap[]; + +private: + bool m_sdpForward; // Include raw SDP for forwarding + bool m_rfc2833; // Offer RFC 2833 to remote party + bool m_secure; // Offer SRTP + bool m_ignorePort; // Ignore port only changes in SDP + String m_sessionName; + String m_audioFormats; // Default audio formats to be advertised to remote party + NamedList m_codecs; // Codecs configuration list + NamedList m_hacks; // Various potentially standard breaking settings +}; + +}; // namespace TelEngine + +#endif /* __YATESDP_H */ + +/* vi: set ts=8 sw=4 sts=4 noet: */