Browse Source

Merge pull request #768 from TheGrandWazoo/master

sca: fix seized lines on seizing held lines, SCA owner and correct CID on seizing held lines
Daniel-Constantin Mierla 9 years ago
parent
commit
2929255df3
4 changed files with 513 additions and 457 deletions
  1. 50 9
      modules/sca/sca_call_info.c
  2. 5 0
      modules/sca/sca_dialog.c
  3. 12 2
      modules/sca/sca_notify.c
  4. 446 446
      modules/sca/sca_util.c

+ 50 - 9
modules/sca/sca_call_info.c

@@ -530,6 +530,11 @@ int sca_call_info_seize_held_call(sip_msg_t *msg, sca_call_info *call_info,
 	int slot_idx = -1;
 	int rc = -1;
 
+	LM_DBG( "From-AOR:%.*s To-AOR:%.*s From-URI:<%.*s> To-URI:<%.*s> "
+			"Contact: <%.*s> Call-Info: appearance-index=%d\n",
+			STR_FMT(from_aor), STR_FMT(to_aor),STR_FMT(&from->uri),
+			STR_FMT(&to->uri), STR_FMT(contact_uri), call_info->index);
+
 	slot_idx = sca_hash_table_index_for_key(sca->appearances, from_aor);
 	sca_hash_table_lock_index(sca->appearances, slot_idx);
 
@@ -729,8 +734,8 @@ static int sca_call_info_uri_update(str *aor, sca_call_info *call_info,
 	}
 
 	dialog.id.s = dlg_buf;
-	if (sca_dialog_build_from_tags(&dialog, sizeof(dlg_buf), call_id, to_tag,
-			from_tag) < 0) {
+	if (sca_dialog_build_from_tags(&dialog, sizeof(dlg_buf), call_id, from_tag,
+			to_tag) < 0) {
 		LM_ERR("sca_call_info_uri_update: Failed to build dialog from tags\n");
 		return (-1);
 	}
@@ -803,6 +808,10 @@ static int sca_call_info_is_line_seize_reinvite(sip_msg_t *msg,
 	str ruri_aor;
 	int state;
 
+	LM_DBG("For From-AOR %.*s To-AOR: %.*s: From: <%.*s> To: <%.*s> "
+			"Call-Info: appearance-index=%d\n",
+			STR_FMT(from_aor), STR_FMT(to_aor), STR_FMT(&from->uri),
+			STR_FMT(&to->uri), call_info->index);
 
 	// a handset in an SCA group is attempting to seize a held line if:
 	//		the RURI, From URI and To URI are identical;
@@ -834,6 +843,8 @@ static int sca_call_info_is_line_seize_reinvite(sip_msg_t *msg,
 				STR_FMT(to_aor), STR_FMT(from_aor), call_info->index);
 		return (0);
 	}
+	LM_DBG("reINVITE to %.*s from %.*s appearance-index %d (seizing held line)\n",
+			STR_FMT(to_aor), STR_FMT(from_aor), call_info->index);
 
 	return (1);
 }
@@ -934,6 +945,11 @@ int sca_call_info_invite_request_handler(sip_msg_t *msg,
 	int state = SCA_APPEARANCE_STATE_UNKNOWN;
 	int rc = -1;
 
+	LM_DBG("For From-AOR %.*s To-AOR: %.*s: From: <%.*s> To: <%.*s> "
+			"Contact: <%.*s> Call-Info: appearance-index=%d\n",
+			STR_FMT(from_aor), STR_FMT(to_aor),STR_FMT(&from->uri), STR_FMT(&to->uri),
+			STR_FMT(contact_uri), call_info->index);
+
 	// if we get here, one of the legs is an SCA endpoint. we want to know
 	// when the e2e ACK comes in so we can notify other members of the group.
 	if (sca->tm_api->register_tmcb(msg, NULL, TMCB_E2EACK_IN,
@@ -1016,6 +1032,11 @@ int sca_call_info_invite_reply_18x_handler(sip_msg_t *msg,
 	int rc = -1;
 	int notify = 0;
 
+	LM_DBG("For From-AOR %.*s To-AOR: %.*s: From: <%.*s> To: <%.*s> "
+			"Contact: <%.*s> Call-Info: appearance-index=%d",
+			STR_FMT(from_aor), STR_FMT(to_aor),STR_FMT(&from->uri), STR_FMT(&to->uri),
+			STR_FMT(contact_uri), call_info->index);
+
 	switch (msg->REPLY_STATUS) {
 	case 180:
 	case 183:
@@ -1165,11 +1186,18 @@ static int sca_call_info_invite_reply_200_handler(sip_msg_t *msg,
 	char dlg_buf[1024];
 	str app_uri_aor = STR_NULL;
 	str state_str = STR_NULL;
+	str to_display = STR_NULL;
 	int state = SCA_APPEARANCE_STATE_UNKNOWN;
 	int slot_idx = -1;
 	int rc = -1;
 
-	if (SCA_CALL_INFO_IS_SHARED_CALLEE(call_info)) {
+	LM_DBG("For From-AOR %.*s To-AOR: %.*s: From: <%.*s> To: <%.*s> "
+			"Contact: <%.*s> Call-Info: appearance-index=%d\n",
+			STR_FMT(from_aor), STR_FMT(to_aor),STR_FMT(&from->uri), STR_FMT(&to->uri),
+			STR_FMT(contact_uri), call_info->index);
+
+	if (SCA_CALL_INFO_IS_SHARED_CALLEE(call_info) &&
+			(!SCA_STR_EQ(from_aor, to_aor))) {
 		rc = sca_call_info_uri_update(to_aor, call_info, from, to, contact_uri,
 				&msg->callid->body);
 	}
@@ -1224,13 +1252,21 @@ static int sca_call_info_invite_reply_200_handler(sip_msg_t *msg,
 				"parse_uri <%.*s> failed\n", STR_FMT(contact_uri));
 		goto done;
 	}
-	if (sca_create_canonical_aor(msg, &app_uri_aor) < 0) {
-		LM_ERR("sca_call_info_invite_200_reply_handler: "
-				"sca_create_canonical_aor failed\n");
-		goto done;
-	}
 
-	if (sca_appearance_update_unsafe(app, state, &to->display, &app_uri_aor,
+	// If the 'from_aor' and 'to_aor' don't match then we need to get the
+	// 'to_display' and 'app_uri_aor' variable values for a 200 reply for an
+	// INVITE for the call to sca_appearance_update_unsafe(). Otherwise its a
+	// 200 reply for a reINVITE and the 'to_display' and 'app_uri_aor' are
+	// already set to NULL and that won't change the appearance-uri.
+	if (!SCA_STR_EQ(from_aor, to_aor)) {
+		to_display = to->display;
+		if (sca_create_canonical_aor(msg, &app_uri_aor) < 0) {
+			LM_ERR( "sca_call_info_invite_200_reply_handler: "
+					"sca_create_canonical_aor failed\n" );
+			goto done;
+		}
+	}
+	if (sca_appearance_update_unsafe(app, state, &to_display, &app_uri_aor,
 			&dialog, NULL, contact_uri) < 0) {
 		sca_appearance_state_to_str(state, &state_str);
 		LM_ERR("sca_call_info_invite_handler: failed to update appearance "
@@ -1940,6 +1976,11 @@ int sca_call_info_update(sip_msg_t *msg, char *p1, char *p2)
 		goto done;
 	}
 
+	LM_DBG( "Calling Dispatch Id: %d handler with From-AOR: %.*s To-AOR: %.*s "
+			"From-URI: <%.*s> To-URI: <%.*s> Contact-URI: <%.*s>\n",
+			i, STR_FMT(&from_aor), STR_FMT(&to_aor),STR_FMT(&from->uri),
+			STR_FMT(&to->uri), STR_FMT(&contact_uri));
+
 	rc = call_info_dispatch[i].handler(msg, &call_info, from, to, &from_aor,
 			&to_aor, &contact_uri);
 	if (rc < 0) {

+ 5 - 0
modules/sca/sca_dialog.c

@@ -32,6 +32,9 @@ int sca_dialog_build_from_tags(sca_dialog *dialog, int maxlen, str *call_id,
 	assert(call_id != NULL);
 	assert(from_tag != NULL);
 
+	LM_DBG( "From-Tag: %.*s To-Tag: %.*s CallId: %.*s\n",
+			STR_FMT(from_tag), STR_FMT(to_tag), STR_FMT(call_id));
+
 	len = call_id->len + from_tag->len;
 	if (!SCA_STR_EMPTY(to_tag)) {
 		len += to_tag->len;
@@ -74,6 +77,8 @@ int sca_dialog_create_replaces_header(sca_dialog *dlg, str *replaces_hdr)
 
 	assert(replaces_hdr != NULL);
 
+	LM_DBG( "Called\n" );
+
 	if (SCA_STR_EMPTY(&dlg->call_id) || SCA_STR_EMPTY(&dlg->from_tag) ||
 	SCA_STR_EMPTY(&dlg->to_tag)) {
 		LM_ERR("sca_dialog_create_replaces_header: dialog %.*s is not a "

+ 12 - 2
modules/sca/sca_notify.c

@@ -22,6 +22,7 @@
 #include <assert.h>
 
 #include "sca.h"
+#include "sca_appearance.h"
 #include "sca_call_info.h"
 #include "sca_event.h"
 #include "sca_notify.h"
@@ -261,8 +262,14 @@ static int sca_notify_subscriber_internal(sca_mod *scam, sca_subscription *sub,
 {
 	uac_req_t request;
 	dlg_t *dlg = NULL;
+	str state_str = STR_NULL;
 	int rc = -1;
 
+	sca_appearance_state_to_str(sub->state, &state_str);
+	LM_DBG("SCA: NOTIFYing subscriber '%.*s' of event '%s' with a state of '%.*s' to index '%d'\n",
+			STR_FMT(&sub->subscriber), sca_event_name_from_type(sub->event),
+			STR_FMT(&state_str), sub->index);
+
 	dlg = sca_notify_dlg_for_subscription(sub);
 	if (dlg == NULL) {
 		LM_ERR("Failed to create dlg_t for %s NOTIFY to %.*s\n",
@@ -298,8 +305,9 @@ int sca_notify_subscriber(sca_mod *scam, sca_subscription *sub, int app_idx)
 	str headers = STR_NULL;
 	char hdrbuf[SCA_HEADERS_MAX_LEN];
 
-	headers.s = hdrbuf;
+	LM_DBG("NOTIFYing subscriber because of a SUBSCRIPTION request\n");
 
+	headers.s = hdrbuf;
 	if (sca_notify_build_headers_from_info(&headers, sizeof(hdrbuf), scam, sub,
 			app_idx) < 0) {
 		LM_ERR("Failed to build NOTIFY headers\n");
@@ -328,8 +336,10 @@ int sca_notify_call_info_subscribers(sca_mod *scam, str *subscription_aor)
 	assert(scam->subscriptions != NULL);
 	assert(!SCA_STR_EMPTY(subscription_aor));
 
-	event_name = sca_event_name_from_type(SCA_EVENT_TYPE_CALL_INFO);
+	LM_DBG("Notifying ALL subscribers of AOR %.*s due to a SUBSCRIBTION request\n",
+			STR_FMT(subscription_aor));
 
+	event_name = sca_event_name_from_type(SCA_EVENT_TYPE_CALL_INFO);
 	if (subscription_aor->len + strlen(event_name) >= sizeof(keybuf)) {
 		LM_ERR("Hash key %.*s + %s is too long\n",
 				STR_FMT(subscription_aor), event_name);

+ 446 - 446
modules/sca/sca_util.c

@@ -1,446 +1,446 @@
-/*
- * Copyright (C) 2012 Andrew Mortensen
- *
- * This file is part of the sca module for Kamailio, a free SIP server.
- *
- * The sca module 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
- *
- * The sca module 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., 51 Franklin Street, Fifth Floor, Boston, MA. 02110-1301 USA
- */
-#include "sca_common.h"
-
-#include <assert.h>
-
-#include "sca_util.h"
-
-#include "../../parser/sdp/sdp.h"
-extern int log_stderr;
-int sca_get_msg_method(sip_msg_t *msg)
-{
-	assert(msg != NULL);
-
-	if (msg->first_line.type == SIP_REQUEST) {
-		return (msg->REQ_METHOD);
-	}
-
-	return (sca_get_msg_cseq_method(msg));
-}
-
-int sca_get_msg_contact_uri(sip_msg_t *msg, str *contact_uri)
-{
-	contact_body_t *contact_body;
-
-	assert(msg != NULL);
-	assert(contact_uri != NULL);
-
-	if (SCA_HEADER_EMPTY(msg->contact)) {
-		LM_DBG("Empty Contact header\n");
-		contact_uri->s = NULL;
-		contact_uri->len = 0;
-
-		return (0);
-	}
-
-	if (parse_contact(msg->contact) < 0) {
-		LM_ERR("Failed to parse Contact header: %.*s\n",
-				STR_FMT(&msg->contact->body));
-		return (-1);
-	}
-	if ((contact_body = (contact_body_t *) msg->contact->parsed) == NULL) {
-		LM_ERR("Invalid Contact header: %.*s\n", STR_FMT(&msg->contact->body));
-		return (-1);
-	}
-	if (contact_body->star) {
-		LM_ERR("Invalid Contact header: SCA Contact must not be \"*\"\n");
-		return (-1);
-	}
-	if (contact_body->contacts == NULL) {
-		LM_ERR("Invalid Contact header: parser found no contacts\n");
-		return (-1);
-	}
-	if (contact_body->contacts->next) {
-		LM_ERR("Invalid Contact header: Contact may only contain one URI\n");
-		return (-1);
-	}
-
-	contact_uri->s = contact_body->contacts->uri.s;
-	contact_uri->len = contact_body->contacts->uri.len;
-
-	return (1);
-}
-
-int sca_get_msg_cseq_number(sip_msg_t *msg)
-{
-	int cseq;
-
-	assert(msg != NULL);
-
-	if (SCA_HEADER_EMPTY(msg->cseq)) {
-		LM_ERR("Empty Cseq header\n");
-		return (-1);
-	}
-	if (str2int(&(get_cseq(msg)->number), (unsigned int *) &cseq) != 0) {
-		LM_ERR("Bad Cseq header: %.*s\n", STR_FMT(&msg->cseq->body));
-		return (-1);
-	}
-
-	return (cseq);
-}
-
-/*
- *  assumes cseq header in msg is already parsed
- */
-int sca_get_msg_cseq_method(sip_msg_t *msg)
-{
-	assert(msg != NULL);
-
-	if (SCA_HEADER_EMPTY(msg->cseq)) {
-		LM_ERR("Empty Cseq header\n");
-		return (-1);
-	}
-
-	return (get_cseq(msg)->method_id);
-}
-
-int sca_get_msg_from_header(sip_msg_t *msg, struct to_body **from)
-{
-	struct to_body *f;
-
-	assert(msg != NULL);
-	assert(from != NULL);
-
-	if (SCA_HEADER_EMPTY(msg->from)) {
-		LM_ERR("Empty From header\n");
-		return (-1);
-	}
-	if (parse_from_header(msg) < 0) {
-		LM_ERR("Bad From header\n");
-		return (-1);
-	}
-	f = get_from(msg);
-	if (SCA_STR_EMPTY(&f->tag_value)) {
-		LM_ERR("Bad From header: no tag parameter\n");
-		return (-1);
-	}
-
-	// ensure the URI is parsed for future use
-	if (parse_uri(f->uri.s, f->uri.len, GET_FROM_PURI(msg)) < 0) {
-		LM_ERR("Failed to parse From URI %.*s\n", STR_FMT(&f->uri));
-		return (-1);
-	}
-
-	*from = f;
-
-	return (0);
-}
-
-int sca_get_msg_to_header(sip_msg_t *msg, struct to_body **to)
-{
-	struct to_body parsed_to;
-	struct to_body *t = NULL;
-
-	assert(msg != NULL);
-	assert(to != NULL);
-
-	if (SCA_HEADER_EMPTY(msg->to)) {
-		LM_ERR("Empty To header\n");
-		return (-1);
-	}
-	t = get_to(msg);
-	if (t == NULL) {
-		parse_to(msg->to->body.s, msg->to->body.s + msg->to->body.len + 1, // end of buffer
-		&parsed_to);
-		if (parsed_to.error != PARSE_OK) {
-			LM_ERR("Bad To header\n");
-			return (-1);
-		}
-		t = &parsed_to;
-	}
-
-	// ensure the URI is parsed for future use
-	if (parse_uri(t->uri.s, t->uri.len, GET_TO_PURI(msg)) < 0) {
-		LM_ERR("Failed to parse To URI %.*s\n", STR_FMT(&t->uri));
-		return (-1);
-	}
-
-	*to = t;
-
-	return (0);
-}
-
-/*
- *  count characters requiring escape as defined by escape_common
- */
-int sca_uri_display_escapes_count(str *display) {
-	int c = 0;
-	int i;
-
-	if (SCA_STR_EMPTY(display)) {
-		return (0);
-	}
-
-	for (i = 0; i < display->len; i++) {
-		switch (display->s[i]) {
-		case '\'':
-		case '"':
-		case '\\':
-		case '\0':
-			c++;
-
-		default:
-			break;
-		}
-	}
-
-	return (c);
-}
-
-int sca_uri_extract_aor(str *uri, str *aor)
-{
-	char *semi;
-
-	assert(aor != NULL);
-
-	if (uri == NULL) {
-		aor->s = NULL;
-		aor->len = 0;
-		return (-1);
-	}
-
-	aor->s = uri->s;
-	semi = memchr(uri->s, ';', uri->len);
-	if (semi != NULL) {
-		aor->len = semi - uri->s;
-	} else {
-		aor->len = uri->len;
-	}
-
-	return (0);
-}
-
-int sca_uri_build_aor(str *aor, int maxlen, str *contact_uri, str *domain_uri)
-{
-	char *p;
-	char *dp;
-	int len;
-
-	assert(aor != NULL);
-	assert(contact_uri != NULL);
-	assert(domain_uri != NULL);
-
-	if (contact_uri->len + domain_uri->len >= maxlen) {
-		return (-1);
-	}
-
-	p = memchr(contact_uri->s, '@', contact_uri->len);
-	if (p == NULL) {
-		// no username, by definition can't be an SCA line
-		aor->s = NULL;
-		aor->len = 0;
-
-		return (0);
-	}
-	dp = memchr(domain_uri->s, '@', domain_uri->len);
-	if (dp == NULL) {
-		// may be nameless URI
-		dp = memchr(domain_uri->s, ':', domain_uri->len);
-		if (dp == NULL) {
-			// bad domain URI
-			return (-1);
-		}
-	}
-	dp++;
-
-	len = p - contact_uri->s;
-	memcpy(aor->s, contact_uri->s, len);
-	aor->s[len] = '@';
-	len += 1;
-	aor->len = len;
-
-	len = domain_uri->len - (dp - domain_uri->s);
-	memcpy(aor->s + aor->len, dp, len);
-	aor->len += len;
-
-	return (aor->len);
-}
-
-int sca_aor_create_from_info(str *aor, uri_type type, str *user, str *domain,
-		str *port)
-{
-	str scheme = STR_NULL;
-	int len = 0;
-
-	assert(aor != NULL);
-
-	uri_type_to_str(type, &scheme);
-
-	// +1 for ':', +1 for '@'
-	len = scheme.len + 1 + user->len + 1 + domain->len;
-	if (!SCA_STR_EMPTY(port)) {
-		// +1 for ':'
-		len += 1 + port->len;
-	}
-
-	aor->s = (char *) pkg_malloc(len);
-	if (aor->s == NULL) {
-		LM_ERR("sca_aor_create_from_info: pkg_malloc %d bytes failed\n", len);
-		return (-1);
-	}
-
-	len = 0;
-	SCA_STR_COPY(aor, &scheme);
-	len += scheme.len;
-
-	*(aor->s + len) = ':';
-	aor->len++;
-	len++;
-
-	SCA_STR_APPEND(aor, user);
-	len += user->len;
-
-	*(aor->s + len) = '@';
-	aor->len++;
-	len++;
-
-	SCA_STR_APPEND(aor, domain);
-	len += domain->len;
-
-	if (!SCA_STR_EMPTY(port)) {
-		*(aor->s + len) = ':';
-		len += 1;
-
-		SCA_STR_APPEND(aor, port);
-		len += port->len;
-	}
-
-	return (aor->len);
-}
-
-int sca_create_canonical_aor_for_ua(sip_msg_t *msg, str *c_aor, int ua_opts)
-{
-	struct to_body *tf = NULL;
-	sip_uri_t c_uri;
-	str tf_aor = STR_NULL;
-	str contact_uri = STR_NULL;
-	int rc = -1;
-
-	assert(msg != NULL);
-	assert(c_aor != NULL);
-
-	memset(c_aor, 0, sizeof(str));
-
-	if ((ua_opts & SCA_AOR_TYPE_AUTO)) {
-		if (msg->first_line.type == SIP_REQUEST) {
-			ua_opts = SCA_AOR_TYPE_UAC;
-		} else {
-			ua_opts = SCA_AOR_TYPE_UAS;
-		}
-	}
-
-	if ((ua_opts & SCA_AOR_TYPE_UAC)) {
-		if (sca_get_msg_from_header(msg, &tf) < 0) {
-			LM_ERR("sca_create_canonical_aor: failed to get From header\n");
-			goto done;
-		}
-	} else {
-		if (sca_get_msg_to_header(msg, &tf) < 0) {
-			LM_ERR("sca_create_canonical_aor: failed to get To header\n");
-			goto done;
-		}
-	}
-
-	if (sca_uri_extract_aor(&tf->uri, &tf_aor) < 0) {
-		LM_ERR("sca_create_canonical_aor: failed to extract AoR from "
-				"URI <%.*s>\n", STR_FMT(&tf->uri));
-		goto done;
-	}
-
-	memset(&c_uri, 0, sizeof(sip_uri_t));
-	if ((rc = sca_get_msg_contact_uri(msg, &contact_uri)) < 0) {
-		LM_ERR("sca_create_canonical_aor: failed to get contact URI from "
-				"Contact <%.*s>\n", STR_FMT(&msg->contact->body));
-		goto done;
-	}
-	if (rc > 0) {
-		if (parse_uri(contact_uri.s, contact_uri.len, &c_uri) < 0) {
-			LM_ERR("sca_create_canonical_aor: failed to parse Contact URI "
-					"<%.*s>\n", STR_FMT(&contact_uri));
-			rc = -1;
-			goto done;
-		}
-	}
-
-	if (SCA_STR_EMPTY(&c_uri.user) ||
-	SCA_STR_EQ(&c_uri.user, &tf->parsed_uri.user)) {
-		// empty contact header or Contact user matches To/From AoR
-		c_aor->s = (char *) pkg_malloc(tf_aor.len);
-		c_aor->len = tf_aor.len;
-		memcpy(c_aor->s, tf_aor.s, tf_aor.len);
-	} else {
-		// Contact user and To/From user mismatch
-		if (sca_aor_create_from_info(c_aor, c_uri.type, &c_uri.user,
-				&tf->parsed_uri.host, &tf->parsed_uri.port) < 0) {
-			LM_ERR("sca_create_canonical_aor: failed to create AoR from "
-					"Contact <%.*s> and URI <%.*s>\n",
-					STR_FMT(&contact_uri), STR_FMT(&tf_aor));
-			goto done;
-		}
-	}
-
-	rc = 1;
-
-	done: return (rc);
-}
-
-int sca_create_canonical_aor(sip_msg_t *msg, str *c_aor)
-{
-	return (sca_create_canonical_aor_for_ua(msg, c_aor, SCA_AOR_TYPE_AUTO));
-}
-
-/*
- * XXX this considers any held stream to mean the call is on hold. correct?
- */
-int sca_call_is_held(sip_msg_t *msg)
-{
-	sdp_session_cell_t *session;
-	sdp_stream_cell_t *stream;
-	int n_sess;
-	int n_str;
-	int is_held = 0;
-	int rc;
-
-	rc = parse_sdp(msg);
-	if (rc < 0) {
-		LM_ERR("sca_call_is_held: parse_sdp body failed\n");
-		return (0);
-	} else if (rc > 0) {
-		LM_DBG("sca_call_is_held: parse_sdp returned %d, no SDP body\n", rc);
-		return (0);
-	}
-
-	// Cf. modules_k/textops's exported is_audio_on_hold
-	for (n_sess = 0, session = get_sdp_session(msg, n_sess); session != NULL;
-			n_sess++, session = get_sdp_session(msg, n_sess)) {
-
-		for (n_str = 0, stream = get_sdp_stream(msg, n_sess, n_str);
-				stream != NULL;
-				n_str++, stream = get_sdp_stream(msg, n_sess, n_str)) {
-			if (stream->is_on_hold) {
-				is_held = 1;
-				goto done;
-			}
-		}
-	}
-
-	done: return (is_held);
-}
+/*
+ * Copyright (C) 2012 Andrew Mortensen
+ *
+ * This file is part of the sca module for Kamailio, a free SIP server.
+ *
+ * The sca module 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
+ *
+ * The sca module 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., 51 Franklin Street, Fifth Floor, Boston, MA. 02110-1301 USA
+ */
+#include "sca_common.h"
+
+#include <assert.h>
+
+#include "sca_util.h"
+
+#include "../../parser/sdp/sdp.h"
+
+int sca_get_msg_method(sip_msg_t *msg)
+{
+	assert(msg != NULL);
+
+	if (msg->first_line.type == SIP_REQUEST) {
+		return (msg->REQ_METHOD);
+	}
+
+	return (sca_get_msg_cseq_method(msg));
+}
+
+int sca_get_msg_contact_uri(sip_msg_t *msg, str *contact_uri)
+{
+	contact_body_t *contact_body;
+
+	assert(msg != NULL);
+	assert(contact_uri != NULL);
+
+	if (SCA_HEADER_EMPTY(msg->contact)) {
+		LM_DBG("Empty Contact header\n");
+		contact_uri->s = NULL;
+		contact_uri->len = 0;
+
+		return (0);
+	}
+
+	if (parse_contact(msg->contact) < 0) {
+		LM_ERR("Failed to parse Contact header: %.*s\n",
+				STR_FMT(&msg->contact->body));
+		return (-1);
+	}
+	if ((contact_body = (contact_body_t *) msg->contact->parsed) == NULL) {
+		LM_ERR("Invalid Contact header: %.*s\n", STR_FMT(&msg->contact->body));
+		return (-1);
+	}
+	if (contact_body->star) {
+		LM_ERR("Invalid Contact header: SCA Contact must not be \"*\"\n");
+		return (-1);
+	}
+	if (contact_body->contacts == NULL) {
+		LM_ERR("Invalid Contact header: parser found no contacts\n");
+		return (-1);
+	}
+	if (contact_body->contacts->next) {
+		LM_ERR("Invalid Contact header: Contact may only contain one URI\n");
+		return (-1);
+	}
+
+	contact_uri->s = contact_body->contacts->uri.s;
+	contact_uri->len = contact_body->contacts->uri.len;
+
+	return (1);
+}
+
+int sca_get_msg_cseq_number(sip_msg_t *msg)
+{
+	int cseq;
+
+	assert(msg != NULL);
+
+	if (SCA_HEADER_EMPTY(msg->cseq)) {
+		LM_ERR("Empty Cseq header\n");
+		return (-1);
+	}
+	if (str2int(&(get_cseq(msg)->number), (unsigned int *) &cseq) != 0) {
+		LM_ERR("Bad Cseq header: %.*s\n", STR_FMT(&msg->cseq->body));
+		return (-1);
+	}
+
+	return (cseq);
+}
+
+/*
+ *  assumes cseq header in msg is already parsed
+ */
+int sca_get_msg_cseq_method(sip_msg_t *msg)
+{
+	assert(msg != NULL);
+
+	if (SCA_HEADER_EMPTY(msg->cseq)) {
+		LM_ERR("Empty Cseq header\n");
+		return (-1);
+	}
+
+	return (get_cseq(msg)->method_id);
+}
+
+int sca_get_msg_from_header(sip_msg_t *msg, struct to_body **from)
+{
+	struct to_body *f;
+
+	assert(msg != NULL);
+	assert(from != NULL);
+
+	if (SCA_HEADER_EMPTY(msg->from)) {
+		LM_ERR("Empty From header\n");
+		return (-1);
+	}
+	if (parse_from_header(msg) < 0) {
+		LM_ERR("Bad From header\n");
+		return (-1);
+	}
+	f = get_from(msg);
+	if (SCA_STR_EMPTY(&f->tag_value)) {
+		LM_ERR("Bad From header: no tag parameter\n");
+		return (-1);
+	}
+
+	// ensure the URI is parsed for future use
+	if (parse_uri(f->uri.s, f->uri.len, GET_FROM_PURI(msg)) < 0) {
+		LM_ERR("Failed to parse From URI %.*s\n", STR_FMT(&f->uri));
+		return (-1);
+	}
+
+	*from = f;
+
+	return (0);
+}
+
+int sca_get_msg_to_header(sip_msg_t *msg, struct to_body **to)
+{
+	struct to_body parsed_to;
+	struct to_body *t = NULL;
+
+	assert(msg != NULL);
+	assert(to != NULL);
+
+	if (SCA_HEADER_EMPTY(msg->to)) {
+		LM_ERR("Empty To header\n");
+		return (-1);
+	}
+	t = get_to(msg);
+	if (t == NULL) {
+		parse_to(msg->to->body.s, msg->to->body.s + msg->to->body.len + 1, // end of buffer
+		&parsed_to);
+		if (parsed_to.error != PARSE_OK) {
+			LM_ERR("Bad To header\n");
+			return (-1);
+		}
+		t = &parsed_to;
+	}
+
+	// ensure the URI is parsed for future use
+	if (parse_uri(t->uri.s, t->uri.len, GET_TO_PURI(msg)) < 0) {
+		LM_ERR("Failed to parse To URI %.*s\n", STR_FMT(&t->uri));
+		return (-1);
+	}
+
+	*to = t;
+
+	return (0);
+}
+
+/*
+ *  count characters requiring escape as defined by escape_common
+ */
+int sca_uri_display_escapes_count(str *display) {
+	int c = 0;
+	int i;
+
+	if (SCA_STR_EMPTY(display)) {
+		return (0);
+	}
+
+	for (i = 0; i < display->len; i++) {
+		switch (display->s[i]) {
+		case '\'':
+		case '"':
+		case '\\':
+		case '\0':
+			c++;
+
+		default:
+			break;
+		}
+	}
+
+	return (c);
+}
+
+int sca_uri_extract_aor(str *uri, str *aor)
+{
+	char *semi;
+
+	assert(aor != NULL);
+
+	if (uri == NULL) {
+		aor->s = NULL;
+		aor->len = 0;
+		return (-1);
+	}
+
+	aor->s = uri->s;
+	semi = memchr(uri->s, ';', uri->len);
+	if (semi != NULL) {
+		aor->len = semi - uri->s;
+	} else {
+		aor->len = uri->len;
+	}
+
+	return (0);
+}
+
+int sca_uri_build_aor(str *aor, int maxlen, str *contact_uri, str *domain_uri)
+{
+	char *p;
+	char *dp;
+	int len;
+
+	assert(aor != NULL);
+	assert(contact_uri != NULL);
+	assert(domain_uri != NULL);
+
+	if (contact_uri->len + domain_uri->len >= maxlen) {
+		return (-1);
+	}
+
+	p = memchr(contact_uri->s, '@', contact_uri->len);
+	if (p == NULL) {
+		// no username, by definition can't be an SCA line
+		aor->s = NULL;
+		aor->len = 0;
+
+		return (0);
+	}
+	dp = memchr(domain_uri->s, '@', domain_uri->len);
+	if (dp == NULL) {
+		// may be nameless URI
+		dp = memchr(domain_uri->s, ':', domain_uri->len);
+		if (dp == NULL) {
+			// bad domain URI
+			return (-1);
+		}
+	}
+	dp++;
+
+	len = p - contact_uri->s;
+	memcpy(aor->s, contact_uri->s, len);
+	aor->s[len] = '@';
+	len += 1;
+	aor->len = len;
+
+	len = domain_uri->len - (dp - domain_uri->s);
+	memcpy(aor->s + aor->len, dp, len);
+	aor->len += len;
+
+	return (aor->len);
+}
+
+int sca_aor_create_from_info(str *aor, uri_type type, str *user, str *domain,
+		str *port)
+{
+	str scheme = STR_NULL;
+	int len = 0;
+
+	assert(aor != NULL);
+
+	uri_type_to_str(type, &scheme);
+
+	// +1 for ':', +1 for '@'
+	len = scheme.len + 1 + user->len + 1 + domain->len;
+	if (!SCA_STR_EMPTY(port)) {
+		// +1 for ':'
+		len += 1 + port->len;
+	}
+
+	aor->s = (char *) pkg_malloc(len);
+	if (aor->s == NULL) {
+		LM_ERR("sca_aor_create_from_info: pkg_malloc %d bytes failed\n", len);
+		return (-1);
+	}
+
+	len = 0;
+	SCA_STR_COPY(aor, &scheme);
+	len += scheme.len;
+
+	*(aor->s + len) = ':';
+	aor->len++;
+	len++;
+
+	SCA_STR_APPEND(aor, user);
+	len += user->len;
+
+	*(aor->s + len) = '@';
+	aor->len++;
+	len++;
+
+	SCA_STR_APPEND(aor, domain);
+	len += domain->len;
+
+	if (!SCA_STR_EMPTY(port)) {
+		*(aor->s + len) = ':';
+		len += 1;
+
+		SCA_STR_APPEND(aor, port);
+		len += port->len;
+	}
+
+	return (aor->len);
+}
+
+int sca_create_canonical_aor_for_ua(sip_msg_t *msg, str *c_aor, int ua_opts)
+{
+	struct to_body *tf = NULL;
+	sip_uri_t c_uri;
+	str tf_aor = STR_NULL;
+	str contact_uri = STR_NULL;
+	int rc = -1;
+
+	assert(msg != NULL);
+	assert(c_aor != NULL);
+
+	memset(c_aor, 0, sizeof(str));
+
+	if ((ua_opts & SCA_AOR_TYPE_AUTO)) {
+		if (msg->first_line.type == SIP_REQUEST) {
+			ua_opts = SCA_AOR_TYPE_UAC;
+		} else {
+			ua_opts = SCA_AOR_TYPE_UAS;
+		}
+	}
+
+	if ((ua_opts & SCA_AOR_TYPE_UAC)) {
+		if (sca_get_msg_from_header(msg, &tf) < 0) {
+			LM_ERR("sca_create_canonical_aor: failed to get From header\n");
+			goto done;
+		}
+	} else {
+		if (sca_get_msg_to_header(msg, &tf) < 0) {
+			LM_ERR("sca_create_canonical_aor: failed to get To header\n");
+			goto done;
+		}
+	}
+
+	if (sca_uri_extract_aor(&tf->uri, &tf_aor) < 0) {
+		LM_ERR("sca_create_canonical_aor: failed to extract AoR from "
+				"URI <%.*s>\n", STR_FMT(&tf->uri));
+		goto done;
+	}
+
+	memset(&c_uri, 0, sizeof(sip_uri_t));
+	if ((rc = sca_get_msg_contact_uri(msg, &contact_uri)) < 0) {
+		LM_ERR("sca_create_canonical_aor: failed to get contact URI from "
+				"Contact <%.*s>\n", STR_FMT(&msg->contact->body));
+		goto done;
+	}
+	if (rc > 0) {
+		if (parse_uri(contact_uri.s, contact_uri.len, &c_uri) < 0) {
+			LM_ERR("sca_create_canonical_aor: failed to parse Contact URI "
+					"<%.*s>\n", STR_FMT(&contact_uri));
+			rc = -1;
+			goto done;
+		}
+	}
+
+	if (SCA_STR_EMPTY(&c_uri.user) ||
+	SCA_STR_EQ(&c_uri.user, &tf->parsed_uri.user)) {
+		// empty contact header or Contact user matches To/From AoR
+		c_aor->s = (char *) pkg_malloc(tf_aor.len);
+		c_aor->len = tf_aor.len;
+		memcpy(c_aor->s, tf_aor.s, tf_aor.len);
+	} else {
+		// Contact user and To/From user mismatch
+		if (sca_aor_create_from_info(c_aor, c_uri.type, &c_uri.user,
+				&tf->parsed_uri.host, &tf->parsed_uri.port) < 0) {
+			LM_ERR("sca_create_canonical_aor: failed to create AoR from "
+					"Contact <%.*s> and URI <%.*s>\n",
+					STR_FMT(&contact_uri), STR_FMT(&tf_aor));
+			goto done;
+		}
+	}
+
+	rc = 1;
+
+	done: return (rc);
+}
+
+int sca_create_canonical_aor(sip_msg_t *msg, str *c_aor)
+{
+	return (sca_create_canonical_aor_for_ua(msg, c_aor, SCA_AOR_TYPE_AUTO));
+}
+
+/*
+ * XXX this considers any held stream to mean the call is on hold. correct?
+ */
+int sca_call_is_held(sip_msg_t *msg)
+{
+	sdp_session_cell_t *session;
+	sdp_stream_cell_t *stream;
+	int n_sess;
+	int n_str;
+	int is_held = 0;
+	int rc;
+
+	rc = parse_sdp(msg);
+	if (rc < 0) {
+		LM_ERR("sca_call_is_held: parse_sdp body failed\n");
+		return (0);
+	} else if (rc > 0) {
+		LM_DBG("sca_call_is_held: parse_sdp returned %d, no SDP body\n", rc);
+		return (0);
+	}
+
+	// Cf. modules_k/textops's exported is_audio_on_hold
+	for (n_sess = 0, session = get_sdp_session(msg, n_sess); session != NULL;
+			n_sess++, session = get_sdp_session(msg, n_sess)) {
+
+		for (n_str = 0, stream = get_sdp_stream(msg, n_sess, n_str);
+				stream != NULL;
+				n_str++, stream = get_sdp_stream(msg, n_sess, n_str)) {
+			if (stream->is_on_hold) {
+				is_held = 1;
+				goto done;
+			}
+		}
+	}
+
+	done: return (is_held);
+}