/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #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" using namespace std; 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); } return ""; } 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); } return ""; } 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_RTP: return "RTP/AVP"; case SDP_TRANS_UDP: return "udp"; default: assert(false); } return ""; } 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 other. return SDP_TRANS_OTHER; } 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); } return ""; } 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_G729A: rtpmap += SDP_RTPMAP_G729A; 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); } return ""; } /////////////////////////////////// // 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; format_dtmf = 0; } t_sdp_media::t_sdp_media(t_sdp_media_type _media_type, unsigned short _port, const list &_formats, unsigned short _format_dtmf, const map &ac2format) { media_type = sdp_media_type2str(_media_type); port = _port; transport = sdp_transport2str(SDP_TRANS_RTP); format_dtmf = _format_dtmf; for (list::const_iterator i = _formats.begin(); i != _formats.end(); i++) { map::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 += ' ' + transport; // Encode media formats. Note that only one of the format lists // will be populated depending on the media type. // Numeric formats. for (list::const_iterator i = formats.begin(); i != formats.end(); ++i) { s += ' ' + int2str(*i); } // Alpha numeric formats. for (list::const_iterator i = alpha_num_formats.begin(); i != alpha_num_formats.end(); ++i) { s += ' ' + *i; } s += CRLF; // Connection information. if (connection.network_type != SDP_NTWK_NULL) { s += connection.encode(); } // Attributes. for (list::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)); } else if (codec == CODEC_G729A) { string fmtp = int2str(f); fmtp += ' '; fmtp += "annexb=no"; // annexb=no means G729A attributes.push_back(t_sdp_attr("fmtp", fmtp)); } } t_sdp_attr *t_sdp_media::get_attribute(const string &name) { for (list::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_media::get_attributes(const string &name) { list l; for (list::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(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); } t_sdp_transport t_sdp_media::get_transport(void) const { return str2sdp_transport(transport); } /////////////////////////////////// // 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 &formats, unsigned short format_dtmf, const map &ac2format) : t_sip_body(), version(0), origin(user, sess_id, sess_version, user_host), connection(media_host) { 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) : t_sip_body(), version(0), origin(user, sess_id, sess_version, user_host), connection(media_host) {} 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::const_iterator i = attributes.begin(); i != attributes.end(); i++) { s += i->encode(); } for (list::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->get_transport() != SDP_TRANS_RTP) { warn_code = W_302_INCOMPATIBLE_TRANS_PROT; return false; } t_sdp_media *m2 = const_cast(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 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(get_first_media(media_type)); assert(m != NULL); const list attrs = m->get_attributes("rtpmap"); if (attrs.empty()) return ""; for (list::const_iterator i = attrs.begin(); i != attrs.end(); i++) { vector 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 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_G729) == 0 && sample_rate == 8000) { return CODEC_G729A; } 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(get_first_media(media_type)); assert(m != NULL); const list attrs = m->get_attributes("fmtp"); if (attrs.empty()) return ""; for (list::const_iterator i = attrs.begin(); i != attrs.end(); i++) { vector 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 l = str2param_list(fmtp); list::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(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(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(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(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(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(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::const_iterator i = media.begin(); i != media.end(); i++) { if (i->get_media_type() == media_type && i->port != 0) { return &(*i); } } return NULL; } bool t_sdp::local_ip_check(void) const { if (origin.address == AUTO_IP4_ADDRESS) return false; if (connection.address == AUTO_IP4_ADDRESS) return false; for (list::const_iterator it = media.begin(); it != media.end(); ++it) { if (it->connection.address == AUTO_IP4_ADDRESS) return false; } return true; }