From 3526441e41791a7f25dc40c6c785f1171f889230 Mon Sep 17 00:00:00 2001
From: Igor Goncharovsky <igorg@iqtek.ru>
Date: Fri, 18 Nov 2022 08:16:50 +0600
Subject: [PATCH] res_pjsip_rfc3326: Add SIP causes support for RFC3326

Add ability to set HANGUPCAUSE when SIP causecode received in BYE (in addition to currently supported Q.850).

ASTERISK-30319 #close

Change-Id: I3f55622dc680ce713a2ffb5a458ef5dd39fcf645
---
 channels/chan_pjsip.c                       | 95 +--------------------
 doc/CHANGES-staging/res_rtp_rfc3326_sip.txt |  5 ++
 include/asterisk/res_pjsip.h                | 13 ++-
 res/res_pjsip.c                             | 92 ++++++++++++++++++++
 res/res_pjsip_rfc3326.c                     | 31 ++++---
 5 files changed, 130 insertions(+), 106 deletions(-)
 create mode 100644 doc/CHANGES-staging/res_rtp_rfc3326_sip.txt

diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 22ed5ecf3f..47f72b0e52 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -2881,97 +2881,6 @@ static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text)
 	return rc;
 }
 
-/*! \brief Convert SIP hangup causes to Asterisk hangup causes */
-static int hangup_sip2cause(int cause)
-{
-	/* Possible values taken from causes.h */
-
-	switch(cause) {
-	case 401:       /* Unauthorized */
-		return AST_CAUSE_CALL_REJECTED;
-	case 403:       /* Not found */
-		return AST_CAUSE_CALL_REJECTED;
-	case 404:       /* Not found */
-		return AST_CAUSE_UNALLOCATED;
-	case 405:       /* Method not allowed */
-		return AST_CAUSE_INTERWORKING;
-	case 407:       /* Proxy authentication required */
-		return AST_CAUSE_CALL_REJECTED;
-	case 408:       /* No reaction */
-		return AST_CAUSE_NO_USER_RESPONSE;
-	case 409:       /* Conflict */
-		return AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
-	case 410:       /* Gone */
-		return AST_CAUSE_NUMBER_CHANGED;
-	case 411:       /* Length required */
-		return AST_CAUSE_INTERWORKING;
-	case 413:       /* Request entity too large */
-		return AST_CAUSE_INTERWORKING;
-	case 414:       /* Request URI too large */
-		return AST_CAUSE_INTERWORKING;
-	case 415:       /* Unsupported media type */
-		return AST_CAUSE_INTERWORKING;
-	case 420:       /* Bad extension */
-		return AST_CAUSE_NO_ROUTE_DESTINATION;
-	case 480:       /* No answer */
-		return AST_CAUSE_NO_ANSWER;
-	case 481:       /* No answer */
-		return AST_CAUSE_INTERWORKING;
-	case 482:       /* Loop detected */
-		return AST_CAUSE_INTERWORKING;
-	case 483:       /* Too many hops */
-		return AST_CAUSE_NO_ANSWER;
-	case 484:       /* Address incomplete */
-		return AST_CAUSE_INVALID_NUMBER_FORMAT;
-	case 485:       /* Ambiguous */
-		return AST_CAUSE_UNALLOCATED;
-	case 486:       /* Busy everywhere */
-		return AST_CAUSE_BUSY;
-	case 487:       /* Request terminated */
-		return AST_CAUSE_INTERWORKING;
-	case 488:       /* No codecs approved */
-		return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
-	case 491:       /* Request pending */
-		return AST_CAUSE_INTERWORKING;
-	case 493:       /* Undecipherable */
-		return AST_CAUSE_INTERWORKING;
-	case 500:       /* Server internal failure */
-		return AST_CAUSE_FAILURE;
-	case 501:       /* Call rejected */
-		return AST_CAUSE_FACILITY_REJECTED;
-	case 502:
-		return AST_CAUSE_DESTINATION_OUT_OF_ORDER;
-	case 503:       /* Service unavailable */
-		return AST_CAUSE_CONGESTION;
-	case 504:       /* Gateway timeout */
-		return AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE;
-	case 505:       /* SIP version not supported */
-		return AST_CAUSE_INTERWORKING;
-	case 600:       /* Busy everywhere */
-		return AST_CAUSE_USER_BUSY;
-	case 603:       /* Decline */
-		return AST_CAUSE_CALL_REJECTED;
-	case 604:       /* Does not exist anywhere */
-		return AST_CAUSE_UNALLOCATED;
-	case 606:       /* Not acceptable */
-		return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
-	default:
-		if (cause < 500 && cause >= 400) {
-			/* 4xx class error that is unknown - someting wrong with our request */
-			return AST_CAUSE_INTERWORKING;
-		} else if (cause < 600 && cause >= 500) {
-			/* 5xx class error - problem in the remote end */
-			return AST_CAUSE_CONGESTION;
-		} else if (cause < 700 && cause >= 600) {
-			/* 6xx - global errors in the 4xx class */
-			return AST_CAUSE_INTERWORKING;
-		}
-		return AST_CAUSE_NORMAL;
-	}
-	/* Never reached */
-	return 0;
-}
-
 static void chan_pjsip_session_begin(struct ast_sip_session *session)
 {
 	RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
@@ -3016,7 +2925,7 @@ static void chan_pjsip_session_end(struct ast_sip_session *session)
 
 	ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0);
 	if (!ast_channel_hangupcause(session->channel) && session->inv_session) {
-		int cause = hangup_sip2cause(session->inv_session->cause);
+		int cause = ast_sip_hangup_sip2cause(session->inv_session->cause);
 
 		ast_queue_hangup_with_cause(session->channel, cause);
 	} else {
@@ -3210,7 +3119,7 @@ static void chan_pjsip_incoming_response_update_cause(struct ast_sip_session *se
 	snprintf(cause_code->code, data_size - sizeof(*cause_code) + 1, "SIP %d %.*s", status.code,
 	(int) pj_strlen(&status.reason), pj_strbuf(&status.reason));
 
-	cause_code->ast_cause = hangup_sip2cause(status.code);
+	cause_code->ast_cause = ast_sip_hangup_sip2cause(status.code);
 	ast_queue_control_data(session->channel, AST_CONTROL_PVT_CAUSE_CODE, cause_code, data_size);
 	ast_channel_hangupcause_hash_set(session->channel, cause_code, data_size);
 
diff --git a/doc/CHANGES-staging/res_rtp_rfc3326_sip.txt b/doc/CHANGES-staging/res_rtp_rfc3326_sip.txt
new file mode 100644
index 0000000000..62a73925ca
--- /dev/null
+++ b/doc/CHANGES-staging/res_rtp_rfc3326_sip.txt
@@ -0,0 +1,5 @@
+Subject: res_pjsip_rfc3326
+
+Add ability to set HANGUPCAUSE when SIP causecode received in BYE Reason header (in
+addition to currently supported Q.850). The first header found will be used to set
+the HANGUPCAUSE variable.
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 3e19d8d17d..a49a56e10d 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -3962,7 +3962,7 @@ int ast_sip_is_uri_sip_sips(pjsip_uri *uri);
  *
  * \param uri The pjsip_uri to check
  *
- * \retva; 1 if allowed
+ * \retval 1 if allowed
  * \retval 0 if not allowed
  */
 int ast_sip_is_allowed_uri(pjsip_uri *uri);
@@ -4013,4 +4013,15 @@ struct pjsip_param *ast_sip_pjsip_uri_get_other_param(pjsip_uri *uri, const pj_s
  */
 unsigned int ast_sip_get_all_codecs_on_empty_reinvite(void);
 
+
+/*!
+ * \brief Convert SIP hangup causes to Asterisk hangup causes
+ *
+ * \param cause SIP cause
+ *
+ * \retval matched cause code from causes.h
+ */
+const int ast_sip_hangup_sip2cause(int cause);
+
+
 #endif /* _RES_PJSIP_H */
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 38a91bdea0..827384741c 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -40,6 +40,7 @@
 #include "asterisk/uuid.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/file.h"
+#include "asterisk/causes.h"
 #include "asterisk/cli.h"
 #include "asterisk/callerid.h"
 #include "asterisk/res_pjsip_cli.h"
@@ -2796,6 +2797,97 @@ struct pjsip_param *ast_sip_pjsip_uri_get_other_param(pjsip_uri *uri, const pj_s
 	return NULL;
 }
 
+/*! \brief Convert SIP hangup causes to Asterisk hangup causes */
+const int ast_sip_hangup_sip2cause(int cause)
+{
+	/* Possible values taken from causes.h */
+
+	switch(cause) {
+	case 401:       /* Unauthorized */
+		return AST_CAUSE_CALL_REJECTED;
+	case 403:       /* Not found */
+		return AST_CAUSE_CALL_REJECTED;
+	case 404:       /* Not found */
+		return AST_CAUSE_UNALLOCATED;
+	case 405:       /* Method not allowed */
+		return AST_CAUSE_INTERWORKING;
+	case 407:       /* Proxy authentication required */
+		return AST_CAUSE_CALL_REJECTED;
+	case 408:       /* No reaction */
+		return AST_CAUSE_NO_USER_RESPONSE;
+	case 409:       /* Conflict */
+		return AST_CAUSE_NORMAL_TEMPORARY_FAILURE;
+	case 410:       /* Gone */
+		return AST_CAUSE_NUMBER_CHANGED;
+	case 411:       /* Length required */
+		return AST_CAUSE_INTERWORKING;
+	case 413:       /* Request entity too large */
+		return AST_CAUSE_INTERWORKING;
+	case 414:       /* Request URI too large */
+		return AST_CAUSE_INTERWORKING;
+	case 415:       /* Unsupported media type */
+		return AST_CAUSE_INTERWORKING;
+	case 420:       /* Bad extension */
+		return AST_CAUSE_NO_ROUTE_DESTINATION;
+	case 480:       /* No answer */
+		return AST_CAUSE_NO_ANSWER;
+	case 481:       /* No answer */
+		return AST_CAUSE_INTERWORKING;
+	case 482:       /* Loop detected */
+		return AST_CAUSE_INTERWORKING;
+	case 483:       /* Too many hops */
+		return AST_CAUSE_NO_ANSWER;
+	case 484:       /* Address incomplete */
+		return AST_CAUSE_INVALID_NUMBER_FORMAT;
+	case 485:       /* Ambiguous */
+		return AST_CAUSE_UNALLOCATED;
+	case 486:       /* Busy everywhere */
+		return AST_CAUSE_BUSY;
+	case 487:       /* Request terminated */
+		return AST_CAUSE_INTERWORKING;
+	case 488:       /* No codecs approved */
+		return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
+	case 491:       /* Request pending */
+		return AST_CAUSE_INTERWORKING;
+	case 493:       /* Undecipherable */
+		return AST_CAUSE_INTERWORKING;
+	case 500:       /* Server internal failure */
+		return AST_CAUSE_FAILURE;
+	case 501:       /* Call rejected */
+		return AST_CAUSE_FACILITY_REJECTED;
+	case 502:
+		return AST_CAUSE_DESTINATION_OUT_OF_ORDER;
+	case 503:       /* Service unavailable */
+		return AST_CAUSE_CONGESTION;
+	case 504:       /* Gateway timeout */
+		return AST_CAUSE_RECOVERY_ON_TIMER_EXPIRE;
+	case 505:       /* SIP version not supported */
+		return AST_CAUSE_INTERWORKING;
+	case 600:       /* Busy everywhere */
+		return AST_CAUSE_USER_BUSY;
+	case 603:       /* Decline */
+		return AST_CAUSE_CALL_REJECTED;
+	case 604:       /* Does not exist anywhere */
+		return AST_CAUSE_UNALLOCATED;
+	case 606:       /* Not acceptable */
+		return AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
+	default:
+		if (cause < 500 && cause >= 400) {
+			/* 4xx class error that is unknown - someting wrong with our request */
+			return AST_CAUSE_INTERWORKING;
+		} else if (cause < 600 && cause >= 500) {
+			/* 5xx class error - problem in the remote end */
+			return AST_CAUSE_CONGESTION;
+		} else if (cause < 700 && cause >= 600) {
+			/* 6xx - global errors in the 4xx class */
+			return AST_CAUSE_INTERWORKING;
+		}
+		return AST_CAUSE_NORMAL;
+	}
+	/* Never reached */
+	return 0;
+}
+
 #ifdef TEST_FRAMEWORK
 AST_TEST_DEFINE(xml_sanitization_end_null)
 {
diff --git a/res/res_pjsip_rfc3326.c b/res/res_pjsip_rfc3326.c
index 7d096c1f7d..458b5e97d3 100644
--- a/res/res_pjsip_rfc3326.c
+++ b/res/res_pjsip_rfc3326.c
@@ -42,6 +42,7 @@ static void rfc3326_use_reason_header(struct ast_sip_session *session, struct pj
 	char *cause;
 	char *text;
 	int code;
+	int cause_q850, cause_sip;
 
 	header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_reason, NULL);
 	for (; header;
@@ -49,21 +50,27 @@ static void rfc3326_use_reason_header(struct ast_sip_session *session, struct pj
 		ast_copy_pj_str(buf, &header->hvalue, sizeof(buf));
 		cause = ast_skip_blanks(buf);
 
-		if (strncasecmp(cause, "Q.850", 5) || !(cause = strstr(cause, "cause="))) {
+		cause_q850 = !strncasecmp(cause, "Q.850", 5);
+		cause_sip = !strncasecmp(cause, "SIP", 3);
+		if ((cause_q850 || cause_sip) && (cause = strstr(cause, "cause="))) {
+			/* If text is present get rid of it */
+			if ((text = strchr(cause, ';'))) {
+				*text = '\0';
+			}
+
+			if (sscanf(cause, "cause=%30d", &code) != 1) {
+				continue;
+			}
+		} else {
 			continue;
 		}
-
-		/* If text is present get rid of it */
-		if ((text = strstr(cause, ";"))) {
-			*text = '\0';
+		if (cause_q850) {
+			ast_channel_hangupcause_set(session->channel, code & 0x7f);
+			break;
+		} else if (cause_sip) {
+			ast_channel_hangupcause_set(session->channel, ast_sip_hangup_sip2cause(code));
+			break;
 		}
-
-		if (sscanf(cause, "cause=%30d", &code) != 1) {
-			continue;
-		}
-
-		ast_channel_hangupcause_set(session->channel, code & 0x7f);
-		break;
 	}
 }
 
-- 
GitLab