From 61101e325f93259da0369e60f6857cfb64c3ca38 Mon Sep 17 00:00:00 2001
From: Moises Silva <moy@sangoma.com>
Date: Sun, 20 Jul 2014 21:51:32 -0400
Subject: [PATCH] freetdm: ftmod_analog_em: Added support for suspending
 channels that are offhook

---
 .../ftmod/ftmod_analog_em/ftdm_analog_em.h    |  6 +-
 .../ftmod/ftmod_analog_em/ftmod_analog_em.c   | 60 ++++++++++++++-----
 2 files changed, 50 insertions(+), 16 deletions(-)

diff --git a/libs/freetdm/src/ftmod/ftmod_analog_em/ftdm_analog_em.h b/libs/freetdm/src/ftmod/ftmod_analog_em/ftdm_analog_em.h
index 121381d522..e52a319155 100644
--- a/libs/freetdm/src/ftmod/ftmod_analog_em/ftdm_analog_em.h
+++ b/libs/freetdm/src/ftmod/ftmod_analog_em/ftdm_analog_em.h
@@ -44,10 +44,12 @@
 #define MAX_DIALSTRING 256
 
 typedef enum {
-	FTDM_ANALOG_EM_RUNNING = (1 << 0)
+	FTDM_ANALOG_EM_RUNNING = (1 << 0),
+	FTDM_ANALOG_EM_LOCAL_WRITE = (1 << 2),
+	FTDM_ANALOG_EM_LOCAL_SUSPEND = (1 << 3),
+	FTDM_ANALOG_EM_REMOTE_SUSPEND = (1 << 4),
 } ftdm_analog_em_flag_t;
 
-
 struct ftdm_analog_data {
 	uint32_t flags;
 	uint32_t max_dialstr;
diff --git a/libs/freetdm/src/ftmod/ftmod_analog_em/ftmod_analog_em.c b/libs/freetdm/src/ftmod/ftmod_analog_em/ftmod_analog_em.c
index 8a537bb5e6..146885f271 100644
--- a/libs/freetdm/src/ftmod/ftmod_analog_em/ftmod_analog_em.c
+++ b/libs/freetdm/src/ftmod/ftmod_analog_em/ftmod_analog_em.c
@@ -178,8 +178,7 @@ static ftdm_status_t ftdm_analog_em_sig_write(ftdm_channel_t *ftdmchan, void *da
 	ftdm_analog_em_data_t *analog_data = ftdmchan->span->signal_data;
 	if (ftdmchan->state == FTDM_CHANNEL_STATE_PROGRESS_MEDIA
 	    && analog_data->immediate_ringback
-	    && ftdmchan->call_data) {
-		/* DO NOT USE ftdmchan->call_data, as is a dummy non-null pointer */
+	    && ftdm_test_sflag(ftdmchan, FTDM_ANALOG_EM_LOCAL_WRITE)) {
 		/* ringback is being played in the analog thread, ignore user data for now */
 		return FTDM_BREAK;
 	}
@@ -283,7 +282,7 @@ static FIO_SPAN_GET_SIG_STATUS_FUNCTION(analog_em_get_span_sig_status)
 	return FTDM_SUCCESS;
 }
 
-static FIO_CHANNEL_SET_SIG_STATUS_FUNCTION(analog_em_set_channel_sig_status)
+static ftdm_status_t analog_em_set_channel_sig_status_ex(ftdm_channel_t *ftdmchan, ftdm_signaling_status_t status, ftdm_bool_t remote)
 {
 	switch (status) {
 	case FTDM_SIG_STATE_DOWN:
@@ -299,17 +298,30 @@ static FIO_CHANNEL_SET_SIG_STATUS_FUNCTION(analog_em_set_channel_sig_status)
 			}
 			ftdm_analog_set_chan_sig_status(ftdmchan, FTDM_SIG_STATE_SUSPENDED);
 		}
+		if (remote) {
+			ftdm_set_sflag(ftdmchan, FTDM_ANALOG_EM_REMOTE_SUSPEND);
+		} else {
+			ftdm_set_sflag(ftdmchan, FTDM_ANALOG_EM_LOCAL_SUSPEND);
+		}
 		break;
 	case FTDM_SIG_STATE_UP:
 		if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_SUSPENDED)) {
-			int cas_bits = 0x00;
-			ftdm_clear_flag(ftdmchan, FTDM_CHANNEL_SUSPENDED);
-			ftdm_channel_command(ftdmchan, FTDM_COMMAND_SET_CAS_BITS, &cas_bits);
-			if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK)) {
-				ftdm_channel_command(ftdmchan, FTDM_COMMAND_ONHOOK, NULL);
+			if (remote) {
+				ftdm_clear_sflag(ftdmchan, FTDM_ANALOG_EM_REMOTE_SUSPEND);
+			} else {
+				ftdm_clear_sflag(ftdmchan, FTDM_ANALOG_EM_LOCAL_SUSPEND);
 			}
-			if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_IN_ALARM)) {
-				ftdm_analog_set_chan_sig_status(ftdmchan, FTDM_SIG_STATE_UP);
+			if (!ftdm_test_sflag(ftdmchan, FTDM_ANALOG_EM_REMOTE_SUSPEND) &&
+				!ftdm_test_sflag(ftdmchan, FTDM_ANALOG_EM_LOCAL_SUSPEND)) {
+				int cas_bits = 0x00;
+				ftdm_clear_flag(ftdmchan, FTDM_CHANNEL_SUSPENDED);
+				ftdm_channel_command(ftdmchan, FTDM_COMMAND_SET_CAS_BITS, &cas_bits);
+				if (ftdm_test_flag(ftdmchan, FTDM_CHANNEL_OFFHOOK)) {
+					ftdm_channel_command(ftdmchan, FTDM_COMMAND_ONHOOK, NULL);
+				}
+				if (!ftdm_test_flag(ftdmchan, FTDM_CHANNEL_IN_ALARM)) {
+					ftdm_analog_set_chan_sig_status(ftdmchan, FTDM_SIG_STATE_UP);
+				}
 			}
 		}
 		break;
@@ -320,6 +332,11 @@ static FIO_CHANNEL_SET_SIG_STATUS_FUNCTION(analog_em_set_channel_sig_status)
 	return FTDM_SUCCESS;
 }
 
+static FIO_CHANNEL_SET_SIG_STATUS_FUNCTION(analog_em_set_channel_sig_status)
+{
+	return analog_em_set_channel_sig_status_ex(ftdmchan, status, FTDM_FALSE);
+}
+
 static FIO_SPAN_SET_SIG_STATUS_FUNCTION(analog_em_set_span_sig_status)
 {
 	ftdm_iterator_t *chaniter = NULL;
@@ -335,7 +352,7 @@ static FIO_SPAN_SET_SIG_STATUS_FUNCTION(analog_em_set_span_sig_status)
 		ftdm_channel_t *fchan = ftdm_iterator_current(citer);
 		/* we set channel's state through analog_em_set_channel_sig_status(), since it already takes care of notifying the user when appropriate */
 		ftdm_channel_lock(fchan);
-		if ((analog_em_set_channel_sig_status(fchan, status)) != FTDM_SUCCESS) {
+		if ((analog_em_set_channel_sig_status_ex(fchan, status, FTDM_FALSE)) != FTDM_SUCCESS) {
 			ftdm_log_chan(fchan, FTDM_LOG_ERROR, "Failed to set signaling status to %s\n", ftdm_signaling_status2str(status));
 		}
 		ftdm_channel_unlock(fchan);
@@ -496,6 +513,7 @@ static void *ftdm_analog_em_channel_run(ftdm_thread_t *me, void *obj)
 	uint32_t cas_hangup = 0;
 	int cas_answer_ms = 500;
 	int cas_hangup_ms = 500;
+	ftdm_bool_t busy_timeout = FTDM_FALSE;
 	FILE *ringback_f = NULL;
 	ftdm_bool_t digits_sent = FTDM_FALSE;
 
@@ -762,6 +780,7 @@ static void *ftdm_analog_em_channel_run(ftdm_thread_t *me, void *obj)
 						indicate = 1;
 					} else {
 						ftdm_set_state_locked(ftdmchan, FTDM_CHANNEL_STATE_DOWN);
+						busy_timeout = FTDM_TRUE;
 					}
 				}
 				break;
@@ -925,17 +944,25 @@ read_try:
 			}
 		}
 
-		/* we must lock the channel and make sure we let our own generated audio thru (ftdmchan->call_data is tested in the ftdm_analog_em_sig_write handler)*/
+		/* we must lock the channel and make sure we let our own generated audio thru (FTDM_ANALOG_EM_LOCAL_WRITE is tested in the ftdm_analog_em_sig_write handler)*/
 		ftdm_channel_lock(ftdmchan);
-		ftdmchan->call_data = (void *)0xFF; /* ugh! */
+		ftdm_set_sflag(ftdmchan, FTDM_ANALOG_EM_LOCAL_WRITE);
 		ftdm_channel_write(ftdmchan, frame, sizeof(frame), &rlen);
-		ftdmchan->call_data = NULL;
+		ftdm_clear_sflag(ftdmchan, FTDM_ANALOG_EM_LOCAL_WRITE);
 		ftdm_channel_unlock(ftdmchan);
 	}
 
  done:
 
 	ftdm_channel_command(ftdmchan, FTDM_COMMAND_ONHOOK, NULL);
+	if (busy_timeout) {
+		ftdm_channel_command(ftdmchan, FTDM_COMMAND_GET_CAS_BITS, &cas_bits);
+		if (cas_bits == 0XF) {
+			/* the remote end never sent any digits, neither moved to onhook, let's stay suspended */
+			ftdm_log_chan_msg(ftdmchan, FTDM_LOG_DEBUG, "Moving channel to suspended after timeout, remote end still offhook\n");
+			analog_em_set_channel_sig_status_ex(ftdmchan, FTDM_SIG_STATE_SUSPENDED, FTDM_TRUE);
+		}
+	}
 	
 	closed_chan = ftdmchan;
 	ftdm_channel_close(&ftdmchan);
@@ -984,6 +1011,11 @@ static __inline__ ftdm_status_t process_event(ftdm_span_t *span, ftdm_event_t *e
 	ftdm_mutex_lock(event->channel->mutex);
 	locked++;
 
+	if (event->enum_id == FTDM_OOB_ONHOOK && ftdm_test_sflag(event->channel, FTDM_ANALOG_EM_REMOTE_SUSPEND)) {
+		/* We've got remote suspend, now we're back on hook, lift the remote suspend status */
+		analog_em_set_channel_sig_status_ex(event->channel, FTDM_SIG_STATE_UP, FTDM_TRUE);
+	}
+
 	if (ftdm_test_flag(event->channel, FTDM_CHANNEL_SUSPENDED)) {
 		ftdm_log(FTDM_LOG_WARNING, "Ignoring event %s on channel %d:%d in state %s, channel is suspended\n",
 				ftdm_oob_event2str(event->enum_id), event->channel->span_id, event->channel->chan_id, ftdm_channel_state2str(event->channel->state));