freeswitch/libs/sofia-sip/libsofia-sip-ua/soa/soa_static.c

1252 lines
31 KiB
C

/*
* This file is part of the Sofia-SIP package
*
* Copyright (C) 2006 Nokia Corporation.
*
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/**@CFILE soa_static.c
*
* @brief Static implementation of Sofia SDP Offer/Answer Engine
*
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
*
* @date Created: Tue Aug 16 17:06:06 EEST 2005
*
* @par Use-cases
* 1. no existing session
* a) generating offer (upgrade with user-SDP)
* b) generating answer (upgrade with remote-SDP, rejects with user-SDP)
* 2. session exists
* a) generating offer:
* upgrades with user-SDP
* b) generating answer:
* upgrades with remote-SDP, rejects with user-SDP
* c) processing answer:
* rejects with user-SDP, no upgrades
*
* Upgrading session with user SDP:
*/
#include "config.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
struct soa_static_complete;
#define SU_MSG_ARG_T struct soa_static_completed
#include <sofia-sip/su_wait.h>
#include <sofia-sip/su_tag_class.h>
#include <sofia-sip/su_tag_class.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su_strlst.h>
#include <sofia-sip/string0.h>
#include <sofia-sip/bnf.h>
#include "sofia-sip/soa.h"
#include <sofia-sip/sdp.h>
#include "sofia-sip/soa_session.h"
#define NONE ((void *)-1)
#define XXX assert(!"implemented")
#if !HAVE_STRCASESTR
char *strcasestr(const char *haystack, const char *needle);
#endif
typedef struct soa_static_session
{
soa_session_t sss_session[1];
char *sss_audio_aux;
}
soa_static_session_t;
static int soa_static_init(char const *, soa_session_t *, soa_session_t *);
static void soa_static_deinit(soa_session_t *);
static int soa_static_set_params(soa_session_t *ss, tagi_t const *tags);
static int soa_static_get_params(soa_session_t const *ss, tagi_t *tags);
static tagi_t *soa_static_get_paramlist(soa_session_t const *ss,
tag_type_t tag, tag_value_t value,
...);
static int soa_static_set_capability_sdp(soa_session_t *ss,
sdp_session_t *sdp,
char const *, isize_t);
static int soa_static_set_remote_sdp(soa_session_t *ss,
int new_version,
sdp_session_t *sdp,
char const *, isize_t);
static int soa_static_set_user_sdp(soa_session_t *ss,
sdp_session_t *sdp,
char const *, isize_t);
static int soa_static_generate_offer(soa_session_t *ss, soa_callback_f *);
static int soa_static_generate_answer(soa_session_t *ss, soa_callback_f *);
static int soa_static_process_answer(soa_session_t *ss, soa_callback_f *);
static int soa_static_process_reject(soa_session_t *ss, soa_callback_f *);
static int soa_static_activate(soa_session_t *ss, char const *option);
static int soa_static_deactivate(soa_session_t *ss, char const *option);
static void soa_static_terminate(soa_session_t *ss, char const *option);
struct soa_session_actions const soa_default_actions =
{
(sizeof soa_default_actions),
sizeof (struct soa_static_session),
"static",
soa_static_init,
soa_static_deinit,
soa_static_set_params,
soa_static_get_params,
soa_static_get_paramlist,
soa_base_media_features,
soa_base_sip_require,
soa_base_sip_supported,
soa_base_remote_sip_features,
soa_static_set_capability_sdp,
soa_static_set_remote_sdp,
soa_static_set_user_sdp,
soa_static_generate_offer,
soa_static_generate_answer,
soa_static_process_answer,
soa_static_process_reject,
soa_static_activate,
soa_static_deactivate,
soa_static_terminate
};
/* Initialize session */
static int soa_static_init(char const *name,
soa_session_t *ss,
soa_session_t *parent)
{
return soa_base_init(name, ss, parent);
}
static void soa_static_deinit(soa_session_t *ss)
{
soa_base_deinit(ss);
}
static int soa_static_set_params(soa_session_t *ss, tagi_t const *tags)
{
soa_static_session_t *sss = (soa_static_session_t *)ss;
char const *audio_aux = sss->sss_audio_aux;
int n, m;
n = tl_gets(tags,
SOATAG_AUDIO_AUX_REF(audio_aux),
TAG_END());
if (n > 0 && str0casecmp(audio_aux, sss->sss_audio_aux)) {
char *s = su_strdup(ss->ss_home, audio_aux), *tbf = sss->sss_audio_aux;
if (s == NULL && audio_aux != NULL)
return -1;
sss->sss_audio_aux = s;
if (tbf)
su_free(ss->ss_home, tbf);
}
m = soa_base_set_params(ss, tags);
if (m < 0)
return m;
return n + m;
}
static int soa_static_get_params(soa_session_t const *ss, tagi_t *tags)
{
soa_static_session_t *sss = (soa_static_session_t *)ss;
int n, m;
n = tl_tgets(tags,
SOATAG_AUDIO_AUX(sss->sss_audio_aux),
TAG_END());
m = soa_base_get_params(ss, tags);
if (m < 0)
return m;
return n + m;
}
static tagi_t *soa_static_get_paramlist(soa_session_t const *ss,
tag_type_t tag, tag_value_t value,
...)
{
soa_static_session_t *sss = (soa_static_session_t *)ss;
ta_list ta;
tagi_t *tl;
ta_start(ta, tag, value);
tl = soa_base_get_paramlist(ss,
TAG_IF(sss->sss_audio_aux,
SOATAG_AUDIO_AUX(sss->sss_audio_aux)),
TAG_NEXT(ta_args(ta)));
ta_end(ta);
return tl;
}
static int soa_static_set_capability_sdp(soa_session_t *ss,
sdp_session_t *sdp,
char const *sdp_str,
isize_t sdp_len)
{
return soa_base_set_capability_sdp(ss, sdp, sdp_str, sdp_len);
}
static int soa_static_set_remote_sdp(soa_session_t *ss,
int new_version,
sdp_session_t *sdp,
char const *sdp_str,
isize_t sdp_len)
{
return soa_base_set_remote_sdp(ss, new_version, sdp, sdp_str, sdp_len);
}
static int soa_static_set_user_sdp(soa_session_t *ss,
sdp_session_t *sdp,
char const *sdp_str,
isize_t sdp_len)
{
return soa_base_set_user_sdp(ss, sdp, sdp_str, sdp_len);
}
/** Generate a rejected m= line */
sdp_media_t *soa_sdp_make_rejected_media(su_home_t *home,
sdp_media_t const *m,
sdp_session_t *sdp,
int include_all_codecs)
{
sdp_media_t rejected[1] = {{ sizeof (rejected) }};
rejected->m_type = m->m_type;
rejected->m_type_name = m->m_type_name;
rejected->m_port = 0;
rejected->m_proto = m->m_proto;
rejected->m_proto_name = m->m_proto_name;
if (include_all_codecs) {
rejected->m_rtpmaps = m->m_rtpmaps;
}
rejected->m_rejected = 1;
return sdp_media_dup(home, rejected, sdp);
}
/** Expand a @a truncated SDP.
*/
sdp_session_t *soa_sdp_expand_media(su_home_t *home,
sdp_session_t const *truncated,
sdp_session_t const *complete)
{
sdp_session_t *expanded;
sdp_media_t **m0;
sdp_media_t * const *m1;
expanded = sdp_session_dup(home, truncated);
if (expanded) {
for (m0 = &expanded->sdp_media, m1 = &complete->sdp_media;
*m1;
m1 = &(*m1)->m_next) {
if (!*m0) {
*m0 = soa_sdp_make_rejected_media(home, *m1, expanded, 0);
if (!*m0)
return NULL;
}
m0 = &(*m0)->m_next;
}
}
return expanded;
}
/** Check if @a session should be upgraded with @a remote */
int soa_sdp_upgrade_is_needed(sdp_session_t const *session,
sdp_session_t const *remote)
{
sdp_media_t const *rm, *lm;
if (!remote)
return 0;
if (!session)
return 1;
for (rm = remote->sdp_media, lm = session->sdp_media;
rm && lm ; rm = rm->m_next, lm = lm->m_next) {
if (rm->m_rejected)
continue;
if (lm->m_rejected)
break;
}
return rm != NULL;
}
/** Check if codec is in auxiliary list */
int soa_sdp_is_auxiliary_codec(sdp_rtpmap_t const *rm, char const *auxiliary)
{
char const *codec;
size_t clen, alen;
char const *match;
if (!rm || !rm->rm_encoding || !auxiliary)
return 0;
codec = rm->rm_encoding;
clen = strlen(codec), alen = strlen(auxiliary);
if (clen > alen)
return 0;
for (match = auxiliary;
(match = strcasestr(match, codec));
match = match + 1) {
if (IS_ALPHANUM(match[clen]) || match[clen] == '-')
continue;
if (match != auxiliary &&
(IS_ALPHANUM(match[-1]) || match[-1] == '-'))
continue;
return 1;
}
return 0;
}
/** Find first matching media in table. */
sdp_media_t *soa_sdp_matching(soa_session_t *ss,
sdp_media_t *mm[],
sdp_media_t const *with,
int *return_common_codecs)
{
int i, j = -1;
sdp_media_t *m;
sdp_rtpmap_t const *rm;
soa_static_session_t *sss = (soa_static_session_t *)ss;
char const *auxiliary;
auxiliary = with->m_type == sdp_media_audio ? sss->sss_audio_aux : NULL;
/* Looking for a single codec */
if (with->m_rtpmaps && with->m_rtpmaps->rm_next == NULL)
auxiliary = NULL;
for (i = 0; mm[i]; i++) {
if (!sdp_media_match_with(mm[i], with))
continue;
if (!sdp_media_uses_rtp(with))
break;
if (!return_common_codecs)
break;
/* Check also rtpmaps */
for (rm = mm[i]->m_rtpmaps; rm; rm = rm->rm_next) {
/* Ignore auxiliary codecs */
if (auxiliary && soa_sdp_is_auxiliary_codec(rm, auxiliary))
continue;
if (sdp_rtpmap_find_matching(with->m_rtpmaps, rm))
break;
}
if (rm)
break;
if (j == -1)
j = i;
}
if (return_common_codecs)
*return_common_codecs = mm[i] != NULL;
if (mm[i] == NULL && j != -1)
i = j; /* return m= line without common codecs */
m = mm[i];
for (; mm[i]; i++)
mm[i] = mm[i + 1];
return m;
}
/** Set payload types in @a l_m according to the values in @a r_m.
*
* @retval number of common codecs
*/
int soa_sdp_set_rtpmap_pt(sdp_media_t *l_m,
sdp_media_t const *r_m)
{
sdp_rtpmap_t *lrm, **next_lrm;
sdp_rtpmap_t const *rrm;
int local_codecs = 0, common_codecs = 0;
unsigned char dynamic_pt[128];
unsigned pt;
for (next_lrm = &l_m->m_rtpmaps; (lrm = *next_lrm); ) {
if (lrm->rm_any) {
/* Remove codecs known only by pt number */
*next_lrm = lrm->rm_next;
continue;
}
else {
next_lrm = &lrm->rm_next;
}
local_codecs++;
rrm = sdp_rtpmap_find_matching(r_m->m_rtpmaps, lrm);
/* XXX - do fmtp comparison */
if (rrm) {
/* Use same payload type as remote */
if (lrm->rm_pt != rrm->rm_pt) {
lrm->rm_predef = 0;
lrm->rm_pt = rrm->rm_pt;
}
common_codecs++;
}
else {
/* Determine payload type later */
lrm->rm_any = 1;
}
}
if (local_codecs == common_codecs)
return common_codecs;
/* Select unique dynamic payload type for each payload */
memset(dynamic_pt, 0, sizeof dynamic_pt);
for (lrm = l_m->m_rtpmaps; lrm; lrm = lrm->rm_next) {
if (!lrm->rm_any)
dynamic_pt[lrm->rm_pt] = 1;
}
for (rrm = r_m->m_rtpmaps; rrm; rrm = rrm->rm_next) {
dynamic_pt[rrm->rm_pt] = 1;
}
for (next_lrm = &l_m->m_rtpmaps; (lrm = *next_lrm); ) {
if (!lrm->rm_any) {
next_lrm = &lrm->rm_next;
continue;
}
lrm->rm_any = 0;
pt = lrm->rm_pt;
if (dynamic_pt[pt]) {
for (pt = 96; pt < 128; pt++)
if (!dynamic_pt[pt])
break;
if (pt == 128) {
for (pt = 0; pt < 128; pt++)
if (!sdp_rtpmap_well_known[pt] && !dynamic_pt[pt])
break;
}
if (pt == 128) {
for (pt = 0; pt < 128; pt++)
if (!dynamic_pt[pt])
break;
}
if (pt == 128) {
/* Too many payload types */
*next_lrm = lrm->rm_next;
continue;
}
lrm->rm_pt = pt;
lrm->rm_predef = 0;
}
dynamic_pt[pt] = 1;
next_lrm = &lrm->rm_next;
}
return common_codecs;
}
/** Sort rtpmaps in @a inout_list according to the values in @a rrm.
*
* @return Number of common codecs
*/
int soa_sdp_sort_rtpmap(sdp_rtpmap_t **inout_list,
sdp_rtpmap_t const *rrm,
char const *auxiliary)
{
sdp_rtpmap_t *sorted = NULL, **next = &sorted, **left;
sdp_rtpmap_t *aux = NULL, **next_aux = &aux;
int common_codecs = 0;
assert(inout_list);
if (!inout_list)
return 0;
/* If remote has only single codec, ignore list of auxiliary codecs */
if (rrm && !rrm->rm_next)
auxiliary = NULL;
/* Insertion sort from *inout_list to sorted */
for (; rrm && *inout_list; rrm = rrm->rm_next) {
for (left = inout_list; *left; left = &(*left)->rm_next) {
if (sdp_rtpmap_match(rrm, (*left)))
break;
}
if (!*left)
continue;
if (auxiliary && soa_sdp_is_auxiliary_codec(rrm, auxiliary)) {
*next_aux = *left, next_aux = &(*next_aux)->rm_next;
}
else {
common_codecs++;
*next = *left; next = &(*next)->rm_next;
}
*left = (*left)->rm_next;
}
/* Append common auxiliary codecs */
if (aux)
*next = aux, next = next_aux;
/* Append leftover codecs */
*next = *inout_list;
*inout_list = sorted;
return common_codecs;
}
/** Select rtpmaps in @a inout_list according to the values in @a rrm.
*
* @return Number of common codecs
*/
int soa_sdp_select_rtpmap(sdp_rtpmap_t **inout_list,
sdp_rtpmap_t const *rrm,
char const *auxiliary,
int select_single)
{
sdp_rtpmap_t **left;
sdp_rtpmap_t *aux = NULL, **next_aux = &aux;
int common_codecs = 0;
assert(inout_list);
if (!inout_list)
return 0;
for (left = inout_list; *left; ) {
if (auxiliary && soa_sdp_is_auxiliary_codec(*left, auxiliary))
/* Insert into list of auxiliary codecs */
*next_aux = *left, *left = (*left)->rm_next,
next_aux = &(*next_aux)->rm_next;
else if (!(select_single && common_codecs > 0)
&& sdp_rtpmap_find_matching(rrm, (*left)))
/* Select */
left = &(*left)->rm_next, common_codecs++;
else
/* Remove */
*left = (*left)->rm_next;
}
*left = aux, *next_aux = NULL;
return common_codecs;
}
/** Sort and select rtpmaps within session */
int soa_sdp_upgrade_rtpmaps(soa_session_t *ss,
sdp_session_t *session,
sdp_session_t const *remote)
{
soa_static_session_t *sss = (soa_static_session_t *)ss;
sdp_media_t *sm;
sdp_media_t const *rm;
for (sm = session->sdp_media, rm = remote->sdp_media;
sm && rm;
sm = sm->m_next, rm = rm->m_next) {
if (sm->m_rejected)
continue;
if (sdp_media_uses_rtp(sm)) {
int common_codecs = soa_sdp_set_rtpmap_pt(sm, rm);
char const *auxiliary =
rm->m_type == sdp_media_audio ? sss->sss_audio_aux : NULL;
if (ss->ss_rtp_sort == SOA_RTP_SORT_REMOTE ||
(ss->ss_rtp_sort == SOA_RTP_SORT_DEFAULT &&
rm->m_mode == sdp_recvonly)) {
soa_sdp_sort_rtpmap(&sm->m_rtpmaps, rm->m_rtpmaps, auxiliary);
}
if (common_codecs == 0)
;
else if (ss->ss_rtp_select == SOA_RTP_SELECT_SINGLE) {
soa_sdp_select_rtpmap(&sm->m_rtpmaps, rm->m_rtpmaps, auxiliary, 1);
}
else if (ss->ss_rtp_select == SOA_RTP_SELECT_COMMON) {
soa_sdp_select_rtpmap(&sm->m_rtpmaps, rm->m_rtpmaps, auxiliary, 0);
}
}
}
return 0;
}
/** Upgrade m= lines within session */
int soa_sdp_upgrade(soa_session_t *ss,
su_home_t *home,
sdp_session_t *session,
sdp_session_t const *caps,
sdp_session_t const *upgrader)
{
soa_static_session_t *sss = (soa_static_session_t *)ss;
int Ns, Nc, Nu, size, i, j;
sdp_media_t *m, **mm, *cm;
sdp_media_t **s_media, **o_media, **c_media;
sdp_media_t const **u_media;
Ns = sdp_media_count(session, sdp_media_any, 0, 0, 0);
Nc = sdp_media_count(caps, sdp_media_any, 0, 0, 0);
Nu = sdp_media_count(upgrader, sdp_media_any, 0, 0, 0);
if (caps == upgrader)
size = Ns + Nc + 1;
else if (Ns < Nu)
size = Nu + 1;
else
size = Ns + 1;
s_media = su_zalloc(home, size * (sizeof *s_media));
o_media = su_zalloc(home, (Ns + 1) * (sizeof *o_media));
c_media = su_zalloc(home, (Nc + 1) * (sizeof *c_media));
u_media = su_zalloc(home, (Nu + 1) * (sizeof *u_media));
cm = sdp_media_dup_all(home, caps->sdp_media, session);
if (!s_media || !c_media || !u_media || !cm)
return -1;
for (i = 0, m = session->sdp_media; m && i < Ns; m = m->m_next)
o_media[i++] = m;
assert(i == Ns);
for (i = 0, m = cm; m && i < Nc; m = m->m_next)
c_media[i++] = m;
assert(i == Nc);
for (i = 0, m = upgrader->sdp_media; m && i < Nu; m = m->m_next)
u_media[i++] = m;
assert(i == Nu);
if (caps != upgrader) {
/* Update session according to remote */
for (i = 0; i < Nu; i++) {
int common_codecs = 0;
m = soa_sdp_matching(ss, c_media, u_media[i], &common_codecs);
if (!m || u_media[i]->m_rejected) {
m = soa_sdp_make_rejected_media(home, u_media[i], session, 0);
}
else if (sdp_media_uses_rtp(m)) {
/* Process rtpmaps */
char const *auxiliary =
m->m_type == sdp_media_audio ? sss->sss_audio_aux : NULL;
if (!common_codecs && !ss->ss_rtp_mismatch)
m = soa_sdp_make_rejected_media(home, m, session, 1);
soa_sdp_set_rtpmap_pt(m, u_media[i]);
if (ss->ss_rtp_sort == SOA_RTP_SORT_REMOTE ||
(ss->ss_rtp_sort == SOA_RTP_SORT_DEFAULT &&
u_media[i]->m_mode == sdp_recvonly)) {
soa_sdp_sort_rtpmap(&m->m_rtpmaps, u_media[i]->m_rtpmaps, auxiliary);
}
if (common_codecs &&
(ss->ss_rtp_select == SOA_RTP_SELECT_SINGLE ||
ss->ss_rtp_select == SOA_RTP_SELECT_COMMON)) {
soa_sdp_select_rtpmap(&m->m_rtpmaps, u_media[i]->m_rtpmaps, auxiliary,
ss->ss_rtp_select == SOA_RTP_SELECT_SINGLE);
}
}
s_media[i] = m;
}
}
else {
/* Update session according to local */
for (i = 0; i < Ns; i++) {
m = soa_sdp_matching(ss, c_media, o_media[i], NULL);
if (!m)
m = soa_sdp_make_rejected_media(home, o_media[i], session, 0);
s_media[i] = m;
}
/* Here we just append new media at the end */
for (j = 0; c_media[j]; j++)
s_media[i++] = c_media[j];
assert(i <= size);
}
mm = &session->sdp_media;
for (i = 0; s_media[i]; i++) {
m = s_media[i]; *mm = m; mm = &m->m_next;
}
*mm = NULL;
return 0;
}
/** Check if @a session contains media that are rejected by @a remote. */
int soa_sdp_reject_is_needed(sdp_session_t const *session,
sdp_session_t const *remote)
{
sdp_media_t const *sm, *rm;
if (!remote)
return 1;
if (!session)
return 0;
for (sm = session->sdp_media, rm = remote->sdp_media;
sm && rm; sm = sm->m_next, rm = rm->m_next) {
if (rm->m_rejected) {
if (!sm->m_rejected)
return 1;
}
else {
/* Mode bits do not match */
if (((rm->m_mode & sdp_recvonly) == sdp_recvonly)
!= ((sm->m_mode & sdp_sendonly) == sdp_sendonly))
return 1;
}
}
if (sm)
return 1;
return 0;
}
/** If m= line is rejected by remote mark m= line rejected within session */
int soa_sdp_reject(su_home_t *home,
sdp_session_t *session,
sdp_session_t const *remote)
{
sdp_media_t *sm;
sdp_media_t const *rm;
if (!session || !session->sdp_media || !remote)
return 0;
rm = remote->sdp_media;
for (sm = session->sdp_media; sm; sm = sm->m_next) {
if (!rm || rm->m_rejected) {
sm->m_rejected = 1;
sm->m_mode = 0;
sm->m_port = 0;
sm->m_number_of_ports = 1;
if (sm->m_format)
sm->m_format->l_next = NULL;
if (sm->m_rtpmaps)
sm->m_rtpmaps->rm_next = NULL;
sm->m_information = NULL;
if (sm->m_connections)
sm->m_connections->c_next = NULL;
sm->m_bandwidths = NULL;
sm->m_key = NULL;
sm->m_attributes = NULL;
sm->m_user = NULL;
}
if (rm)
rm = rm->m_next;
}
return 0;
}
/** Check if @a session mode should be changed. */
int soa_sdp_mode_set_is_needed(sdp_session_t const *session,
sdp_session_t const *remote,
char const *hold)
{
sdp_media_t const *sm, *rm, *rm_next;
int hold_all;
sdp_mode_t recv_mode;
SU_DEBUG_7(("soa_sdp_mode_set_is_needed(%p, %p, \"%s\"): called\n",
session, remote, hold ? hold : ""));
if (!session )
return 0;
hold_all = str0cmp(hold, "*") == 0;
rm = remote ? remote->sdp_media : NULL, rm_next = NULL;
for (sm = session->sdp_media; sm; sm = sm->m_next, rm = rm_next) {
rm_next = rm ? rm->m_next : NULL;
if (sm->m_rejected)
continue;
if (rm) {
/* Mode bits do not match */
if (((rm->m_mode & sdp_recvonly) == sdp_recvonly)
!= ((sm->m_mode & sdp_sendonly) == sdp_sendonly))
return 1;
}
recv_mode = sm->m_mode & sdp_recvonly;
if (recv_mode && hold &&
(hold_all || strcasestr(hold, sm->m_type_name)))
return 1;
}
return 0;
}
/** Update mode within session */
int soa_sdp_mode_set(sdp_session_t *session,
sdp_session_t const *remote,
char const *hold)
{
sdp_media_t *sm;
sdp_media_t const *rm, *rm_next;
int hold_all;
sdp_mode_t send_mode, recv_mode;
SU_DEBUG_7(("soa_sdp_mode_set(%p, %p, \"%s\"): called\n",
session, remote, hold ? hold : ""));
if (!session || !session->sdp_media)
return 0;
rm = remote ? remote->sdp_media : NULL, rm_next = NULL;
hold_all = str0cmp(hold, "*") == 0;
for (sm = session->sdp_media; sm; sm = sm->m_next, rm = rm_next) {
rm_next = rm ? rm->m_next : NULL;
if (sm->m_rejected)
continue;
send_mode = sdp_sendonly;
if (rm)
send_mode = (rm->m_mode & sdp_recvonly) ? sdp_sendonly : 0;
recv_mode = sm->m_mode & sdp_recvonly;
if (recv_mode && hold && (hold_all || strcasestr(hold, sm->m_type_name)))
recv_mode = 0;
sm->m_mode = recv_mode | send_mode;
}
return 0;
}
enum offer_answer_action {
generate_offer,
generate_answer,
process_answer
};
/**
* Updates the modified copy of local SDP based
* on application provided local SDP and remote SDP.
*/
static int offer_answer_step(soa_session_t *ss,
enum offer_answer_action action,
char const *by)
{
char c_address[64];
sdp_session_t *local = ss->ss_local->ssd_sdp;
sdp_session_t local0[1];
sdp_session_t *user = ss->ss_user->ssd_sdp;
unsigned user_version = ss->ss_user_version;
sdp_session_t *remote = ss->ss_remote->ssd_sdp;
unsigned remote_version = ss->ss_remote_version;
sdp_origin_t o[1] = {{ sizeof(o) }};
sdp_connection_t *c, c0[1] = {{ sizeof(c0) }};
sdp_time_t t[1] = {{ sizeof(t) }};
char const *phrase = "Internal Media Error";
su_home_t tmphome[SU_HOME_AUTO_SIZE(8192)];
su_home_auto(tmphome, sizeof tmphome);
SU_DEBUG_7(("soa_static_offer_answer_action(%p, %s): called\n", ss, by));
if (user == NULL)
return soa_set_status(ss, 500, "No session set by user");
if (action == generate_offer)
remote = NULL;
/* Pre-negotiation Step: Expand truncated remote SDP */
if (local && remote) switch (action) {
case generate_answer:
case process_answer:
if (sdp_media_count(remote, sdp_media_any, "*", 0, 0) <
sdp_media_count(local, sdp_media_any, "*", 0, 0)) {
SU_DEBUG_5(("%s: remote %s is truncated: expanding\n",
by, action == generate_answer ? "offer" : "answer"));
remote = soa_sdp_expand_media(tmphome, remote, local);
}
default:
break;
}
/* Step A: Create local SDP session (based on user-supplied SDP) */
if (local == NULL) switch (action) {
case generate_offer:
case generate_answer:
SU_DEBUG_7(("soa_static(%p, %s): generating local description\n", ss, by));
local = local0;
*local = *user, local->sdp_media = NULL;
if (local->sdp_origin) {
o->o_username = local->sdp_origin->o_username;
/* o->o_address = local->sdp_origin->o_address; */
}
if (!o->o_address)
o->o_address = c0;
local->sdp_origin = o;
if (soa_init_sdp_origin(ss, o, c_address) < 0) {
phrase = "Cannot Get IP Address for Media";
goto internal_error;
}
break;
case process_answer:
default:
goto internal_error;
}
/* Step B: upgrade local SDP (add m= lines to it) */
switch (action) {
case generate_offer:
/* Upgrade local SDP based on user SDP */
if (local != local0 && ss->ss_local_user_version == user_version)
break;
if (local != local0)
*local0 = *local, local = local0;
SU_DEBUG_7(("soa_static(%p, %s): %s\n", ss, by,
"upgrade with local description"));
soa_sdp_upgrade(ss, tmphome, local, user, user);
break;
case generate_answer:
/* Upgrade local SDP based on remote SDP */
if (ss->ss_local_user_version == user_version &&
ss->ss_local_remote_version == remote_version)
break;
if (1) {
if (local != local0)
*local0 = *local, local = local0;
SU_DEBUG_7(("soa_static(%p, %s): %s\n", ss, by,
"upgrade with remote description"));
soa_sdp_upgrade(ss, tmphome, local, user, remote);
}
break;
case process_answer:
default:
break;
}
/* Step C: reject media */
switch (action) {
case generate_offer:
/* Local media is marked as rejected already in upgrade phase */
break;
case generate_answer:
case process_answer:
if (ss->ss_local_remote_version == remote_version)
break;
if (soa_sdp_reject_is_needed(local, remote)) {
if (local != local0) {
*local0 = *local, local = local0;
#define DUP_LOCAL(local) \
do { \
if (!local->sdp_media) break; \
local->sdp_media = \
sdp_media_dup_all(tmphome, local->sdp_media, local); \
if (!local->sdp_media) \
goto internal_error; \
} while (0)
DUP_LOCAL(local);
}
SU_DEBUG_7(("soa_static(%p, %s): marking rejected media\n", ss, by));
soa_sdp_reject(tmphome, local, remote);
}
break;
default:
break;
}
/* Step D: Set media mode bits */
switch (action) {
case generate_offer:
case generate_answer:
case process_answer:
if (soa_sdp_mode_set_is_needed(local, remote, ss->ss_hold)) {
if (local != local0) {
*local0 = *local, local = local0;
DUP_LOCAL(local);
}
soa_sdp_mode_set(local, remote, ss->ss_hold);
}
break;
default:
break;
}
/* Step E: Upgrade codecs by answer. */
switch (action) {
case process_answer:
/* Upgrade local SDP based on remote SDP */
if (ss->ss_local_remote_version == remote_version)
break;
if (1 /* We don't have good test for codecs */) {
SU_DEBUG_7(("soa_static(%p, %s): %s\n", ss, by,
"upgrade codecs with remote description"));
if (local != local0) {
*local0 = *local, local = local0;
DUP_LOCAL(local);
}
soa_sdp_upgrade_rtpmaps(ss, local, remote);
}
break;
case generate_offer:
case generate_answer:
default:
break;
}
/* Step F: Update c= line */
switch (action) {
case generate_offer:
case generate_answer:
/* Upgrade local SDP based of user SDP */
if (ss->ss_local_user_version == user_version &&
local->sdp_connection)
break;
if (local->sdp_connection == NULL ||
(user->sdp_connection != NULL &&
sdp_connection_cmp(local->sdp_connection, user->sdp_connection))) {
sdp_media_t *m;
/* Every m= line (even rejected one) must have a c= line
* or there must be a c= line at session level
*/
if (user->sdp_connection)
c = user->sdp_connection;
else
c = local->sdp_origin->o_address;
for (m = local->sdp_media; m; m = m->m_next)
if (m->m_connections == NULL)
break;
if (m) {
if (local != local0) {
*local0 = *local, local = local0;
DUP_LOCAL(local);
}
local->sdp_connection = c;
}
}
break;
default:
break;
}
soa_description_free(ss, ss->ss_previous);
if (ss->ss_local->ssd_sdp != local &&
sdp_session_cmp(ss->ss_local->ssd_sdp, local)) {
/* We have modfied local session: update origin-line */
if (local->sdp_origin != o)
*o = *local->sdp_origin, local->sdp_origin = o;
o->o_version++;
/* Do sanity checks for the created SDP */
if (!local->sdp_subject) /* s= is mandatory */
local->sdp_subject = "-";
if (!local->sdp_time) /* t= is mandatory */
local->sdp_time = t;
if (action == generate_offer) {
/* Keep a copy of previous session state */
*ss->ss_previous = *ss->ss_local;
memset(ss->ss_local, 0, (sizeof *ss->ss_local));
ss->ss_previous_user_version = ss->ss_local_user_version;
ss->ss_previous_remote_version = ss->ss_local_remote_version;
}
SU_DEBUG_7(("soa_static(%p, %s): storing local description\n", ss, by));
/* Update the unparsed and pretty-printed descriptions */
if (soa_description_set(ss, ss->ss_local, local, NULL, 0) < 0) {
goto internal_error;
}
}
/* Update version numbers */
switch (action) {
case generate_offer:
ss->ss_local_user_version = user_version;
break;
case generate_answer:
ss->ss_local_user_version = user_version;
ss->ss_local_remote_version = remote_version;
break;
case process_answer:
ss->ss_local_remote_version = remote_version;
default:
break;
}
su_home_deinit(tmphome);
return 0;
internal_error:
su_home_deinit(tmphome);
return soa_set_status(ss, 500, phrase);
}
/**
* Generates offer based on local SDP.
*/
static int soa_static_generate_offer(soa_session_t *ss,
soa_callback_f *completed)
{
if (!ss->ss_user->ssd_sdp)
return soa_set_status(ss, 500, "No session set by user");
if (offer_answer_step(ss, generate_offer, "soa_generate_offer") < 0)
return -1;
return soa_base_generate_offer(ss, NULL);
}
static int soa_static_generate_answer(soa_session_t *ss,
soa_callback_f *completed)
{
/* NOTE:
* - local SDP might have changed
* - remote SDP might have been updated
*/
if (offer_answer_step(ss, generate_answer, "soa_generate_answer") < 0)
return -1;
return soa_base_generate_answer(ss, NULL);
}
static int soa_static_process_answer(soa_session_t *ss,
soa_callback_f *completed)
{
/* NOTE:
* - both local and remote information is available
* - local SDP might have changed
* - remote SDP might have been updated
*/
if (offer_answer_step(ss, process_answer, "soa_process_answer") < 0)
return -1;
return soa_base_process_answer(ss, NULL);
}
/** Process rejected offer */
static int soa_static_process_reject(soa_session_t *ss,
soa_callback_f *completed)
{
struct soa_description d[1];
*d = *ss->ss_local;
*ss->ss_local = *ss->ss_previous;
ss->ss_local_user_version = ss->ss_previous_user_version;
ss->ss_local_remote_version = ss->ss_previous_remote_version;
memset(ss->ss_previous, 0, (sizeof *ss->ss_previous));
soa_description_free(ss, d);
return soa_base_process_reject(ss, NULL);
}
static int soa_static_activate(soa_session_t *ss, char const *option)
{
return soa_base_activate(ss, option);
}
static int soa_static_deactivate(soa_session_t *ss, char const *option)
{
return soa_base_deactivate(ss, option);
}
static void soa_static_terminate(soa_session_t *ss, char const *option)
{
soa_description_free(ss, ss->ss_user);
soa_base_terminate(ss, option);
}