/** * 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-2023 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * 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. */ #include namespace TelEngine { /* * SDPSession */ SDPSession::SDPSession(SDPParser* parser) : m_parser(parser), m_mediaStatus(MediaMissing), m_rtpForward(false), m_sdpForward(parser->sdpForward()), m_rtpMedia(0), m_sdpSession(0), m_sdpVersion(0), m_sdpHash(YSTRING_INIT_HASH), m_secure(m_parser->m_secure), m_rfc2833(m_parser->m_rfc2833), m_ipv6(false), m_gpmd(parser->m_gpmd), m_amrExtra(""), m_parsedParams(0), m_createSdpParams(""), m_enabler(0), m_ptr(0) { setSdpDebug(); } SDPSession::SDPSession(SDPParser* parser, NamedList& params) : m_parser(parser), m_mediaStatus(MediaMissing), m_rtpForward(false), m_sdpForward(parser->sdpForward()), m_rtpMedia(0), m_sdpSession(0), m_sdpVersion(0), m_sdpHash(YSTRING_INIT_HASH), m_ipv6(false), m_gpmd(false), m_amrExtra(""), m_parsedParams(0), m_createSdpParams(""), m_enabler(0), m_ptr(0) { setSdpDebug(); m_rtpForward = params.getBoolValue(YSTRING("rtp_forward")); m_sdpForward = params.getBoolValue(YSTRING("forward_sdp"),m_sdpForward); m_secure = params.getBoolValue(YSTRING("secure"),parser->m_secure); m_gpmd = params.getBoolValue(YSTRING("forward_gpmd"),parser->m_gpmd); m_rfc2833 = parser->m_rfc2833; setRfc2833(params,false); } SDPSession::~SDPSession() { resetSdp(); } // Set new media list. Return true if changed bool SDPSession::setMedia(ObjList* media, bool preserveExisting) { if (media == m_rtpMedia) return false; DDebug(m_enabler,DebugAll,"SDPSession::setMedia(%p) [%p]",media,m_ptr); 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) { ObjList* l = media->find(*m); SDPMedia* newMedia = l ? static_cast(l->get()) : 0; if (newMedia && m->sameAs(newMedia,m_parser->ignorePort(),preserveExisting)) { if (preserveExisting && m->isStarted()) { XDebug(m_enabler,DebugAll, "SDPSession::setMedia(%p) keeping existing media='%s' format='%s' [%p]", media,m->c_str(),m->format().c_str(),m_ptr); newMedia->keepRtp(*m); } continue; } } chg = true; mediaChanged(*m); } TelEngine::destruct(tmp); } printRtpMedia("Set media"); return chg; } // Put the list of net media in a parameter list void SDPSession::putMedia(NamedList& msg, ObjList* mList, bool putPort, const NamedList* sessParams) { if (sessParams) { const char* prefix = sessParams->safe("ssdp_"); for (ObjList* o = sessParams->paramList()->skipNull(); o; o = o->skipNext()) { const NamedString* ns = static_cast(o->get()); msg.addParam(prefix + ns->name(),*ns); } } if (!mList) return; bool audio = false; bool other = false; for (mList = mList->skipNull(); mList; mList = mList->skipNext()) { SDPMedia* m = static_cast(mList->get()); m->putMedia(msg,putPort); if (m->isAudio()) audio = true; else other = true; } if (other && !audio) msg.setParam("media",String::boolText(false)); } // Update the RFC 2833 availability and payload void SDPSession::setRfc2833(const String& value, int rate) { m_rfc2833.update(rate,value,m_parser->m_rfc2833); } // Update the RFC 2833 availability and payload void SDPSession::setRfc2833(const NamedList& params, bool force) { m_rfc2833.update(params,m_parser->m_rfc2833,force); #ifdef DEBUG String tmp; TraceDebug(m_traceId,m_enabler,DebugAll,"Updated RFC 2833: %s [%p]",m_rfc2833.dump(tmp).c_str(),m_ptr); #endif } // 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_enabler,DebugAll,"SDPSession::dispatchRtp(%p,%s,%u,%u,%p) [%p]", media,addr,start,pick,context,m_ptr); Message* m = buildChanRtp(media,addr,start,context); if (m) dispatchingRtp(m,media); 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 = m_rtpLocalAddr.null() ? MediaMuted : MediaStarted; String sdpPrefix = m->getValue("osdp-prefix","osdp"); if (sdpPrefix) { if (!sdpPrefix.endsWith("_")) sdpPrefix += "_"; unsigned int n = m->length(); int dir = SDPMedia::DirUnknown; 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) { DDebug(m_enabler,DebugInfo, "Updating (from RTP message) %s parameter '%s' to '%s' [%p]", media->c_str(),tmp.c_str(),param->c_str(),this); media->parameter(tmp,*param,false); SDPMedia::setDirection(dir,tmp); } } media->direction(dir,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_enabler,DebugAll,"SDPSession::dispatchRtp(%s,%u,%p) [%p]", addr,start,context,m_ptr); 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 { TraceDebug(m_traceId,m_enabler,DebugMild, "Removing failed SDP media '%s' format '%s' from offer [%p]", m->c_str(),m->format().safe(),m_ptr); 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_enabler,DebugAll,"SDPSession::startRtp(%p) [%p]",context,m_ptr); 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, bool defaults) { DDebug(m_enabler,DebugAll,"SDPSession::updateSdp('%s',%s) [%p]", params.c_str(),String::boolText(defaults),m_ptr); const char* sdpPrefix = params.getValue("osdp-prefix","osdp"); updateSessionParams(params); 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 media or media_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) { String prefix; prefix << sdpPrefix << rtp->suffix() << "_"; for (unsigned int j = 0; j < n; j++) { const NamedString* param = params.getParam(j); if (!param) continue; tmp = param->name(); if (tmp.startSkip(prefix,false) && (tmp.find('_') < 0)) { DDebug(m_enabler,DebugInfo,"%s (SDP update) %s parameter '%s' to '%s' [%p]", append ? "Adding" : "Updating",rtp->c_str(),tmp.c_str(),param->c_str(),this); 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_enabler,DebugAll,"SDPSession::updateRtpSDP(%s) [%p]",params.c_str(),m_ptr); String addr; ObjList* tmp = updateRtpSDP(params,addr,m_rtpMedia); if (tmp) { updateSessionParams(params); bool chg = (m_rtpLocalAddr != addr); m_rtpLocalAddr = addr; return setMedia(tmp) || chg; } return false; } // Utility used in createSDP static int addIP(String& buf, const char* addr, int family = SocketAddr::Unknown) { if (family != SocketAddr::IPv4 && family != SocketAddr::IPv6) { if (addr) { family = SocketAddr::family(addr); if (family != SocketAddr::IPv4 && family != SocketAddr::IPv6) family = SocketAddr::IPv4; } else family = SocketAddr::IPv4; } if (family == SocketAddr::IPv4) buf << "IN IP4 "; else buf << "IN IP6 "; if (!TelEngine::null(addr)) buf << addr; else if (family == SocketAddr::IPv4) buf << SocketAddr::ipv4NullAddr(); else buf << SocketAddr::ipv6NullAddr(); return family; } // Utility used in createSDP // Build a 'fmtp' line if not already done. Add a parameter to it static inline void setFmtpLine(String*& line, int payload, const char* param) { if (line) line->append(param,";"); else { line = new String("fmtp:"); *line << payload << " " << param; } } // 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_enabler,DebugAll,"SDPSession::createSDP('%s',%p) [%p]",addr,mediaList,m_ptr); ObjList* mList = mediaList; if (!mList) mList = m_rtpMedia; // if we got no media descriptors we simply create no SDP if (!mList) return 0; if (!m_sdpSession) m_sdpVersion = m_sdpSession = Time::secNow(); // override the address with the externally advertised if needed if (addr && m_rtpNatAddr) addr = m_rtpNatAddr; if (!m_originAddr) m_originAddr = addr ? addr : m_host.safe(); // no address means on hold or muted String origin; int f = addIP(origin,m_originAddr); String conn; addIP(conn,addr,f); MimeSdpBody* sdp = new MimeSdpBody(true); sdp->addLine("v","0"); // insert incomplete origin just for hashing purpose NamedString* org = sdp->addLine("o",origin); sdp->addLine("s",m_parser->m_sessionName); sdp->addLine("c",conn); sdp->addLine("t","0 0"); Lock lock(m_parser); addSdpParams(sdp,m_createSdpParams); int sessDir = 0; // TODO: Add session level direction bool defcodecs = m_parser->m_codecs.getBoolValue("default",true); for (ObjList* ml = mList->skipNull(); ml; ml = ml->skipNext()) { SDPMedia* m = static_cast(ml->get()); Rfc2833 rfc2833; if (m->isAudio()) { if (!m_rtpForward) { const Rfc2833& mr = m->rfc2833(); for (int i = 0; i < Rfc2833::RateCount; ++i) rfc2833[i] = (mr[i] >= 0) ? mr[i] : m_rfc2833[i]; } else rfc2833 = m->rfc2833(); } 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; ObjList* dest = &rtpmap; String frm; int ptime = 0; ObjList* f = l; Rfc2833 rfc2833Send; for (; f; f = f->next()) { const String* s = static_cast(f->get()); if (s) { if (*s == "g729b") continue; int payload = s->toInteger(SDPParser::s_payloads,-1); int defcode = payload; String tmp = *s; tmp << "="; bool found = false; for (ObjList* pl = map; pl; pl = pl->next()) { const String* mapping = static_cast(pl->get()); if (!mapping) continue; found = mapping->startsWith(tmp); if (!found) { for (ObjList* o = pl->skipNext(); o; o = o->skipNext()) { const String* tmpM = static_cast(o->get()); if (tmpM->startsWith(tmp)) { found = true; mapping = tmpM; break; } } } if (found) { payload = -1; tmp = *mapping; tmp >> "=" >> payload; found = true; XDebug(m_enabler,DebugAll,"RTP mapped payload %d for '%s' [%p]", payload,s->c_str(),m_ptr); break; } String tmp2 = *mapping; int pload; tmp2 >> "=" >> pload; if (payload == pload) { XDebug(m_enabler,DebugAll,"RTP conflict for payload %d, allocating new [%p]", payload,m_ptr); payload = -1; u_int32_t bmap = 0; for (ObjList* sl = map; sl; sl = sl->next()) { mapping = static_cast(sl->get()); if (!mapping) continue; tmp2 = *mapping; pload = 0; tmp2 >> "=" >> pload; if (pload >= 96 && pload < 127) bmap |= 1 << (pload - 96); } // allocate free and non-standard is possible for (pload = 96; pload < 127; pload++) { if (rfc2833.includes(pload)) continue; if (lookup(pload,SDPParser::s_rtpmap)) continue; if ((bmap & (1 << (pload - 96))) == 0) { payload = pload; break; } } if (payload >= 0) break; // none free, allocate from "standard" ones too for (pload = 96; pload < 127; pload++) { if (rfc2833.includes(pload)) continue; if ((bmap & (1 << (pload - 96))) == 0) { payload = pload; break; } } break; } } if (payload >= 0) { if (!found) { tmp = *s; tmp << "=" << payload; map->append(new String(tmp)); } 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))) { // Update RFC 2833 support to advertise if (m->isAudio()) { int r = Rfc2833::fmtRate(*s); if (r < Rfc2833::RateCount) rfc2833Send[r] = rfc2833[r]; } frm << " " << payload; String* temp = new String("rtpmap:"); *temp << payload << " " << map; dest = dest->append(temp); const String* fmtp = m->getParam("fmtp:" + *s); temp = 0; if (*s == YSTRING("ilbc20")) { ptime = 20; setFmtpLine(temp,payload,"mode=20"); } else if (*s == YSTRING("ilbc30")) { ptime = 30; setFmtpLine(temp,payload,"mode=30"); } else if (*s == YSTRING("g729")) { setFmtpLine(temp,payload,"annexb="); *temp << ((0 != l->find("g729b")) ? "yes" : "no"); } else if (*s == YSTRING("amr") || s->startsWith("amr/")) { setFmtpLine(temp,payload,"octet-align=0"); if (!m_rtpForward && m_amrExtra) { addFmtpAmrExtra(*temp,fmtp); fmtp = 0; } } else if (*s == YSTRING("amr-o") || s->startsWith("amr-o/")) { setFmtpLine(temp,payload,"octet-align=1"); if (!m_rtpForward && m_amrExtra) { addFmtpAmrExtra(*temp,fmtp); fmtp = 0; } } if (fmtp) setFmtpLine(temp,payload,*fmtp); if (temp) dest = dest->append(temp); if (mediaList || m_gpmd) { // RTP forward propagates General Purpose Media Descriptor const String* gpmd = m->getParam("gpmd:" + *s); if (gpmd) { temp = new String("gpmd:"); *temp << payload << " " << *gpmd; dest = dest->append(temp); } } } } } } TelEngine::destruct(l); TelEngine::destruct(map); if (frm && m->isAudio()) { for (int i = 0; i < Rfc2833::RateCount; ++i) { if (rfc2833Send[i] < 0) continue; frm << " " << rfc2833Send[i]; String* s = new String; s->printf("rtpmap:%d telephone-event/%s",rfc2833Send[i],Rfc2833::rateValue(i).c_str()); dest = dest->append(s); } } if (frm.null()) { if (m->isAudio() || !m->fmtList()) { TraceDebug(m_traceId,m_enabler,DebugMild,"No formats for '%s', excluding from SDP [%p]", m->c_str(),m_ptr); continue; } TraceDebug(m_traceId,m_enabler,DebugInfo,"Assuming formats '%s' for media '%s' [%p]", m->fmtList(),m->c_str(),m_ptr); 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; dest = dest->append(temp); } sdp->addLine("m",mline + frm); bool enc = false; bool addedDir = false; if (m->isModified()) addSdpParams(sdp,*m,&enc,&addedDir); 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"); } if (!addedDir) { const char* dir = lookup(m->direction(sessDir),SDPMedia::s_sdpDir); if (dir) sdp->addLine("a",dir); } } // increment version if body hash changed if ((YSTRING_INIT_HASH != m_sdpHash) && (sdp->hash() != m_sdpHash)) m_sdpVersion++; m_sdpHash = sdp->hash(); // insert version in the origin line origin.clear(); origin << "yate " << m_sdpSession << " " << m_sdpVersion << " " << *org; *org = origin; 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, bool allowEmptyAddr) { XDebug(m_enabler,DebugAll,"createPasstroughSDP(%s,%u,%u) [%p]", msg.c_str(),update,allowEmptyAddr,m_ptr); 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) { msg.setParam("rtp_forward","accepted"); return new MimeSdpBody("application/sdp",raw->safe(),raw->length()); } updateSessionParams(msg); String addr; ObjList* lst = updateRtpSDP(msg,addr,update ? m_rtpMedia : 0,allowEmptyAddr); 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, bool changeMedia) { if (!m_rtpMedia) return; unsigned int n = msg.length(); unsigned int i; if (changeMedia) { // check if any media is to be removed for (i = 0; i < n; i++) { const NamedString* p = msg.getParam(i); if (!p) continue; // search for media or media_MEDIANAME parameters String tmp = p->name(); if (!tmp.startSkip("media",false)) continue; if (tmp && (tmp[0] != '_')) continue; // only check for explicit disabled media if (p->toBoolean(true)) continue; if (tmp.null()) tmp = "audio"; else tmp = tmp.substr(1); SDPMedia* rtp = static_cast(m_rtpMedia->operator[](tmp)); if (!rtp) continue; TraceDebug(m_traceId,m_enabler,DebugNote,"Removing disabled media '%s' [%p]", tmp.c_str(),m_ptr); m_rtpMedia->remove(rtp,false); mediaChanged(*rtp); TelEngine::destruct(rtp); } } for (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; const char* trans = 0; // make sure we don't re-add explicitely disabled media if (changeMedia && msg.getBoolValue("media"+tmp,true)) trans = msg.getValue("transport"+tmp); if (tmp.null()) tmp = "audio"; else tmp = tmp.substr(1); SDPMedia* rtp = static_cast(m_rtpMedia->operator[](tmp)); if (rtp) { if (rtp->update(*p)) TraceDebug(m_traceId,m_enabler,DebugNote,"Formats for '%s' changed to '%s' [%p]", tmp.c_str(),rtp->formats().c_str(),m_ptr); } else if (*p) { TraceDebug(m_traceId,m_enabler,DebugNote,"Got formats '%s' for absent media '%s' [%p]", p->c_str(),tmp.c_str(),m_ptr); if (trans) { rtp = new SDPMedia(tmp,trans,p->c_str()); m_rtpMedia->append(rtp); mediaChanged(*rtp); } } } String sdpPrefix = msg.getValue("osdp-prefix"); if (!sdpPrefix) return; if (!sdpPrefix.endsWith("_")) sdpPrefix += "_"; for (i = 0; i < n; i++) { const NamedString* param = msg.getParam(i); if (!param) continue; String tmp = param->name(); if (!tmp.startSkip(sdpPrefix,false)) continue; int sep = tmp.find("_"); String media("audio"); if (sep > 0) { media = tmp.substr(0,sep); tmp = tmp.substr(sep + 1); } if (tmp.null() || (tmp.find("_") >= 0)) continue; SDPMedia* rtp = static_cast(m_rtpMedia->operator[](media)); if (rtp) { DDebug(m_enabler,DebugInfo,"Updating (formats update) %s parameter '%s' to '%s' [%p]", media.c_str(),tmp.c_str(),param->c_str(),this); rtp->parameter(tmp,*param,false); } } } // 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, bool allowEmptyAddr) { XDebug(m_enabler,DebugAll,"addRtpParams(%s,%s,%p,%u,%u) media=%p rtpaddr=%s [%p]", msg.c_str(),natAddr.c_str(),body,force,allowEmptyAddr,m_rtpMedia, m_rtpAddr.c_str(),m_ptr); if (!(m_rtpMedia && (m_rtpAddr || allowEmptyAddr))) 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()) m->rfc2833().put(msg); } addSdpParams(msg,body); return true; } return false; } // Reset this object to default values void SDPSession::resetSdp(bool all) { 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(); if (all) { m_secure = m_parser->secure(); m_gpmd = m_parser->gpmd(); m_rfc2833 = m_parser->rfc2833(); } setSessionParams(0); m_createSdpParams.clearParams(); } // 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; if (media->id()) m->addParam("rtpid",media->id()); m->addParam("media",*media); m->addParam("transport",media->transport()); m->addParam("direction","bidir"); if (media->format()) m->addParam("format",media->format()); m->addParam("ipv6_support",String::boolText(m_ipv6)); if (m_rtpLocalAddr) m->addParam("localip",m_rtpLocalAddr); m->addParam("remoteip",addr); if (start) { m->addParam("remoteport",media->remotePort()); int payload = SDPMedia::payloadMapping(media->mappings(),media->format()); if (payload >= 0) m->addParam("payload",String(payload)); int evpayload = media->selectRfc2833(media->format()); if (evpayload >= 0) m->addParam("evpayload",String(evpayload)); else m->addParam("evpayload",String::boolText(false)); } if (m_secure) { if (media->remoteCrypto()) { String sdes = media->remoteCrypto(); static const 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 TraceDebug(m_traceId,m_enabler,DebugWarn,"Invalid SDES: '%s' [%p]",sdes.c_str(),m_ptr); } 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, bool allowEmptyAddr) { XDebug(DebugAll,"SDPSession::updateRtpSDP(%s,%s,%p,%u)", params.c_str(),rtpAddr.c_str(),oldList,allowEmptyAddr); rtpAddr = params.getValue("rtp_addr"); if (!(rtpAddr || allowEmptyAddr)) 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 || allowEmptyAddr)) 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; } int dir = SDPMedia::DirUnknown; 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); SDPMedia::setDirection(dir,tmp); } } } rtp->mappings(params.getValue("rtp_mapping" + rtp->suffix())); if (audio) { Rfc2833 rfc2833; for (int i = 0; i < Rfc2833::RateCount; ++i) { if (i == Rfc2833::Rate8khz) rfc2833[i] = params.getIntValue(YSTRING("rtp_rfc2833"),-1); else rfc2833[i] = params.getIntValue("rtp_rfc2833_" + Rfc2833::rateValue(i),-1); } rtp->rfc2833(rfc2833); } rtp->crypto(params.getValue("crypto" + rtp->suffix()),false); rtp->direction(dir,false); if (!lst) lst = new ObjList; lst->append(rtp); } return lst; } // Media changed notification. void SDPSession::mediaChanged(const SDPMedia& media) { XDebug(m_enabler,DebugAll,"SDPSession::mediaChanged('%s' %p)%s%s [%p]", media.c_str(),&media,(media.id() ? " id=" : ""),media.id().safe(),m_ptr); } // Dispatch rtp notification void SDPSession::dispatchingRtp(Message*& msg, SDPMedia* media) { XDebug(m_enabler,DebugAll,"SDPSession::dispatchingRtp(%p,%p) [%p]",msg,media,m_ptr); } // Set data used in debug void SDPSession::setSdpDebug(DebugEnabler* enabler, void* ptr, const String& traceId) { m_enabler = enabler ? enabler : static_cast(m_parser); m_ptr = ptr ? ptr : (void*)this; m_traceId = traceId; } // Print current media to output void SDPSession::printRtpMedia(const char* reason) { if (!(m_rtpMedia && m_enabler->debugAt(DebugAll))) return; String tmp; for (ObjList* o = m_rtpMedia->skipNull(); o; o = o->skipNext()) { SDPMedia* m = static_cast(o->get()); if (tmp) tmp << " "; tmp << m->c_str() << "=" << m->formats(); } TraceDebug(m_traceId,m_enabler,DebugAll,"%s: %s [%p]",reason,tmp.c_str(),m_ptr); } // Set extra parameters for formats void SDPSession::setFormatsExtra(const NamedList& list, bool out) { const String* amr = list.getParam(out ? YSTRING("oamr_extra") : YSTRING("iamr_extra")); m_amrExtra.assign(""); m_amrExtra.clearParams(); if (!TelEngine::null(amr)) { ObjList* l = amr->split(';',false); ObjList* o = l->skipNull(); if (o) { m_amrExtra.assign(*amr); for (; o; o = o->skipNext()) { const String* s = static_cast(o->get()); int pos = s->find('='); if (pos >= 0) { String n = s->substr(0,pos + 1).trimBlanks(); if (n) { m_amrExtra.addParam(n,s->substr(pos + 1)); continue; } } m_amrExtra.addParam(*s,0); } } TelEngine::destruct(l); } } // Parse a received SDP body, process session level parameters. NamedList* SDPSession::parseSessionParams(const MimeSdpBody* sdp) { NamedList* nl = 0; for (const ObjList* o = (sdp ? sdp->lines().skipNull() : 0); o; o = o->skipNext()) { const NamedString* l = static_cast(o->get()); if (l->name() == YSTRING("m")) break; const char* pref = 0; if (l->name() == YSTRING("b")) pref = "BW-"; else if (l->name() != YSTRING("a")) continue; NamedString* ns = 0; int pos = l->find(':'); if (pos >= 0) ns = new NamedString(pref + l->substr(0,pos),l->substr(pos + 1)); else ns = new NamedString(pref + *l); XDebug(m_enabler,DebugAll,"Parsed sess SDP param %s='%s' [%p]", ns->name().c_str(),ns->safe(),m_ptr); if (!nl) { Lock lck(m_parser); String pref = m_parser->m_ssdpParam; if (!pref) break; nl = new NamedList(pref + "_"); } nl->addParam(ns); } return nl; } // Replace SDP sesison void SDPSession::updateSessionParams(const NamedList& nl) { String pref; Lock lck(m_parser); if (m_parser->m_ssdpParam) pref = "o" + m_parser->m_ssdpParam; lck.drop(); pref = nl.getValue(YSTRING("ossdp-prefix"),pref); XDebug(m_enabler,DebugAll,"updateSessionParams('%s') pref='%s' [%p]", nl.c_str(),pref.c_str(),m_ptr); if (!pref) return; pref << "_"; m_createSdpParams.clearParams(); int pLen = pref.length(); for (ObjList* o = nl.paramList()->skipNull(); o; o = o->skipNext()) { const NamedString* p = static_cast(o->get()); if (p->name().startsWith(pref) && p->name().rfind('_') < pLen) { NamedString* ns = new NamedString(p->name().substr(pLen),*p); m_createSdpParams.addParam(ns); XDebug(m_enabler,DebugAll,"Added sess create SDP param %s='%s' [%p]", ns->name().c_str(),ns->safe(),m_ptr); } } } // Add extra AMR params to fmtp line void SDPSession::addFmtpAmrExtra(String& buf, const String* fmtp) { if (TelEngine::null(fmtp)) { buf.append(m_amrExtra,";"); return; } // Remove from 'fmtp' parameters we are setting from extra ObjList* l = fmtp->split(';',false); for (ObjList* f = l->skipNull(); f;) { String& s = *static_cast(f->get()); s = s.trimBlanks(); NamedString* found = 0; if (s) { int pos = s.find('='); if (pos >= 0) { for (ObjList* o = m_amrExtra.paramList()->skipNull(); o; o = o->skipNext()) { NamedString* ns = static_cast(o->get()); if (s.startsWith(ns->name())) { found = ns; break; } } } else found = m_amrExtra.getParam(s); } if (found) { f->remove(); f = f->skipNull(); } else f = f->skipNext(); } buf.append(m_amrExtra,";"); buf.append(l,";"); TelEngine::destruct(l); } // Add session or media parameters void SDPSession::addSdpParams(MimeSdpBody* sdp, const NamedList& params, bool* enc, bool* dir) { for (ObjList* o = params.paramList()->skipNull(); o; o = o->skipNext()) { const NamedString* p = static_cast(o->get()); if (p->name().find(':') >= 0) continue; const char* type = "a"; String tmp = p->name(); if (tmp.startSkip("BW-",false)) { if (!tmp) continue; type = "b"; } else { if (dir) *dir = *dir || lookup(tmp,SDPMedia::s_sdpDir); if (enc) *enc = *enc || (tmp == YSTRING("encryption")); } if (*p) tmp << ":" << *p; sdp->addLine(type,tmp); } } }; // namespace TelEngine /* vi: set ts=8 sw=4 sts=4 noet: */