/* call handling * * (C) 2020 by Andreas Eversberg * All Rights Reserved * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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, see . */ /* Call forking process * * A call can be forwarded to one or multiple endpoints (call forking). * * In case of call forking, each terminating endpoint that disconnects or * releases, is removed from the list of terminating endpoints. The cause is * collected according to mulitpoint process at Q.931. If all endpoints have * disconnected or released, the originating endpoint is released with the * collected cause. * * If one terminating endpoint answers the call, all other endpoints are * released with the cause "non-selected user clearing". * * If the originating endpoint disconnectes or releases prior answer, all * terminating endpoints are released with the same cause. */ /* SDP negotiation process * * The originating endpoint sends a CC-SETUP-REQ with SDP included. * * If no RTP proxy is enabled, the SDP is forwarded towards terminating * endpoint or multiple terminating endpoints (in case of call forking). The * first reply with SDP from the terminating endpoint is stored. In case of * single terminating endpoint, it is forwarded towards the originating * endpoint with progress indicator set to 1 or 8. In case of multiple * terminating endpoints (call forking) the SDP is forwarded as soon a * CC-SETUP-RSP is received from the first terminating endpoint that answers. * The SDP negotiation is complete. * * If RTP proxy is enabled, the SDP is negotiated with the supported codecs of * this router. The first reply to the CC-SETUP-REQ message will include the * SDP reply as well as progress indicator set to 8 (if not a CC-SETUP-CNF * message) towards originating endpoint. The SDP negotiation on the * originating side is complete. If the call gets forwarded to a single or * multiple terminating endpoints, an SDP is generated with the supported * codecs of this router. In case of single terminating endpoint, the SDP of the * first reply to the CC-SETUP-IND message is used to negotiate the codec. In * case of multiple terminating endpoints (call forking) the SDP reply is * stored and processed when a CC-SETUP-RSP is received from the first * terminating endpoint that answers. The SDP negotiation on the terminating * side is complete. */ #include #include #include #include #include #include #include #include #include #include "../libdebug/debug.h" #include "../libg711/g711.h" #include "call.h" #include "audio.h" #include "display.h" call_t *call_list = NULL; struct timer status_timer; static osmo_cc_endpoint_t *cc_ep_list[3]; static const char *routing_script, *routing_shell; static struct osmo_cc_helper_audio_codecs codecs_def[] = { { "PCMA", 8000, 1, g711_encode_alaw, g711_decode_alaw }, { "PCMU", 8000, 1, g711_encode_ulaw, g711_decode_ulaw }, { "telephone-event", 8000, 1, encode_te, decode_te }, { "L16", 8000, 1, encode_l16, decode_l16 }, { NULL, 0, 0, NULL, NULL}, }; static void refresh_status(void) { osmo_cc_call_t *cc_call; int from_count, to_count; call_t *call; call_relation_t *relation; int e; display_status_start(); for (e = 0; cc_ep_list[e]; e++) { for (cc_call = cc_ep_list[e]->call_list; cc_call; cc_call = cc_call->next) { if (cc_call->state != OSMO_CC_STATE_ATTACH_IN) continue; if (!cc_call->attached_name) continue; from_count = 0; for (call = call_list; call; call = call->next) { if (!call->relation_list) continue; if (!!strcmp(cc_call->attached_name, call->relation_list->interface)) continue; to_count = 0; for (relation = call->relation_list->next; relation; relation = relation->next) { display_status_line(cc_call->attached_name, from_count, call->relation_list->id, to_count, relation->interface, relation->id, relation->state); to_count++; } if (!to_count) display_status_line(cc_call->attached_name, from_count, call->relation_list->id, 0, NULL, NULL, 0); from_count++; } if (!from_count) display_status_line(cc_call->attached_name, 0, NULL, 0, NULL, NULL, 0); } } display_status_end(); } static int status_needs_update = 0; static void status_timeout(struct timer *timer) { static int last_interfaces = -1; osmo_cc_call_t *cc_call; int interfaces = 0; int e; for (e = 0; cc_ep_list[e]; e++) { for (cc_call = cc_ep_list[e]->call_list; cc_call; cc_call = cc_call->next) { if (cc_call->state != OSMO_CC_STATE_ATTACH_IN) continue; interfaces++; } } if (interfaces != last_interfaces) { last_interfaces = interfaces; status_needs_update = 1; } if (status_needs_update) { refresh_status(); status_needs_update = 0; } timer_start(timer, 0.1); } static const char *state_names[] = { "IDLE", "SETUP", "OVERLAP", "PROCEEDING", "ALERTING", "CONNECT", "DISC-FROM-ORIG", "DISC-FROM-TERM", }; static void new_state(call_t *call, enum call_state state) { PDEBUG(DROUTER, DEBUG_DEBUG, "(call #%d) Changing state %s -> %s.\n", call->num, state_names[call->state], state_names[state]); call->state = state; } static const char *relation_name(call_relation_t *relation) { static char text[128]; if (relation->num == 0) sprintf(text, "(call #%d, originator)", relation->call->num); else sprintf(text, "(call #%d, terminator #%d)", relation->call->num, relation->num); return text; } static call_t *call_create(void) { call_t *call, **call_p; static int call_num = 0; call = calloc(1, sizeof(*call)); if (!call) { PDEBUG(DROUTER, DEBUG_ERROR, "No memory!\n"); abort(); } call->num = ++call_num; call->routing.call = call; /* append to list */ call_p = &call_list; while (*call_p) call_p = &((*call_p)->next); *call_p = call; PDEBUG(DROUTER, DEBUG_DEBUG, "(call #%d) Created new call instance.\n", call->num); return call; } static void relation_destroy(call_relation_t *relation); static void call_destroy(call_t *call) { call_t **call_p; new_state(call, CALL_STATE_IDLE); /* remove setup message */ free(call->setup_msg); /* destroy all relations */ while (call->relation_list) relation_destroy(call->relation_list); /* destroy routing */ routing_stop(&call->routing); routing_env_free(&call->routing); /* detach */ call_p = &call_list; while (*call_p) { if (*call_p == call) break; call_p = &((*call_p)->next); } *call_p = call->next; PDEBUG(DROUTER, DEBUG_DEBUG, "(call #%d) Destroyed call instance.\n", call->num); free(call); } static call_relation_t *relation_create(call_t *call) { call_relation_t *relation, **relation_p; int rc; relation = calloc(1, sizeof(*relation)); if (!relation) { PDEBUG(DROUTER, DEBUG_ERROR, "No memory!\n"); abort(); } relation->call = call; /* allocate jitter buffer */ rc = jitter_create(&relation->orig_dejitter, 8000 / 10); // FIXME: size if (rc < 0) abort(); rc = jitter_create(&relation->term_dejitter, 8000 / 10); // FIXME: size if (rc < 0) abort(); /* append to list, count number of relation */ relation_p = &call->relation_list; while (*relation_p) { relation->num++; relation_p = &((*relation_p)->next); } *relation_p = relation; PDEBUG(DROUTER, DEBUG_DEBUG, "%s Created new endpoint relation instance.\n", relation_name(relation)); return relation; } static void relation_destroy(call_relation_t *relation) { call_relation_t **relation_p; /* playback and record */ wave_destroy_playback(&relation->play); wave_destroy_record(&relation->rec); /* dtmf decoder */ dtmf_decode_exit(&relation->dtmf_dec); /* SDP */ free((char *)relation->sdp); /* session */ if (relation->cc_session) { osmo_cc_free_session(relation->cc_session); relation->cc_session = NULL; } /* destroy jitter buffer */ jitter_destroy(&relation->orig_dejitter); jitter_destroy(&relation->term_dejitter); /* detach */ relation_p = &relation->call->relation_list; while (*relation_p) { if (*relation_p == relation) break; relation_p = &((*relation_p)->next); } *relation_p = relation->next; PDEBUG(DROUTER, DEBUG_DEBUG, "%s Destroyed endpoint relation instance.\n", relation_name(relation)); free(relation); /* refresh status: we lost a call (originating and/or terminating) */ status_needs_update = 1; } int call_init(osmo_cc_endpoint_t *cc_ep1, osmo_cc_endpoint_t *cc_ep2, const char *_routing_script, const char *_routing_shell) { cc_ep_list[0] = cc_ep1; cc_ep_list[1] = cc_ep2; cc_ep_list[2] = NULL; routing_script = _routing_script; routing_shell = _routing_shell; timer_init(&status_timer, status_timeout, NULL); status_timeout(&status_timer); return 0; } void call_exit(void) { timer_exit(&status_timer); /* destroy all calls */ while (call_list) call_destroy(call_list); } /* handle all calls, if it returns 1, work has been done, so it must be called again */ int call_handle(void) { int w; call_t *call; int status; pid_t pid; for (call = call_list; call; call = call->next) { /* must return, call may be destroyed */ w = routing_handle(&call->routing); if (w) return 1; } /* eat zombies */ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { PDEBUG(DROUTER, DEBUG_DEBUG, "Script child %d terminated.\n", pid); for (call = call_list; call; call = call->next) { if (call->routing.script_pid == pid) { /* tell routing that script has terminated */ call->routing.script_pid = 0; } } } return 0; } /* * RTP-Proxy */ /* send SDP answer to originator */ static void proxy_send_sdp_answer(call_relation_t *relation, osmo_cc_msg_t *msg) { const char *sdp; /* no proxy */ if (!relation->rtp_proxy) return; /* NOTE: The SDP was removed at cc_message() */ /* add progreess if not connect message, but for first message or disconnect message */ if (msg->type != OSMO_CC_MSG_SETUP_CNF && (msg->type == OSMO_CC_MSG_DISC_IND || !relation->codec_negotiated)) { /* process */ PDEBUG(DROUTER, DEBUG_DEBUG, "Sending progress indicator 8 to allow audio.\n"); osmo_cc_add_ie_progress(msg, OSMO_CC_CODING_ITU_T, OSMO_CC_LOCATION_BEYOND_INTERWORKING, OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE); } // if (!relation->codec_negotiated || msg->type == OSMO_CC_MSG_SETUP_CNF) { gibt einen crash, da codec vor der antwort schon gesetzt ist. warum sollten wir nach einer antwort denn nochmal den codec schicken? if (!relation->codec_negotiated) { sdp = osmo_cc_helper_audio_accept(&relation->cc_ep->session_config, relation, relation->orig_codecs, receive_originator, relation->call->setup_msg, &relation->cc_session, &relation->codec, 0); if (sdp) { relation->codec_negotiated = 1; PDEBUG(DROUTER, DEBUG_DEBUG, "Sending SDP answer to originator with supported codecs.\n"); /* SDP */ osmo_cc_add_ie_sdp(msg, sdp); } else { relation->rtp_proxy = 0; PDEBUG(DROUTER, DEBUG_ERROR, "Originator's setup message does not contain a codec we support, cannot use RTP-Proxy!\n"); } } } /* * process call from originator */ /* a new call is received */ static void orig_setup(osmo_cc_endpoint_t *cc_ep, uint32_t callref, osmo_cc_msg_t *old_msg) { call_t *call; call_relation_t *relation; char sdp[65536]; uint8_t type, plan, present, screen; char number[256]; int rc; /* creating call instance, transparent until setup with hdlc */ call = call_create(); if (!call) { PDEBUG(DROUTER, DEBUG_ERROR, "Cannot create calll instance.\n"); abort(); } relation = relation_create(call); /* link with cc */ relation->cc_ep = cc_ep; relation->cc_callref = callref; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-REQ from originator.\n", relation_name(relation)); /* store setup message in call structure */ call->setup_msg = osmo_cc_clone_msg(old_msg); /* store SDP */ rc = osmo_cc_get_ie_sdp(old_msg, 0, sdp, sizeof(sdp)); if (rc >= 0) { free((char *)relation->sdp); relation->sdp = strdup(sdp); } /* store called number */ rc = osmo_cc_get_ie_called(old_msg, 0, &type, &plan, number, sizeof(number)); if (rc >= 0 && number[0]) { /* add number to current dial string */ if (strlen(number) < sizeof(call->dialing_number)) strcpy(call->dialing_number, number); } /* store keypad */ rc = osmo_cc_get_ie_keypad(old_msg, 0, number, sizeof(number)); if (rc >= 0 && number[0]) { /* add number to current dial string */ if (strlen(number) < sizeof(call->dialing_keypad)) strcpy(call->dialing_keypad, number); } /* store peer info for status */ rc = osmo_cc_get_ie_calling_interface(old_msg, 0, relation->interface, sizeof(relation->interface)); rc = osmo_cc_get_ie_calling(old_msg, 0, &type, &plan, &present, &screen, relation->id, sizeof(relation->id)); /* apply environment for routing */ routing_env_msg(&call->routing, old_msg); /* start routing script */ routing_start(&call->routing, routing_script, routing_shell); /* go into setup state and return */ new_state(call, CALL_STATE_SETUP); /* refresh status: we have originating call */ status_needs_update = 1; return; } /* information (dialing) is received from originating side */ static void orig_info(call_t *call, osmo_cc_msg_t *old_msg) { uint8_t type, plan; char number[256]; char keypad[256]; int complete = 0; uint8_t duration_ms, pause_ms, dtmf_mode; char dtmf[256]; char command[512]; int rc; osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-INFO-REQ from originator.\n", relation_name(call->relation_list)); /* add called number to dial string */ rc = osmo_cc_get_ie_called(old_msg, 0, &type, &plan, number, sizeof(number)); if (rc < 0) number[0] = '\0'; if (number[0]) { /* add number to current dial string */ if (strlen(number) < sizeof(call->dialing_number) - strlen(call->dialing_number)) strcat(call->dialing_number, number); } /* add keypad to dial string */ rc = osmo_cc_get_ie_keypad(old_msg, 0, keypad, sizeof(keypad)); if (rc < 0) keypad[0] = '\0'; if (keypad[0]) { /* add number to current dial string */ if (strlen(keypad) < sizeof(call->dialing_keypad) - strlen(call->dialing_keypad)) strcat(call->dialing_keypad, keypad); } /* indicate complete number to environment */ rc = osmo_cc_get_ie_complete(old_msg, 0); if (rc) complete = 1; /* dtmf digits */ rc = osmo_cc_get_ie_dtmf(old_msg, 0, &duration_ms, &pause_ms, &dtmf_mode, dtmf, sizeof(dtmf)); if (rc < 0 || (dtmf_mode != OSMO_CC_DTMF_MODE_ON && dtmf_mode != OSMO_CC_DTMF_MODE_DIGITS)) dtmf[0] = '\0'; /* if there is only one call relation, forward that message */ if (call->relation_list->next && !call->relation_list->next->next) { /* concat number to id of relation */ if (strlen(call->relation_list->next->id) + strlen(number) < sizeof(call->relation_list->next->id)) strcat(call->relation_list->next->id, number); new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_INFO_IND; osmo_cc_ll_msg(call->relation_list->next->cc_ep, call->relation_list->next->cc_callref, new_msg); return; } /* if there is no call in overlap state, perform routing */ if (call->state == CALL_STATE_OVERLAP && !call->relation_list->next) { if (!call->routing.routing) { /* restart routing with new dial string */ routing_env_dialing(&call->routing, call->dialing_number, call->dialing_keypad, complete); routing_start(&call->routing, routing_script, routing_shell); } else { /* send digits to routing */ if (number[0]) { snprintf(command, sizeof(command) - 1, "dialing %s", number); command[sizeof(command) - 1] = '\0'; routing_send(&call->routing, command); } if (keypad[0]) { snprintf(command, sizeof(command) - 1, "keypad %s", keypad); command[sizeof(command) - 1] = '\0'; routing_send(&call->routing, command); } } } /* send dtmf, to routing in all other states */ if (!call->relation_list->next) { if (call->routing.routing) { if (dtmf[0]) { snprintf(command, sizeof(command) - 1, "dtmf %s", dtmf); command[sizeof(command) - 1] = '\0'; routing_send(&call->routing, command); } } } } /* disconnect is received from originating side */ static void orig_disc(call_t *call, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; call_relation_t *relation; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-DISC-REQ from originator.\n", relation_name(call->relation_list)); /* stop routing, if originator hangs up */ if (call->routing.routing) { routing_stop(&call->routing); } /* if there is no terminating call, release originator and destroy call */ if (!call->relation_list->next) { new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); /* destroy call */ call_destroy(call); return; } /* if there is one terminating call, forward the disc message */ if (!call->relation_list->next->next) { /* update call state for status display */ call->relation_list->next->state = CALL_STATE_DISC_FROM_TERM; status_needs_update = 1; /* forward disc message */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_DISC_IND; osmo_cc_ll_msg(call->relation_list->next->cc_ep, call->relation_list->next->cc_callref, new_msg); new_state(call, CALL_STATE_DISC_FROM_ORIG); return; } /* if there are multiple terminating calls, release them and originator and destroy call */ for (relation = call->relation_list->next; relation; relation = relation->next) { new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); /* destroy call */ call_destroy(call); } /* release is received from originating side */ static void orig_rel(call_t *call, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; call_relation_t *relation; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-REQ from originator.\n", relation_name(call->relation_list)); /* stop routing, if originator hangs up */ if (call->routing.routing) { routing_stop(&call->routing); } /* release all terminating calls, if any and confirm originator and destroy call */ for (relation = call->relation_list->next; relation; relation = relation->next) { new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_CNF; osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); /* destroy call */ call_destroy(call); } /* other message is received from originating side */ static void orig_other(call_t *call, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s Other CC message from originator.\n", relation_name(call->relation_list)); /* if there is one terminating call, forward the message */ if (call->relation_list->next && !call->forking) { new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = old_msg->type | 1; /* convert REQ->IND, RSP->CNF */ osmo_cc_ll_msg(call->relation_list->next->cc_ep, call->relation_list->next->cc_callref, new_msg); return; } } /* * process call from terminator */ /* overlap dialing is received from terminating side */ static void term_progress(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-PROGRESS-REQ from terminator.\n", relation_name(relation)); /* if single call exists, forward progress to originator */ if (!call->forking) { /* forward message to originator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_PROGRESS_IND; /* send SDP answer */ proxy_send_sdp_answer(call->relation_list, new_msg); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); } } /* overlap dialing is received from terminating side */ static void term_overlap(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-ACK-REQ from terminator.\n", relation_name(relation)); /* update call state for status display */ relation->state = CALL_STATE_OVERLAP; status_needs_update = 1; /* notify routing */ if (call->routing.routing) routing_send(&call->routing, "call-overlap"); /* if we already reached/passed that state, we ignore it */ if (call->state != CALL_STATE_SETUP) return; /* change state */ new_state(call, CALL_STATE_OVERLAP); if (!call->forking) { /* forward message to originator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_SETUP_ACK_IND; } else { /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_ACK_IND); } /* send SDP answer */ proxy_send_sdp_answer(call->relation_list, new_msg); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); } /* proceeding is received from terminating side */ static void term_proc(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-PROC-REQ from terminator.\n", relation_name(relation)); /* update call state for status display */ relation->state = CALL_STATE_PROCEEDING; status_needs_update = 1; /* notify routing */ if (call->routing.routing) routing_send(&call->routing, "call-proceeding"); /* if we already reached/passed that state, we ignore it */ if (call->state != CALL_STATE_SETUP && call->state != CALL_STATE_OVERLAP) return; /* change state */ new_state(call, CALL_STATE_PROCEEDING); if (!call->forking) { /* forward message to originator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_PROC_IND; } else { /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND); } /* send SDP answer */ proxy_send_sdp_answer(call->relation_list, new_msg); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); } /* alerting is received from terminating side */ static void term_alert(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-ALERT-REQ from terminator.\n", relation_name(relation)); /* update call state for status display */ relation->state = CALL_STATE_ALERTING; status_needs_update = 1; /* notify routing */ if (call->routing.routing) routing_send(&call->routing, "call-alerting"); /* if we already reached/passed that state, we ignore it */ if (call->state != CALL_STATE_SETUP && call->state != CALL_STATE_OVERLAP && call->state != CALL_STATE_PROCEEDING) return; /* change state */ new_state(call, CALL_STATE_ALERTING); if (!call->forking) { /* forward message to originator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_ALERT_IND; } else { /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND); } /* send SDP answer */ proxy_send_sdp_answer(call->relation_list, new_msg); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); } /* connect is received from terminating side */ static void term_connect(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; char sdp[65536]; int rc; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-RSP (connect) from terminator.\n", relation_name(relation)); /* update call state for status display */ relation->state = CALL_STATE_CONNECT; status_needs_update = 1; /* notify routing */ if (call->routing.routing) routing_send(&call->routing, "call-answer"); /* if we already reached/passed that state, we ignore it */ if (call->state != CALL_STATE_SETUP && call->state != CALL_STATE_OVERLAP && call->state != CALL_STATE_PROCEEDING && call->state != CALL_STATE_ALERTING) { PDEBUG(DROUTER, DEBUG_ERROR, "Connect message from terminating call now allowed in state '%s'.\n", state_names[call->state]); return; } /* change state */ new_state(call, CALL_STATE_CONNECT); /* release all other relations with "non-selected user clearing" */ while (call->relation_list->next->next) { call_relation_t *other; /* select other terminating call (not the one that answered) */ if (call->relation_list->next == relation) other = call->relation_list->next->next; else other = call->relation_list->next; PDEBUG(DROUTER, DEBUG_DEBUG, "Sending 'non-selected user clearing' to other terminator.\n"); /* send release message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); osmo_cc_add_ie_cause(new_msg, other->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NONSE_USER_CLR, 0, 0); osmo_cc_ll_msg(other->cc_ep, other->cc_callref, new_msg); /* destroy terminating relation */ relation_destroy(other); } /* call is not forking anymore */ call->forking = 0; rc = osmo_cc_get_ie_sdp(old_msg, 0, sdp, sizeof(sdp)); if (rc < 0) sdp[0] = '\0'; /* forward message to originator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_SETUP_CNF; /* only if RTP-Proxy is used */ if (call->relation_list->rtp_proxy) { /* send SDP answer */ proxy_send_sdp_answer(call->relation_list, new_msg); } else /* use earlier SDP if not included */ if (!sdp[0] && relation->sdp) osmo_cc_add_ie_sdp(new_msg, relation->sdp); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); } /* disconnect is received from terminating side */ static void term_disc(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-DISC-REQ from terminator.\n", relation_name(relation)); /* update call state for status display */ relation->state = CALL_STATE_DISC_FROM_TERM; status_needs_update = 1; /* if we have not yet connected a call */ if (call->state == CALL_STATE_SETUP || call->state == CALL_STATE_OVERLAP || call->state == CALL_STATE_PROCEEDING || call->state == CALL_STATE_ALERTING) { uint8_t location, isdn_cause, socket_cause; uint16_t sip_cause; int rc; PDEBUG(DROUTER, DEBUG_DEBUG, "Got a disconnect before connect.\n"); /* if there is only one terminating call, forward that disconnect */ if (!call->forking) { PDEBUG(DROUTER, DEBUG_DEBUG, "Call is not forking, so we directly forward this disconnect.\n"); /* notify routing */ if (call->routing.routing) routing_send(&call->routing, "call-disconnect"); /* change state */ new_state(call, CALL_STATE_DISC_FROM_TERM); /* forward message to originator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_DISC_IND; /* send SDP answer */ proxy_send_sdp_answer(call->relation_list, new_msg); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); return; } /* collect cause */ PDEBUG(DROUTER, DEBUG_DEBUG, "Call is forking, so we collect ISDN cause and destroy relation.\n"); rc = osmo_cc_get_ie_cause(old_msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); if (rc >= 0) call->collect_cause = osmo_cc_collect_cause(call->collect_cause, isdn_cause); /* release the terminator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); /* remove relation */ relation_destroy(relation); /* if all terminating calls have been released, release the originator and destroy call */ if (!call->relation_list->next) { PDEBUG(DROUTER, DEBUG_DEBUG, "All terminators have disconnected, so we forward a release with the collected cause.\n"); new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); osmo_cc_add_ie_cause(new_msg, call->relation_list->cc_ep->serving_location, call->collect_cause, 0, 0); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); /* destroy call */ call_destroy(call); } return; } /* this is connect or disconnect collision. the state implies that there is only one terminating call */ if (call->state == CALL_STATE_CONNECT || call->state == CALL_STATE_DISC_FROM_ORIG) { PDEBUG(DROUTER, DEBUG_DEBUG, "Got a disconnect after connect, so we directly forward this disconnect.\n"); new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); /* destroy call */ call_destroy(call); return; } PDEBUG(DROUTER, DEBUG_ERROR, "Disconnect message from terminating call now allowed in state '%s'.\n", state_names[call->state]); } /* rel is received from terminating side */ static void term_rel(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-REQ from terminator.\n", relation_name(relation)); /* if we have not yet connected a call */ if (call->state == CALL_STATE_SETUP || call->state == CALL_STATE_OVERLAP || call->state == CALL_STATE_PROCEEDING || call->state == CALL_STATE_ALERTING) { uint8_t location, isdn_cause, socket_cause; uint16_t sip_cause; int rc; PDEBUG(DROUTER, DEBUG_DEBUG, "Got a release before connect.\n"); /* if there is only one terminating call, forward that disconnect */ if (!call->forking) { PDEBUG(DROUTER, DEBUG_DEBUG, "Call is not forking, so we directly forward this release.\n"); /* forward message to originator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); /* confirm to terminator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_CNF; osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); /* destroy call */ call_destroy(call); return; } /* collect cause */ PDEBUG(DROUTER, DEBUG_DEBUG, "Call is forking, so we collect ISDN cause and destroy relation.\n"); rc = osmo_cc_get_ie_cause(old_msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); if (rc >= 0) call->collect_cause = osmo_cc_collect_cause(call->collect_cause, isdn_cause); /* confirm the terminator */ new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_CNF; osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); /* remove relation */ relation_destroy(relation); /* if all terminating calls have been released, release the originator and destroy call */ if (!call->relation_list->next) { PDEBUG(DROUTER, DEBUG_DEBUG, "All terminators have released, so we forward it with the collected cause.\n"); new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); osmo_cc_add_ie_cause(new_msg, call->relation_list->cc_ep->serving_location, call->collect_cause, 0, 0); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); /* destroy call */ call_destroy(call); } return; } /* forward release to originator and confirm to terminator. the state implies that there is only one terminating call */ if (call->state == CALL_STATE_CONNECT || call->state == CALL_STATE_DISC_FROM_ORIG) { PDEBUG(DROUTER, DEBUG_DEBUG, "Got a release after connect, so we directly forward this release.\n"); new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_IND; osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = OSMO_CC_MSG_REL_CNF; osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); /* destroy call */ call_destroy(call); return; } PDEBUG(DROUTER, DEBUG_ERROR, "Release message from terminating call now allowed in state '%s'.\n", state_names[call->state]); } /* other message is received from terminating side */ static void term_other(call_t *call, call_relation_t *relation, osmo_cc_msg_t *old_msg) { osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_DEBUG, "%s Other CC message from terminator.\n", relation_name(relation)); /* if there is one terminating call, forward the message */ if (!call->relation_list->next->next) { new_msg = osmo_cc_clone_msg(old_msg); new_msg->type = old_msg->type | 1; /* convert REQ->IND, RSP->CNF */ osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); return; } } /* handle message from upper layer */ void cc_message(osmo_cc_endpoint_t *cc_ep, uint32_t callref, osmo_cc_msg_t *msg) { call_t *call; call_relation_t *relation; char sdp[65536]; int rc_sdp; /* hunt for callref */ call = call_list; while (call) { relation = call->relation_list; while (relation) { if (relation->cc_callref == callref) break; relation = relation->next; } if (relation) break; call = call->next; } /* process SETUP (new call) */ if (!call) { if (msg->type != OSMO_CC_MSG_SETUP_REQ) { PDEBUG(DROUTER, DEBUG_ERROR, "Received message without call instance, please fix!\n"); return; } orig_setup(cc_ep, callref, msg); osmo_cc_free_msg(msg); return; } if (call->relation_list == relation) { /* handle messages from caller */ switch (msg->type) { case OSMO_CC_MSG_INFO_REQ: orig_info(call, msg); break; case OSMO_CC_MSG_DISC_REQ: orig_disc(call, msg); break; case OSMO_CC_MSG_REL_REQ: orig_rel(call, msg); break; default: orig_other(call, msg); } } else { /* store sdp, if present */ rc_sdp = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp)); if (rc_sdp >= 0) { free((char *)relation->sdp); relation->sdp = strdup(sdp); } /* negotiate codec if RTP-Proxy is used and not already negotiated */ if (call->relation_list->rtp_proxy && relation->cc_session && !relation->codec_negotiated) { /* remove progress, since it will be added with the SDP answer */ osmo_cc_remove_ie(msg, OSMO_CC_IE_PROGRESS, 0); /* negotiate codec */ osmo_cc_helper_audio_negotiate(msg, &relation->cc_session, &relation->codec); if (relation->codec) relation->codec_negotiated = 1; } /* remove SDP, if we do RTP-Proxy */ if (rc_sdp >= 0 && call->relation_list->rtp_proxy) osmo_cc_remove_ie(msg, OSMO_CC_IE_SDP, 0); /* handle messages from called */ switch (msg->type) { case OSMO_CC_MSG_PROGRESS_IND: term_progress(call, relation, msg); break; case OSMO_CC_MSG_SETUP_ACK_REQ: term_overlap(call, relation, msg); break; case OSMO_CC_MSG_PROC_REQ: term_proc(call, relation, msg); break; case OSMO_CC_MSG_ALERT_REQ: term_alert(call, relation, msg); break; case OSMO_CC_MSG_SETUP_RSP: term_connect(call, relation, msg); break; case OSMO_CC_MSG_DISC_REQ: term_disc(call, relation, msg); break; case OSMO_CC_MSG_REL_REQ: case OSMO_CC_MSG_REJ_REQ: term_rel(call, relation, msg); break; default: term_other(call, relation, msg); } } osmo_cc_free_msg(msg); } /* * process message from routing */ static struct param { /* rtp-proxy */ struct osmo_cc_helper_audio_codecs orig_codecs[MAX_CODECS + 1]; struct osmo_cc_helper_audio_codecs term_codecs[MAX_CODECS + 1]; int orig_given, term_given; /* play */ char *filename, *volume, *loop; /* gain */ char *gain; /* call */ char *interface; int bearer_coding, bearer_capability, bearer_mode; char *calling; int calling_type, calling_plan, calling_present, calling_screen, no_calling; char *calling2; int calling2_type, calling2_plan, calling2_present, calling2_screen, no_calling2; char *redirecting; int redirecting_type, redirecting_plan, redirecting_present, redirecting_screen, redirecting_reason, no_redirecting; char *dialing; int dialing_type, dialing_plan; char *keypad; /* disc/rel */ int isdn_cause, sip_cause; /* error */ char *error; } param; struct param_def { const char *n; char **s; int *i; struct osmo_cc_helper_audio_codecs *o, *t; const char *d; int mandatory; int no_value; const char *(*value2name)(int value); int (*name2value)(const char *name); int num; }; struct command_def { const char *name; void (*func)(call_t *call, int argc, char *argv[], struct command_def *command_def); const char *description; struct param_def *param_list; } command_def[]; static void add_codecs_by_names(struct osmo_cc_helper_audio_codecs *codecs, char *names) { int c = 0, i; const char *name; while ((name = strsep(&names, ","))) { for (i = 0; codecs_def[i].payload_name; i++) { if (!strcasecmp(name, codecs_def[i].payload_name)) { if (c == MAX_CODECS) { PDEBUG(DROUTER, DEBUG_ERROR, "Maximum of %d codecs are reached. Ignoring codec '%s'.\n", c, name); break; } memcpy(&codecs[c], &codecs_def[i], sizeof(*codecs)); PDEBUG(DROUTER, DEBUG_INFO, "Adding given codec '%s'.\n", codecs[c].payload_name); c++; break; } } if (!codecs_def[i].payload_name) { PDEBUG(DROUTER, DEBUG_ERROR, "Given codec '%s' not supported!\n", name); } } memset(&codecs[c], 0, sizeof(*codecs)); } static int parse_params(int argc, char *argv[], struct command_def *command_def) { struct param_def *param_def = command_def->param_list; int i, j; char *arg; /* clear parameter set */ memset(¶m, 0, sizeof(param)); /* no parameter list given */ if (param_def == NULL) return -EINVAL; /* loop through all arguments and stop if there is a ':' */ for (i = 0; i < argc && argv[i][0] != ':'; i++) { arg = argv[i]; /* loop through all possible parameter definitions */ for (j = 0; param_def[j].n; j++) { /* stop if parameter has been found */ if (!strncasecmp(arg, param_def[j].n, strlen(param_def[j].n))) { arg += strlen(param_def[j].n); /* no value */ if (*arg == '\0') { arg = ""; break; } /* has value */ if (*arg == '=') { arg++; break; } /* continue, if more digits given */ } } if (!param_def[j].n) { PDEBUG(DROUTER, DEBUG_ERROR, "Unknown '%s' parameter '%s' from routing.\n", command_def->name, argv[i]); continue; } if (arg[0] && param_def[j].no_value) { PDEBUG(DROUTER, DEBUG_ERROR, "'%s' parameter '%s' has no value, ignoring.\n", command_def->name, argv[i]); continue; } if (param_def[j].no_value) arg = "1"; if (param_def[j].i) { if (param_def[j].name2value) { *param_def[j].i = param_def[j].name2value(arg); if (*param_def[j].i < 0) *param_def[j].i = atoi(arg); } else *param_def[j].i = atoi(arg); } if (param_def[j].s) *param_def[j].s = arg; if (param_def[j].o) { PDEBUG(DROUTER, DEBUG_INFO, "Originating codecs given: '%s'.\n", arg); add_codecs_by_names(param_def[j].o, arg); param.orig_given = 1; } if (param_def[j].t) { PDEBUG(DROUTER, DEBUG_INFO, "Terminating codecs given: '%s'.\n", arg); add_codecs_by_names(param_def[j].t, arg); param.term_given = 1; } } return i; } /* routing orders us to activate rtp proxy */ struct param_def param_rtp_proxy[] = { { .n = "orig-codecs", .o = param.orig_codecs, .d = "Define allowed codecs to be accepted by originating endpoint." }, { .n = "term-codecs", .t = param.term_codecs, .d = "Define allowed codecs on terminating endpoint in order of preference." }, { .n = NULL } }; static void routing_rtp_proxy(call_t *call, int argc, char *argv[], struct command_def *command_def) { call_relation_t *relation = call->relation_list; /* ignore, if already enabled */ if (relation->rtp_proxy) { PDEBUG(DROUTER, DEBUG_NOTICE, "RTP proxy is already enabled!.\n"); return; } parse_params(argc, argv, command_def); if (!param.orig_given) { memcpy(&relation->orig_codecs[0], &codecs_def[0], sizeof(relation->orig_codecs[0])); memcpy(&relation->orig_codecs[1], &codecs_def[1], sizeof(relation->orig_codecs[1])); memcpy(&relation->orig_codecs[2], &codecs_def[2], sizeof(relation->orig_codecs[2])); memset(&relation->orig_codecs[3], 0, sizeof(relation->orig_codecs[3])); PDEBUG(DROUTER, DEBUG_INFO, "No originating codeds given, using default '%s' and '%s' and '%s'.\n", relation->orig_codecs[0].payload_name, relation->orig_codecs[1].payload_name, relation->orig_codecs[2].payload_name); } else memcpy(relation->orig_codecs, param.orig_codecs, sizeof(param.orig_codecs)); if (!param.term_given) { memcpy(&relation->term_codecs[0], &codecs_def[0], sizeof(relation->term_codecs[0])); memcpy(&relation->term_codecs[1], &codecs_def[1], sizeof(relation->term_codecs[1])); memset(&relation->term_codecs[2], 0, sizeof(relation->term_codecs[2])); PDEBUG(DROUTER, DEBUG_INFO, "No terminating codeds given, using default '%s' and '%s'.\n", relation->term_codecs[0].payload_name, relation->term_codecs[1].payload_name); } else memcpy(relation->term_codecs, param.term_codecs, sizeof(param.term_codecs)); /* error, if we already negotiated */ if (call->sdp_forwarded) { PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy cannot be enabled now, because we already forwarded a call.\n"); return; } /* no SDP */ if (!relation->sdp) { PDEBUG(DROUTER, DEBUG_ERROR, "Originator's setup message does not contain SDP, please fix!.\n"); return; } relation->rtp_proxy = 1; } /* routing orders us to play a wave file */ struct param_def param_play[] = { { .n = "filename", .s = ¶m.filename, .d = "file name of audio file", .mandatory = 1 }, { .n = "volume", .s = ¶m.volume, .d = "speech level, which is '1.0'" }, { .n = "loop", .s = ¶m.loop, .d = "play in an endless loop", .no_value = 1 }, { .n = NULL } }; static void routing_play(call_t *call, int argc, char *argv[], struct command_def *command_def) { call_relation_t *relation = call->relation_list; int samplerate = 8000, channels = 0; double deviation; int rc; wave_destroy_playback(&relation->play); if (!relation->rtp_proxy) { PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to play a file!.\n"); return; } parse_params(argc, argv, command_def); if (!param.filename) { PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires a file name as parameter.\n"); return; } if (!param.volume) param.volume = "1.0"; deviation = 1.0 / SPEECH_LEVEL * atof(param.volume); rc = wave_create_playback(&relation->play, param.filename, &samplerate, &channels, deviation); if (rc < 0) return; strncpy(relation->play_filename, param.filename, sizeof(relation->play_filename) - 1); relation->play_deviation = deviation; if (channels != 1 && channels != 2) { wave_destroy_playback(&relation->play); PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires a wave file that has 1 or 2 channels only.\n"); return; } if (param.loop) relation->play_loop = 1; } /* routing orders us stop playing a wave file */ static void routing_play_stop(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation = call->relation_list; wave_destroy_playback(&relation->play); } /* routing orders us to record a wave file */ struct param_def param_record[] = { { .n = "filename", .s = ¶m.filename, .d = "file name of audio file", .mandatory = 1 }, { .n = "volume", .s = ¶m.volume, .d = "speech level, which is '1.0'" }, { .n = NULL } }; static void routing_record(call_t *call, int argc, char *argv[], struct command_def *command_def) { call_relation_t *relation = call->relation_list; int samplerate = 8000, channels = 2; int rc; wave_destroy_record(&relation->rec); if (!relation->rtp_proxy) { PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!.\n"); return; } parse_params(argc, argv, command_def); if (!param.filename) { PDEBUG(DROUTER, DEBUG_ERROR, "'record' command reqires a file name as parameter.\n"); return; } if (!param.volume) param.volume = "1.0"; rc = wave_create_record(&relation->rec, param.filename, samplerate, channels, 1.0 / SPEECH_LEVEL / atof(param.volume)); if (rc < 0) return; } /* routing orders us stop recording a wave file */ static void routing_record_stop(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation = call->relation_list; wave_destroy_record(&relation->rec); } /* routing orders us to set local gain */ struct param_def param_gain[] = { { .n = "gain", .s = ¶m.gain, .d = "gain in dB" }, { .n = NULL } }; static void routing_tx_gain(call_t *call, int argc, char *argv[], struct command_def *command_def) { if (!call->relation_list->rtp_proxy) { PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!\n"); return; } parse_params(argc, argv, command_def); if (!param.gain) { PDEBUG(DROUTER, DEBUG_ERROR, "'tx-gain' command reqires a gain value as parameter.\n"); return; } call->tx_gain = atof(param.gain); } static void routing_rx_gain(call_t *call, int argc, char *argv[], struct command_def *command_def) { if (!call->relation_list->rtp_proxy) { PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!\n"); return; } parse_params(argc, argv, command_def); if (!param.gain) { PDEBUG(DROUTER, DEBUG_ERROR, "'rx-gain' command reqires a gain value as parameter.\n"); return; } call->rx_gain = atof(param.gain); } /* routing orders us to call remote end */ struct param_def param_call[] = { { .n = "interface", .s = ¶m.interface, .d = "name of interface to route the call to", .mandatory = 1}, { .n = "bearer-coding", .i = ¶m.bearer_coding, .d = "coding of bearer capability", .name2value = osmo_cc_coding_name2value, .value2name = osmo_cc_coding_value2name, .num = OSMO_CC_CODING_NUM }, { .n = "bearer-capability", .i = ¶m.bearer_capability, .d = "bearer capability value", .name2value = osmo_cc_capability_name2value, .value2name = osmo_cc_capability_value2name, .num = OSMO_CC_CAPABILITY_NUM }, { .n = "bearer-mode", .i = ¶m.bearer_mode, .d = "bearer mode", .name2value = osmo_cc_mode_name2value, .value2name = osmo_cc_mode_value2name, .num = OSMO_CC_MODE_NUM }, { .n = "calling", .s = ¶m.calling, .d = "calling party number" }, { .n = "calling-type", .i = ¶m.calling_type, .d = "type of calling party number", .name2value = osmo_cc_type_name2value, .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM}, { .n = "calling-plan", .i = ¶m.calling_plan, .d = "numbering plan of calling party number", .name2value = osmo_cc_plan_name2value, .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM }, { .n = "calling-present", .i = ¶m.calling_present, .d = "presentation of calling party number", .name2value = osmo_cc_present_name2value, .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM }, { .n = "calling-screen", .i = ¶m.calling_screen, .d = "screening indicator of calling party number", .name2value = osmo_cc_screen_name2value, .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM }, { .n = "no-calling", .i = ¶m.no_calling, .d = "disable calling party number", .no_value = 1}, { .n = "calling2", .s = ¶m.calling2, .d = "second calling party number" }, { .n = "calling2-type", .i = ¶m.calling2_type, .d = "type of second calling party number", .name2value = osmo_cc_type_name2value, .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM}, { .n = "calling2-plan", .i = ¶m.calling2_plan, .d = "numbering plan of second calling party number", .name2value = osmo_cc_plan_name2value, .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM }, { .n = "calling2-present", .i = ¶m.calling2_present, .d = "presentation of second calling party number", .name2value = osmo_cc_present_name2value, .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM }, { .n = "calling2-screen", .i = ¶m.calling2_screen, .d = "screening indicator of second calling party number", .name2value = osmo_cc_screen_name2value, .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM }, { .n = "no-calling2", .i = ¶m.no_calling2, .d = "disable seconds calling party number", .no_value = 1 }, { .n = "redirecting", .s = ¶m.redirecting, .d = "redirecting number" }, { .n = "redirecting-type", .i = ¶m.redirecting_type, .d = "type of redirecting number", .name2value = osmo_cc_type_name2value, .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM }, { .n = "redirecting-plan", .i = ¶m.redirecting_plan, .d = "numbering plan of redirecting number", .name2value = osmo_cc_plan_name2value, .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM }, { .n = "redirecting-present", .i = ¶m.redirecting_present, .d = "presentation of redirecting number", .name2value = osmo_cc_present_name2value, .value2name = osmo_cc_present_value2name, .num = OSMO_CC_PRESENT_NUM }, { .n = "redirecting-screen", .i = ¶m.redirecting_screen, .d = "screening indicator of redirecting number", .name2value = osmo_cc_screen_name2value, .value2name = osmo_cc_screen_value2name, .num = OSMO_CC_SCREEN_NUM }, { .n = "redirecting-reason", .i = ¶m.redirecting_reason, .d = "reason for redirecting the call", .name2value = osmo_cc_redir_reason_name2value, .value2name = osmo_cc_redir_reason_value2name, .num = OSMO_CC_REDIR_REASON_NUM }, { .n = "no-redirecting", .i = ¶m.no_redirecting, .d = "disable redirecting number", .no_value = 1 }, { .n = "dialing", .s = ¶m.dialing, .d = "alter dialed number" }, { .n = "dialing-type", .i = ¶m.dialing_type, .d = "type of dialed number", .name2value = osmo_cc_type_name2value, .value2name = osmo_cc_type_value2name, .num = OSMO_CC_TYPE_NUM }, { .n = "dialing-plan", .i = ¶m.dialing_plan, .d = "numbering plan of dialed number", .name2value = osmo_cc_plan_name2value, .value2name = osmo_cc_plan_value2name, .num = OSMO_CC_PLAN_NUM }, { .n = "keypad", .s = ¶m.keypad, .d = "keypulse digit or digits (might be required by some ISDN applications)" }, { .n = NULL } }; static void routing_call(call_t *call, int argc, char *argv[], struct command_def *command_def) { osmo_cc_call_t *att = NULL; uint8_t coding, capability, mode; uint8_t type, plan, present, screen, reason; char number[256]; int e, i, rc, calls = 0; osmo_cc_msg_t *new_msg, *setup_msg; /* if we have a call, we don't add more terminators */ if (call->relation_list->next) { PDEBUG(DROUTER, DEBUG_ERROR, "Multiple call commands from routing are not allowed.\n"); return; } setup_msg = call->setup_msg; next_call: i = parse_params(argc, argv, command_def); /* if more calls, then forward arguments behind colon, otherwise set argc to 0 */ if (i >= 0 && i < argc) { argv += i + 1; argc -= i + 1; } else argc = 0; if (!param.interface) { PDEBUG(DROUTER, DEBUG_ERROR, "'call' command without 'interface' parameter.\n"); goto try_next; } for (e = 0; cc_ep_list[e]; e++) { att = osmo_cc_get_attached_interface(cc_ep_list[e], param.interface); if (att) break; } if (!att) { PDEBUG(DROUTER, DEBUG_ERROR, "'call' command with 'interface' parameter '%s' which is not attached.\n", param.interface); goto try_next; } calls++; /* set call forking, if we have more than one terminating call */ if (calls > 1) call->forking = 1; /* create endpoint */ osmo_cc_call_t *cc_call = osmo_cc_call_new(att->ep); call_relation_t *relation = relation_create(call); /* link with cc */ relation->cc_ep = att->ep; relation->cc_callref = cc_call->callref; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-IND to terminator.\n", relation_name(relation)); /* create setup message */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND); /* interface name */ osmo_cc_add_ie_called_interface(new_msg, param.interface); /* bearer capability */ rc = osmo_cc_get_ie_bearer(setup_msg, 0, &coding, &capability, &mode); if (rc >= 0 || param.bearer_coding || param.bearer_capability || param.bearer_mode) { /* if not preset, use default values */ if (rc < 0) { coding = OSMO_CC_CODING_ITU_T; capability = OSMO_CC_CAPABILITY_AUDIO; mode = OSMO_CC_MODE_CIRCUIT; } /* alter values */ if (param.bearer_coding) coding = param.bearer_coding; if (param.bearer_capability) capability = param.bearer_capability; if (param.bearer_mode) mode = param.bearer_coding; osmo_cc_add_ie_bearer(new_msg, coding, capability, mode); } /* calling */ if (!param.no_calling) { rc = osmo_cc_get_ie_calling(setup_msg, 0, &type, &plan, &present, &screen, number, sizeof(number)); if (rc >= 0 || param.calling_type || param.calling_plan || param.calling_present || param.calling_screen || param.calling) { /* if not preset, use default values */ if (rc < 0) { type = OSMO_CC_TYPE_UNKNOWN; plan = OSMO_CC_PLAN_TELEPHONY; present = 0; screen = 0; number[0] = '\0'; } /* alter values */ if (param.calling_type) type = param.calling_type; if (param.calling_plan) plan = param.calling_plan; if (param.calling_present) present = param.calling_present; if (param.calling_screen) screen = param.calling_screen; if (!param.calling) param.calling = number; osmo_cc_add_ie_calling(new_msg, type, plan, present, screen, param.calling); } } if (!param.no_calling && !param.no_calling2) { rc = osmo_cc_get_ie_calling(setup_msg, 1, &type, &plan, &present, &screen, number, sizeof(number)); if (rc >= 0 || param.calling2_type || param.calling2_plan || param.calling2_present || param.calling2_screen || param.calling2) { /* if not preset, use default values */ if (rc < 0) { type = OSMO_CC_TYPE_UNKNOWN; plan = OSMO_CC_PLAN_TELEPHONY; present = 0; screen = 0; number[0] = '\0'; } /* alter values */ if (param.calling2_type) type = param.calling2_type; if (param.calling2_plan) plan = param.calling2_plan; if (param.calling2_present) present = param.calling2_present; if (param.calling2_screen) screen = param.calling2_screen; if (!param.calling2) param.calling2 = number; osmo_cc_add_ie_calling(new_msg, type, plan, present, screen, param.calling2); } } /* redirecting */ if (!param.no_redirecting) { rc = osmo_cc_get_ie_redir(setup_msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number)); if (rc >= 0 || param.redirecting_type || param.redirecting_plan || param.redirecting_present || param.redirecting_screen || param.redirecting) { /* if not preset, use default values */ if (rc < 0) { type = OSMO_CC_TYPE_UNKNOWN; plan = OSMO_CC_PLAN_TELEPHONY; present = 0; screen = 0; reason = OSMO_CC_REDIR_REASON_UNKNOWN; number[0] = '\0'; } /* alter values */ if (param.redirecting_type) type = param.redirecting_type; if (param.redirecting_plan) plan = param.redirecting_plan; if (param.redirecting_present) present = param.redirecting_present; if (param.redirecting_screen) screen = param.redirecting_screen; if (!param.redirecting) param.redirecting = number; osmo_cc_add_ie_redir(new_msg, type, plan, present, screen, reason, param.redirecting); } } /* dialing */ rc = osmo_cc_get_ie_called(setup_msg, 0, &type, &plan, number, sizeof(number)); if (rc >= 0 || param.dialing_type || param.dialing_plan || param.dialing) { /* if not preset, use default values */ if (rc < 0) { type = OSMO_CC_TYPE_UNKNOWN; plan = OSMO_CC_PLAN_TELEPHONY; } /* alter values */ if (param.dialing_type) type = param.dialing_type; if (param.dialing_plan) plan = param.dialing_plan; if (!param.dialing) param.dialing = ""; osmo_cc_add_ie_called(new_msg, type, plan, param.dialing); } /* keypad */ if (param.keypad) { if (param.keypad[0]) osmo_cc_add_ie_keypad(new_msg, param.keypad); } /* only if RTP-Proxy is used */ if (call->relation_list->rtp_proxy) { PDEBUG(DROUTER, DEBUG_DEBUG, "Sending our codecs to the terminator.\n"); relation->cc_session = osmo_cc_helper_audio_offer(&relation->cc_ep->session_config, relation, call->relation_list->term_codecs, receive_terminator, new_msg, 1); } else /* sdp from originator's setup message */ if (call->relation_list->sdp) osmo_cc_add_ie_sdp(new_msg, call->relation_list->sdp); /* send message to osmo-cc */ osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); /* remember this, since we cannot do RTP-Proxy after */ call->sdp_forwarded = 1; /* store peer info for status */ strncpy(relation->interface, (param.interface) ? : "", sizeof(relation->interface) - 1); strncpy(relation->id, (param.dialing) ? : "", sizeof(relation->id) - 1); /* update call state for status display */ relation->state = CALL_STATE_SETUP; status_needs_update = 1; try_next: /* there is another call */ if (argc) goto next_call; } /* routing orders us to hangup all terminating calls */ static void routing_call_stop(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation; osmo_cc_msg_t *new_msg; /* send message to all terminators, if any */ while (call->relation_list->next) { relation = call->relation_list->next; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-IND to terminator.\n", relation_name(relation)); new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); osmo_cc_add_ie_cause(new_msg, relation->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); relation_destroy(relation); } } /* routing orders us to set call into overlap state */ static void routing_overlap(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation = call->relation_list; osmo_cc_msg_t *new_msg; if (call->state != CALL_STATE_SETUP) return; new_state(call, CALL_STATE_OVERLAP); PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-ACK-IND to originator.\n", relation_name(relation)); /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_ACK_IND); /* send SDP answer */ proxy_send_sdp_answer(relation, new_msg); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } /* routing orders us to set call into proceeding state */ static void routing_proceeding(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation = call->relation_list; osmo_cc_msg_t *new_msg; if (call->state != CALL_STATE_SETUP && call->state != CALL_STATE_OVERLAP) return; new_state(call, CALL_STATE_PROCEEDING); PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-PROC-IND to originator.\n", relation_name(relation)); /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND); /* send SDP answer */ proxy_send_sdp_answer(relation, new_msg); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } /* routing orders us to set call into alerting state */ static void routing_alerting(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation = call->relation_list; osmo_cc_msg_t *new_msg; if (call->state != CALL_STATE_SETUP && call->state != CALL_STATE_OVERLAP && call->state != CALL_STATE_PROCEEDING) return; new_state(call, CALL_STATE_ALERTING); PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-ALERT-IND to originator.\n", relation_name(relation)); /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND); /* send SDP answer */ proxy_send_sdp_answer(relation, new_msg); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } /* routing orders us to set call into answer state */ static void routing_answer(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation = call->relation_list; osmo_cc_msg_t *new_msg; if (call->state != CALL_STATE_SETUP && call->state != CALL_STATE_OVERLAP && call->state != CALL_STATE_PROCEEDING && call->state != CALL_STATE_ALERTING) return; PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-SETUP-CNF to originator.\n", relation_name(relation)); new_state(call, CALL_STATE_CONNECT); /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF); /* send SDP answer */ proxy_send_sdp_answer(relation, new_msg); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } /* routing orders us to dsiconnect/release the call */ struct param_def param_disc_rel[] = { { .n = "isdn-cause", .i = ¶m.isdn_cause, .d = "ISDN cause number (Will be generated, if omitted.)" }, { .n = "sip-cause", .i = ¶m.sip_cause, .d = "SIP cause number (Will be generated, if omitted.)" }, { .n = NULL } }; static void routing_disconnect(call_t *call, int argc, char *argv[], struct command_def *command_def) { call_relation_t *relation = call->relation_list; osmo_cc_msg_t *new_msg; parse_params(argc, argv, command_def); PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-DISC-IND to originator.\n", relation_name(relation)); /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_DISC_IND); /* send SDP answer */ proxy_send_sdp_answer(relation, new_msg); /* add cause */ osmo_cc_add_ie_cause(new_msg, relation->cc_ep->serving_location, param.isdn_cause, param.sip_cause, 0); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); /* send message to all terminators, if any */ for (relation = relation->next; relation; relation = relation->next) { PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-IND to terminator.\n", relation_name(relation)); new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); /* add cause */ osmo_cc_add_ie_cause(new_msg, relation->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } } static void routing_release(call_t *call, int argc, char *argv[], struct command_def *command_def) { call_relation_t *relation = call->relation_list; osmo_cc_msg_t *new_msg; parse_params(argc, argv, command_def); PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-IND to originator.\n", relation_name(relation)); /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); /* add cause */ osmo_cc_add_ie_cause(new_msg, relation->cc_ep->serving_location, param.isdn_cause, param.sip_cause, 0); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); /* send message to all terminators, if any */ for (relation = relation->next; relation; relation = relation->next) { PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-REL-IND to terminator.\n", relation_name(relation)); new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); /* add cause */ osmo_cc_add_ie_cause(new_msg, relation->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } /* destroy call */ call_destroy(call); } #define db2level(db) pow(10, (double)(db) / 20.0) void recv_dtmf(void *priv, char digit, dtmf_meas_t __attribute__((unused)) *meas) { call_relation_t *relation = (call_relation_t *)priv; char digit_string[7] = "dtmf x"; if (!relation->call->routing.routing) return; digit_string[5] = digit; routing_send(&relation->call->routing, digit_string); } /* routing orders us to enable DTMF decoding */ static void routing_dtmf(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation = call->relation_list; dtmf_decode_exit(&relation->dtmf_dec); /* we add 13, because we working at speech level and not at 0dBm, which is -13 dBm */ dtmf_decode_init(&relation->dtmf_dec, relation, recv_dtmf, 8000, db2level(6.0 + 13.0), db2level(-30.0 + 13.0)); relation->dtmf_dec_enable = 1; } /* routing orders us to disable DTMF decoding */ static void routing_dtmf_stop(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { call_relation_t *relation = call->relation_list; dtmf_decode_exit(&relation->dtmf_dec); relation->dtmf_dec_enable = 0; } /* routing failed, release the call */ struct param_def param_error[] = { { .n = "string", .s = ¶m.error, .d = "error string to be displayed", .mandatory = 1 }, { .n = NULL } }; static void routing_error(call_t *call, int __attribute__((unused)) argc, char __attribute__((unused)) *argv[], struct command_def __attribute__((unused)) *command_def) { osmo_cc_msg_t *new_msg; call_relation_t *relation; parse_params(argc, argv, command_def); if (!param.error) { PDEBUG(DROUTER, DEBUG_ERROR, "'error' command reqires an error string as parameter.\n"); return; } PDEBUG(DROUTER, DEBUG_ERROR, "Routing script error: '%s'\n", param.error); /* send message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); osmo_cc_add_ie_cause(new_msg, call->relation_list->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NETWORK_OOO, 0, 0); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); /* send message to all terminators, if any */ for (relation = call->relation_list->next; relation; relation = relation->next) { new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); osmo_cc_add_ie_cause(new_msg, relation->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NETWORK_OOO, 0, 0); osmo_cc_ll_msg(relation->cc_ep, relation->cc_callref, new_msg); } } struct command_def command_def[] = { { "rtp-proxy", routing_rtp_proxy, "Turn on RTP proxy, so that audio processing is possible.", param_rtp_proxy }, { "play", routing_play, "Play given audio file.", param_play }, { "play-stop", routing_play_stop, "Stop playing audio file.", NULL }, { "record", routing_record, "Record to given audio file.", param_record }, { "record-stop", routing_record_stop, "Stop recording audio file.", NULL }, { "tx-gain", routing_tx_gain, "Set gain of audio sent interface.", param_gain }, { "rx-gain", routing_rx_gain, "Set gain of audio received from interface.", param_gain }, { "call", routing_call, "Make one or mulriple calls to given interface.", param_call }, { "call-stop", routing_call_stop, "Stop outgoing call(s).", NULL }, { "overlap", routing_overlap, "Set caller into overlap state, digit may be dialed.", NULL }, { "proceeding", routing_proceeding, "Set caller into proceeding state, no digits may be dialed.", NULL }, { "alerting", routing_alerting, "Set caller into alerting state.", NULL }, { "answer", routing_answer, "Answer call from caller.", NULL }, { "disconnect", routing_disconnect, "Disconnect call towards caller and release callee, if any.", param_disc_rel }, { "release", routing_release, "Release call towards caller and release callee, if any.", param_disc_rel }, { "dtmf", routing_dtmf, "Turn on DTMF detection.", NULL }, { "dtmf-stop", routing_dtmf_stop, "Turn off DTMF detection.", NULL }, { "error", routing_error, "Display error message.", param_error }, { NULL, NULL, NULL, NULL } }; void routing_receive_stdout(routing_t *routing, const char *string) { call_t *call = routing->call; int argc = 0; char *argv[256], *token; int i; /* convert string into tokens */ while ((token = osmo_cc_strtok_quotes(&string))) argv[argc++] = strdup(token); if (!argc) return; for (i = 0; command_def[i].name; i++) { if (!strcasecmp(argv[0], command_def[i].name)) { command_def[i].func(call, argc - 1, argv + 1, &command_def[i]); break; } } if (!command_def[i].name) PDEBUG(DROUTER, DEBUG_ERROR, "Unknown command '%s' from routing.\n", argv[0]); while (argc) free((char *)argv[--argc]); } void routing_receive_stderr(routing_t *routing, const char *string) { if (routing->call->relation_list) PDEBUG(DSTDERR, DEBUG_NOTICE, "(call #%d) Routing STDERR: %s\n", routing->call->num, string); else PDEBUG(DSTDERR, DEBUG_NOTICE, "Routing STDERR: %s\n", string); } void routing_close(routing_t *routing) { call_t *call = routing->call; osmo_cc_msg_t *new_msg; PDEBUG(DROUTER, DEBUG_INFO, "(call #%d) Routing script exitted.\n", call->num); /* if we have a terminating call, it is fine to continue without routing process */ if (call->relation_list->next) return; /* in setup state we change to overlap state, so that routing can be restartet with dialing information added */ if (call->state == CALL_STATE_SETUP) { /* change state */ new_state(call, CALL_STATE_OVERLAP); /* forward message to originator */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_ACK_IND); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); return; } /* in overlap state we wait for more information to be added */ if (call->state == CALL_STATE_OVERLAP) { return; } /* there is no way to dial more digits, so we release the call */ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); osmo_cc_add_ie_cause(new_msg, call->relation_list->cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NO_ROUTE, 0, 0); osmo_cc_ll_msg(call->relation_list->cc_ep, call->relation_list->cc_callref, new_msg); /* destroy call */ call_destroy(call); } void telephone_event(call_relation_t *relation, struct telephone_event *te) { char digit_string[7] = "dtmf x"; if (te->event < 16) { if (!relation->te_started && !te->e && te->volume <= 36) { PDEBUG(DROUTER, DEBUG_INFO, "Received start of Telephone-Event '%d'\n", te->event); relation->te_started = 1; digit_string[5] = "0123456789*#ABCD"[te->event]; } if (relation->te_started && te->e) { PDEBUG(DROUTER, DEBUG_INFO, "Received end of Telephone-Event '%d'\n", te->event); relation->te_started = 0; } } else PDEBUG(DROUTER, DEBUG_INFO, "Received unsupported Telephone-Event '%d'\n", te->event); if (!relation->call->routing.routing) return; if (digit_string[5] != 'x') routing_send(&relation->call->routing, digit_string); } void routing_help(const char *command) { int i, j, k; if (!command) { printf("Available routing commands:\n\n"); for (i = 0; command_def[i].name; i++) { printf("Command: %s\n", command_def[i].name); printf(" Description: %s\n", command_def[i].description); } printf("\nUse '-h ' for detailled description of a command.\n"); return; } for (i = 0; command_def[i].name; i++) { if (!strcasecmp(command, command_def[i].name)) break; } if (!command_def[i].name) { printf("Given Command '%s' unknown!\n", command); return; } printf("Command: %s\n", command_def[i].name); printf("Description: %s\n", command_def[i].description); struct param_def *param_def = command_def[i].param_list; if (!param_def) { printf("This command does not have any parameters.\n"); return; } for (j = 0; param_def[j].n; j++) { printf("Parameter: %s%s\n", param_def[j].n, (param_def[j].mandatory) ? " (manatory)" : ""); printf(" Description: %s\n", param_def[j].d); if (param_def[j].o || param_def[j].t) { for (k = 0; codecs_def[k].payload_name; k++) { printf(" Value: '%s'\n", codecs_def[k].payload_name); } } else if (param_def[j].no_value) { printf(" No value\n"); } else if (param_def[j].i && param_def[j].value2name) { for (k = 0; k < param_def[j].num; k++) { const char *name = param_def[j].value2name(k); if (name && name[0] != '<') printf(" Value: '%d' or '%s'\n", k, name); } } } } #warning add progress, if terminating call sends sdp but call state already reached #warning beim disc muss progress geprueft werden und damit entschieden ob wir audio mitsenden sollen