// 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)) { free(req); return -1; } break; 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); return 0; } //----------------------------------------------------------------- // 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: case ACTION_RINGING_STOP: 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)) break; 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"); return; } 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++); // Check also DECT event map 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) return; // 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) return; break; default: break; } } 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); } return; } //------------------------------------------------------------- // 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; do { 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; }