Пример #1
0
/* Compare media descriptor */
PJ_DEF(pj_status_t) pjmedia_sdp_media_cmp( const pjmedia_sdp_media *sd1,
					   const pjmedia_sdp_media *sd2,
					   unsigned option)
{
    unsigned i;
    pj_status_t status;

    PJ_ASSERT_RETURN(sd1 && sd2 && option==0, PJ_EINVAL);

    PJ_UNUSED_ARG(option);

    /* Compare media type. */
    if (pj_strcmp(&sd1->desc.media, &sd2->desc.media) != 0)
	return PJMEDIA_SDP_EMEDIANOTEQUAL;

    /* Compare port number. */
    if (sd1->desc.port != sd2->desc.port)
	return PJMEDIA_SDP_EPORTNOTEQUAL;

    /* Compare port count. */
    if (sd1->desc.port_count != sd2->desc.port_count)
	return PJMEDIA_SDP_EPORTNOTEQUAL;

    /* Compare transports. */
    if (pj_strcmp(&sd1->desc.transport, &sd2->desc.transport) != 0)
	return PJMEDIA_SDP_ETPORTNOTEQUAL;

    /* For zeroed port media, stop comparing here */
    if (sd1->desc.port == 0)
	return PJ_SUCCESS;

    /* Compare number of formats. */
    if (sd1->desc.fmt_count != sd2->desc.fmt_count)
	return PJMEDIA_SDP_EFORMATNOTEQUAL;

    /* Compare formats, in order. */
    for (i=0; i<sd1->desc.fmt_count; ++i) {
	if (pj_strcmp(&sd1->desc.fmt[i], &sd2->desc.fmt[i]) != 0)
	    return PJMEDIA_SDP_EFORMATNOTEQUAL;
    }

    /* Compare connection line, if they exist. */
    if (sd1->conn) {
	if (!sd2->conn)
	    return PJMEDIA_SDP_EMEDIANOTEQUAL;
	status = compare_conn(sd1->conn, sd2->conn);
    } else {
	if (sd2->conn)
	    return PJMEDIA_SDP_EMEDIANOTEQUAL;
    }

    /* Compare attributes. */
    status = compare_attr(sd1->attr_count, sd1->attr, 
			  sd2->attr_count, sd2->attr);
    if (status != PJ_SUCCESS)
	return status;

    /* Looks equal */
    return PJ_SUCCESS;
}
Пример #2
0
/**
 * VoX Mobile :: negative SIP response handler
 */
int get_vox_error_info(pjsip_event *e)
{
    int cause = 0;
    const pj_str_t HDR = { "Error-Info", 10 };
    const pj_str_t BLOCKED_PSTN = { "PSTN not allowed", 16 };
    const pj_str_t BLOCKED_DESTINATION = { "Blocked destination", 19 };

    if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {

        pjsip_generic_string_hdr *hdr = (pjsip_generic_string_hdr*)pjsip_msg_find_hdr_by_name(
                                         e->body.tsx_state.src.rdata->msg_info.msg,
                                         &HDR,
                                         NULL);

        if (hdr) {
            if (pj_strcmp(&hdr->hvalue, &BLOCKED_PSTN) == 0)
                cause = 403;
            else if (pj_strcmp(&hdr->hvalue, &BLOCKED_DESTINATION) == 0)
                cause = 580;
            else {
                cause = (int)pj_strtoul(&hdr->hvalue);
            }
        }
    }

    return cause;
}
Пример #3
0
/*
 * Update authentication session with a challenge.
 */
static void update_digest_session( pj_pool_t *ses_pool, 
				   pjsip_cached_auth *cached_auth,
				   const pjsip_www_authenticate_hdr *hdr )
{
    if (hdr->challenge.digest.qop.slen == 0)
	return;

    /* Initialize cnonce and qop if not present. */
    if (cached_auth->cnonce.slen == 0) {
	/* Save the whole challenge */
	cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
				 pjsip_hdr_clone(ses_pool, hdr);

	/* Create cnonce */
	pj_create_unique_string( ses_pool, &cached_auth->cnonce );

	/* Initialize nonce-count */
	cached_auth->nc = 1;

	/* Save realm. */
	pj_assert(cached_auth->realm.slen != 0);
	if (cached_auth->realm.slen == 0) {
	    pj_strdup(ses_pool, &cached_auth->realm, 
		      &hdr->challenge.digest.realm);
	}

    } else {
	/* Update last_nonce and nonce-count */
	if (!pj_strcmp(&hdr->challenge.digest.nonce, 
		       &cached_auth->last_chal->challenge.digest.nonce)) 
	{
	    /* Same nonce, increment nonce-count */
	    ++cached_auth->nc;
	} else {
	    /* Server gives new nonce. */
	    pj_strdup(ses_pool, &cached_auth->last_chal->challenge.digest.nonce,
		      &hdr->challenge.digest.nonce);
	    /* Has the opaque changed? */
	    if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque,
			  &hdr->challenge.digest.opaque)) 
	    {
		pj_strdup(ses_pool, 
			  &cached_auth->last_chal->challenge.digest.opaque,
			  &hdr->challenge.digest.opaque);
	    }
	    cached_auth->nc = 1;
	}
    }
}
Пример #4
0
static pj_bool_t mod_stateless_on_rx_request(pjsip_rx_data *rdata)
{
    const pj_str_t stateless_user = { "0", 1 };
    pjsip_uri *uri;
    pjsip_sip_uri *sip_uri;

    uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);

    /* Only want to receive SIP/SIPS scheme */
    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))
	return PJ_FALSE;

    sip_uri = (pjsip_sip_uri*) uri;

    /* Check for matching user part */
    if (pj_strcmp(&sip_uri->user, &stateless_user)!=0)
	return PJ_FALSE;

    /*
     * Yes, this is for us.
     */

    /* Ignore ACK request */
    if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD)
	return PJ_TRUE;

    /*
     * Respond statelessly with 200/OK.
     */
    pjsip_endpt_respond_stateless(app.sip_endpt, rdata, 200, NULL,
				  NULL, NULL);
    app.server.cur_state.stateless_cnt++;
    return PJ_TRUE;
}
Пример #5
0
static int pjsip_name_addr_compare(  pjsip_uri_context_e context,
                                     const pjsip_name_addr *naddr1,
                                     const pjsip_name_addr *naddr2)
{
    int d;

    /* Check that naddr2 is also a name_addr */
    if (naddr1->vptr != naddr2->vptr)
        return -1;

    /* I'm not sure whether display name is included in the comparison. */
    if (pj_strcmp(&naddr1->display, &naddr2->display) != 0) {
        return -1;
    }

    pj_assert( naddr1->uri != NULL );
    pj_assert( naddr2->uri != NULL );

    /* Compare name-addr as URL */
    d = pjsip_uri_cmp( context, naddr1->uri, naddr2->uri);
    if (d)
        return d;

    return 0;
}
Пример #6
0
PJ_DEF(unsigned) pjmedia_sdp_attr_remove_all(unsigned *count,
					     pjmedia_sdp_attr *attr_array[],
					     const char *name)
{
    unsigned i, removed = 0;
    pj_str_t attr_name;

    PJ_ASSERT_RETURN(count && attr_array && name, PJ_EINVAL);

    attr_name.ptr = (char*)name;
    attr_name.slen = pj_ansi_strlen(name);

    for (i=0; i<*count; ) {
	if (pj_strcmp(&attr_array[i]->name, &attr_name)==0) {
	    pj_array_erase(attr_array, sizeof(pjmedia_sdp_attr*),
			   *count, i);
	    --(*count);
	    ++removed;
	} else {
	    ++i;
	}   
    }

    return removed;
}
Пример #7
0
/* Set the published address of the transport */
static void udp_set_pub_name(struct udp_transport *tp,
			     const pjsip_host_port *a_name)
{
    enum { INFO_LEN = 80 };
    char local_addr[PJ_INET6_ADDRSTRLEN+10];
    char pub_addr[PJ_INET6_ADDRSTRLEN+10];

    pj_assert(a_name->host.slen != 0);
    
    if (pj_strcmp(&tp->base.local_name.host, &a_name->host) == 0 &&
        tp->base.local_name.port == a_name->port)
    {
        return;
    }
    
    pj_strdup_with_null(tp->base.pool, &tp->base.local_name.host, 
			&a_name->host);
    tp->base.local_name.port = a_name->port;

    /* Update transport info. */
    if (tp->base.info == NULL) {
	tp->base.info = (char*) pj_pool_alloc(tp->base.pool, INFO_LEN);
    }

    pj_sockaddr_print(&tp->base.local_addr, local_addr, sizeof(local_addr), 3);
    pj_addr_str_print(&tp->base.local_name.host, 
		      tp->base.local_name.port, 
		      pub_addr, sizeof(pub_addr), 1),

    pj_ansi_snprintf( tp->base.info, INFO_LEN, "udp %s [published as %s]",
		      local_addr, pub_addr);
}
/*! \brief Helper function which returns a UDP transport bound to the given address and port */
static pjsip_transport *multihomed_get_udp_transport(pj_str_t *address, int port)
{
	struct ao2_container *transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport",
		AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
	struct ast_sip_transport *transport;
	struct ao2_iterator iter;
	pjsip_transport *sip_transport = NULL;

	if (!transports) {
		return NULL;
	}

	for (iter = ao2_iterator_init(transports, 0); (transport = ao2_iterator_next(&iter)); ao2_ref(transport, -1)) {
		if ((transport->type != AST_TRANSPORT_UDP) ||
			(pj_strcmp(&transport->state->transport->local_name.host, address)) ||
			(transport->state->transport->local_name.port != port)) {
			continue;
		}

		sip_transport = transport->state->transport;
		ao2_ref(transport, -1);
		break;
	}
	ao2_iterator_destroy(&iter);

	ao2_ref(transports, -1);

	return sip_transport;
}
Пример #9
0
pjmedia_sdp_attr_find (unsigned count, 
		       pjmedia_sdp_attr *const attr_array[],
		       const pj_str_t *name,
		       const pj_str_t *c_fmt)
{
    unsigned i;
    unsigned c_pt = 0xFFFF;

    if (c_fmt)
	c_pt = pj_strtoul(c_fmt);

    for (i=0; i<count; ++i) {
	if (pj_strcmp(&attr_array[i]->name, name) == 0) {
	    const pjmedia_sdp_attr *a = attr_array[i];
	    if (c_fmt) {
		unsigned pt = (unsigned) pj_strtoul2(&a->value, NULL, 10);
		if (pt == c_pt) {
		    return (pjmedia_sdp_attr*)a;
		}
	    } else 
		return (pjmedia_sdp_attr*)a;
	}
    }
    return NULL;
}
Пример #10
0
/* Compare connection line. */
static pj_status_t compare_conn(const pjmedia_sdp_conn *c1,
				const pjmedia_sdp_conn *c2)
{
    /* Compare network type. */
    if (pj_strcmp(&c1->net_type, &c2->net_type) != 0)
	return PJMEDIA_SDP_ECONNNOTEQUAL;

    /* Compare address type. */
    if (pj_strcmp(&c1->addr_type, &c2->addr_type) != 0)
	return PJMEDIA_SDP_ECONNNOTEQUAL;

    /* Compare address. */
    if (pj_strcmp(&c1->addr, &c2->addr) != 0)
	return PJMEDIA_SDP_ECONNNOTEQUAL;

    return PJ_SUCCESS;
}
Пример #11
0
PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1,
				const pjsip_cred_info *cred2)
{
    int result;

    result = pj_strcmp(&cred1->realm, &cred2->realm);
    if (result) goto on_return;
    result = pj_strcmp(&cred1->scheme, &cred2->scheme);
    if (result) goto on_return;
    result = pj_strcmp(&cred1->username, &cred2->username);
    if (result) goto on_return;
    result = pj_strcmp(&cred1->data, &cred2->data);
    if (result) goto on_return;
    result = (cred1->data_type != cred2->data_type);
    if (result) goto on_return;

    if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
	result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k);
	if (result) goto on_return;
	result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op);
	if (result) goto on_return;
	result = pj_strcmp(&cred1->ext.aka.amf, &cred2->ext.aka.amf);
	if (result) goto on_return;
    }

on_return:
    return result;
}
Пример #12
0
/* 
 * Find a dialog.
 */
PJ_DEF(pjsip_dialog*) pjsip_ua_find_dialog(const pj_str_t *call_id,
					   const pj_str_t *local_tag,
					   const pj_str_t *remote_tag,
					   pj_bool_t lock_dialog)
{
    struct dlg_set *dlg_set;
    pjsip_dialog *dlg;

    PJ_ASSERT_RETURN(call_id && local_tag && remote_tag, NULL);

    /* Lock user agent. */
    pj_mutex_lock(mod_ua.mutex);

    /* Lookup the dialog set. */
    dlg_set = (struct dlg_set*)
    	      pj_hash_get(mod_ua.dlg_table, local_tag->ptr, local_tag->slen,
			  NULL);
    if (dlg_set == NULL) {
	/* Not found */
	pj_mutex_unlock(mod_ua.mutex);
	return NULL;
    }

    /* Dialog set is found, now find the matching dialog based on the
     * remote tag.
     */
    dlg = dlg_set->dlg_list.next;
    while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {	
	if (pj_strcmp(&dlg->remote.info->tag, remote_tag) == 0)
	    break;
	dlg = dlg->next;
    }

    if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
	/* Not found */
	pj_mutex_unlock(mod_ua.mutex);
	return NULL;
    }

    /* Dialog has been found. It SHOULD have the right Call-ID!! */
    PJ_ASSERT_ON_FAIL(pj_strcmp(&dlg->call_id->id, call_id)==0, 
			{pj_mutex_unlock(mod_ua.mutex); return NULL;});
Пример #13
0
/* Verify incoming Authorization/Proxy-Authorization header against the 
 * specified credential.
 */
static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr,
				      const pj_str_t *method,
				      const pjsip_cred_info *cred_info )
{
    if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
	char digest_buf[PJSIP_MD5STRLEN];
	pj_str_t digest;
	const pjsip_digest_credential *dig = &hdr->credential.digest;

	/* Check that username and realm match. 
	 * These checks should have been performed before entering this
	 * function.
	 */
	PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0,
			 PJ_EINVALIDOP);
	PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0,
			 PJ_EINVALIDOP);

	/* Prepare for our digest calculation. */
	digest.ptr = digest_buf;
	digest.slen = PJSIP_MD5STRLEN;

	/* Create digest for comparison. */
	pjsip_auth_create_digest(&digest, 
				 &hdr->credential.digest.nonce,
				 &hdr->credential.digest.nc, 
				 &hdr->credential.digest.cnonce,
				 &hdr->credential.digest.qop,
				 &hdr->credential.digest.uri,
				 &cred_info->realm,
				 cred_info, 
				 method );

	/* Compare digest. */
	return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ?
	       PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST;

    } else {
	pj_assert(!"Unsupported authentication scheme");
	return PJSIP_EINVALIDAUTHSCHEME;
    }
}
static int find_transport_in_use(void *obj, void *arg, int flags)
{
	struct ast_sip_transport *transport = obj;
	pjsip_rx_data *rdata = arg;

	if ((transport->state->transport == rdata->tp_info.transport) ||
		(transport->state->factory && !pj_strcmp(&transport->state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) &&
			transport->state->factory->addr_name.port == rdata->tp_info.transport->local_name.port)) {
		return CMP_MATCH | CMP_STOP;
	}

	return 0;
}
Пример #15
0
/* Verify incoming Authorization/Proxy-Authorization header against existing
 * credentials. Will return TRUE if the authorization request matches any of
 * the credential.
 */
PJ_DEF(pj_bool_t) pjsip_auth_verify(const pjsip_authorization_hdr *hdr,
				    const pj_str_t *method,
				    const pjsip_cred_info *cred_info )
{
    if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
	char digest_buf[MD5STRLEN];
	pj_str_t digest;
	const pjsip_digest_credential *dig = &hdr->credential.digest;

	/* Check that username match. */
	if (pj_strcmp(&dig->username, &cred_info->username) != 0)
	    return PJ_FALSE;

	/* Check that realm match. */
	if (pj_strcmp(&dig->realm, &cred_info->realm) != 0)
	    return PJ_FALSE;

	/* Prepare for our digest calculation. */
	digest.ptr = digest_buf;
	digest.slen = MD5STRLEN;

	/* Create digest for comparison. */
	create_digest(  &digest, 
			&hdr->credential.digest.nonce,
			&hdr->credential.digest.nc, 
			&hdr->credential.digest.cnonce,
			&hdr->credential.digest.qop,
			&hdr->credential.digest.uri,
			cred_info, 
			method );

	return pj_stricmp(&digest, &hdr->credential.digest.response) == 0;

    } else {
	pj_assert(0);
	return PJ_FALSE;
    }
}
Пример #16
0
/*
 * Apply direction attribute in session to all media.
 */
static void apply_media_direction(pjmedia_sdp_session *sdp)
{
    pjmedia_sdp_attr *dir_attr = NULL;
    unsigned i;

    const pj_str_t inactive = { "inactive", 8 };
    const pj_str_t sendonly = { "sendonly", 8 };
    const pj_str_t recvonly = { "recvonly", 8 };
    const pj_str_t sendrecv = { "sendrecv", 8 };

    /* Find direction attribute in session, don't need to find default 
     * direction "sendrecv".
     */
    for (i = 0; i < sdp->attr_count && !dir_attr; ++i) {
	if (!pj_strcmp(&sdp->attr[i]->name, &sendonly) ||
	    !pj_strcmp(&sdp->attr[i]->name, &recvonly) ||
	    !pj_strcmp(&sdp->attr[i]->name, &inactive)) 
	{
	    dir_attr = sdp->attr[i];
	}
    }

    /* Found the direction attribute */
    if (dir_attr) {
	/* Remove the direction attribute in session */
	pjmedia_sdp_attr_remove(&sdp->attr_count, sdp->attr, dir_attr);

	/* Apply the direction attribute to all media, but not overriding it
	 * if media already has direction attribute.
	 */
	for (i = 0; i < sdp->media_count; ++i) {
	    pjmedia_sdp_media *m;
	    unsigned j;

	    /* Find direction attribute in this media */
	    m = sdp->media[i];
	    for (j = 0; j < m->attr_count; ++j) {
		if (!pj_strcmp(&m->attr[j]->name, &sendrecv) ||
		    !pj_strcmp(&m->attr[j]->name, &sendonly) ||
		    !pj_strcmp(&m->attr[j]->name, &recvonly) ||
		    !pj_strcmp(&m->attr[j]->name, &inactive)) 
		{
		    break;
		}
	    }

	    /* Not found, apply direction attribute from session */
	    if (j == m->attr_count)
		pjmedia_sdp_media_add_attr(m, dir_attr);
	}
    }
}
Пример #17
0
/* 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]);
}
Пример #18
0
static int srtp_crypto_cmp(const pjmedia_srtp_crypto* c1,
			   const pjmedia_srtp_crypto* c2)
{
    int r;

    r = pj_strcmp(&c1->key, &c2->key);
    if (r != 0)
	return r;

    r = pj_stricmp(&c1->name, &c2->name);
    if (r != 0)
	return r;

    return (c1->flags != c2->flags);
}
Пример #19
0
static pj_bool_t mod_stateful_on_rx_request(pjsip_rx_data *rdata)
{
    const pj_str_t stateful_user = { "1", 1 };
    pjsip_uri *uri;
    pjsip_sip_uri *sip_uri;

    uri = pjsip_uri_get_uri(rdata->msg_info.msg->line.req.uri);

    /* Only want to receive SIP/SIPS scheme */
    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))
	return PJ_FALSE;

    sip_uri = (pjsip_sip_uri*) uri;

    /* Check for matching user part */
    if (pj_strcmp(&sip_uri->user, &stateful_user)!=0)
	return PJ_FALSE;

    /*
     * Yes, this is for us.
     * Respond statefully with 200/OK.
     */
    switch (rdata->msg_info.msg->line.req.method.id) {
    case PJSIP_INVITE_METHOD:
	{
	    pjsip_msg_body *body;

	    if (dummy_sdp_str.slen == 0)
		dummy_sdp_str.slen = pj_ansi_strlen(dummy_sdp_str.ptr);

	    body = pjsip_msg_body_create(rdata->tp_info.pool, 
					 &mime_application, &mime_sdp, 
					 &dummy_sdp_str);
	    pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
				200, NULL, NULL, body, NULL);
	}
	break;
    case PJSIP_ACK_METHOD:
	return PJ_TRUE;
    default:
	pjsip_endpt_respond(app.sip_endpt, &mod_stateful_server, rdata,
			    200, NULL, NULL, NULL, NULL);
	break;
    }

    app.server.cur_state.stateful_cnt++;
    return PJ_TRUE;
}
Пример #20
0
/* Schedule timer to terminate transaction. */
static void schedule_terminate_tsx( pjsip_transaction *tsx,
				    int status_code,
				    int msec_delay )
{
    pj_time_val delay;

    delay.sec = 0;
    delay.msec = msec_delay;
    pj_time_val_normalize(&delay);

    pj_assert(pj_strcmp(&tsx->transaction_key, &tsx_key)==0);
    timer.user_data = NULL;
    timer.id = status_code;
    timer.cb = &terminate_tsx_timer;
    pjsip_endpt_schedule_timer(endpt, &timer, &delay);
}
Пример #21
0
PJ_DEF(pj_status_t) pjsip_regc_set_via_sent_by( pjsip_regc *regc,
				                pjsip_host_port *via_addr,
                                                pjsip_transport *via_tp)
{
    PJ_ASSERT_RETURN(regc, PJ_EINVAL);

    if (!via_addr)
        pj_bzero(&regc->via_addr, sizeof(regc->via_addr));
    else {
        if (pj_strcmp(&regc->via_addr.host, &via_addr->host))
            pj_strdup(regc->pool, &regc->via_addr.host, &via_addr->host);
        regc->via_addr.port = via_addr->port;
    }
    regc->via_tp = via_tp;

    return PJ_SUCCESS;
}
Пример #22
0
/* Start ICE session with the specified remote SDP */
static pj_status_t start_ice(struct transport_ice *tp_ice,
			     pj_pool_t *tmp_pool,
			     const pjmedia_sdp_session *rem_sdp,
			     unsigned media_index)
{
    pjmedia_sdp_media *rem_m = rem_sdp->media[media_index];
    const pjmedia_sdp_attr *ufrag_attr, *pwd_attr;
    pj_ice_sess_cand *cand;
    unsigned i, cand_cnt;
    pj_status_t status;

    get_ice_attr(rem_sdp, rem_m, &ufrag_attr, &pwd_attr);

    /* Allocate candidate array */
    cand = (pj_ice_sess_cand*)
	   pj_pool_calloc(tmp_pool, PJ_ICE_MAX_CAND, 
			  sizeof(pj_ice_sess_cand));

    /* Get all candidates in the media */
    cand_cnt = 0;
    for (i=0; i<rem_m->attr_count && cand_cnt < PJ_ICE_MAX_CAND; ++i) {
	pjmedia_sdp_attr *attr;

	attr = rem_m->attr[i];

	if (pj_strcmp(&attr->name, &STR_CANDIDATE)!=0)
	    continue;

	/* Parse candidate */
	status = parse_cand(tp_ice->base.name, tmp_pool, &attr->value, 
			    &cand[cand_cnt]);
	if (status != PJ_SUCCESS) {
	    PJ_LOG(4,(tp_ice->base.name, 
		      "Error in parsing SDP candidate attribute '%.*s', "
		      "candidate is ignored",
		      (int)attr->value.slen, attr->value.ptr));
	    continue;
	}

	cand_cnt++;
    }

    /* Start ICE */
    return pj_ice_strans_start_ice(tp_ice->ice_st, &ufrag_attr->value, 
				   &pwd_attr->value, cand_cnt, cand);
}
Пример #23
0
bool SessionExpiresHelper::timer_supported(pjsip_msg* msg)
{
  pjsip_supported_hdr* supported_hdr = (pjsip_supported_hdr*)
    pjsip_msg_find_hdr(msg, PJSIP_H_SUPPORTED, NULL);

  if (supported_hdr != NULL)
  {
    for (unsigned ii = 0; ii < supported_hdr->count; ++ii)
    {
      if (pj_strcmp(&supported_hdr->values[ii], &STR_TIMER) == 0)
      {
        return true;
      }
    }
  }

  return false;
}
Пример #24
0
/*! \brief Callback function for finding the transport the request is going out on */
static int find_transport_in_use(void *obj, void *arg, int flags)
{
	struct ast_sip_transport *transport = obj;
	struct request_transport_details *details = arg;

	/* If an explicit transport or factory matches then this is what is in use, if we are unavailable
	 * to compare based on that we make sure that the type is the same and the source IP address/port are the same
	 */
	if ((details->transport && details->transport == transport->state->transport) ||
		(details->factory && details->factory == transport->state->factory) ||
		((details->type == transport->type) && (transport->state->factory) &&
			!pj_strcmp(&transport->state->factory->addr_name.host, &details->local_address) &&
			transport->state->factory->addr_name.port == details->local_port)) {
		return CMP_MATCH | CMP_STOP;
	}

	return 0;
}
/*! \brief Helper function which determines if the existing address has priority over new one */
static int multihomed_rewrite_header(pj_str_t *source, pjsip_transport *transport)
{
	pj_uint32_t loop6[4] = {0, 0, 0, 0};

	/* If the transport is bound to any it should always rewrite */
	if ((transport->local_addr.addr.sa_family == pj_AF_INET() &&
		transport->local_addr.ipv4.sin_addr.s_addr == PJ_INADDR_ANY) ||
		(transport->local_addr.addr.sa_family == pj_AF_INET6() &&
		!pj_memcmp(&transport->local_addr.ipv6.sin6_addr, loop6, sizeof(loop6)))) {
		return 1;
	}

	/* If the transport is explicitly bound but the determined source differs favor the transport */
	if (!pj_strcmp(source, &transport->local_name.host)) {
		return 1;
	}

	return 0;
}
Пример #26
0
// Reject request unless it's a SUBSCRIBE targeted at the home domain / this node.
pj_bool_t subscription_on_rx_request(pjsip_rx_data *rdata)
{
  SAS::TrailId trail = get_trail(rdata);

  if (rdata->tp_info.transport->local_name.port != stack_data.scscf_port)
  {
    // Not an S-CSCF, so don't handle SUBSCRIBEs.
    return PJ_FALSE; // LCOV_EXCL_LINE
  }

  if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method()))
  {
    // This isn't a SUBSCRIBE, so this module can't process it.
    return PJ_FALSE;
  }

  if (!((PJUtils::is_home_domain(rdata->msg_info.msg->line.req.uri) ||
         (PJUtils::is_uri_local(rdata->msg_info.msg->line.req.uri))) &&
        PJUtils::check_route_headers(rdata)))
  {
    LOG_DEBUG("Rejecting subscription request not targeted at this domain or node");
    SAS::Event event(trail, SASEvent::SUBSCRIBE_FAILED_EARLY_DOMAIN, 0);
    SAS::report_event(event);
    return PJ_FALSE;
  }

  // SUBSCRIBE request targeted at the home domain or specifically at this node. Check
  // whether it should be processed by this module or passed up to an AS.
  pjsip_msg *msg = rdata->msg_info.msg;

  // A valid subscription must have the Event header set to "reg". This is case-sensitive
  pj_str_t event_name = pj_str("Event");
  pjsip_event_hdr* event = (pjsip_event_hdr*)pjsip_msg_find_hdr_by_name(msg, &event_name, NULL);

  if (!event || (PJUtils::pj_str_to_string(&event->event_type) != "reg"))
  {
    // The Event header is missing or doesn't match "reg"
    LOG_DEBUG("Rejecting subscription request with invalid event header");

    SAS::Event sas_event(trail, SASEvent::SUBSCRIBE_FAILED_EARLY_EVENT, 0);
    if (event)
    {
      char event_hdr_str[256];
      memset(event_hdr_str, 0, 256);
      pjsip_hdr_print_on(event, event_hdr_str, 255);
      sas_event.add_var_param(event_hdr_str);
    }
    SAS::report_event(sas_event);

    return PJ_FALSE;
  }

  // Accept header may be present - if so must include the application/reginfo+xml
  pjsip_accept_hdr* accept = (pjsip_accept_hdr*)pjsip_msg_find_hdr(msg, PJSIP_H_ACCEPT, NULL);
  if (accept)
  {
    bool found = false;
    pj_str_t reginfo = pj_str("application/reginfo+xml");
    for (uint32_t i = 0; i < accept->count; i++)
    {
      if (!pj_strcmp(accept->values + i, &reginfo))
      {
        found = true;
      }
    }

    if (!found)
    {
      // The Accept header (if it exists) doesn't contain "application/reginfo+xml"
      LOG_DEBUG("Rejecting subscription request with invalid accept header");
      char accept_hdr_str[256];
      memset(accept_hdr_str, 0, 256);
      pjsip_hdr_print_on(accept, accept_hdr_str, 255);
      SAS::Event event(trail, SASEvent::SUBSCRIBE_FAILED_EARLY_ACCEPT, 0);
      event.add_var_param(accept_hdr_str);
      SAS::report_event(event);

      return PJ_FALSE;
    }
  }

  process_subscription_request(rdata);
  return PJ_TRUE;
}
Пример #27
0
static pj_int32_t calculate_response_expiration(const pjsip_regc *regc,
					        const pjsip_rx_data *rdata,
						unsigned *contact_cnt,
						unsigned max_contact,
						pjsip_contact_hdr *contacts[])
{
    pj_int32_t expiration = NOEXP;
    const pjsip_msg *msg = rdata->msg_info.msg;
    const pjsip_hdr *hdr;

    /* Enumerate all Contact headers in the response */
    *contact_cnt = 0;
    for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) {
	if (hdr->type == PJSIP_H_CONTACT && 
	    *contact_cnt < max_contact) 
	{
	    contacts[*contact_cnt] = (pjsip_contact_hdr*)hdr;
	    ++(*contact_cnt);
	}
    }

    if (regc->current_op == REGC_REGISTERING) {
	pj_bool_t has_our_contact = PJ_FALSE;
	const pjsip_expires_hdr *expires;

	/* Get Expires header */
	expires = (const pjsip_expires_hdr*)
		  pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);

	/* Try to find the Contact URIs that we register, in the response
	 * to get the expires value. We'll try both with comparing the URI
	 * and comparing the extension param only.
	 */
	if (pjsip_cfg()->regc.check_contact || regc->add_xuid_param) {
	    unsigned i;
	    for (i=0; i<*contact_cnt; ++i) {
		const pjsip_contact_hdr *our_hdr;

		our_hdr = (const pjsip_contact_hdr*) 
			  regc->contact_hdr_list.next;

		/* Match with our Contact header(s) */
		while ((void*)our_hdr != (void*)&regc->contact_hdr_list) {

		    const pjsip_uri *uri1, *uri2;
		    pj_bool_t matched = PJ_FALSE;

		    /* Exclude the display name when comparing the URI 
		     * since server may not return it.
		     */
		    uri1 = (const pjsip_uri*)
			   pjsip_uri_get_uri(contacts[i]->uri);
		    uri2 = (const pjsip_uri*)
			   pjsip_uri_get_uri(our_hdr->uri);

		    /* First try with exact matching, according to RFC 3261
		     * Section 19.1.4 URI Comparison
		     */
		    if (pjsip_cfg()->regc.check_contact) {
			matched = pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR,
						uri1, uri2)==0;
		    }

		    /* If no match is found, try with matching the extension
		     * parameter only if extension parameter was added.
		     */
		    if (!matched && regc->add_xuid_param &&
			(PJSIP_URI_SCHEME_IS_SIP(uri1) ||
			 PJSIP_URI_SCHEME_IS_SIPS(uri1)) && 
			(PJSIP_URI_SCHEME_IS_SIP(uri2) ||
			 PJSIP_URI_SCHEME_IS_SIPS(uri2))) 
		    {
			const pjsip_sip_uri *sip_uri1, *sip_uri2;
			const pjsip_param *p1, *p2;
		
			sip_uri1 = (const pjsip_sip_uri*)uri1;
			sip_uri2 = (const pjsip_sip_uri*)uri2;

			p1 = pjsip_param_cfind(&sip_uri1->other_param,
					       &XUID_PARAM_NAME);
			p2 = pjsip_param_cfind(&sip_uri2->other_param,
					       &XUID_PARAM_NAME);
			matched = p1 && p2 &&
				  pj_strcmp(&p1->value, &p2->value)==0;

		    }

		    if (matched) {
			has_our_contact = PJ_TRUE;

			if (contacts[i]->expires >= 0 && 
			    contacts[i]->expires < expiration) 
			{
			    /* Get the lowest expiration time. */
			    expiration = contacts[i]->expires;
			}

			break;
		    }

		    our_hdr = our_hdr->next;

		} /* while ((void.. */

	    }  /* for (i=.. */

	    /* If matching Contact header(s) are found but the
	     * header doesn't contain expires parameter, get the
	     * expiration value from the Expires header. And
	     * if Expires header is not present, get the expiration
	     * value from the request.
	     */
	    if (has_our_contact && expiration == NOEXP) {
		if (expires) {
		    expiration = expires->ivalue;
		} else if (regc->expires_hdr) {
		    expiration = regc->expires_hdr->ivalue;
		} else {
		    /* We didn't request explicit expiration value,
		     * and server doesn't specify it either. This 
		     * shouldn't happen unless we have a broken
		     * registrar.
		     */
		    expiration = 3600;
		}
	    }

	}

	/* If we still couldn't get matching Contact header(s), it means
	 * there must be something wrong with the  registrar (e.g. it may
	 * have modified the URI's in the response, which is prohibited).
	 */
	if (expiration==NOEXP) {
	    /* If the number of Contact headers in the response matches 
	     * ours, they're all probably ours. Get the expiration
	     * from there if this is the case, or from Expires header
	     * if we don't have exact Contact header count, or
	     * from the request as the last resort.
	     */
	    pj_size_t our_contact_cnt;

	    our_contact_cnt = pj_list_size(&regc->contact_hdr_list);

	    if (*contact_cnt == our_contact_cnt && *contact_cnt &&
		contacts[0]->expires >= 0) 
	    {
		expiration = contacts[0]->expires;
	    } else if (expires)
		expiration = expires->ivalue;
	    else if (regc->expires_hdr)
		expiration = regc->expires_hdr->ivalue;
	    else
		expiration = 3600;
	}

    } else {
	/* Just assume that the unregistration has been successful. */
	expiration = 0;
    }

    /* Must have expiration value by now */
    pj_assert(expiration != NOEXP);

    return expiration;
}
Пример #28
0
static pj_status_t pjsip_url_compare( pjsip_uri_context_e context,
                                      const pjsip_sip_uri *url1,
                                      const pjsip_sip_uri *url2)
{
    const pjsip_param *p1;

    /*
     * Compare two SIP URL's according to Section 19.1.4 of RFC 3261.
     */

    /* SIP and SIPS URI are never equivalent.
     * Note: just compare the vptr to avoid string comparison.
     *       Pretty neat huh!!
     */
    if (url1->vptr != url2->vptr)
        return PJSIP_ECMPSCHEME;

    /* Comparison of the userinfo of SIP and SIPS URIs is case-sensitive.
     * This includes userinfo containing passwords or formatted as
     * telephone-subscribers.
     */
    if (pj_strcmp(&url1->user, &url2->user) != 0)
        return PJSIP_ECMPUSER;
    if (pj_strcmp(&url1->passwd, &url2->passwd) != 0)
        return PJSIP_ECMPPASSWD;

    /* Comparison of all other components of the URI is
     * case-insensitive unless explicitly defined otherwise.
     */

    /* The ordering of parameters and header fields is not significant
     * in comparing SIP and SIPS URIs.
     */

    /* Characters other than those in the reserved set (see RFC 2396 [5])
     * are equivalent to their encoding.
     */

    /* An IP address that is the result of a DNS lookup of a host name
     * does not match that host name.
     */
    if (pj_stricmp(&url1->host, &url2->host) != 0)
        return PJSIP_ECMPHOST;

    /* A URI omitting any component with a default value will not match a URI
     * explicitly containing that component with its default value.
     * For instance, a URI omitting the optional port component will not match
     * a URI explicitly declaring port 5060.
     * The same is true for the transport-parameter, ttl-parameter,
     * user-parameter, and method components.
     */

    /* Port is not allowed in To and From header.
     */
    if (context != PJSIP_URI_IN_FROMTO_HDR) {
        if (url1->port != url2->port)
            return PJSIP_ECMPPORT;
    }
    /* Transport is not allowed in From/To header. */
    if (context != PJSIP_URI_IN_FROMTO_HDR) {
        if (pj_stricmp(&url1->transport_param, &url2->transport_param) != 0)
            return PJSIP_ECMPTRANSPORTPRM;
    }
    /* TTL param is not allowed in From, To, Route, and Record-Route header. */
    if (context != PJSIP_URI_IN_FROMTO_HDR &&
            context != PJSIP_URI_IN_ROUTING_HDR)
    {
        if (url1->ttl_param != url2->ttl_param)
            return PJSIP_ECMPTTLPARAM;
    }
    /* User param is allowed in all contexes */
    if (pj_stricmp(&url1->user_param, &url2->user_param) != 0)
        return PJSIP_ECMPUSERPARAM;
    /* Method param is only allowed in external/other context. */
    if (context == PJSIP_URI_IN_OTHER) {
        if (pj_stricmp(&url1->method_param, &url2->method_param) != 0)
            return PJSIP_ECMPMETHODPARAM;
    }
    /* maddr param is not allowed in From and To header. */
    if (context != PJSIP_URI_IN_FROMTO_HDR) {
        if (pj_stricmp(&url1->maddr_param, &url2->maddr_param) != 0)
            return PJSIP_ECMPMADDRPARAM;
    }

    /* lr parameter is ignored (?) */
    /* lr param is not allowed in From, To, and Contact header. */


    /* All other uri-parameters appearing in only one URI are ignored when
     * comparing the URIs.
     */
    if (pjsip_param_cmp(&url1->other_param, &url2->other_param, 1)!=0)
        return PJSIP_ECMPOTHERPARAM;

    /* URI header components are never ignored. Any present header component
     * MUST be present in both URIs and match for the URIs to match.
     * The matching rules are defined for each header field in Section 20.
     */
    p1 = url1->header_param.next;
    while (p1 != &url1->header_param) {
        const pjsip_param *p2;
        p2 = pjsip_param_find(&url2->header_param, &p1->name);
        if (p2) {
            /* It seems too much to compare two header params according to
             * the rule of each header. We'll just compare them string to
             * string..
             */
            if (pj_stricmp(&p1->value, &p2->value) != 0)
                return PJSIP_ECMPHEADERPARAM;
        } else {
            return PJSIP_ECMPHEADERPARAM;
        }
        p1 = p1->next;
    }

    /* Equal!! Pheuww.. */
    return PJ_SUCCESS;
}
Пример #29
0
/* Compare attributes array. */
static pj_status_t compare_attr_imp(int inst_id, 
					unsigned count1,
				    pjmedia_sdp_attr *const attr1[],
				    unsigned count2,
				    pjmedia_sdp_attr *const attr2[])
{
    pj_status_t status;
    unsigned i;
    const pj_str_t inactive = { "inactive", 8 };
    const pj_str_t sendrecv = { "sendrecv", 8 };
    const pj_str_t sendonly = { "sendonly", 8 };
    const pj_str_t recvonly = { "recvonly", 8 };
    const pj_str_t fmtp = { "fmtp", 4 };
    const pj_str_t rtpmap = { "rtpmap", 6 };

    /* For simplicity, we only compare the following attributes, and ignore
     * the others:
     *	- direction, eg. inactive, sendonly, recvonly, sendrecv
     *	- fmtp for each payload.
     *	- rtpmap for each payload.
     */
    for (i=0; i<count1; ++i) {
	const pjmedia_sdp_attr *a1 = attr1[i];

	if (pj_strcmp(&a1->name, &inactive) == 0 || 
	    pj_strcmp(&a1->name, &sendrecv) == 0 ||
	    pj_strcmp(&a1->name, &sendonly) == 0 ||
	    pj_strcmp(&a1->name, &recvonly) == 0)
	{
	    /* For inactive, sendrecv, sendonly, and recvonly attributes,
	     * the same attribute must be present on the other SDP.
	     */
	    const pjmedia_sdp_attr *a2;
	    a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, NULL);
	    if (!a2)
		return PJMEDIA_SDP_EDIRNOTEQUAL;

	} else if (pj_strcmp(&a1->name, &fmtp) == 0) {
	    /* For fmtp attribute, find the fmtp attribute in the other SDP
	     * for the same payload type, and compare the fmtp param/value.
	     */
	    pjmedia_sdp_fmtp fmtp1, fmtp2;
	    const pjmedia_sdp_attr *a2;

	    status = pjmedia_sdp_attr_get_fmtp(a1, &fmtp1);
	    if (status != PJ_SUCCESS)
		return PJMEDIA_SDP_EFMTPNOTEQUAL;

	    a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &fmtp1.fmt);
	    if (!a2)
		return PJMEDIA_SDP_EFMTPNOTEQUAL;

	    status = pjmedia_sdp_attr_get_fmtp(a2, &fmtp2);
	    if (status != PJ_SUCCESS)
		return PJMEDIA_SDP_EFMTPNOTEQUAL;

	    if (pj_strcmp(&fmtp1.fmt_param, &fmtp2.fmt_param) != 0)
		return PJMEDIA_SDP_EFMTPNOTEQUAL;

	} else if (pj_strcmp(&a1->name, &rtpmap) == 0) {
	    /* For rtpmap attribute, find rtpmap attribute on the other SDP
	     * for the same payload type, and compare both rtpmap atribute
	     * values.
	     */
	    pjmedia_sdp_rtpmap r1, r2;
	    const pjmedia_sdp_attr *a2;

	    status = pjmedia_sdp_attr_get_rtpmap(inst_id, a1, &r1);
	    if (status != PJ_SUCCESS)
		return PJMEDIA_SDP_ERTPMAPNOTEQUAL;

	    a2 = pjmedia_sdp_attr_find(count2, attr2, &a1->name, &r1.pt);
	    if (!a2)
		return PJMEDIA_SDP_ERTPMAPNOTEQUAL;

	    status = pjmedia_sdp_attr_get_rtpmap(inst_id, a2, &r2);
	    if (status != PJ_SUCCESS)
		return PJMEDIA_SDP_ERTPMAPNOTEQUAL;

	    if (pj_strcmp(&r1.pt, &r2.pt) != 0)
		return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
	    if (pj_strcmp(&r1.enc_name, &r2.enc_name) != 0)
		return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
	    if (r1.clock_rate != r2.clock_rate)
		return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
	    if (pj_strcmp(&r1.param, &r2.param) != 0)
		return PJMEDIA_SDP_ERTPMAPNOTEQUAL;
	}
    }

    return PJ_SUCCESS;
}
Пример #30
0
/*
 * Compare two SDP session for equality.
 */
PJ_DEF(pj_status_t) pjmedia_sdp_session_cmp( int inst_id, 
						 const pjmedia_sdp_session *sd1,
					     const pjmedia_sdp_session *sd2,
					     unsigned option)
{
    unsigned i;
    pj_status_t status;

    PJ_ASSERT_RETURN(sd1 && sd2 && option==0, PJ_EINVAL);

    PJ_UNUSED_ARG(option);

    /* Compare the origin line. */
    if (pj_strcmp(&sd1->origin.user, &sd2->origin.user) != 0)
	return PJMEDIA_SDP_EORIGINNOTEQUAL;

    if (sd1->origin.id != sd2->origin.id)
	return PJMEDIA_SDP_EORIGINNOTEQUAL;

    if (sd1->origin.version != sd2->origin.version)
	return PJMEDIA_SDP_EORIGINNOTEQUAL;

    if (pj_strcmp(&sd1->origin.net_type, &sd2->origin.net_type) != 0)
	return PJMEDIA_SDP_EORIGINNOTEQUAL;

    if (pj_strcmp(&sd1->origin.addr_type, &sd2->origin.addr_type) != 0)
	return PJMEDIA_SDP_EORIGINNOTEQUAL;

    if (pj_strcmp(&sd1->origin.addr, &sd2->origin.addr) != 0)
	return PJMEDIA_SDP_EORIGINNOTEQUAL;

    
    /* Compare the subject line. */
    if (pj_strcmp(&sd1->name, &sd2->name) != 0)
	return PJMEDIA_SDP_ENAMENOTEQUAL;

    /* Compare connection line, when they exist */
    if (sd1->conn) {
	if (!sd2->conn)
	    return PJMEDIA_SDP_ECONNNOTEQUAL;
	status = compare_conn(sd1->conn, sd2->conn);
	if (status != PJ_SUCCESS)
	    return status;
    } else {
	if (sd2->conn)
	    return PJMEDIA_SDP_ECONNNOTEQUAL;
    }

    /* Compare time line. */
    if (sd1->time.start != sd2->time.start)
	return PJMEDIA_SDP_ETIMENOTEQUAL;

    if (sd1->time.stop != sd2->time.stop)
	return PJMEDIA_SDP_ETIMENOTEQUAL;

    /* Compare attributes. */
    status = compare_attr(inst_id, sd1->attr_count, sd1->attr, 
			  sd2->attr_count, sd2->attr);
    if (status != PJ_SUCCESS)
	return status;

    /* Compare media lines. */
    if (sd1->media_count != sd2->media_count)
	return PJMEDIA_SDP_EMEDIANOTEQUAL;

    for (i=0; i<sd1->media_count; ++i) {
	status = pjmedia_sdp_media_cmp(inst_id, sd1->media[i], sd2->media[i], 0);
	if (status != PJ_SUCCESS)
	    return status;
    }

    /* Looks equal. */
    return PJ_SUCCESS;
}