Skip to content
Snippets Groups Projects
line-dect.c 13.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Handles UBUS communication between Asterisk <-> dectmngr
    // Takes care of translating Asterisk UBUS request to
    // corresponding dectmngr requests.
    
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <assert.h>
    
    
    #include "libvoice.h"
    
    #include "line-dect.h"
    #include "line.h"
    #include "ubus.h"
    
    //-------------------------------------------------------------
    
    static struct timespec ts_last_req_to_dectmngr = { 0, };
    static int num_dectmngr_pending_reqs = 0;           // Number of of pending UBUS RPC calls both from and to dectmngr
    static int current_handled_line;                    // Current Asterisk line request in investigation
    static pe_list_t *ubus_req_list_to_dect = NULL;     // List of pending UBUS calls to dectmngr.
    static pe_list_t *ubus_req_list_to_asterisk = NULL; // List of DECT UBUS calls waiting for Asterisk to answer.
    
    
    //-------------------------------------------------------------
    // Add a pending UBUS request destined for dectmngr to a wait queue.
    
    int ubus_queue_req_to_dectmngr(struct line_req_t *req) {
    
    	clock_gettime(CLOCK_MONOTONIC, &req->time_stamp);
    
    	pe_list_add(ubus_req_list_to_dect, req);
    
    	ENDPT_DBG("Added a new line_req to list for dectmngr and num_dectmngr_pending_reqs is %d\n",
    				num_dectmngr_pending_reqs);
    
    	return 0;
    }
    
    //-------------------------------------------------------------
    // Add a pending UBUS request destined for Asterisk to a wait queue.
    
    int ubus_queue_req_to_asterisk(struct line_req_t *req) {
    
    	clock_gettime(CLOCK_MONOTONIC, &req->time_stamp);
    
    	pe_list_add(ubus_req_list_to_asterisk, req);
    
    	num_dectmngr_pending_reqs++;
    	ENDPT_DBG("Added a new line_req to list for Asterisk and increased num_dectmngr_pending_reqs to %d\n",
    				num_dectmngr_pending_reqs);
    	return 0;
    }
    
    //-------------------------------------------------------------
    // Callback function to find an old line_req_t
    static int pe_list_find_old_req(void *arg) {
    	struct line_req_t *req = (struct line_req_t*)arg;
    	struct timespec ts_now = { 0, };
    
    	clock_gettime(CLOCK_MONOTONIC, &ts_now);
    	if ((ts_now.tv_sec - req->time_stamp.tv_sec) >= (UBUS_REQUEST_TIMEOUT + 1) && ts_now.tv_nsec >= req->time_stamp.tv_nsec)
    		return 1;
    
    	return 0;
    }
    
    //-------------------------------------------------------------
    // Remove old line requests
    static int rm_old_line_reqs() {
    	while (num_dectmngr_pending_reqs > 0 && ubus_req_list_to_asterisk->count > 0) {
    		struct line_req_t *req = pe_list_find(ubus_req_list_to_asterisk, pe_list_find_old_req);
    
    		if (!req) {
    			break;
    		}
    		pe_list_delete(ubus_req_list_to_asterisk, req);
    		ENDPT_DBG("Remove one old line_req_t to Asterisk: line:%d, pcm_id:%d, connection_id:%d, action:%d, "
    			"caller_id:%s, caller_name:%s\n",
    			req->line, req->pcm_id, req->connection_id, req->action, req->caller_id, req->caller_name);
    		free(req);
    		num_dectmngr_pending_reqs--;
    	}
    
    	if (num_dectmngr_pending_reqs > 0 && ts_last_req_to_dectmngr.tv_sec > 0) {
    		struct timespec ts_now = { 0, };
    
    		clock_gettime(CLOCK_MONOTONIC, &ts_now);
    		if ((ts_now.tv_sec - ts_last_req_to_dectmngr.tv_sec) >= (UBUS_REQUEST_TIMEOUT + 1) &&
    			ts_now.tv_nsec >= ts_last_req_to_dectmngr.tv_nsec) {
    			ENDPT_DBG("The last ubus request to dectmngr may have been timeout\n");
    			num_dectmngr_pending_reqs--;
    		}
    	}
    
    
    	return 0;
    }
    
    //-------------------------------------------------------------
    
    // Pop oldest pending UBUS request from waiting queue if
    
    // we have no outstanding communication with dectmngr.
    int ubus_process_queued_reqs_to_dectmngr(void) {
    
    	struct line_req_t *req;
    
    	struct dectmngr_rpc_t dectmngr_rpc = { 0, };
    
    
    	// Only one outstanding request at a time
    
    	if (rm_old_line_reqs() || num_dectmngr_pending_reqs > 0) {
    		ENDPT_DBG("Already %d pending request(s)\n", num_dectmngr_pending_reqs);
    
    		return 0;
    
    
    	// Fetch oldest waiting request
    
    	req = pe_list_get(ubus_req_list_to_dect);
    	if (!req)
    
    		return 0;
    
    
    	ENDPT_DBG("Poped DECT req %p from list, action: %d pcmId: %d, caller_id: %s, callerName: %s\n",
    			req, req->action, req->pcm_id, req->caller_id, req->caller_name);
    
    	pcm_states_dump(__func__, req->line);
    
    
    	switch (req->action) {
    
    		case ACTION_CONN_CREATE:
    		case ACTION_SIG_RING:
    
    			if(ubus_call_dectmngr(voicemngr_line_to_dectmngr_line(req->line), 1, 0, req->caller_id, req->caller_name, req->pcm_id, req)) {
    
    				free(req);
    
    				return -1;
    			}
    			break;
    
    		case ACTION_SIG_CW_CLOSE:
    			if (get_callid_state(lines[req->line].pcm_callid[req->pcm_id]) == CALLID_ESTABLISHED){
    				// do nothing if accept the call waiting
    				free(req);
    				return 0;
    			}
    			// ACTION_CONN_CLOSE: could not just skip due to compile warning is treated as error.
    			if(ubus_call_dectmngr(voicemngr_line_to_dectmngr_line(req->line), 0, 1, req->caller_id, req->caller_name, req->pcm_id, req)) {
    				free(req);
    				return -1;
    			}
    			break;
    
    		case ACTION_RINGING_STOP:
    			dectmngr_rpc.action = req->action;
    			dectmngr_rpc.extension_id = voicemngr_line_to_dectmngr_line(req->line);
    			if(ubus_dectmngr_rpc(&dectmngr_rpc, req)) {
    				free(req);
    				return -1;
    			}
    			break;
    
    		case ACTION_CONN_CLOSE:
    
    			dectmngr_rpc.action = req->action;
    			dectmngr_rpc.extension_id = voicemngr_line_to_dectmngr_line(req->line);
    			dectmngr_rpc.pcm_id = req->pcm_id;
    			if(ubus_dectmngr_rpc(&dectmngr_rpc, req)) {
    
    				free(req);
    
    				return -1;
    			}
    			break;
    
    		case ACTION_SIG_BUSY_TONE:
    
    			if(ubus_call_dectmngr(voicemngr_line_to_dectmngr_line(req->line), 0, 2, req->caller_id, req->caller_name, req->pcm_id, req)) {
    
    				free(req);
    				return -1;
    			}
    			break;
    
    		case ACTION_SIG_ANSWERED:
    
    			if(ubus_call_dectmngr(voicemngr_line_to_dectmngr_line(req->line), 2, 0, req->caller_id, req->caller_name, req->pcm_id, req)) {
    
    		default:
    
    			ENDPT_DBG("Invalid action: %d\n", req->action);
    			return -1;
    
    	clock_gettime(CLOCK_MONOTONIC, &ts_last_req_to_dectmngr);
    
    	num_dectmngr_pending_reqs++;
    
    	ENDPT_DBG("Increased num_dectmngr_pending_reqs to %d\n", num_dectmngr_pending_reqs);
    
    //-----------------------------------------------------------------
    // If appropriate, simulate keypad digit pressesing. Returned value
    
    // -1  Something gets wrong
    //  0  Does nothing
    //  1  Keypad events have been handled
    
    int simulate_digits_pressing(int line, const char *pressed_digits) {
    
    	char *digits;
    	
    
    	if(lines[line].type != VOICE_LINE_DECT)
    
    		return 0;
    
    	ENDPT_DBG("line: %d pressed_digits: %s\n", line, (pressed_digits ? pressed_digits : ""));
    
    
    	// Store digits in a list.
    
    	if(pressed_digits && lines[line].pending_digits) {
    		digits = strndup(pressed_digits, MAX_KEYPAD_DIGITS);
    
    		if(digits) {
    
    			ENDPT_DBG("Queueing line %d digits %s\n", line, pressed_digits);
    
    			pe_list_add(lines[line].pending_digits, digits);
    
    	if(!voice_line_is_offhook(line))
    
    		return 0;
    
    	// Pop digits from list.
    
    	digits = pe_list_get(lines[line].pending_digits);
    
    	if(digits) {
    		int res;
    		ENDPT_DBG("Dequeued line %d digits %s\n", line, digits);
    
    		res = line_signal(line, "keypad", digits, NULL);
    
    		free(digits);
    		return (res == 0 ? 1 : -1);
    	}
    
    	return 0;
    }
    
    //-------------------------------------------------------------
    // dectmngr has answered an UBUS RPC "call" request. Did it
    
    // accept the line action request? Then we call libvoice to
    
    // start/stop the audio stream and inform Asterisk to proceed
    // with phone call setup.
    
    int ubus_cb_dectmngr_replied(struct line_req_t *req, enum ubus_msg_status reply_status) {
    	if(!req)
    		return -1;
    
    
    	ENDPT_DBG("got answer req %p from dectmngr, action: %d, line: %d, pcmId: %d, conId: %d\n",
    
    		req, req->action, req->line, req->pcm_id, req->connection_id);
    
    	pcm_states_dump(__func__, req->line);
    
    	switch (req->action) {
    
    		case ACTION_CONN_CREATE:
    
    			if(reply_status == UBUS_STATUS_OK && voice_connection_create(req->line, req->line)) {
    				reply_status = UBUS_STATUS_UNKNOWN_ERROR;
    
    			}
    			break;
    		case ACTION_CONN_CLOSE:
    
    			if (req->pcm_id == PCM_0 || req->pcm_id == PCM_1) {
    				lines[req->line].pcm_callid[req->pcm_id] = CALLID_INVALID;
    			} else if (req->pcm_id == -1) {
    				// Close all calls
    				lines[req->line].pcm_callid[PCM_0] = CALLID_INVALID;
    				lines[req->line].pcm_callid[PCM_1] = CALLID_INVALID;
    			}
    
    			if (get_callid_state(lines[req->line].pcm_callid[PCM_0]) == CALLID_ESTABLISHED ||
    				get_callid_state(lines[req->line].pcm_callid[PCM_1]) == CALLID_ESTABLISHED)
    
    				break;
    
    			reply_status = voice_connection_close(req->line, req->line) ? // Close regardless of any dectmngr error.
    				UBUS_STATUS_UNKNOWN_ERROR : UBUS_STATUS_OK;
    
    			break;
    		case ACTION_SIG_RING:
    
    			if ((req->pcm_id == PCM_0 && get_callid_state(lines[req->line].pcm_callid[PCM_1]) == CALLID_OBTAINING) ||
    				(req->pcm_id == PCM_1 && get_callid_state(lines[req->line].pcm_callid[PCM_0]) == CALLID_OBTAINING))
    
    			if (req->pcm_id == PCM_0 || req->pcm_id == PCM_1)
    				lines[req->line].pcm_callid[req->pcm_id] = CALLID_OBTAINING;
    
    			break;
    		default:
    			break;
    	}
    
    
    	send_reply_asterisk(&req->ubus, reply_status);
    
    	free(req);
    
    	if (num_dectmngr_pending_reqs > 0) {
    		num_dectmngr_pending_reqs--;
    		ENDPT_DBG("Decreased num_dectmngr_pending_reqs to %d\n", num_dectmngr_pending_reqs);
    	}
    
    
    	return 0;
    }
    
    //-------------------------------------------------------------
    // libpicoevent let us iterate though a list with dect ubus
    
    // requests waiting for an answer. In this list find the
    
    // UBUS request object of interest and ignore the rest.
    
    static int pe_list_cb_find_req(void *arg) {
    
    	struct line_req_t *req = (struct line_req_t*) arg;
    	return (req->line == current_handled_line);
    
    }
    
    //-------------------------------------------------------------
    // Asterisk has answered an UBUS RPC "call" request initiated
    
    // by dectmngr. Now relay the Asterisk reply back to dectmngr.
    
    void ubus_cb_asterisk_replied(struct line_event_t *lineEv, enum ubus_msg_status reply_status) {
    
    	struct line_req_t *req;
    
    	int epEvntIdx, dectEvntIdx;
    
    
    	if(!lineEv || !voice_line_is_ready(lineEv->line)) {
    		ENDPT_ERR("Invalid argument\n");
    
    	if(lines[lineEv->line].simulated_hook) {
    
    		voice_hook_simulation_maintain(lineEv->line);
    
    	// Find map index to the line event that was sent to Asterisk
    
    	for(epEvntIdx = 0; event_map[epEvntIdx].event != VOICE_EVT_END &&
    
    		strcasecmp(lineEv->name, event_map[epEvntIdx].name); epEvntIdx++);
    
    	for(dectEvntIdx = 0; dect_event_map[dectEvntIdx].event != DECT_EVT_END &&
    		strcasecmp(lineEv->name, dect_event_map[dectEvntIdx].name); dectEvntIdx++);
    
    	if(event_map[epEvntIdx].event == VOICE_EVT_END && dect_event_map[dectEvntIdx].event == DECT_EVT_END)
    
    	// Find the current line request matching the line event.
    
    	assert(current_handled_line == -1);
    	current_handled_line = lineEv->line;
    
    	req = pe_list_find(ubus_req_list_to_asterisk, pe_list_cb_find_req);
    
    	current_handled_line = -1;
    
    	if (!req)
    		return; // None found, then do nothing
    
    
    	assert(req->ubus.ctx);
    
    	ENDPT_DBG("Poped Asterisk request %p line %d event %s from list, action %d\n",
    
    		req, lineEv->line, lineEv->name, req->action);
    
    	if(reply_status == UBUS_STATUS_OK) {
    
    		switch (req->action) {
    
    			case ACTION_CONN_CREATE:
    				switch(event_map[epEvntIdx].event) {
    
    					case VOICE_EVT_DTMF0:
    					case VOICE_EVT_DTMF1:
    					case VOICE_EVT_DTMF2:
    					case VOICE_EVT_DTMF3:
    					case VOICE_EVT_DTMF4:
    					case VOICE_EVT_DTMF5:
    					case VOICE_EVT_DTMF6:
    					case VOICE_EVT_DTMF7:
    					case VOICE_EVT_DTMF8:
    					case VOICE_EVT_DTMF9:
    					case VOICE_EVT_DTMFS:
    					case VOICE_EVT_DTMFH:
    					case VOICE_EVT_DTMFA:
    					case VOICE_EVT_DTMFB:
    					case VOICE_EVT_DTMFC:
    					case VOICE_EVT_DTMFD:
    					case VOICE_EVT_OFFHOOK:
    					case VOICE_EVT_FLASH:
    
    						/* By now Asterisk knows the terminal is off-hook. If we have any queued phone number digits
    						 * they are sent now. */
    						if (simulate_digits_pressing(lineEv->line, NULL) != 0)
    							return;
    
    						break;
    
    					default: // Ignore other events
    
    						if (dect_event_map[dectEvntIdx].event == DECT_EVT_END)
    							return;
    						break;
    
    				}
    				break;
    
    			case ACTION_CONN_CLOSE:
    
    				if(event_map[epEvntIdx].event != VOICE_EVT_ONHOOK && dect_event_map[dectEvntIdx].event != DECT_EVT_RELEASE)
    
    	send_reply_dectmngr(&req->ubus, voicemngr_line_to_dectmngr_line(lineEv->line), lineEv->line, reply_status);
    
    	pe_list_delete(ubus_req_list_to_asterisk, req);
    	free(req);
    
    	if (num_dectmngr_pending_reqs > 0) {
    		num_dectmngr_pending_reqs--;
    		ENDPT_DBG("Decreased num_dectmngr_pending_reqs to %d\n", num_dectmngr_pending_reqs);
    	}
    
    }
    
    //-------------------------------------------------------------
    
    // Translate dectmngr line to voicemngr line
    int dectmngr_line_to_voicemngr_line(int line) {
    	return line - 1;
    }
    
    //-------------------------------------------------------------
    // Translate voicemngr line to dectmngr line
    int voicemngr_line_to_dectmngr_line(int line) {
    	return line + 1;
    }
    
    
    //-------------------------------------------------------------
    
    int line_dect_init(void) {
    
    	ubus_req_list_to_dect = pe_list_new();
    	ubus_req_list_to_asterisk = pe_list_new();
    	current_handled_line = -1;
    
    	return 0;
    }
    
    //-------------------------------------------------------------
    // Free resources and cleanup
    
    int line_dect_deinit(void) {
    
    	struct line_req_t *req;
    
    	pe_list_t *reqLists[2];
    	int i;
    
    	reqLists[0] = ubus_req_list_to_dect;
    	reqLists[1] = ubus_req_list_to_asterisk;
    
    	for(i = 0; i < 2; i++) {
    
    		if(!reqLists[i])
    			continue;
    
    			req = pe_list_get(reqLists[i]);
    			if(req) {
    
    				send_reply_asterisk(&req->ubus, UBUS_STATUS_CONNECTION_FAILED);
    
    				free(req);
    
    		} while(req);
    
    
    		free(reqLists[i]);
    	}
    
    	num_dectmngr_pending_reqs = 0;
    
    	return 0;
    }