/* * This file is part of the Sofia-SIP package * * Copyright (C) 2006 Nokia Corporation. * * Contact: Pekka Pessi * * 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 * * @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 #include #include #include struct soa_static_complete; #define SU_MSG_ARG_T struct soa_static_completed #include #include #include #include #include #include #include #include "sofia-sip/soa.h" #include #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); }