/*
Copyright (C) 2005-2007 Michel de Boer <michel@twinklephone.com>
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <assert.h>
#include <cstdlib>
#include <iostream>
#include "protocol.h"
#include "sdp_parse_ctrl.h"
#include "sdp.h"
#include "util.h"
#include "parser/hdr_warning.h"
#include "parser/parameter.h"
#include "audits/memman.h"
string sdp_ntwk_type2str(t_sdp_ntwk_type n) {
switch(n) {
case SDP_NTWK_NULL: return "NULL";
case SDP_NTWK_IN: return "IN";
default:
assert(false);
}
}
t_sdp_ntwk_type str2sdp_ntwk_type(string s) {
if (s == "IN") return SDP_NTWK_IN;
throw (t_sdp_syntax_error("unknown network type: " + s));
}
string sdp_addr_type2str(t_sdp_addr_type a) {
switch(a) {
case SDP_ADDR_NULL: return "NULL";
case SDP_ADDR_IP4: return "IP4";
case SDP_ADDR_IP6: return "IP6";
default:
assert(false);
}
}
t_sdp_addr_type str2sdp_addr_type(string s) {
if (s == "IP4") return SDP_ADDR_IP4;
if (s == "IP6") return SDP_ADDR_IP6;
throw (t_sdp_syntax_error("unknown address type: " + s));
}
string sdp_transport2str(t_sdp_transport t) {
switch(t) {
case SDP_TRANS_NULL: return "NULL";
case SDP_TRANS_RTP: return "RTP/AVP";
case SDP_TRANS_UDP: return "udp";
default:
assert(false);
}
}
t_sdp_transport str2sdp_transport(string s) {
if (s == "RTP/AVP") return SDP_TRANS_RTP;
if (s == "udp") return SDP_TRANS_UDP;
// Other transports are not recognized and are mapped to NULL.
return SDP_TRANS_NULL;
}
t_sdp_media_type str2sdp_media_type(string s) {
if (s == "audio") return SDP_AUDIO;
if (s == "video") return SDP_VIDEO;
return SDP_OTHER;
}
string sdp_media_type2str(t_sdp_media_type m) {
switch(m) {
case SDP_AUDIO: return "audio";
case SDP_VIDEO: return "video";
default:
assert(false);
}
}
string get_rtpmap(unsigned format, t_audio_codec codec) {
string rtpmap;
rtpmap = int2str(format);
rtpmap += ' ';
switch(codec) {
case CODEC_G711_ULAW:
rtpmap += SDP_RTPMAP_G711_ULAW;
break;
case CODEC_G711_ALAW:
rtpmap += SDP_RTPMAP_G711_ALAW;
break;
case CODEC_GSM:
rtpmap += SDP_RTPMAP_GSM;
break;
case CODEC_SPEEX_NB:
rtpmap += SDP_RTPMAP_SPEEX_NB;
break;
case CODEC_SPEEX_WB:
rtpmap += SDP_RTPMAP_SPEEX_WB;
break;
case CODEC_SPEEX_UWB:
rtpmap += SDP_RTPMAP_SPEEX_UWB;
break;
case CODEC_ILBC:
rtpmap += SDP_RTPMAP_ILBC;
break;
case CODEC_G726_16:
rtpmap += SDP_RTPMAP_G726_16;
break;
case CODEC_G726_24:
rtpmap += SDP_RTPMAP_G726_24;
break;
case CODEC_G726_32:
rtpmap += SDP_RTPMAP_G726_32;
break;
case CODEC_G726_40:
rtpmap += SDP_RTPMAP_G726_40;
break;
case CODEC_TELEPHONE_EVENT:
rtpmap += SDP_RTPMAP_TELEPHONE_EV;
break;
default:
assert(false);
}
return rtpmap;
}
string sdp_media_direction2str(t_sdp_media_direction d) {
switch(d) {
case SDP_INACTIVE: return "inactive";
case SDP_SENDONLY: return "sendonly";
case SDP_RECVONLY: return "recvonly";
case SDP_SENDRECV: return "sendrecv";
default:
assert(false);
}
}
///////////////////////////////////
// class t_sdp_origin
///////////////////////////////////
t_sdp_origin::t_sdp_origin() {
network_type = SDP_NTWK_NULL;
address_type = SDP_ADDR_NULL;
}
t_sdp_origin::t_sdp_origin(string _username, string _session_id,
string _session_version, string _address) :
username(_username),
session_id(_session_id),
session_version(_session_version),
address(_address)
{
network_type = SDP_NTWK_IN;
address_type = SDP_ADDR_IP4;
}
string t_sdp_origin::encode(void) const {
string s;
s = "o=";
s += username;
s += ' ' + session_id;
s += ' ' + session_version;
s += ' ' + sdp_ntwk_type2str(network_type);
s += ' ' + sdp_addr_type2str(address_type);
s += ' ' + address;
s += CRLF;
return s;
}
///////////////////////////////////
// class t_sdp_connection
///////////////////////////////////
t_sdp_connection::t_sdp_connection() {
network_type = SDP_NTWK_NULL;
}
t_sdp_connection::t_sdp_connection(string _address) :
address(_address)
{
network_type = SDP_NTWK_IN;
address_type = SDP_ADDR_IP4;
}
string t_sdp_connection::encode(void) const {
string s;
s = "c=";
s += sdp_ntwk_type2str(network_type);
s += ' ' + sdp_addr_type2str(address_type);
s += ' ' + address;
s += CRLF;
return s;
}
///////////////////////////////////
// class t_sdp_attr
///////////////////////////////////
t_sdp_attr::t_sdp_attr(string _name) {
name = _name;
}
t_sdp_attr::t_sdp_attr(string _name, string _value) {
name = _name;
value = _value;
}
string t_sdp_attr::encode(void) const {
string s;
s = "a=";
s += name;
if (value != "") {
s += ':' + value;
}
s += CRLF;
return s;
}
///////////////////////////////////
// class t_sdp_media
///////////////////////////////////
t_sdp_media::t_sdp_media() {
port = 0;
transport = SDP_TRANS_NULL;
format_dtmf = 0;
}
t_sdp_media::t_sdp_media(t_sdp_media_type _media_type,
unsigned short _port, const list<t_audio_codec> &_formats,
unsigned short _format_dtmf,
const map<t_audio_codec, unsigned short> &ac2format)
{
media_type = sdp_media_type2str(_media_type);
port = _port;
transport = SDP_TRANS_RTP;
format_dtmf = _format_dtmf;
for (list<t_audio_codec>::const_iterator i = _formats.begin();
i != _formats.end(); i++)
{
map<t_audio_codec, unsigned short>::const_iterator it;
it = ac2format.find(*i);
assert(it != ac2format.end());
add_format(it->second, *i);
}
if (format_dtmf > 0) {
add_format(format_dtmf, CODEC_TELEPHONE_EVENT);
}
}
string t_sdp_media::encode(void) const {
string s;
s = "m=";
s += media_type;
s += ' ' + int2str(port);
s += ' ' + sdp_transport2str(transport);
for (list<unsigned short>::const_iterator i = formats.begin();
i != formats.end(); i++)
{
s += ' ' + int2str(*i);
}
s += CRLF;
if (connection.network_type != SDP_NTWK_NULL) {
s += connection.encode();
}
for (list<t_sdp_attr>::const_iterator i = attributes.begin();
i != attributes.end(); i++)
{
s += i->encode();
}
return s;
}
void t_sdp_media::add_format(unsigned short f, t_audio_codec codec) {
formats.push_back(f);
// RFC 3264 5.1
// All media descriptions SHOULD contain an rtpmap
string rtpmap = get_rtpmap(f, codec);
attributes.push_back(t_sdp_attr("rtpmap", rtpmap));
// RFC 2833 3.9
// Add fmtp parameter
if (format_dtmf > 0 && f == format_dtmf) {
string fmtp = int2str(f);
fmtp += ' ';
fmtp += "0-15";
attributes.push_back(t_sdp_attr("fmtp", fmtp));
}
}
t_sdp_attr *t_sdp_media::get_attribute(const string &name) {
for (list<t_sdp_attr>::iterator i = attributes.begin();
i != attributes.end(); i++)
{
if (cmp_nocase(i->name, name) == 0) return &(*i);
}
// Attribute does not exist
return NULL;
}
list<t_sdp_attr *> t_sdp_media::get_attributes(const string &name) {
list<t_sdp_attr *> l;
for (list<t_sdp_attr>::iterator i = attributes.begin();
i != attributes.end(); i++)
{
if (cmp_nocase(i->name, name) == 0) l.push_back(&(*i));
}
return l;
}
t_sdp_media_direction t_sdp_media::get_direction(void) const {
t_sdp_attr *a;
t_sdp_media *self = const_cast<t_sdp_media *>(this);
a = self->get_attribute("inactive");
if (a) return SDP_INACTIVE;
a = self->get_attribute("sendonly");
if (a) return SDP_SENDONLY;
a = self->get_attribute("recvonly");
if (a) return SDP_RECVONLY;
return SDP_SENDRECV;
}
t_sdp_media_type t_sdp_media::get_media_type(void) const {
return str2sdp_media_type(media_type);
}
///////////////////////////////////
// class t_sdp
///////////////////////////////////
t_sdp::t_sdp() : t_sip_body() {
version = 0;
}
t_sdp::t_sdp(const string &user, const string &sess_id, const string &sess_version,
const string &user_host, const string &media_host, unsigned short media_port,
const list<t_audio_codec> &formats, unsigned short format_dtmf,
const map<t_audio_codec, unsigned short> &ac2format) :
origin(user, sess_id, sess_version, user_host),
connection(media_host)
{
version = 0;
media.push_back(t_sdp_media(SDP_AUDIO, media_port, formats, format_dtmf,
ac2format));
}
t_sdp::t_sdp(const string &user, const string &sess_id, const string &sess_version,
const string &user_host, const string &media_host) :
origin(user, sess_id, sess_version, user_host),
connection(media_host)
{
version = 0;
}
void t_sdp::add_media(const t_sdp_media &m) {
media.push_back(m);
}
string t_sdp::encode(void) const {
string s;
s = "v=" + int2str(version) + CRLF;
s += origin.encode();
if (session_name == "") {
// RFC 3264 5
// Session name may no be empty. Recommende is '-'
s += "s=-";
s += CRLF;
} else {
s += "s=" + session_name + CRLF;
}
if (connection.network_type != SDP_NTWK_NULL) {
s += connection.encode();
}
// RFC 3264 5
// Time parameter should be 0 0
s += "t=0 0";
s += CRLF;
for (list<t_sdp_attr>::const_iterator i = attributes.begin();
i != attributes.end(); i++)
{
s += i->encode();
}
for (list<t_sdp_media>::const_iterator i = media.begin();
i != media.end(); i++)
{
s += i->encode();
}
return s;
}
t_sip_body *t_sdp::copy(void) const {
t_sdp *s = new t_sdp(*this);
MEMMAN_NEW(s);
return s;
}
t_body_type t_sdp::get_type(void) const {
return BODY_SDP;
}
t_media t_sdp::get_media(void) const {
return t_media("application", "sdp");
}
bool t_sdp::is_supported(int &warn_code, string &warn_text) const {
warn_text = "";
if (version != 0) {
warn_code = W_399_MISCELLANEOUS;
warn_text = "SDP version ";
warn_text += int2str(version);
warn_text += " not supported";
return false;
}
const t_sdp_media *m = get_first_media(SDP_AUDIO);
// Connection information must be present at the session level
// and/or the media level
if (connection.network_type == SDP_NTWK_NULL) {
if (m == NULL || m->connection.network_type == SDP_NTWK_NULL) {
warn_code = W_399_MISCELLANEOUS;
warn_text = "c-line missing";
return false;
}
} else {
// Only Internet is supported
if (connection.network_type != SDP_NTWK_IN) {
warn_code = W_300_INCOMPATIBLE_NWK_PROT;
return false;
}
// Only IPv4 is supported
if (connection.address_type != SDP_ADDR_IP4) {
warn_code = W_301_INCOMPATIBLE_ADDR_FORMAT;
return false;
}
}
// There must be at least 1 audio stream with a non-zero port value
if (m == NULL && !media.empty()) {
warn_code = W_304_MEDIA_TYPE_NOT_AVAILABLE;
warn_text = "Valid media stream for audio is missing";
return false;
}
// RFC 3264 5, RFC 3725 flow IV
// There may be 0 media streams
if (media.empty()) {
return true;
}
// Check connection information on media level
if (m->connection.network_type != SDP_NTWK_NULL &&
m->connection.address_type != SDP_ADDR_IP4) {
warn_code = W_301_INCOMPATIBLE_ADDR_FORMAT;
return false;
}
if (m->transport != SDP_TRANS_RTP) {
warn_code = W_302_INCOMPATIBLE_TRANS_PROT;
return false;
}
t_sdp_media *m2 = const_cast<t_sdp_media *>(m);
const t_sdp_attr *a = m2->get_attribute("ptime");
if (a) {
unsigned short p = atoi(a->value.c_str());
if (p < MIN_PTIME) {
warn_code = W_306_ATTRIBUTE_NOT_UNDERSTOOD;
warn_text = "Attribute 'ptime' too small. must be >= ";
warn_text += int2str(MIN_PTIME);
return false;
}
if (p > MAX_PTIME) {
warn_code = W_306_ATTRIBUTE_NOT_UNDERSTOOD;
warn_text = "Attribute 'ptime' too big. must be <= ";
warn_text += int2str(MAX_PTIME);
return false;
}
}
return true;
}
string t_sdp::get_rtp_host(t_sdp_media_type media_type) const {
const t_sdp_media *m = get_first_media(media_type);
assert(m != NULL);
// If the media line has its own connection information, then
// take the host information from there.
if (m->connection.network_type == SDP_NTWK_IN) {
return m->connection.address;
}
// The host information must be in the session connection info
assert(connection.network_type == SDP_NTWK_IN);
return connection.address;
}
unsigned short t_sdp::get_rtp_port(t_sdp_media_type media_type) const {
const t_sdp_media *m = get_first_media(media_type);
assert(m != NULL);
return m->port;
}
list<unsigned short> t_sdp::get_codecs(t_sdp_media_type media_type) const {
const t_sdp_media *m = get_first_media(media_type);
assert(m != NULL);
return m->formats;
}
string t_sdp::get_codec_description(t_sdp_media_type media_type,
unsigned short codec) const
{
t_sdp_media *m = const_cast<t_sdp_media *>(get_first_media(media_type));
assert(m != NULL);
const list<t_sdp_attr *> attrs = m->get_attributes("rtpmap");
if (attrs.empty()) return "";
for (list<t_sdp_attr *>::const_iterator i = attrs.begin();
i != attrs.end(); i++)
{
vector<string> l = split_ws((*i)->value);
if (atoi(l.front().c_str()) == codec) {
return l.back();
}
}
return "";
}
t_audio_codec t_sdp::get_rtpmap_codec(const string &rtpmap) const {
if (rtpmap.empty()) return CODEC_NULL;
vector<string> rtpmap_elems = split(rtpmap, '/');
if (rtpmap_elems.size() < 2) {
// RFC 2327
// The rtpmap should at least contain the encoding name
// and sample rate
return CODEC_UNSUPPORTED;
}
string codec_name = trim(rtpmap_elems[0]);
int sample_rate = atoi(trim(rtpmap_elems[1]).c_str());
if (cmp_nocase(codec_name, SDP_AC_NAME_G711_ULAW) == 0 && sample_rate == 8000) {
return CODEC_G711_ULAW;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_G711_ALAW) == 0 && sample_rate == 8000) {
return CODEC_G711_ALAW;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_GSM) == 0 && sample_rate == 8000) {
return CODEC_GSM;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_SPEEX) == 0 && sample_rate == 8000) {
return CODEC_SPEEX_NB;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_SPEEX) == 0 && sample_rate == 16000) {
return CODEC_SPEEX_WB;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_SPEEX) == 0 && sample_rate == 32000) {
return CODEC_SPEEX_UWB;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_ILBC) == 0 && sample_rate == 8000) {
return CODEC_ILBC;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_G726_16) == 0 && sample_rate == 8000) {
return CODEC_G726_16;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_G726_24) == 0 && sample_rate == 8000) {
return CODEC_G726_24;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_G726_32) == 0 && sample_rate == 8000) {
return CODEC_G726_32;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_G726_40) == 0 && sample_rate == 8000) {
return CODEC_G726_40;
} else if (cmp_nocase(codec_name, SDP_AC_NAME_TELEPHONE_EV) == 0) {
return CODEC_TELEPHONE_EVENT;
}
return CODEC_UNSUPPORTED;
}
t_audio_codec t_sdp::get_codec(t_sdp_media_type media_type,
unsigned short codec) const
{
string rtpmap = get_codec_description(media_type, codec);
// If there is no rtpmap description then use the static
// payload definition as defined by RFC 3551
if (rtpmap.empty()) {
switch(codec) {
case SDP_FORMAT_G711_ULAW:
return CODEC_G711_ULAW;
case SDP_FORMAT_G711_ALAW:
return CODEC_G711_ALAW;
case SDP_FORMAT_GSM:
return CODEC_GSM;
default:
return CODEC_UNSUPPORTED;
}
}
// Use the rtpmap description to map the payload number
// to a codec
return get_rtpmap_codec(rtpmap);
}
t_sdp_media_direction t_sdp::get_direction(t_sdp_media_type media_type) const {
const t_sdp_media *m = get_first_media(media_type);
assert(m != NULL);
return m->get_direction();
}
string t_sdp::get_fmtp(t_sdp_media_type media_type, unsigned short codec) const {
t_sdp_media *m = const_cast<t_sdp_media *>(get_first_media(media_type));
assert(m != NULL);
const list<t_sdp_attr *> attrs = m->get_attributes("fmtp");
if (attrs.empty()) return "";
for (list<t_sdp_attr *>::const_iterator i = attrs.begin();
i != attrs.end(); i++)
{
vector<string> l = split_ws((*i)->value);
if (atoi(l.front().c_str()) == codec) {
return l.back();
}
}
return "";
}
int t_sdp::get_fmtp_int_param(t_sdp_media_type media_type, unsigned short codec,
const string param) const
{
string fmtp = get_fmtp(media_type, codec);
if (fmtp.empty()) return -1;
int value;
list<t_parameter> l = str2param_list(fmtp);
list<t_parameter>::const_iterator it = find(l.begin(), l.end(), t_parameter(param, ""));
if (it != l.end()) {
value = atoi(it->value.c_str());
} else {
value = -1;
}
return value;
}
unsigned short t_sdp::get_ptime(t_sdp_media_type media_type) const {
t_sdp_media *m = const_cast<t_sdp_media *>(get_first_media(media_type));
assert(m != NULL);
const t_sdp_attr *a = m->get_attribute("ptime");
if (!a) return 0;
return atoi(a->value.c_str());
}
bool t_sdp::get_zrtp_support(t_sdp_media_type media_type) const {
t_sdp_media *m = const_cast<t_sdp_media *>(get_first_media(media_type));
assert(m != NULL);
const t_sdp_attr *a = m->get_attribute("zrtp");
if (!a) return false;
return true;
}
void t_sdp::set_ptime(t_sdp_media_type media_type, unsigned short ptime) {
t_sdp_media *m = const_cast<t_sdp_media *>(get_first_media(media_type));
assert(m != NULL);
t_sdp_attr a("ptime", int2str(ptime));
m->attributes.push_back(a);
}
void t_sdp::set_direction(t_sdp_media_type media_type, t_sdp_media_direction direction) {
t_sdp_media *m = const_cast<t_sdp_media *>(get_first_media(media_type));
assert(m != NULL);
t_sdp_attr a(sdp_media_direction2str(direction));
m->attributes.push_back(a);
}
void t_sdp::set_fmtp(t_sdp_media_type media_type, unsigned short codec, const string &fmtp) {
t_sdp_media *m = const_cast<t_sdp_media *>(get_first_media(media_type));
assert(m != NULL);
string s = int2str(codec);
s += ' ';
s += fmtp;
t_sdp_attr a("fmtp", s);
m->attributes.push_back(a);
}
void t_sdp::set_fmtp_int_param(t_sdp_media_type media_type, unsigned short codec,
const string ¶m, int value)
{
string fmtp(param);
fmtp += '=';
fmtp += int2str(value);
set_fmtp(media_type, codec, fmtp);
}
void t_sdp::set_zrtp_support(t_sdp_media_type media_type) {
t_sdp_media *m = const_cast<t_sdp_media *>(get_first_media(media_type));
assert(m != NULL);
t_sdp_attr a("zrtp");
m->attributes.push_back(a);
}
const t_sdp_media *t_sdp::get_first_media(t_sdp_media_type media_type) const {
for (list<t_sdp_media>::const_iterator i = media.begin();
i != media.end(); i++)
{
if (i->get_media_type() == media_type && i->port != 0) {
return &(*i);
}
}
return NULL;
}
syntax highlighted by Code2HTML, v. 0.9.1