You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sems/core/AmSdp.cpp

750 lines
18 KiB

/*
* $Id: AmSdp.cpp,v 1.20.2.4 2005/06/01 12:00:24 rco Exp $
*
* Copyright (C) 2002-2003 Fhg Fokus
*
* This file is part of sems, a free SIP media server.
*
* sems 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
*
* For a license to use the ser software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* sems 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>
#include "AmConfig.h"
#include "AmSdp.h"
#include "AmCmd.h"
#include "AmUtils.h"
#include "AmPlugIn.h"
#include "amci/amci.h"
#include "log.h"
inline char* get_next_line(char* s);
inline bool parse_string_tok(char*& s, string& res, char sep_char = ' ');
inline bool parse_type(char*& s, int& v, char** ref, const char* name);
static bool parse_sdp_connection(AmSdp* sdp_msg, char*& s, char*&);
static bool parse_sdp_media(AmSdp* sdp_msg, char*& s, char*&);
static bool parse_sdp_attribute(AmSdp* sdp_msg, char*& s, char*&);
// inline functions
inline string net_t_2_str(int nt)
{
switch(nt){
case NT_IN: return "IN";
default: return "<unknown network type>";
}
}
inline string addr_t_2_str(int at)
{
switch(at){
case AT_V4: return "IP4";
case AT_V6: return "IP6";
default: return "<unknown address type>";
}
}
inline string media_t_2_str(int mt)
{
switch(mt){
case MT_AUDIO: return "audio";
case MT_VIDEO: return "video";
case MT_APPLICATION: return "application";
case MT_DATA: return "data";
default: return "<unknown media type>";
}
}
inline string transport_p_2_str(int tp)
{
switch(tp){
case TP_RTPAVP: return "RTP/AVP";
case TP_UDP: return "udp";
default: return "<unknown transport protocol>";
}
}
bool SdpPayload::operator == (int r)
{
DBG("pl == r: payload_type = %i; r = %i\n",payload_type, r);
return payload_type == r;
}
// WARNING:
//
// If you call this function from a handler function,
// do not forget to set 'next_line' to the proper value
// (else, it could think EOT has been reached !)
//
// Generally: call this function if you know what your doing !
// Else call 'parse_sdp_line'
//
inline bool parse_sdp_line_ex( AmSdp* sdp_msg, char*& s, char discr,
bool optional,
bool (*parse_func)(AmSdp*,char*&,char*&),
bool only_one, char*& next_line);
// WARNING:
//
// Do not touch the handler's third parameter until you known what you are doing.
//
inline bool parse_sdp_line( AmSdp* sdp_msg, char*& s, char discr,
bool optional, bool (*parse_func)(AmSdp*,char*&,char*&),
bool only_one = true );
//
// class AmSdp: Methods
//
AmSdp::AmSdp()
{
}
AmSdp::AmSdp(const AmSdp& p_sdp_msg)
: version(p_sdp_msg.version),
origin(p_sdp_msg.origin),
sessionName(p_sdp_msg.sessionName),
conn(p_sdp_msg.conn),
media(p_sdp_msg.media),
telephone_event_pt(NULL),
remote_active(false)
{
memcpy(r_buf,p_sdp_msg.r_buf,BUFFER_SIZE);
}
void AmSdp::setBody(const char* _sdp_msg)
{
strcpy(r_buf,_sdp_msg);
}
int AmSdp::parse()
{
char* s = r_buf;
media.clear();
bool ret =
parse_sdp_line(this,s,'v',false,NULL) ||
parse_sdp_line(this,s,'o',false,NULL) ||
parse_sdp_line(this,s,'s',false,NULL) ||
parse_sdp_line(this,s,'i',true,NULL) ||
parse_sdp_line(this,s,'u',true,NULL) ||
parse_sdp_line(this,s,'e',true,NULL,false) ||
parse_sdp_line(this,s,'p',true,NULL,false) ||
parse_sdp_line(this,s,'c',true,parse_sdp_connection) ||
parse_sdp_line(this,s,'b',true,NULL,false) ||
parse_sdp_line(this,s,'t',true,NULL,false) ||
parse_sdp_line(this,s,'k',true,NULL) ||
parse_sdp_line(this,s,'a',true,NULL,false) ||
parse_sdp_line(this,s,'m',false,parse_sdp_media,false);
if(!ret && conn.address.empty()){
for(vector<SdpMedia>::iterator it = media.begin();
!ret && (it != media.end()); ++it)
ret = it->conn.address.empty();
if(ret){
ERROR("A connection field must be present in every\n");
ERROR("media description or at the session level.\n");
}
}
telephone_event_pt = findPayload("telephone-event");
//DBG("telephone_event_pt = %i\n",telephone_event_pt);
return ret;
}
int AmSdp::genResponse(const string& localip, int localport,
string& out_buf)
{
string l_ip = "IP4 " + localip;
#ifdef SUPPORT_IPV6
if(localip.find('.') = string::npos)
l_ip = "IP6 " + localip;
#endif
out_buf =
"v=0\r\n"
"o=username 0 0 IN " + l_ip + "\r\n"
"s=session\r\n"
"c=IN " + l_ip + "\r\n"
"t=0 0\r\n"
"m=audio " + int2str(localport) + " RTP/AVP";
string payloads;
string options;
for(vector<SdpPayload*>::iterator it = sup_pl.begin();
it != sup_pl.end(); ++it){
payloads += " " + int2str((*it)->payload_type);
//if ((*it)->payload_type >= 96) // dynamic payload
options += "a=rtpmap:" + int2str((*it)->payload_type) + " "
+ (*it)->encoding_name + "/" + int2str((*it)->clock_rate) + "\r\n";
if ((*it)->sdp_format_parameters.size()) {
// return format parameters as sent in the invite
// (we have initialized our codec with those)
options += "a=fmtp:" + int2str((*it)->payload_type) + " "
+ (*it)->sdp_format_parameters + "\r\n";
}
}
if (hasTelephoneEvent())
payloads += " " + int2str(telephone_event_pt->payload_type);
out_buf += payloads + "\r\n"
+ options;
if (hasTelephoneEvent())
{
out_buf += "a=rtpmap:" + int2str(telephone_event_pt->payload_type) + " " +
telephone_event_pt->encoding_name + "/" +
int2str(telephone_event_pt->clock_rate) + "\r\n"
"a=fmtp:" + int2str(telephone_event_pt->payload_type) + " 0-15\r\n";
}
if(remote_active /* dir == SdpMedia::DirActive */)
out_buf += "a=direction:passive\r\n";
return 0;
}
int AmSdp::genRequest(const string& localip,int localport, string& out_buf)
{
AmPlugIn* plugin = AmPlugIn::instance();
const map<int,amci_payload_t*>& payloads = plugin->getPayloads();
if(payloads.empty()){
ERROR("no payload plugin loaded.\n");
return -1;
}
string l_ip = "IP4 " + localip;
#ifdef SUPPORT_IPV6
if(localip.find('.') == string::npos)
l_ip = "IP6 " + localip;
#endif
out_buf =
"v=0\r\n"
"o=username 0 0 IN " + l_ip + "\r\n"
"s=session\r\n"
"c=IN " + l_ip + "\r\n"
"t=0 0\r\n"
"m=audio " + int2str(localport) + " RTP/AVP ";
map<int,amci_payload_t*>::const_iterator it = payloads.begin();
out_buf += int2str((it++)->first);
for(;it != payloads.end();++it)
out_buf += string(" ") + int2str(it->first);
out_buf += "\r\n";
for(it = payloads.begin();it != payloads.end();++it) {
//if(it->first >= 96) {
out_buf += "a=rtpmap:" + int2str(it->first)
+ " " + string(it->second->name)
+ "/" + int2str(it->second->sample_rate)
+ "\r\n";
//}
}
return 0;
}
SdpPayload* AmSdp::getCompatiblePayload(int media_type, string& addr, int& port)
{
vector<SdpMedia>::iterator m_it;
SdpPayload* payload=0;
AmPlugIn* pi = AmPlugIn::instance();
for( m_it = media.begin(); m_it != media.end(); ++m_it ){
if( (media_type != m_it->type) )
continue;
vector<SdpPayload>::iterator it = m_it->payloads.begin();
for(; it != m_it->payloads.end(); ++it ) {
amci_payload_t* a_pl = NULL;
if(it->payload_type < 96){
// try static payloads
a_pl = pi->payload(it->payload_type);
}
if( a_pl ) {
payload = &(*it);
payload->int_pt = a_pl->payload_id;
payload->encoding_name = a_pl->name;
payload->clock_rate = a_pl->sample_rate;
goto end;
}
else {
// Try dynamic payloads
// and give a chance to broken
// implementation using a static payload number
// for dynamic ones.
int int_pt = getDynPayload(it->encoding_name,
it->clock_rate);
if(int_pt != -1){
payload = &(*it);
payload->int_pt = int_pt;
goto end;
}
}
}
}
end:
sup_pl.clear();
if(payload){
if(m_it->conn.address.empty()){
DBG("using global address: %s\n",conn.address.c_str());
addr = conn.address;
}
else {
DBG("using media specific address: %s\n",m_it->conn.address.c_str());
addr = m_it->conn.address;
}
if(m_it->dir == SdpMedia::DirActive)
remote_active = true;
sup_pl.push_back(payload);
port = (int)m_it->port;
}
return payload;
}
bool AmSdp::hasTelephoneEvent()
{
return telephone_event_pt != NULL;
}
int AmSdp::getDynPayload(const string& name, int rate)
{
AmPlugIn* pi = AmPlugIn::instance();
const map<int, amci_payload_t*>& ref_payloads = pi->getPayloads();
for(map<int, amci_payload_t*>::const_iterator pl_it = ref_payloads.begin();
pl_it != ref_payloads.end(); ++pl_it)
if( (name == pl_it->second->name)
&& (rate == pl_it->second->sample_rate) )
return pl_it->first;
return -1;
}
const SdpPayload *AmSdp::findPayload(const string& name)
{
vector<SdpMedia>::iterator m_it;
for (m_it = media.begin(); m_it != media.end(); ++m_it)
{
vector<SdpPayload>::iterator it = m_it->payloads.begin();
for(; it != m_it->payloads.end(); ++it )
{
if (it->encoding_name == name)
{
return new SdpPayload(*it);
}
}
}
return NULL;
}
// enum { TP_NONE=0, TP_RTPAVP, TP_UDP };
char* transport_prot_lookup[] = { "RTP/AVP", "udp", 0 };
inline bool parse_transport_prot(char*& s, int& tp)
{ return parse_type(s,tp,transport_prot_lookup,"transport protocol"); }
// enum { MT_NONE=0, MT_AUDIO, MT_VIDEO, MT_APPLICATION, MT_DATA };
char* media_type_lookup[] = { "audio", "video", "application", "data", 0 };
#define parse_media_type(s,mt) parse_type(s,mt,media_type_lookup,"media type")
//inline bool parse_media_type(char*& s, int& mt)
//{ return parse_type(s,mt,media_type_lookup,"media type"); }
// enum { NT_OTHER=0, NT_IN };
char* net_type_lookup[] = { "IN", 0 };
inline bool parse_net_type(char*& s, int& network)
{ return parse_type(s,network,net_type_lookup,"net type"); }
// enum { AT_NONE=0, AT_V4, AT_V6 };
char* addr_type_lookup[] = { "IP4", "IP6", 0 };
inline bool parse_addr_type(char*& s, int& addr_t)
{ return parse_type(s,addr_t,addr_type_lookup,"address type"); }
inline char* get_next_line(char* s)
{
char* next_line=s;
// search for next line
while( *next_line != '\0') {
if(*next_line == 13){
*next_line = '\0';
next_line += 2;
break;
}
else if(*next_line == 10){
*(next_line++) = '\0';
break;
}
next_line++;
}
return next_line;
}
// WARNING:
//
// If you call this function from a handler function,
// do not forget to set 'next_line' to the proper value
// (else, it could think EOT has been reached !)
//
// Generally: call this function if you know what your doing !
// Else call 'parse_sdp_line'
//
inline bool parse_sdp_line_ex( AmSdp* sdp_msg, char*& s, char discr,
bool optional,
bool (*parse_func)(AmSdp*,char*&,char*&),
bool only_one, char*& next_line)
{
while(true){
if((*s == '\0') && !optional){
ERROR("parse_sdp_line : unexpected end of text while looking for '%c'\n",discr);
return true;
}
if(*s == discr) {
if( *(++s) != '=' ){
ERROR("parse_sdp_line : expected '=' but "
"<%c> found \n",*s);
return true;
}
s++;
next_line = get_next_line(s);
bool ret=false;
if(parse_func)
ret = (*parse_func)(sdp_msg,s,next_line);
s = next_line;
if(only_one || (*s != discr))
return ret;
continue;
}
else if(!optional){
ERROR(" parse_sdp_line : parameter '%c=' was "
"not found\n",discr);
return true;
}
// token is optional and has not been found.
return false;
}
}
// WARNING:
//
// Do not touch the handler's third parameter until you known what you are doing.
//
inline bool parse_sdp_line( AmSdp* sdp_msg, char*& s, char discr,
bool optional, bool (*parse_func)(AmSdp*,char*&,char*&),
bool only_one)
{
char* next_line=0;
return parse_sdp_line_ex(sdp_msg,s,discr,optional,parse_func,only_one,next_line);
}
inline bool parse_string_tok(char*& s, string& res, char sep_char)
{
for( ;(*s != '\0') && (*s == ' '); s++);
char* begin = s;
while(*s != '\0'){
if(*s == sep_char){
*(s++) = '\0';
break;
}
s++;
}
res = begin;
return res.empty();
}
inline bool parse_type(char*& s, int& v, char** ref, const char* name)
{
string e;
v=0;
if(parse_string_tok(s,e)){
ERROR(" parse_type : while parsing %s\n",name);
return true;
}
char** cur = ref;
while(*cur){
if(e == *cur){
v = cur-ref+1;
break;
}
cur++;
}
if(!*cur){
ERROR("unkown %s: <%s>\n",name,e.c_str());
return true;
}
return false;
}
/*
static bool parse_sdp_version(AmSdp* sdp_msg, char*& s, char*&)
{
return str2i(s,sdp_msg->version);
}
*/
inline bool parse_sdp_connection_struct(SdpConnection& c, char*& s)
{
return parse_net_type(s,c.network) ||
parse_addr_type(s,c.addrType) ||
parse_string_tok(s,c.address);
}
/*
static bool parse_sdp_origin(AmSdp* sdp_msg, char*& s, char*&)
{
SdpOrigin& o = sdp_msg->origin;
return parse_string_tok(s,o.user) ||
str2i(s,o.sessId) ||
str2i(s,o.sessV) ||
parse_sdp_connection_struct(o.conn,s);
parse_net_type(s,o.conn.network) ||
parse_addr_type(s,o.conn.addrType) ||
parse_string_tok(s,o.conn.address);
}
*/
static bool parse_sdp_connection(AmSdp* sdp_msg, char*& s, char*&)
{
return parse_sdp_connection_struct(sdp_msg->media.empty() ?
sdp_msg->conn :
sdp_msg->media.back().conn,
s);
}
inline bool parse_codec_list(char*& s, vector<SdpPayload>& payloads)
{
unsigned int payload;
while(*s != '\0'){
if(!str2i(s,payload)){
payloads.push_back(SdpPayload(payload));
}
else {
ERROR("invalid payload number found in media line\n");
return true;
}
}
return false;
}
static bool parse_sdp_media(AmSdp* sdp_msg, char*& s, char*& next_line)
{
SdpMedia m;
m.dir = SdpMedia::DirBoth;
char* old_s = s;
bool ret = parse_media_type(s,m.type) ||
str2i(s,m.port) ||
parse_transport_prot(s,m.transport);
if(ret){
ERROR("while parsing 'm=%s' line.\n",old_s);
return true;
}
if(!ret && (m.transport == TP_RTPAVP))
ret = ret || parse_codec_list(s,m.payloads);
sdp_msg->media.push_back(m);
s = next_line;
DBG("next_line=<%s>\n",next_line);
ret = ret
// Media title
|| parse_sdp_line(sdp_msg,s,'i',true,NULL)
// connection information - optional if included at session-level
|| parse_sdp_line(sdp_msg,s,'c',true,parse_sdp_connection)
// bandwidth information
|| parse_sdp_line(sdp_msg,s,'b',true,NULL,false)
// encryption key
|| parse_sdp_line(sdp_msg,s,'k',true,NULL)
// zero or more media attribute lines
|| parse_sdp_line(sdp_msg,s,'a',true,parse_sdp_attribute,false);
if(ret){
ERROR("while parsing media attributes.\n");
return true;
}
next_line = get_next_line(s);
DBG("ret=%i; next_line=<%s>\n",ret,next_line);
return ret;
}
static bool parse_sdp_attribute(AmSdp* sdp_msg, char*& s, char*& next_line)
{
DBG("parse_sdp_attribute: s=%s\n",s);
if(sdp_msg->media.empty()){
ERROR("While parsing media options: no actual media !\n");
return true;
}
SdpMedia& media = sdp_msg->media.back();
char* sep=0;
for( sep=s; *sep!='\0' && *sep!=':'; sep++ );
if( *sep == ':' ){
// attribute definition: 'attribute:value'
string attr_name(s,int(sep-s));
char* old_s = s;
s = sep + 1;
if(attr_name == "rtpmap"){
//fmt: "<payload type> <encoding name>/<clock rate>[/<encoding parameters>]"
unsigned int payload_type=0,clock_rate=0;
string encoding_name, params;
bool ret = str2i(s,payload_type)
|| parse_string_tok(s,encoding_name,'/')
|| str2i(s,clock_rate,'/');
if(ret){
ERROR("while parsing 'a=%s'\n",old_s);
return true;
}
parse_string_tok(s,params,'\0');
DBG("sdp attribute: pt=%u; enc=%s; cr=%u\n",
payload_type,encoding_name.c_str(),clock_rate);
vector<SdpPayload>::iterator pl_it;
for( pl_it=media.payloads.begin();
(pl_it != media.payloads.end())
&& (pl_it->payload_type != int(payload_type));
++pl_it);
if(pl_it != media.payloads.end()){
*pl_it = SdpPayload( int(payload_type),
encoding_name,
int(clock_rate));
}
return ret;
}
else if (attr_name == "fmtp") {
// fmt: "<payload type> parameters" (?)
// z.b. a=fmtp:101 0-15
unsigned int payload_type=0;
string params;
bool ret = str2i(s, payload_type) || parse_string_tok(s, params, '\0');
vector<SdpPayload>::iterator pl_it;
for( pl_it=media.payloads.begin();
(pl_it != media.payloads.end())
&& (pl_it->payload_type != int(payload_type));
++pl_it);
if(pl_it != media.payloads.end())
pl_it->sdp_format_parameters = params;
return ret;
}
else if(attr_name == "direction"){
if(!strncmp(s,"active",6/*sizeof("active")*/))
media.dir = SdpMedia::DirActive;
else if(!strncmp(s,"passive",7/*sizeof("passive")*/))
media.dir = SdpMedia::DirPassive;
else if(!strncmp(s,"both",4/*sizeof("both")*/))
media.dir = SdpMedia::DirBoth;
else
DBG("unknown value for a=direction:%s",s);
}
else {
DBG("unknown attribute definition '%s'\n",old_s);
}
}
else {
// flag: 'flag_name'
DBG("flag definition is not yet supported (%s)\n",s);
}
return false;
}