diff options
author | David M. Lee <dlee@digium.com> | 2013-01-07 14:24:28 -0600 |
---|---|---|
committer | David M. Lee <dlee@digium.com> | 2013-01-07 14:24:28 -0600 |
commit | f3ab456a17af1c89a6e3be4d20c5944853df1cb0 (patch) | |
tree | d00e1a332cd038a6d906a1ea0ac91e1a4458e617 /pjmedia/src/pjmedia/sdp_neg.c |
Import pjproject-2.0.1
Diffstat (limited to 'pjmedia/src/pjmedia/sdp_neg.c')
-rw-r--r-- | pjmedia/src/pjmedia/sdp_neg.c | 1549 |
1 files changed, 1549 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c new file mode 100644 index 0000000..bdce18d --- /dev/null +++ b/pjmedia/src/pjmedia/sdp_neg.c @@ -0,0 +1,1549 @@ +/* $Id: sdp_neg.c 3980 2012-03-20 09:23:20Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * 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 <pjmedia/sdp_neg.h> +#include <pjmedia/sdp.h> +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/ctype.h> +#include <pj/array.h> + +/** + * This structure describes SDP media negotiator. + */ +struct pjmedia_sdp_neg +{ + pjmedia_sdp_neg_state state; /**< Negotiator state. */ + pj_bool_t prefer_remote_codec_order; + pj_bool_t has_remote_answer; + pj_bool_t answer_was_remote; + + pjmedia_sdp_session *initial_sdp, /**< Initial local SDP */ + *active_local_sdp, /**< Currently active local SDP. */ + *active_remote_sdp, /**< Currently active remote's. */ + *neg_local_sdp, /**< Temporary local SDP. */ + *neg_remote_sdp; /**< Temporary remote SDP. */ +}; + +static const char *state_str[] = +{ + "STATE_NULL", + "STATE_LOCAL_OFFER", + "STATE_REMOTE_OFFER", + "STATE_WAIT_NEGO", + "STATE_DONE", +}; + +/* Definition of customized SDP format negotiation callback */ +struct fmt_match_cb_t +{ + pj_str_t fmt_name; + pjmedia_sdp_neg_fmt_match_cb cb; +}; + +/* Number of registered customized SDP format negotiation callbacks */ +static unsigned fmt_match_cb_cnt; + +/* The registered customized SDP format negotiation callbacks */ +static struct fmt_match_cb_t + fmt_match_cb[PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB]; + +/* Redefining a very long identifier name, just for convenience */ +#define ALLOW_MODIFY_ANSWER PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER + +static pj_status_t custom_fmt_match( pj_pool_t *pool, + const pj_str_t *fmt_name, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option); + + +/* + * Get string representation of negotiator state. + */ +PJ_DEF(const char*) pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_state state) +{ + if (state >=0 && state < (pjmedia_sdp_neg_state)PJ_ARRAY_SIZE(state_str)) + return state_str[state]; + + return "<?UNKNOWN?>"; +} + + +/* + * Create with local offer. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_local_offer( pj_pool_t *pool, + const pjmedia_sdp_session *local, + pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && local && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate local offer. */ + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(local))==PJ_SUCCESS, status); + + /* Create and initialize negotiator. */ + neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER; + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + + *p_neg = neg; + return PJ_SUCCESS; +} + +/* + * Create with remote offer and initial local offer/answer. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_create_w_remote_offer(pj_pool_t *pool, + const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *remote, + pjmedia_sdp_neg **p_neg) +{ + pjmedia_sdp_neg *neg; + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && remote && p_neg, PJ_EINVAL); + + *p_neg = NULL; + + /* Validate remote offer and initial answer */ + status = pjmedia_sdp_validate(remote); + if (status != PJ_SUCCESS) + return status; + + /* Create and initialize negotiator. */ + neg = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_neg); + PJ_ASSERT_RETURN(neg != NULL, PJ_ENOMEM); + + neg->prefer_remote_codec_order = PJMEDIA_SDP_NEG_PREFER_REMOTE_CODEC_ORDER; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + if (initial) { + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(initial))==PJ_SUCCESS, + status); + + neg->initial_sdp = pjmedia_sdp_session_clone(pool, initial); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, initial); + + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + + } else { + + neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; + + } + + *p_neg = neg; + return PJ_SUCCESS; +} + + +/* + * Set codec order preference. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_prefer_remote_codec_order( + pjmedia_sdp_neg *neg, + pj_bool_t prefer_remote) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + neg->prefer_remote_codec_order = prefer_remote; + return PJ_SUCCESS; +} + + +/* + * Get SDP negotiator state. + */ +PJ_DEF(pjmedia_sdp_neg_state) pjmedia_sdp_neg_get_state( pjmedia_sdp_neg *neg ) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg != NULL, PJMEDIA_SDP_NEG_STATE_NULL); + return neg->state; +} + + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_local( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *local = neg->active_local_sdp; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_active_remote( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->active_remote_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + *remote = neg->active_remote_sdp; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_bool_t) pjmedia_sdp_neg_was_answer_remote(pjmedia_sdp_neg *neg) +{ + PJ_ASSERT_RETURN(neg, PJ_FALSE); + + return neg->answer_was_remote; +} + + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_remote( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **remote) +{ + PJ_ASSERT_RETURN(neg && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJMEDIA_SDPNEG_ENONEG); + + *remote = neg->neg_remote_sdp; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **local) +{ + PJ_ASSERT_RETURN(neg && local, PJ_EINVAL); + PJ_ASSERT_RETURN(neg->neg_local_sdp, PJMEDIA_SDPNEG_ENONEG); + + *local = neg->neg_local_sdp; + return PJ_SUCCESS; +} + +static pjmedia_sdp_media *sdp_media_clone_deactivate( + pj_pool_t *pool, + const pjmedia_sdp_media *rem_med, + const pjmedia_sdp_media *local_med, + const pjmedia_sdp_session *local_sess) +{ + pjmedia_sdp_media *res; + + res = pjmedia_sdp_media_clone_deactivate(pool, rem_med); + if (!res) + return NULL; + + if (!res->conn && (!local_sess || !local_sess->conn)) { + if (local_med && local_med->conn) + res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn); + else { + res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + res->conn->net_type = pj_str("IN"); + res->conn->addr_type = pj_str("IP4"); + res->conn->addr = pj_str("127.0.0.1"); + } + } + + return res; +} + +/* + * Modify local SDP and wait for remote answer. + */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *local) +{ + pjmedia_sdp_session *new_offer; + pjmedia_sdp_session *old_offer; + char media_used[PJMEDIA_MAX_SDP_MEDIA]; + unsigned oi; /* old offer media index */ + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); + + /* Can only do this in STATE_DONE. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, + PJMEDIA_SDPNEG_EINSTATE); + + /* Validate the new offer */ + status = pjmedia_sdp_validate(local); + if (status != PJ_SUCCESS) + return status; + + /* Change state to STATE_LOCAL_OFFER */ + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + + /* Init vars */ + pj_bzero(media_used, sizeof(media_used)); + old_offer = neg->active_local_sdp; + new_offer = pjmedia_sdp_session_clone(pool, local); + + /* RFC 3264 Section 8: When issuing an offer that modifies the session, + * the "o=" line of the new SDP MUST be identical to that in the + * previous SDP, except that the version in the origin field MUST + * increment by one from the previous SDP. + */ + pj_strdup(pool, &new_offer->origin.user, &old_offer->origin.user); + new_offer->origin.id = old_offer->origin.id; + new_offer->origin.version = old_offer->origin.version + 1; + pj_strdup(pool, &new_offer->origin.net_type, &old_offer->origin.net_type); + pj_strdup(pool, &new_offer->origin.addr_type,&old_offer->origin.addr_type); + pj_strdup(pool, &new_offer->origin.addr, &old_offer->origin.addr); + + /* Generating the new offer, in the case media lines doesn't match the + * active SDP (e.g. current/active SDP's have m=audio and m=video lines, + * and the new offer only has m=audio line), the negotiator will fix + * the new offer by reordering and adding the missing media line with + * port number set to zero. + */ + for (oi = 0; oi < old_offer->media_count; ++oi) { + pjmedia_sdp_media *om; + pjmedia_sdp_media *nm; + unsigned ni; /* new offer media index */ + pj_bool_t found = PJ_FALSE; + + om = old_offer->media[oi]; + for (ni = oi; ni < new_offer->media_count; ++ni) { + nm = new_offer->media[ni]; + if (pj_strcmp(&nm->desc.media, &om->desc.media) == 0) { + if (ni != oi) { + /* The same media found but the position unmatched to the + * old offer, so let's put this media in the right place, + * and keep the order of the rest. + */ + pj_array_insert(new_offer->media, /* array */ + sizeof(new_offer->media[0]), /* elmt size*/ + ni, /* count */ + oi, /* pos */ + &nm); /* new elmt */ + } + found = PJ_TRUE; + break; + } + } + if (!found) { + pjmedia_sdp_media *m; + + m = sdp_media_clone_deactivate(pool, om, om, local); + + pj_array_insert(new_offer->media, sizeof(new_offer->media[0]), + new_offer->media_count++, oi, &m); + } + } + + /* New_offer fixed */ + neg->initial_sdp = new_offer; + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, new_offer); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_send_local_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session **offer) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(neg && offer, PJ_EINVAL); + + *offer = NULL; + + /* Can only do this in STATE_DONE or STATE_LOCAL_OFFER. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE || + neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + if (neg->state == PJMEDIA_SDP_NEG_STATE_DONE) { + /* If in STATE_DONE, set the active SDP as the offer. */ + PJ_ASSERT_RETURN(neg->active_local_sdp, PJMEDIA_SDPNEG_ENOACTIVE); + + neg->state = PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER; + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, + neg->active_local_sdp); + *offer = neg->active_local_sdp; + + } else { + /* We assume that we're in STATE_LOCAL_OFFER. + * In this case set the neg_local_sdp as the offer. + */ + *offer = neg->neg_local_sdp; + } + + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_answer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_LOCAL_OFFER. + * If we haven't provided local offer, then rx_remote_offer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + /* We're ready to negotiate. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + neg->has_remote_answer = PJ_TRUE; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_remote_offer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *remote) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && remote, PJ_EINVAL); + + /* Can only do this in STATE_DONE. + * If we already provide local offer, then rx_remote_answer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_DONE, + PJMEDIA_SDPNEG_EINSTATE); + + /* State now is STATE_REMOTE_OFFER. */ + neg->state = PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER; + neg->neg_remote_sdp = pjmedia_sdp_session_clone(pool, remote); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_sdp_neg_set_local_answer( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + const pjmedia_sdp_session *local) +{ + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg && local, PJ_EINVAL); + + /* Can only do this in STATE_REMOTE_OFFER. + * If we already provide local offer, then rx_remote_answer() should + * be called instead of this function. + */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + /* State now is STATE_WAIT_NEGO. */ + neg->state = PJMEDIA_SDP_NEG_STATE_WAIT_NEGO; + if (local) { + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, local); + if (neg->initial_sdp) { + /* I don't think there is anything in RFC 3264 that mandates + * answerer to place the same origin (and increment version) + * in the answer, but probably it won't hurt either. + * Note that the version will be incremented in + * pjmedia_sdp_neg_negotiate() + */ + neg->neg_local_sdp->origin.id = neg->initial_sdp->origin.id; + } else { + neg->initial_sdp = pjmedia_sdp_session_clone(pool, local); + } + } else { + PJ_ASSERT_RETURN(neg->initial_sdp, PJMEDIA_SDPNEG_ENOINITIAL); + neg->neg_local_sdp = pjmedia_sdp_session_clone(pool, neg->initial_sdp); + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_bool_t) pjmedia_sdp_neg_has_local_answer(pjmedia_sdp_neg *neg) +{ + pj_assert(neg && neg->state==PJMEDIA_SDP_NEG_STATE_WAIT_NEGO); + return !neg->has_remote_answer; +} + + +/* Swap string. */ +static void str_swap(pj_str_t *str1, pj_str_t *str2) +{ + pj_str_t tmp = *str1; + *str1 = *str2; + *str2 = tmp; +} + +static void remove_all_media_directions(pjmedia_sdp_media *m) +{ + pjmedia_sdp_media_remove_all_attr(m, "inactive"); + pjmedia_sdp_media_remove_all_attr(m, "sendrecv"); + pjmedia_sdp_media_remove_all_attr(m, "sendonly"); + pjmedia_sdp_media_remove_all_attr(m, "recvonly"); +} + +/* Update media direction based on peer's media direction */ +static void update_media_direction(pj_pool_t *pool, + const pjmedia_sdp_media *remote, + pjmedia_sdp_media *local) +{ + pjmedia_dir old_dir = PJMEDIA_DIR_ENCODING_DECODING, + new_dir; + + /* Get the media direction of local SDP */ + if (pjmedia_sdp_media_find_attr2(local, "sendonly", NULL)) + old_dir = PJMEDIA_DIR_ENCODING; + else if (pjmedia_sdp_media_find_attr2(local, "recvonly", NULL)) + old_dir = PJMEDIA_DIR_DECODING; + else if (pjmedia_sdp_media_find_attr2(local, "inactive", NULL)) + old_dir = PJMEDIA_DIR_NONE; + + new_dir = old_dir; + + /* Adjust local media direction based on remote media direction */ + if (pjmedia_sdp_media_find_attr2(remote, "inactive", NULL) != NULL) { + /* If remote has "a=inactive", then local is inactive too */ + + new_dir = PJMEDIA_DIR_NONE; + + } else if(pjmedia_sdp_media_find_attr2(remote, "sendonly", NULL) != NULL) { + /* If remote has "a=sendonly", then set local to "recvonly" if + * it is currently "sendrecv". Otherwise if local is NOT "recvonly", + * then set local direction to "inactive". + */ + switch (old_dir) { + case PJMEDIA_DIR_ENCODING_DECODING: + new_dir = PJMEDIA_DIR_DECODING; + break; + case PJMEDIA_DIR_DECODING: + /* No change */ + break; + default: + new_dir = PJMEDIA_DIR_NONE; + break; + } + + } else if(pjmedia_sdp_media_find_attr2(remote, "recvonly", NULL) != NULL) { + /* If remote has "a=recvonly", then set local to "sendonly" if + * it is currently "sendrecv". Otherwise if local is NOT "sendonly", + * then set local direction to "inactive" + */ + + switch (old_dir) { + case PJMEDIA_DIR_ENCODING_DECODING: + new_dir = PJMEDIA_DIR_ENCODING; + break; + case PJMEDIA_DIR_ENCODING: + /* No change */ + break; + default: + new_dir = PJMEDIA_DIR_NONE; + break; + } + + } else { + /* Remote indicates "sendrecv" capability. No change to local + * direction + */ + } + + if (new_dir != old_dir) { + pjmedia_sdp_attr *a = NULL; + + remove_all_media_directions(local); + + switch (new_dir) { + case PJMEDIA_DIR_NONE: + a = pjmedia_sdp_attr_create(pool, "inactive", NULL); + break; + case PJMEDIA_DIR_ENCODING: + a = pjmedia_sdp_attr_create(pool, "sendonly", NULL); + break; + case PJMEDIA_DIR_DECODING: + a = pjmedia_sdp_attr_create(pool, "recvonly", NULL); + break; + default: + /* sendrecv */ + break; + } + + if (a) { + pjmedia_sdp_media_add_attr(local, a); + } + } +} + + +/* Update single local media description to after receiving answer + * from remote. + */ +static pj_status_t process_m_answer( pj_pool_t *pool, + pjmedia_sdp_media *offer, + pjmedia_sdp_media *answer, + pj_bool_t allow_asym) +{ + unsigned i; + + /* Check that the media type match our offer. */ + + if (pj_strcmp(&answer->desc.media, &offer->desc.media)!=0) { + /* The media type in the answer is different than the offer! */ + return PJMEDIA_SDPNEG_EINVANSMEDIA; + } + + + /* Check that transport in the answer match our offer. */ + + /* At this point, transport type must be compatible, + * the transport instance will do more validation later. + */ + if (pjmedia_sdp_transport_cmp(&answer->desc.transport, + &offer->desc.transport) + != PJ_SUCCESS) + { + return PJMEDIA_SDPNEG_EINVANSTP; + } + + + /* Check if remote has rejected our offer */ + if (answer->desc.port == 0) { + + /* Remote has rejected our offer. + * Deactivate our media too. + */ + pjmedia_sdp_media_deactivate(pool, offer); + + /* Don't need to proceed */ + return PJ_SUCCESS; + } + + /* Ticket #1148: check if remote answer does not set port to zero when + * offered with port zero. Let's just tolerate it. + */ + if (offer->desc.port == 0) { + /* Don't need to proceed */ + return PJ_SUCCESS; + } + + /* Process direction attributes */ + update_media_direction(pool, answer, offer); + + /* If asymetric media is allowed, then just check that remote answer has + * codecs that are within the offer. + * + * Otherwise if asymetric media is not allowed, then we will choose only + * one codec in our initial offer to match the answer. + */ + if (allow_asym) { + for (i=0; i<answer->desc.fmt_count; ++i) { + unsigned j; + pj_str_t *rem_fmt = &answer->desc.fmt[i]; + + for (j=0; j<offer->desc.fmt_count; ++j) { + if (pj_strcmp(rem_fmt, &answer->desc.fmt[j])==0) + break; + } + + if (j != offer->desc.fmt_count) { + /* Found at least one common codec. */ + break; + } + } + + if (i == answer->desc.fmt_count) { + /* No common codec in the answer! */ + return PJMEDIA_SDPNEG_EANSNOMEDIA; + } + + PJ_TODO(CHECK_SDP_NEGOTIATION_WHEN_ASYMETRIC_MEDIA_IS_ALLOWED); + + } else { + /* Offer format priority based on answer format index/priority */ + unsigned offer_fmt_prior[PJMEDIA_MAX_SDP_FMT]; + + /* Remove all format in the offer that has no matching answer */ + for (i=0; i<offer->desc.fmt_count;) { + unsigned pt; + pj_uint32_t j; + pj_str_t *fmt = &offer->desc.fmt[i]; + + + /* Find matching answer */ + pt = pj_strtoul(fmt); + + if (pt < 96) { + for (j=0; j<answer->desc.fmt_count; ++j) { + if (pj_strcmp(fmt, &answer->desc.fmt[j])==0) + break; + } + } else { + /* This is dynamic payload type. + * For dynamic payload type, we must look the rtpmap and + * compare the encoding name. + */ + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap or_; + + /* Get the rtpmap for the payload type in the offer. */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(a, &or_); + + /* Find paylaod in answer SDP with matching + * encoding name and clock rate. + */ + for (j=0; j<answer->desc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", + &answer->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap ar; + pjmedia_sdp_attr_get_rtpmap(a, &ar); + + /* See if encoding name, clock rate, and channel + * count match + */ + if (!pj_stricmp(&or_.enc_name, &ar.enc_name) && + or_.clock_rate == ar.clock_rate && + (pj_stricmp(&or_.param, &ar.param)==0 || + (ar.param.slen==1 && *ar.param.ptr=='1'))) + { + /* Call custom format matching callbacks */ + if (custom_fmt_match(pool, &or_.enc_name, + offer, i, answer, j, 0) == + PJ_SUCCESS) + { + /* Match! */ + break; + } + } + } + } + } + + if (j == answer->desc.fmt_count) { + /* This format has no matching answer. + * Remove it from our offer. + */ + pjmedia_sdp_attr *a; + + /* Remove rtpmap associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "rtpmap", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove fmtp associated with this format */ + a = pjmedia_sdp_media_find_attr2(offer, "fmtp", fmt); + if (a) + pjmedia_sdp_media_remove_attr(offer, a); + + /* Remove this format from offer's array */ + pj_array_erase(offer->desc.fmt, sizeof(offer->desc.fmt[0]), + offer->desc.fmt_count, i); + --offer->desc.fmt_count; + + } else { + offer_fmt_prior[i] = j; + ++i; + } + } + + if (0 == offer->desc.fmt_count) { + /* No common codec in the answer! */ + return PJMEDIA_SDPNEG_EANSNOMEDIA; + } + + /* Post process: + * - Resort offer formats so the order match to the answer. + * - Remove answer formats that unmatches to the offer. + */ + + /* Resort offer formats */ + for (i=0; i<offer->desc.fmt_count; ++i) { + unsigned j; + for (j=i+1; j<offer->desc.fmt_count; ++j) { + if (offer_fmt_prior[i] > offer_fmt_prior[j]) { + unsigned tmp = offer_fmt_prior[i]; + offer_fmt_prior[i] = offer_fmt_prior[j]; + offer_fmt_prior[j] = tmp; + str_swap(&offer->desc.fmt[i], &offer->desc.fmt[j]); + } + } + } + + /* Remove unmatched answer formats */ + { + unsigned del_cnt = 0; + for (i=0; i<answer->desc.fmt_count;) { + /* The offer is ordered now, also the offer_fmt_prior */ + if (i >= offer->desc.fmt_count || + offer_fmt_prior[i]-del_cnt != i) + { + pj_str_t *fmt = &answer->desc.fmt[i]; + pjmedia_sdp_attr *a; + + /* Remove rtpmap associated with this format */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", fmt); + if (a) + pjmedia_sdp_media_remove_attr(answer, a); + + /* Remove fmtp associated with this format */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", fmt); + if (a) + pjmedia_sdp_media_remove_attr(answer, a); + + /* Remove this format from answer's array */ + pj_array_erase(answer->desc.fmt, + sizeof(answer->desc.fmt[0]), + answer->desc.fmt_count, i); + --answer->desc.fmt_count; + + ++del_cnt; + } else { + ++i; + } + } + } + } + + /* Looks okay */ + return PJ_SUCCESS; +} + + +/* Update local media session (offer) to create active local session + * after receiving remote answer. + */ +static pj_status_t process_answer(pj_pool_t *pool, + pjmedia_sdp_session *offer, + pjmedia_sdp_session *answer, + pj_bool_t allow_asym, + pjmedia_sdp_session **p_active) +{ + unsigned omi = 0; /* Offer media index */ + unsigned ami = 0; /* Answer media index */ + pj_bool_t has_active = PJ_FALSE; + pj_status_t status; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && offer && answer && p_active, PJ_EINVAL); + + /* Check that media count match between offer and answer */ + // Ticket #527, different media count is allowed for more interoperability, + // however, the media order must be same between offer and answer. + // if (offer->media_count != answer->media_count) + // return PJMEDIA_SDPNEG_EMISMEDIA; + + /* Now update each media line in the offer with the answer. */ + for (; omi<offer->media_count; ++omi) { + if (ami == answer->media_count) { + /* The answer has less media than the offer */ + pjmedia_sdp_media *am; + + /* Generate matching-but-disabled-media for the answer */ + am = sdp_media_clone_deactivate(pool, offer->media[omi], + offer->media[omi], offer); + answer->media[answer->media_count++] = am; + ++ami; + + /* Deactivate our media offer too */ + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + + /* No answer media to be negotiated */ + continue; + } + + status = process_m_answer(pool, offer->media[omi], answer->media[ami], + allow_asym); + + /* If media type is mismatched, just disable the media. */ + if (status == PJMEDIA_SDPNEG_EINVANSMEDIA) { + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + continue; + } + /* No common format in the answer media. */ + else if (status == PJMEDIA_SDPNEG_EANSNOMEDIA) { + pjmedia_sdp_media_deactivate(pool, offer->media[omi]); + pjmedia_sdp_media_deactivate(pool, answer->media[ami]); + } + /* Return the error code, for other errors. */ + else if (status != PJ_SUCCESS) { + return status; + } + + if (offer->media[omi]->desc.port != 0) + has_active = PJ_TRUE; + + ++ami; + } + + *p_active = offer; + + return has_active ? PJ_SUCCESS : PJMEDIA_SDPNEG_ENOMEDIA; +} + + +/* Internal function to rewrite the format string in SDP attribute rtpmap + * and fmtp. + */ +PJ_INLINE(void) rewrite_pt(pj_pool_t *pool, pj_str_t *attr_val, + const pj_str_t *old_pt, const pj_str_t *new_pt) +{ + int len_diff = new_pt->slen - old_pt->slen; + + /* Note that attribute value should be null-terminated. */ + if (len_diff > 0) { + pj_str_t new_val; + new_val.ptr = (char*)pj_pool_alloc(pool, attr_val->slen+len_diff+1); + new_val.slen = attr_val->slen + len_diff; + pj_memcpy(new_val.ptr + len_diff, attr_val->ptr, attr_val->slen + 1); + *attr_val = new_val; + } else if (len_diff < 0) { + attr_val->slen += len_diff; + pj_memmove(attr_val->ptr, attr_val->ptr - len_diff, + attr_val->slen + 1); + } + pj_memcpy(attr_val->ptr, new_pt->ptr, new_pt->slen); +} + + +/* Internal function to apply symmetric PT for the local answer. */ +static void apply_answer_symmetric_pt(pj_pool_t *pool, + pjmedia_sdp_media *answer, + unsigned pt_cnt, + const pj_str_t pt_offer[], + const pj_str_t pt_answer[]) +{ + pjmedia_sdp_attr *a_tmp[PJMEDIA_MAX_SDP_ATTR]; + unsigned i, a_tmp_cnt = 0; + + /* Rewrite the payload types in the answer if different to + * the ones in the offer. + */ + for (i = 0; i < pt_cnt; ++i) { + pjmedia_sdp_attr *a; + + /* Skip if the PTs are the same already, e.g: static PT. */ + if (pj_strcmp(&pt_answer[i], &pt_offer[i]) == 0) + continue; + + /* Rewrite payload type in the answer to match to the offer */ + pj_strdup(pool, &answer->desc.fmt[i], &pt_offer[i]); + + /* Also update payload type in rtpmap */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", &pt_answer[i]); + if (a) { + rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]); + /* Temporarily remove the attribute in case the new payload + * type is being used by another format in the media. + */ + pjmedia_sdp_media_remove_attr(answer, a); + a_tmp[a_tmp_cnt++] = a; + } + + /* Also update payload type in fmtp */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", &pt_answer[i]); + if (a) { + rewrite_pt(pool, &a->value, &pt_answer[i], &pt_offer[i]); + /* Temporarily remove the attribute in case the new payload + * type is being used by another format in the media. + */ + pjmedia_sdp_media_remove_attr(answer, a); + a_tmp[a_tmp_cnt++] = a; + } + } + + /* Return back 'rtpmap' and 'fmtp' attributes */ + for (i = 0; i < a_tmp_cnt; ++i) + pjmedia_sdp_media_add_attr(answer, a_tmp[i]); +} + + +/* Try to match offer with answer. */ +static pj_status_t match_offer(pj_pool_t *pool, + pj_bool_t prefer_remote_codec_order, + const pjmedia_sdp_media *offer, + const pjmedia_sdp_media *preanswer, + const pjmedia_sdp_session *preanswer_sdp, + pjmedia_sdp_media **p_answer) +{ + unsigned i; + pj_bool_t master_has_codec = 0, + master_has_telephone_event = 0, + master_has_other = 0, + found_matching_codec = 0, + found_matching_telephone_event = 0, + found_matching_other = 0; + unsigned pt_answer_count = 0; + pj_str_t pt_answer[PJMEDIA_MAX_SDP_FMT]; + pj_str_t pt_offer[PJMEDIA_MAX_SDP_FMT]; + pjmedia_sdp_media *answer; + const pjmedia_sdp_media *master, *slave; + + /* If offer has zero port, just clone the offer */ + if (offer->desc.port == 0) { + answer = sdp_media_clone_deactivate(pool, offer, preanswer, + preanswer_sdp); + *p_answer = answer; + return PJ_SUCCESS; + } + + /* If the preanswer define zero port, this media is being rejected, + * just clone the preanswer. + */ + if (preanswer->desc.port == 0) { + answer = pjmedia_sdp_media_clone(pool, preanswer); + *p_answer = answer; + return PJ_SUCCESS; + } + + /* Set master/slave negotiator based on prefer_remote_codec_order. */ + if (prefer_remote_codec_order) { + master = offer; + slave = preanswer; + } else { + master = preanswer; + slave = offer; + } + + /* With the addition of telephone-event and dodgy MS RTC SDP, + * the answer generation algorithm looks really shitty... + */ + for (i=0; i<master->desc.fmt_count; ++i) { + unsigned j; + + if (pj_isdigit(*master->desc.fmt[i].ptr)) { + /* This is normal/standard payload type, where it's identified + * by payload number. + */ + unsigned pt; + + pt = pj_strtoul(&master->desc.fmt[i]); + + if (pt < 96) { + /* For static payload type, it's enough to compare just + * the payload number. + */ + + master_has_codec = 1; + + /* We just need to select one codec. + * Continue if we have selected matching codec for previous + * payload. + */ + if (found_matching_codec) + continue; + + /* Find matching codec in local descriptor. */ + for (j=0; j<slave->desc.fmt_count; ++j) { + unsigned p; + p = pj_strtoul(&slave->desc.fmt[j]); + if (p == pt && pj_isdigit(*slave->desc.fmt[j].ptr)) { + found_matching_codec = 1; + pt_offer[pt_answer_count] = slave->desc.fmt[j]; + pt_answer[pt_answer_count++] = slave->desc.fmt[j]; + break; + } + } + + } else { + /* This is dynamic payload type. + * For dynamic payload type, we must look the rtpmap and + * compare the encoding name. + */ + const pjmedia_sdp_attr *a; + pjmedia_sdp_rtpmap or_; + pj_bool_t is_codec; + + /* Get the rtpmap for the payload type in the master. */ + a = pjmedia_sdp_media_find_attr2(master, "rtpmap", + &master->desc.fmt[i]); + if (!a) { + pj_assert(!"Bug! Offer should have been validated"); + return PJMEDIA_SDP_EMISSINGRTPMAP; + } + pjmedia_sdp_attr_get_rtpmap(a, &or_); + + if (!pj_stricmp2(&or_.enc_name, "telephone-event")) { + master_has_telephone_event = 1; + if (found_matching_telephone_event) + continue; + is_codec = 0; + } else { + master_has_codec = 1; + if (found_matching_codec) + continue; + is_codec = 1; + } + + /* Find paylaod in our initial SDP with matching + * encoding name and clock rate. + */ + for (j=0; j<slave->desc.fmt_count; ++j) { + a = pjmedia_sdp_media_find_attr2(slave, "rtpmap", + &slave->desc.fmt[j]); + if (a) { + pjmedia_sdp_rtpmap lr; + pjmedia_sdp_attr_get_rtpmap(a, &lr); + + /* See if encoding name, clock rate, and + * channel count match + */ + if (!pj_stricmp(&or_.enc_name, &lr.enc_name) && + or_.clock_rate == lr.clock_rate && + (pj_stricmp(&or_.param, &lr.param)==0 || + (lr.param.slen==0 && or_.param.slen==1 && + *or_.param.ptr=='1') || + (or_.param.slen==0 && lr.param.slen==1 && + *lr.param.ptr=='1'))) + { + /* Match! */ + if (is_codec) { + pjmedia_sdp_media *o, *a; + unsigned o_fmt_idx, a_fmt_idx; + + o = (pjmedia_sdp_media*)offer; + a = (pjmedia_sdp_media*)preanswer; + o_fmt_idx = prefer_remote_codec_order? i:j; + a_fmt_idx = prefer_remote_codec_order? j:i; + + /* Call custom format matching callbacks */ + if (custom_fmt_match(pool, &or_.enc_name, + o, o_fmt_idx, + a, a_fmt_idx, + ALLOW_MODIFY_ANSWER) != + PJ_SUCCESS) + { + continue; + } + found_matching_codec = 1; + } else { + found_matching_telephone_event = 1; + } + + pt_offer[pt_answer_count] = + prefer_remote_codec_order? + offer->desc.fmt[i]: + offer->desc.fmt[j]; + pt_answer[pt_answer_count++] = + prefer_remote_codec_order? + preanswer->desc.fmt[j]: + preanswer->desc.fmt[i]; + break; + } + } + } + } + + } else { + /* This is a non-standard, brain damaged SDP where the payload + * type is non-numeric. It exists e.g. in Microsoft RTC based + * UA, to indicate instant messaging capability. + * Example: + * - m=x-ms-message 5060 sip null + */ + master_has_other = 1; + if (found_matching_other) + continue; + + for (j=0; j<slave->desc.fmt_count; ++j) { + if (!pj_strcmp(&master->desc.fmt[i], &slave->desc.fmt[j])) { + /* Match */ + found_matching_other = 1; + pt_offer[pt_answer_count] = prefer_remote_codec_order? + offer->desc.fmt[i]: + offer->desc.fmt[j]; + pt_answer[pt_answer_count++] = prefer_remote_codec_order? + preanswer->desc.fmt[j]: + preanswer->desc.fmt[i]; + break; + } + } + } + } + + /* See if all types of master can be matched. */ + if (master_has_codec && !found_matching_codec) { + return PJMEDIA_SDPNEG_NOANSCODEC; + } + + /* If this comment is removed, negotiation will fail if remote has offered + telephone-event and local is not configured with telephone-event + + if (offer_has_telephone_event && !found_matching_telephone_event) { + return PJMEDIA_SDPNEG_NOANSTELEVENT; + } + */ + + if (master_has_other && !found_matching_other) { + return PJMEDIA_SDPNEG_NOANSUNKNOWN; + } + + /* Seems like everything is in order. + * Build the answer by cloning from preanswer, but rearrange the payload + * to suit the offer. + */ + answer = pjmedia_sdp_media_clone(pool, preanswer); + for (i=0; i<pt_answer_count; ++i) { + unsigned j; + for (j=i; j<answer->desc.fmt_count; ++j) { + if (!pj_strcmp(&answer->desc.fmt[j], &pt_answer[i])) + break; + } + pj_assert(j != answer->desc.fmt_count); + str_swap(&answer->desc.fmt[i], &answer->desc.fmt[j]); + } + + /* Remove unwanted local formats. */ + for (i=pt_answer_count; i<answer->desc.fmt_count; ++i) { + pjmedia_sdp_attr *a; + + /* Remove rtpmap for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "rtpmap", + &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + + /* Remove fmtp for this format */ + a = pjmedia_sdp_media_find_attr2(answer, "fmtp", + &answer->desc.fmt[i]); + if (a) { + pjmedia_sdp_media_remove_attr(answer, a); + } + } + answer->desc.fmt_count = pt_answer_count; + +#if PJMEDIA_SDP_NEG_ANSWER_SYMMETRIC_PT + apply_answer_symmetric_pt(pool, answer, pt_answer_count, + pt_offer, pt_answer); +#endif + + /* Update media direction. */ + update_media_direction(pool, offer, answer); + + *p_answer = answer; + return PJ_SUCCESS; +} + +/* Create complete answer for remote's offer. */ +static pj_status_t create_answer( pj_pool_t *pool, + pj_bool_t prefer_remote_codec_order, + const pjmedia_sdp_session *initial, + const pjmedia_sdp_session *offer, + pjmedia_sdp_session **p_answer) +{ + pj_status_t status = PJMEDIA_SDPNEG_ENOMEDIA; + pj_bool_t has_active = PJ_FALSE; + pjmedia_sdp_session *answer; + char media_used[PJMEDIA_MAX_SDP_MEDIA]; + unsigned i; + + /* Validate remote offer. + * This should have been validated before. + */ + PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(offer))==PJ_SUCCESS, status); + + /* Create initial answer by duplicating initial SDP, + * but clear all media lines. The media lines will be filled up later. + */ + answer = pjmedia_sdp_session_clone(pool, initial); + PJ_ASSERT_RETURN(answer != NULL, PJ_ENOMEM); + + answer->media_count = 0; + + pj_bzero(media_used, sizeof(media_used)); + + /* For each media line, create our answer based on our initial + * capability. + */ + for (i=0; i<offer->media_count; ++i) { + const pjmedia_sdp_media *om; /* offer */ + const pjmedia_sdp_media *im; /* initial media */ + pjmedia_sdp_media *am = NULL; /* answer/result */ + unsigned j; + + om = offer->media[i]; + + /* Find media description in our initial capability that matches + * the media type and transport type of offer's media, has + * matching codec, and has not been used to answer other offer. + */ + for (im=NULL, j=0; j<initial->media_count; ++j) { + im = initial->media[j]; + if (pj_strcmp(&om->desc.media, &im->desc.media)==0 && + pj_strcmp(&om->desc.transport, &im->desc.transport)==0 && + media_used[j] == 0) + { + pj_status_t status2; + + /* See if it has matching codec. */ + status2 = match_offer(pool, prefer_remote_codec_order, + om, im, initial, &am); + if (status2 == PJ_SUCCESS) { + /* Mark media as used. */ + media_used[j] = 1; + break; + } else { + status = status2; + } + } + } + + if (j==initial->media_count) { + /* No matching media. + * Reject the offer by setting the port to zero in the answer. + */ + /* For simplicity in the construction of the answer, we'll + * just clone the media from the offer. Anyway receiver will + * ignore anything in the media once it sees that the port + * number is zero. + */ + am = sdp_media_clone_deactivate(pool, om, om, answer); + } else { + /* The answer is in am */ + pj_assert(am != NULL); + } + + /* Add the media answer */ + answer->media[answer->media_count++] = am; + + /* Check if this media is active.*/ + if (am->desc.port != 0) + has_active = PJ_TRUE; + } + + *p_answer = answer; + + return has_active ? PJ_SUCCESS : status; +} + +/* Cancel offer */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_cancel_offer(pjmedia_sdp_neg *neg) +{ + PJ_ASSERT_RETURN(neg, PJ_EINVAL); + + /* Must be in LOCAL_OFFER state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER || + neg->state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER, + PJMEDIA_SDPNEG_EINSTATE); + + /* Reset state to done */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + /* Clear temporary SDP */ + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = PJ_FALSE; + + return PJ_SUCCESS; +} + + +/* The best bit: SDP negotiation function! */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool, + pjmedia_sdp_neg *neg, + pj_bool_t allow_asym) +{ + pj_status_t status; + + /* Check arguments are valid. */ + PJ_ASSERT_RETURN(pool && neg, PJ_EINVAL); + + /* Must be in STATE_WAIT_NEGO state. */ + PJ_ASSERT_RETURN(neg->state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO, + PJMEDIA_SDPNEG_EINSTATE); + + /* Must have remote offer. */ + PJ_ASSERT_RETURN(neg->neg_remote_sdp, PJ_EBUG); + + if (neg->has_remote_answer) { + pjmedia_sdp_session *active; + status = process_answer(pool, neg->neg_local_sdp, neg->neg_remote_sdp, + allow_asym, &active); + if (status == PJ_SUCCESS) { + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = active; + neg->active_remote_sdp = neg->neg_remote_sdp; + } + } else { + pjmedia_sdp_session *answer = NULL; + + status = create_answer(pool, neg->prefer_remote_codec_order, + neg->neg_local_sdp, neg->neg_remote_sdp, + &answer); + if (status == PJ_SUCCESS) { + pj_uint32_t active_ver; + + if (neg->active_local_sdp) + active_ver = neg->active_local_sdp->origin.version; + else + active_ver = neg->initial_sdp->origin.version; + + /* Only update active SDPs when negotiation is successfull */ + neg->active_local_sdp = answer; + neg->active_remote_sdp = neg->neg_remote_sdp; + + /* Increment SDP version */ + neg->active_local_sdp->origin.version = ++active_ver; + } + } + + /* State is DONE regardless */ + neg->state = PJMEDIA_SDP_NEG_STATE_DONE; + + /* Save state */ + neg->answer_was_remote = neg->has_remote_answer; + + /* Clear temporary SDP */ + neg->neg_local_sdp = neg->neg_remote_sdp = NULL; + neg->has_remote_answer = PJ_FALSE; + + return status; +} + + +static pj_status_t custom_fmt_match(pj_pool_t *pool, + const pj_str_t *fmt_name, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option) +{ + unsigned i; + + for (i = 0; i < fmt_match_cb_cnt; ++i) { + if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) { + pj_assert(fmt_match_cb[i].cb); + return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx, + answer, a_fmt_idx, + option); + } + } + + /* Not customized format matching found, should be matched */ + return PJ_SUCCESS; +} + +/* Register customized SDP format negotiation callback function. */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb( + const pj_str_t *fmt_name, + pjmedia_sdp_neg_fmt_match_cb cb) +{ + struct fmt_match_cb_t *f = NULL; + unsigned i; + + PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL); + + /* Check if the callback for the format name has been registered */ + for (i = 0; i < fmt_match_cb_cnt; ++i) { + if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) + break; + } + + /* Unregistration */ + + if (cb == NULL) { + if (i == fmt_match_cb_cnt) + return PJ_ENOTFOUND; + + pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]), + fmt_match_cb_cnt, i); + fmt_match_cb_cnt--; + + return PJ_SUCCESS; + } + + /* Registration */ + + if (i < fmt_match_cb_cnt) { + /* The same format name has been registered before */ + if (cb != fmt_match_cb[i].cb) + return PJ_EEXISTS; + else + return PJ_SUCCESS; + } + + if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb)) + return PJ_ETOOMANY; + + f = &fmt_match_cb[fmt_match_cb_cnt++]; + f->fmt_name = *fmt_name; + f->cb = cb; + + return PJ_SUCCESS; +} + + +/* Match format in the SDP media offer and answer. */ +PJ_DEF(pj_status_t) pjmedia_sdp_neg_fmt_match(pj_pool_t *pool, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option) +{ + const pjmedia_sdp_attr *attr; + pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap; + unsigned o_pt; + unsigned a_pt; + + o_pt = pj_strtoul(&offer->desc.fmt[o_fmt_idx]); + a_pt = pj_strtoul(&answer->desc.fmt[a_fmt_idx]); + + if (o_pt < 96 || a_pt < 96) { + if (o_pt == a_pt) + return PJ_SUCCESS; + else + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Get the format rtpmap from the offer. */ + attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap", + &offer->desc.fmt[o_fmt_idx]); + if (!attr) { + pj_assert(!"Bug! Offer haven't been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap); + + /* Get the format rtpmap from the answer. */ + attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap", + &answer->desc.fmt[a_fmt_idx]); + if (!attr) { + pj_assert(!"Bug! Answer haven't been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap); + + if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 || + o_rtpmap.clock_rate != a_rtpmap.clock_rate) + { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + return custom_fmt_match(pool, &o_rtpmap.enc_name, + offer, o_fmt_idx, answer, a_fmt_idx, option); +} + |