summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2020-09-27 14:17:11 +0200
committerAndreas Eversberg <jolly@eversberg.eu>2020-12-29 19:02:56 +0100
commitfde7cc2ce319bf294ded54da0822672fe33b1923 (patch)
treeb3c87039dbc99fa5dc2f67a11a91ae8196e7ab4c /src
Initial GIT import
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am16
-rw-r--r--src/router/Makefile.am26
-rw-r--r--src/router/audio.c286
-rw-r--r--src/router/audio.h6
-rw-r--r--src/router/call.c1905
-rw-r--r--src/router/call.h84
-rw-r--r--src/router/display.h9
-rw-r--r--src/router/display_status.c235
-rw-r--r--src/router/main.c260
-rw-r--r--src/router/routing.c490
-rw-r--r--src/router/routing.h47
-rw-r--r--src/router/rtp_bridge.c20
12 files changed, 3384 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..4fc676a
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,16 @@
+AUTOMAKE_OPTIONS = foreign
+
+SUBDIRS = \
+ liboptions \
+ libdebug \
+ libsample \
+ libtimer \
+ libjitter \
+ libosmocc \
+ libg711 \
+ libwave \
+ libdtmf \
+ libfm \
+ libfilter \
+ router
+
diff --git a/src/router/Makefile.am b/src/router/Makefile.am
new file mode 100644
index 0000000..e7d9384
--- /dev/null
+++ b/src/router/Makefile.am
@@ -0,0 +1,26 @@
+AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
+
+bin_PROGRAMS = \
+ osmo-cc-router
+
+osmo_cc_router_SOURCES = \
+ call.c \
+ routing.c \
+ audio.c \
+ display_status.c \
+ main.c
+
+osmo_cc_router_LDADD = \
+ $(COMMON_LA) \
+ ../libdebug/libdebug.a \
+ ../liboptions/liboptions.a \
+ ../libsample/libsample.a \
+ ../libtimer/libtimer.a \
+ ../libjitter/libjitter.a \
+ ../libosmocc/libosmocc.a \
+ ../libg711/libg711.a \
+ ../libdtmf/libdtmf.a \
+ ../libfm/libfm.a \
+ ../libfilter/libfilter.a \
+ ../libwave/libwave.a
+
diff --git a/src/router/audio.c b/src/router/audio.c
new file mode 100644
index 0000000..0c4b6c3
--- /dev/null
+++ b/src/router/audio.c
@@ -0,0 +1,286 @@
+/* audio handling
+ *
+ * (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Audio flow diagram:
+ *
+ * This diagrams shows the audio processing. The function for each processing
+ * segment is given by the names ending with "()".
+ *
+ * ORIGINATOR
+ *
+ * receive_originator()
+ * | /|\
+ * | |
+ * \|/ |
+ * +-------+ +-------+
+ * |int to | |samples|
+ * |samples| |to int |
+ * +-------+ +-------+
+ * | /|\
+ * +------+ | |
+ * | |/ | |
+ * | DTMF |---| |
+ * | |\ | |
+ * +------+ | |
+ * \|/ |
+ * +-------+ +-------+
+ * | TX- | | RX- |
+ * | GAIN | | GAIN |
+ * +-------+ +-------+
+ * | /|\
+ * | |
+ * | |
+ * +------+ | | +------+
+ * | TX- |/ | | \| RX- |
+ * | |---| |---| |
+ * |JITTER|\ | | /|JITTER|
+ * +------+ | | +------+
+ * | |
+ * +------+ | |
+ * | WAVE | | |
+ * | |_ | |
+ * | PLAY | \ | |
+ * +------+ \| |
+ * | |
+ * \|/ send_originator()
+ *-----------------------------------
+ * send_terminator() /|\
+ * | | +------+
+ * | |\ | WAVE |
+ * | | \_| | call_clock()
+ * | | | PLAY |
+ * \|/ | +------+
+ * +-------+ +-------+
+ * |samples| |int to |
+ * |to int | |samples|
+ * +-------+ +-------+
+ * | /|\
+ * | |
+ * \|/ |
+ * receive_terminator()
+ *
+ * TERMINATOR
+ *
+ * In recording mode:
+ * Data is stored into jitter buffer of each endpoint.
+ * The clock triggers dejittering of TX and RX data and writes it to wave file.
+ *
+ * In playback mode:
+ * The clock triggers read from wave file and forwards it to the originator.
+ *
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+#include "../libdebug/debug.h"
+#include "call.h"
+#include "audio.h"
+
+#define db2level(db) pow(10, (double)db / 20.0)
+
+static void gain_samples(sample_t *samples, int length, double gain)
+{
+ double level = db2level(gain);
+ int i;
+
+ for (i = 0; i < length; i++)
+ *samples++ *= level;
+}
+
+static void send_terminator(call_relation_t *relation, sample_t *samples, int len)
+{
+ int16_t spl[len];
+
+ /* convert samples to int16 */
+ samples_to_int16(spl, samples, len);
+
+ /* encode and send via RTP */
+ osmo_cc_rtp_send(relation->codec, (uint8_t *)spl, len * sizeof(*spl), 1, len);
+}
+
+void receive_originator(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len)
+{
+ call_relation_t *relation = codec->media->session->priv;
+ len = len / 2;
+ sample_t samples[len];
+
+ /* convert int16 to samples */
+ int16_to_samples(samples, (int16_t *)data, len);
+
+ /* dtmf decoding */
+ if (relation->dtmf_dec_enable)
+ dtmf_decode(&relation->dtmf_dec, samples, len);
+
+ /* adjust gain */
+ if (relation->call->tx_gain)
+ gain_samples(samples, len, relation->call->tx_gain);
+
+ /* store to originator jitter buffer */
+ jitter_save(&relation->orig_dejitter, samples, len);
+
+ /* forward to terminators */
+ for (relation = relation->next; relation; relation = relation->next) {
+ if (relation->cc_session && relation->codec && !relation->play.fp)
+ send_terminator(relation, samples, len);
+ }
+}
+
+static void send_originator(call_relation_t *relation, sample_t *samples, int len)
+{
+ int16_t spl[len];
+
+ /* store to terminator jitter buffer */
+ jitter_save(&relation->term_dejitter, samples, len);
+
+ if (relation->call->rx_gain)
+ gain_samples(samples, len, relation->call->rx_gain);
+
+ samples_to_int16(spl, samples, len);
+
+ osmo_cc_rtp_send(relation->codec, (uint8_t *)spl, len * sizeof(*spl), 1, len);
+}
+
+void receive_terminator(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len)
+{
+ call_relation_t *relation = codec->media->session->priv;
+ len = len / 2;
+ sample_t samples[len];
+
+ int16_to_samples(samples, (int16_t *)data, len);
+
+ /* forward to originator, if not a forking call */
+ if (!relation->call->forking) {
+ relation = relation->call->relation_list;
+ if (relation->cc_session && relation->codec && !relation->play.fp)
+ send_originator(relation, samples, len);
+ }
+}
+
+void call_media_handle(void)
+{
+ call_t *call;
+ call_relation_t *relation;
+
+ for (call = call_list; call; call = call->next) {
+ for (relation = call->relation_list; relation; relation = relation->next) {
+ if (relation->cc_session)
+ osmo_cc_session_handle(relation->cc_session);
+ }
+ }
+}
+
+void call_clock(int len)
+{
+ call_t *call;
+ call_relation_t *relation;
+ sample_t buffer[len], buffer2[len], *samples[2];
+ int i;
+ int rc;
+
+ for (call = call_list; call; call = call->next) {
+ relation = call->relation_list;
+ if (!relation->cc_session || !relation->codec)
+ continue;
+ /* play */
+ if (relation->play.fp) {
+ int got = 0;
+ read_again:
+ samples[0] = buffer + got;
+ samples[1] = buffer2 + got;
+ rc = wave_read(&relation->play, samples, len - got);
+ got += rc;
+ /* we have a short read (hit the end) or nothing to play left (hit the end without short read) */
+ if (!relation->play.left) {
+ wave_destroy_playback(&relation->play);
+ if (relation->play_loop) {
+ int samplerate = 0, channels = 0;
+ int rc;
+ rc = wave_create_playback(&relation->play, relation->play_filename, &samplerate, &channels, relation->play_deviation);
+ if (rc >= 0)
+ goto read_again;
+ } else {
+ /* notify routing about finished playback */
+ if (call->routing.routing)
+ routing_send(&call->routing, "wave-finished");
+ }
+ }
+ /* in case wie do not get all samples filled, append silence */
+ while (got < len)
+ buffer[got++] = 0;
+ /* convert stereo to mono */
+ if (relation->play.channels == 2) {
+ for (i = 0; i < len; i++)
+ buffer[i] += buffer2[i];
+ }
+ /* forward audio */
+ if (relation == call->relation_list)
+ send_originator(relation, buffer, len);
+ else
+ send_terminator(relation, buffer, len);
+ }
+ /* record
+ * NOTE: jitter buffer is recorded at send_originator() or send_terminator, so it already includes wave playback */
+ if (relation->rec.fp) {
+ samples[0] = buffer;
+ samples[1] = buffer2;
+ jitter_load(&relation->orig_dejitter, samples[0], len);
+ if (!call->forking && relation->next)
+ jitter_load(&relation->term_dejitter, samples[1], len);
+ else
+ memset(samples[1], 0, len * sizeof(sample_t));
+ wave_write(&relation->rec, samples, len);
+ }
+ }
+}
+
+void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
+{
+ uint16_t *src = (uint16_t *)src_data, *dst;
+ int len = src_len / 2, i;
+
+ dst = malloc(len * 2);
+ if (!dst)
+ return;
+ for (i = 0; i < len; i++)
+ dst[i] = htons(src[i]);
+ *dst_data = (uint8_t *)dst;
+ *dst_len = len * 2;
+}
+
+void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
+{
+ uint16_t *src = (uint16_t *)src_data, *dst;
+ int len = src_len / 2, i;
+
+ dst = malloc(len * 2);
+ if (!dst)
+ return;
+ for (i = 0; i < len; i++)
+ dst[i] = ntohs(src[i]);
+ *dst_data = (uint8_t *)dst;
+ *dst_len = len * 2;
+}
+
diff --git a/src/router/audio.h b/src/router/audio.h
new file mode 100644
index 0000000..390e54f
--- /dev/null
+++ b/src/router/audio.h
@@ -0,0 +1,6 @@
+void receive_originator(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len);
+void receive_terminator(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len);
+void call_media_handle(void);
+void call_clock(int len);
+void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
+void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
diff --git a/src/router/call.c b/src/router/call.c
new file mode 100644
index 0000000..9933a2f
--- /dev/null
+++ b/src/router/call.c
@@ -0,0 +1,1905 @@
+/* call handling
+ *
+ * (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* 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 negoatiation on the
+ * originating side is complete. If the call gets forwarded to a single or
+ * multiple terminating endpoints, an SDP is generated with teh supported
+ * codecs of this router. In case of single terminating endpont, 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 <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#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;
+static const char *routing_script, *routing_shell;
+
+static struct osmo_cc_helper_audio_codecs codecs[] = {
+// { "L16", 8000, 1, encode_l16, decode_l16 }, FIXME: make codecs selectable, if more codecs are supported in the future
+ { "PCMA", 8000, 1, g711_encode_alaw, g711_decode_alaw },
+ { "PCMU", 8000, 1, g711_encode_ulaw, g711_decode_ulaw },
+ { 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;
+
+ display_status_start();
+
+ for (cc_call = cc_ep->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;
+
+ for (cc_call = cc_ep->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 *ep, const char *_routing_script, const char *_routing_shell)
+{
+ cc_ep = ep;
+ 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, 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(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_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];
+ 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);
+ }
+
+ /* 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(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);
+ 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(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(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(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(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(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(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(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(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(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(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(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, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NONSE_USER_CLR, 0, 0);
+ osmo_cc_ll_msg(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(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(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(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, cc_ep->serving_location, call->collect_cause, 0, 0);
+ osmo_cc_ll_msg(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(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(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(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(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(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, cc_ep->serving_location, call->collect_cause, 0, 0);
+ osmo_cc_ll_msg(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(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(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(cc_ep, call->relation_list->cc_callref, new_msg);
+ return;
+ }
+}
+
+/* handle message from upper layer */
+void cc_message(osmo_cc_endpoint_t __attribute__((unused)) *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(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 const char *value_of_param(const char *arg, const char *param, const char **value_p)
+{
+ if (!!strncmp(arg, param, strlen(param)))
+ return NULL;
+ arg += strlen(param);
+
+ if (*arg == '\0') {
+ if (value_p)
+ *value_p = "";
+ return "";
+ }
+
+ if (*arg != '=')
+ return NULL;
+ arg++;
+
+ if (value_p)
+ *value_p = arg;
+ return arg;
+}
+
+/* routing orders us to activate rtp proxy */
+static void routing_rtp_proxy(call_t *call)
+{
+ 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;
+ }
+
+ /* 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 */
+static void routing_play(call_t *call, int argc, const char *argv[])
+{
+ call_relation_t *relation = call->relation_list;
+ const char *filename = NULL, *volume = "1.0", *loop = NULL;
+ int i;
+ 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;
+ }
+
+ /* loop through all arguments and stop if there is a ':' */
+ for (i = 0; i < argc ; i++) {
+ if (value_of_param(argv[i], "volume", &volume));
+ else if (value_of_param(argv[i], "loop", &loop));
+ else {
+ if (filename) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires only one file name, you specified '%s' and '%s'.\n", filename, argv[i]);
+ return;
+ }
+ filename = argv[i];
+ }
+ }
+
+ if (!filename) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "'play' command reqires a file name as parameter.\n");
+ return;
+ }
+
+ deviation = 1.0 / SPEECH_LEVEL * atof(volume);
+ rc = wave_create_playback(&relation->play, filename, &samplerate, &channels, deviation);
+ if (rc < 0)
+ return;
+ strncpy(relation->play_filename, 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 (loop)
+ relation->play_loop = 1;
+}
+
+/* routing orders us stop playing a wave file */
+static void routing_play_stop(call_t *call)
+{
+ call_relation_t *relation = call->relation_list;
+
+ wave_destroy_playback(&relation->play);
+}
+
+/* routing orders us to record a wave file */
+static void routing_record(call_t *call, int argc, const char *argv[])
+{
+ call_relation_t *relation = call->relation_list;
+ const char *filename = NULL, *volume = "1.0";
+ int i;
+ 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;
+ }
+
+ /* loop through all arguments and stop if there is a ':' */
+ for (i = 0; i < argc ; i++) {
+ if (value_of_param(argv[i], "volume", &volume));
+ else {
+ if (filename) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "'record' command reqires only one file name, you specified '%s' and '%s'.\n", filename, argv[i]);
+ return;
+ }
+ filename = argv[i];
+ }
+ }
+
+ if (!filename) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "'record' command reqires a file name as parameter.\n");
+ return;
+ }
+
+ rc = wave_create_record(&relation->rec, filename, samplerate, channels, 1.0 / SPEECH_LEVEL / atof(volume));
+ if (rc < 0)
+ return;
+}
+
+/* routing orders us stop recording a wave file */
+static void routing_record_stop(call_t *call)
+{
+ call_relation_t *relation = call->relation_list;
+
+ wave_destroy_record(&relation->rec);
+}
+
+/* routing orders us to set local gain */
+static void routing_gain(call_t *call, int argc, const char *argv[], int tx)
+{
+ int i;
+ const char *gain = NULL;
+
+ if (!call->relation_list->rtp_proxy) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "RTP-Proxy must be enabled to record a file!\n");
+ return;
+ }
+
+ /* loop through all arguments and stop if there is a ':' */
+ for (i = 0; i < argc ; i++) {
+ if (gain) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "'%s-gain' command reqires only parameter, you specified '%s' and '%s'.\n", (tx) ? "tx" : "rx", gain, argv[i]);
+ return;
+ }
+ gain = argv[i];
+ }
+
+ if (!gain) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "'%s-gain' command reqires a gain value as parameter.\n", (tx) ? "tx" : "rx");
+ return;
+ }
+
+ if (tx)
+ call->tx_gain = atof(gain);
+ else
+ call->rx_gain = atof(gain);
+
+}
+
+/* routing orders us to call remote end */
+static void routing_call(call_t *call, int argc, const char *argv[])
+{
+ const char *interface;
+ const char *bearer_coding, *bearer_capability, *bearer_mode;
+ const char *calling, *calling_type, *calling_plan, *calling_present, *calling_screen, *no_calling;
+ const char *calling2, *calling2_type, *calling2_plan, *calling2_present, *calling2_screen, *no_calling2;
+ const char *redirecting, *redirecting_type, *redirecting_plan, *redirecting_present, *redirecting_screen, *redirecting_reason, *no_redirecting;
+ const char *dialing, *dialing_type, *dialing_plan;
+ const char *keypad;
+ uint8_t coding, capability, mode;
+ uint8_t type, plan, present, screen, reason;
+ char number[256];
+ int 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:
+ interface = NULL;
+ bearer_coding = bearer_capability = bearer_mode = NULL;
+ calling = calling_type = calling_plan = calling_present = calling_screen = no_calling = NULL;
+ calling2 = calling2_type = calling2_plan = calling2_present = calling2_screen = no_calling2 = NULL;
+ redirecting = redirecting_type = redirecting_plan = redirecting_present = redirecting_screen = redirecting_reason = no_redirecting = NULL;
+ dialing = dialing_type = dialing_plan = NULL;
+ keypad = NULL;
+ /* loop through all arguments and stop if there is a ':' */
+ for (i = 0; i < argc && argv[i][0] != ':'; i++) {
+ if (value_of_param(argv[i], "interface", &interface));
+ else if (value_of_param(argv[i], "bearer-coding", &bearer_coding));
+ else if (value_of_param(argv[i], "bearer-capability", &bearer_capability));
+ else if (value_of_param(argv[i], "bearer-mode", &bearer_mode));
+ else if (value_of_param(argv[i], "calling", &calling));
+ else if (value_of_param(argv[i], "calling-type", &calling_type));
+ else if (value_of_param(argv[i], "calling-plan", &calling_plan));
+ else if (value_of_param(argv[i], "calling-present", &calling_present));
+ else if (value_of_param(argv[i], "calling-screen", &calling_screen));
+ else if (value_of_param(argv[i], "no-calling", &no_calling));
+ else if (value_of_param(argv[i], "calling2", &calling2));
+ else if (value_of_param(argv[i], "calling2-type", &calling2_type));
+ else if (value_of_param(argv[i], "calling2-plan", &calling2_plan));
+ else if (value_of_param(argv[i], "calling2-present", &calling2_present));
+ else if (value_of_param(argv[i], "calling2-screen", &calling2_screen));
+ else if (value_of_param(argv[i], "no-calling2", &no_calling2));
+ else if (value_of_param(argv[i], "redirecting", &redirecting));
+ else if (value_of_param(argv[i], "redirecting-type", &redirecting_type));
+ else if (value_of_param(argv[i], "redirecting-plan", &redirecting_plan));
+ else if (value_of_param(argv[i], "redirecting-present", &redirecting_present));
+ else if (value_of_param(argv[i], "redirecting-screen", &redirecting_screen));
+ else if (value_of_param(argv[i], "redirecting-reason", &redirecting_reason));
+ else if (value_of_param(argv[i], "no-redirecting", &no_redirecting));
+ else if (value_of_param(argv[i], "dialing", &dialing));
+ else if (value_of_param(argv[i], "dialing-type", &dialing_type));
+ else if (value_of_param(argv[i], "dialing-plan", &dialing_plan));
+ else if (value_of_param(argv[i], "keypad", &keypad));
+ else
+ PDEBUG(DROUTER, DEBUG_ERROR, "Unknown 'call' parameter '%s' from routing.\n", argv[i]);
+ }
+ /* if more calls, then forward arguments behind colon, otherwise set argc to 0 */
+ if (i < argc) {
+ argv += i + 1;
+ argc -= i + 1;
+ } else
+ argc = 0;
+
+ if (!interface) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "'call' command without 'interface' parameter.\n");
+ 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(cc_ep);
+ call_relation_t *relation = relation_create(call);
+
+ /* link with cc */
+ 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, interface);
+
+ /* bearer capability */
+ rc = osmo_cc_get_ie_bearer(setup_msg, 0, &coding, &capability, &mode);
+ if (rc >= 0 || bearer_coding || bearer_capability || 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 (bearer_coding)
+ coding = atoi(bearer_coding);
+ if (bearer_capability)
+ capability = atoi(bearer_capability);
+ if (bearer_mode)
+ mode = atoi(bearer_coding);
+ osmo_cc_add_ie_bearer(new_msg, coding, capability, mode);
+ }
+
+ /* calling */
+ if (!no_calling) {
+ rc = osmo_cc_get_ie_calling(setup_msg, 0, &type, &plan, &present, &screen, number, sizeof(number));
+ if (rc >= 0 || calling_type || calling_plan || calling_present || calling_screen || 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 (calling_type)
+ type = atoi(calling_type);
+ if (calling_plan)
+ plan = atoi(calling_plan);
+ if (calling_present)
+ present = atoi(calling_present);
+ if (calling_screen)
+ screen = atoi(calling_screen);
+ if (!calling)
+ calling = number;
+ osmo_cc_add_ie_calling(new_msg, type, plan, present, screen, calling);
+ }
+ }
+ if (!no_calling && !no_calling2) {
+ rc = osmo_cc_get_ie_calling(setup_msg, 1, &type, &plan, &present, &screen, number, sizeof(number));
+ if (rc >= 0 || calling2_type || calling2_plan || calling2_present || calling2_screen || 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 (calling2_type)
+ type = atoi(calling2_type);
+ if (calling2_plan)
+ plan = atoi(calling2_plan);
+ if (calling2_present)
+ present = atoi(calling2_present);
+ if (calling2_screen)
+ screen = atoi(calling2_screen);
+ if (!calling2)
+ calling2 = number;
+ osmo_cc_add_ie_calling(new_msg, type, plan, present, screen, calling2);
+ }
+ }
+
+ /* redirecting */
+ if (!no_redirecting) {
+ rc = osmo_cc_get_ie_redir(setup_msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number));
+ if (rc >= 0 || redirecting_type || redirecting_plan || redirecting_present || redirecting_screen || 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 (redirecting_type)
+ type = atoi(redirecting_type);
+ if (redirecting_plan)
+ plan = atoi(redirecting_plan);
+ if (redirecting_present)
+ present = atoi(redirecting_present);
+ if (redirecting_screen)
+ screen = atoi(redirecting_screen);
+ if (!redirecting)
+ redirecting = number;
+ osmo_cc_add_ie_redir(new_msg, type, plan, present, screen, reason, redirecting);
+ }
+ }
+
+ /* dialing */
+ rc = osmo_cc_get_ie_called(setup_msg, 0, &type, &plan, number, sizeof(number));
+ if (rc >= 0 || dialing_type || dialing_plan || dialing) {
+ /* if not preset, use default values */
+ if (rc < 0) {
+ type = OSMO_CC_TYPE_UNKNOWN;
+ plan = OSMO_CC_PLAN_TELEPHONY;
+ }
+ /* alter values */
+ if (dialing_type)
+ type = atoi(dialing_type);
+ if (dialing_plan)
+ plan = atoi(dialing_plan);
+ if (!dialing)
+ dialing = "";
+ osmo_cc_add_ie_called(new_msg, type, plan, dialing);
+ }
+
+ /* keypad */
+ if (keypad) {
+ if (keypad[0])
+ osmo_cc_add_ie_keypad(new_msg, 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, 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(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, interface, sizeof(relation->interface) - 1);
+ strncpy(relation->id, 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)
+{
+ 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, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0);
+ osmo_cc_ll_msg(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)
+{
+ 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(cc_ep, relation->cc_callref, new_msg);
+}
+
+/* routing orders us to set call into proceeding state */
+static void routing_proceeding(call_t *call)
+{
+ 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(cc_ep, relation->cc_callref, new_msg);
+}
+
+/* routing orders us to set call into alerting state */
+static void routing_alerting(call_t *call)
+{
+ 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(cc_ep, relation->cc_callref, new_msg);
+}
+
+/* routing orders us to set call into answer state */
+static void routing_answer(call_t *call)
+{
+ 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(cc_ep, relation->cc_callref, new_msg);
+}
+
+/* routing orders us to dsiconnect/release the call */
+static void routing_disc_rel(call_t *call, int argc, const char *argv[], int disconnect)
+{
+ call_relation_t *relation = call->relation_list;
+ uint8_t cause = 0;
+ osmo_cc_msg_t *new_msg;
+ int i;
+
+ /* get cause, if any */
+ for (i = 0; i < argc; i++) {
+ if (!strncmp(argv[i], "cause=", 6))
+ cause = atoi(argv[i] + 6);
+ else
+ PDEBUG(DROUTER, DEBUG_ERROR, "Unknown 'disconnect' / 'release' parameter '%s' from routing.\n", argv[i]);
+ }
+
+ PDEBUG(DROUTER, DEBUG_DEBUG, "%s CC-%s-IND to originator.\n", relation_name(relation), (disconnect) ? "DISC" : "REL");
+
+ /* send message to originator */
+ new_msg = osmo_cc_new_msg((disconnect) ? OSMO_CC_MSG_DISC_IND : OSMO_CC_MSG_REL_IND);
+ if (disconnect) {
+ /* send SDP answer */
+ proxy_send_sdp_answer(relation, new_msg);
+ }
+ /* add cause */
+ osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, cause, 0, 0);
+ osmo_cc_ll_msg(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, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR, 0, 0);
+ osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
+ }
+
+ if (!disconnect) {
+ /* 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)
+{
+ 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, wich 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)
+{
+ call_relation_t *relation = call->relation_list;
+
+ dtmf_decode_exit(&relation->dtmf_dec);
+
+ relation->dtmf_dec_enable = 0;
+}
+
+/* routing failed, release the call */
+static void routing_error(call_t *call, const char *error)
+{
+ osmo_cc_msg_t *new_msg;
+ call_relation_t *relation;
+
+ PDEBUG(DROUTER, DEBUG_ERROR, "Routing script error: '%s'\n", error);
+
+ /* send message to originator */
+ new_msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND);
+ osmo_cc_add_ie_cause(new_msg, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NETWORK_OOO, 0, 0);
+ osmo_cc_ll_msg(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, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NETWORK_OOO, 0, 0);
+ osmo_cc_ll_msg(cc_ep, relation->cc_callref, new_msg);
+ }
+}
+
+#if 0
+/* routing script says something */
+static void routing_say(call_t *call, const char *error)
+{
+ char text[1024] = "";
+ fuer alle argv
+ fuege text hinzu
+ PDEBUG(DROUTER, DEBUG_NOTICE, "Routing script says: %s\n", text);
+ das script anpassen
+}
+#endif
+
+void routing_receive_stdout(routing_t *routing, const char *string)
+{
+ call_t *call = routing->call;
+ int argc = 0;
+ const char *argv[256], *token;
+
+ /* convert string into tokens */
+ while ((token = osmo_cc_strtok_quotes(&string)))
+ argv[argc++] = strdup(token);
+ if (!argc)
+ return;
+
+ if (!strcasecmp(argv[0], "rtp-proxy"))
+ routing_rtp_proxy(call);
+ else
+ if (!strcasecmp(argv[0], "play"))
+ routing_play(call, argc - 1, argv + 1);
+ else
+ if (!strcasecmp(argv[0], "play-stop"))
+ routing_play_stop(call);
+ else
+ if (!strcasecmp(argv[0], "record"))
+ routing_record(call, argc - 1, argv + 1);
+ else
+ if (!strcasecmp(argv[0], "record-stop"))
+ routing_record_stop(call);
+ else
+ if (!strcasecmp(argv[0], "tx-gain"))
+ routing_gain(call, argc - 1, argv + 1, 1);
+ else
+ if (!strcasecmp(argv[0], "rx-gain"))
+ routing_gain(call, argc - 1, argv + 1, 0);
+ else
+ if (!strcasecmp(argv[0], "call"))
+ routing_call(call, argc - 1, argv + 1);
+ else
+ if (!strcasecmp(argv[0], "call-stop"))
+ routing_call_stop(call);
+ else
+ if (!strcasecmp(argv[0], "overlap"))
+ routing_overlap(call);
+ else
+ if (!strcasecmp(argv[0], "proceeding"))
+ routing_proceeding(call);
+ else
+ if (!strcasecmp(argv[0], "alerting"))
+ routing_alerting(call);
+ else
+ if (!strcasecmp(argv[0], "answer"))
+ routing_answer(call);
+ else
+ if (!strcasecmp(argv[0], "disconnect"))
+ routing_disc_rel(call, argc - 1, argv + 1, 1);
+ else
+ if (!strcasecmp(argv[0], "release"))
+ routing_disc_rel(call, argc - 1, argv + 1, 0);
+ else
+ if (!strcasecmp(argv[0], "dtmf"))
+ routing_dtmf(call);
+ else
+ if (!strcasecmp(argv[0], "dtmf-stop"))
+ routing_dtmf_stop(call);
+ else
+ if (!strcasecmp(argv[0], "error"))
+ routing_error(call, (argc > 1) ? argv[1] : "<unknown>");
+ else
+#if 0
+ if (!strcasecmp(argv[0], "say"))
+ routing_say(call, argc - 1, argv + 1, 0);
+ else
+#endif
+ 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(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, cc_ep->serving_location, OSMO_CC_ISDN_CAUSE_NO_ROUTE, 0, 0);
+ osmo_cc_ll_msg(cc_ep, call->relation_list->cc_callref, new_msg);
+
+ /* destroy call */
+ call_destroy(call);
+}
+
+#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
+
diff --git a/src/router/call.h b/src/router/call.h
new file mode 100644
index 0000000..12b62c5
--- /dev/null
+++ b/src/router/call.h
@@ -0,0 +1,84 @@
+
+#include "../libtimer/timer.h"
+#include "../libosmocc/endpoint.h"
+#include "../libosmocc/helper.h"
+#include "../libsample/sample.h"
+#include "../libjitter/jitter.h"
+#include "../libwave/wave.h"
+#include "../libdtmf/dtmf_decode.h"
+
+
+enum call_state {
+ CALL_STATE_IDLE = 0,
+ CALL_STATE_SETUP,
+ CALL_STATE_OVERLAP,
+ CALL_STATE_PROCEEDING,
+ CALL_STATE_ALERTING,
+ CALL_STATE_CONNECT,
+ CALL_STATE_DISC_FROM_ORIG,
+ CALL_STATE_DISC_FROM_TERM,
+};
+
+#include "routing.h"
+
+/* relation to upper layer */
+typedef struct call_relation {
+ struct call_relation *next;
+ int num; /* number counter for debugging */
+ struct call *call; /* points to the call */
+ uint32_t cc_callref; /* callref for each releation */
+ const char *sdp; /* received SDP? */
+ int tones_recv; /* are inband tones available? */
+ jitter_t orig_dejitter; /* jitter buffer for call recording (originating source) */
+ jitter_t term_dejitter; /* jitter buffer for call recording (terminating source) */
+
+ char interface[256]; /* interface */
+ char id[256]; /* caller ID / dialing*/
+ enum call_state state; /* state for status display */
+
+ int rtp_proxy;
+ osmo_cc_session_t *cc_session;
+ int codec_negotiated;
+ osmo_cc_session_codec_t *codec;
+
+ wave_play_t play; /* play a wave file */
+ int play_loop; /* set to play loop */
+ char play_filename[256];/* stored for reopen on loop */
+ double play_deviation; /* stored for reopen on loop */
+ wave_rec_t rec; /* record a wave file */
+ dtmf_dec_t dtmf_dec; /* dtmf decoder */
+ int dtmf_dec_enable;/* feed decoder with data */
+} call_relation_t;
+
+/* call instance */
+typedef struct call {
+ struct call *next;
+ int num; /* number counter for debugging */
+
+ /* call */
+ enum call_state state; /* current state of call */
+ osmo_cc_msg_t *setup_msg; /* stored setup message for later IE forwarding */
+ char dialing_number[256]; /* accumulated dial string (setup + info) */
+ char dialing_keypad[256]; /* accumulated keypad string (setup + info) */
+ int forking; /* set, if call is forked (started with 2 or more calls) */
+ uint8_t collect_cause; /* cause from forking calls */
+ call_relation_t *relation_list; /* list of all upper layer relations */
+ /* NOTE: the first relation is always the originator */
+ int tones_sent; /* did we announce inband tones? */
+ int sdp_forwarded; /* if set, we cannot do RTP-Proxy anymore */
+
+ /* routing */
+ routing_t routing;
+
+ /* audio */
+ double tx_gain; /* gain of what is received from originator */
+ double rx_gain; /* gain of what is transmitted to the originator */
+} call_t;
+
+extern call_t *call_list;
+
+int call_init(osmo_cc_endpoint_t *ep, const char *_routing_script, const char *_routing_shell);
+void call_exit(void);
+int call_handle(void);
+void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg);
+
diff --git a/src/router/display.h b/src/router/display.h
new file mode 100644
index 0000000..6491629
--- /dev/null
+++ b/src/router/display.h
@@ -0,0 +1,9 @@
+
+#define MAX_DISPLAY_WIDTH 1024
+#define MAX_HEIGHT_STATUS 64
+
+void display_status_on(int on);
+void display_status_start(void);
+void display_status_line(const char *from_if, int from_count, const char *from_id, int to_count, const char *to_if, const char *to_id, enum call_state to_state);
+void display_status_end(void);
+
diff --git a/src/router/display_status.c b/src/router/display_status.c
new file mode 100644
index 0000000..7f3723f
--- /dev/null
+++ b/src/router/display_status.c
@@ -0,0 +1,235 @@
+/* display status functions
+ *
+ * (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include "../libdebug/debug.h"
+#include "call.h"
+#include "display.h"
+
+static int status_on = 0;
+static int line_count = 0;
+static int lines_total = 0;
+static char screen[MAX_HEIGHT_STATUS][MAX_DISPLAY_WIDTH];
+static char screen_color[MAX_HEIGHT_STATUS][MAX_DISPLAY_WIDTH];
+
+static void print_status(int on)
+{
+ int i, j;
+ int w, h;
+ char color, last_color = -1;
+
+ get_win_size(&w, &h);
+ if (w > MAX_DISPLAY_WIDTH - 1)
+ w = MAX_DISPLAY_WIDTH - 1;
+
+ if (w > MAX_DISPLAY_WIDTH)
+ w = MAX_DISPLAY_WIDTH;
+ h--;
+ if (h > lines_total)
+ h = lines_total;
+
+ printf("\0337\033[H");
+ for (i = 0; i < h; i++) {
+ j = 0;
+ if (on) {
+ for (j = 0; j < w; j++) {
+ color = screen_color[i][j];
+ if (screen[i][j] > ' ' && last_color != color) {
+ printf("\033[%d;3%dm", color / 10, color % 10);
+ last_color = color;
+ }
+ putchar(screen[i][j]);
+ }
+ } else {
+ for (j = 0; j < w; j++)
+ putchar(' ');
+ }
+ putchar('\n');
+ }
+ printf("\033[0;39m\0338"); fflush(stdout);
+}
+
+void display_status_on(int on)
+{
+ if (status_on)
+ print_status(0);
+
+ if (on < 0)
+ status_on = 1 - status_on;
+ else
+ status_on = on;
+
+ if (status_on)
+ print_status(1);
+
+ if (status_on)
+ debug_limit_scroll = lines_total;
+ else
+ debug_limit_scroll = 0;
+}
+
+/* start status display */
+void display_status_start(void)
+{
+ memset(screen, ' ', sizeof(screen));
+ memset(screen_color, 7, sizeof(screen_color));
+ memset(screen[0], '-', sizeof(screen[0]));
+ memcpy(screen[0] + 4, "Call Status", 11);
+ line_count = 1;
+}
+
+void display_status_line(const char *from_if, int from_count, const char *from_id, int to_count, const char *to_if, const char *to_id, enum call_state to_state)
+{
+ char line[MAX_DISPLAY_WIDTH + 4096];
+ char color[MAX_DISPLAY_WIDTH + 4096];
+ static int from_id_pos, to_if_pos;
+
+ memset(color, 7, sizeof(color)); // make valgrind happy
+
+ if (line_count == MAX_HEIGHT_STATUS)
+ return;
+
+ if (!from_if)
+ from_if = "<unknown>";
+
+ /* at first interface or when it changes */
+ if (!from_count && !to_count) {
+ from_id_pos = strlen(from_if) + 1;
+ line_count++;
+ }
+
+ /* at first call */
+ if (from_id && !to_count) {
+ to_if_pos = from_id_pos + 1 + strlen(from_id) + 1 + 4; /* quote,id,quote,arrow */
+ }
+
+ /* check line count again */
+ if (line_count == MAX_HEIGHT_STATUS)
+ return;
+
+ if (!from_id) {
+ /* only interface is given, since there is no call */
+ strcpy(line, from_if);
+ memset(color, 3, strlen(from_if));
+ } else {
+ /* originating call */
+ memset(line, ' ', to_if_pos);
+ if (!from_count && !to_count) {
+ /* <if> */
+ memcpy(line, from_if, strlen(from_if));
+ memset(color, 3, strlen(from_if));
+ }
+ if (!to_count) {
+ /* '<id>' */
+ line[from_id_pos] = '\'';
+ memcpy(line + from_id_pos + 1, from_id, strlen(from_id));
+ line[from_id_pos + 1 + strlen(from_id)] = '\'';
+ memset(color + from_id_pos, 1, 1 + strlen(from_id) + 1);
+ }
+ line[to_if_pos] = '\0';
+ /* terminating call */
+ if (to_if && to_id) {
+ int to_id_pos, to_state_pos;
+ /* arrow in the first line of a call */
+ if (!to_count) {
+ /* -> <if> '<id>' */
+ line[to_if_pos - 3] = '-';
+ line[to_if_pos - 2] = '>';
+ color[to_if_pos - 3] = 7;
+ color[to_if_pos - 2] = 7;
+ }
+ sprintf(line + to_if_pos, "%s '%s' ", to_if, to_id);
+ memset(color + to_if_pos, 3, strlen(to_if));
+ to_id_pos = to_if_pos + strlen(to_if) + 1;
+ memset(color + to_id_pos, 2, 1 + strlen(to_id) + 1);
+ to_state_pos = to_id_pos + 1 + strlen(to_id) + 1 + 1;
+
+ switch (to_state) {
+ case CALL_STATE_SETUP:
+ strcpy(line + to_state_pos, "[setup]");
+ /* magenta */
+ memset(color + to_state_pos + 1, 15, 5);
+ break;
+ case CALL_STATE_OVERLAP:
+ strcpy(line + to_state_pos, "[overlap]");
+ /* green */
+ memset(color + to_state_pos + 1, 12, 7);
+ break;
+ case CALL_STATE_PROCEEDING:
+ strcpy(line + to_state_pos, "[proceeding]");
+ /* cyan */
+ memset(color + to_state_pos + 1, 16, 10);
+ break;
+ case CALL_STATE_ALERTING:
+ strcpy(line + to_state_pos, "[alerting]");
+ /* yellow */
+ memset(color + to_state_pos + 1, 13, 8);
+ break;
+ case CALL_STATE_CONNECT:
+ strcpy(line + to_state_pos, "[connect]");
+ /* white */
+ memset(color + to_state_pos + 1, 17, 7);
+ break;
+ case CALL_STATE_DISC_FROM_ORIG:
+ strcpy(line + to_state_pos, "[out disconnect]");
+ /* red */
+ memset(color + to_state_pos + 1, 11, 14);
+ break;
+ case CALL_STATE_DISC_FROM_TERM:
+ strcpy(line + to_state_pos, "[in disconnect]");
+ /* red */
+ memset(color + to_state_pos + 1, 11, 13);
+ break;
+ default:
+ ;
+ }
+ }
+ }
+
+ /* store line without CR, but not more than MAX_DISPLAY_WIDTH - 1 */
+ line[MAX_DISPLAY_WIDTH - 1] = '\0';
+ memcpy(screen[line_count], line, strlen(line));
+ memcpy(screen_color[line_count], color, strlen(line));
+ line_count++;
+}
+
+void display_status_end(void)
+{
+ if (line_count < MAX_HEIGHT_STATUS)
+ line_count++;
+
+ if (line_count < MAX_HEIGHT_STATUS) {
+ memset(screen[line_count], '-', sizeof(screen[line_count]));
+ line_count++;
+ }
+ /* if last total lines exceed current line count, keep it, so removed lines are overwritten with spaces */
+ if (line_count > lines_total)
+ lines_total = line_count;
+ if (status_on)
+ print_status(1);
+ /* set new total lines */
+ lines_total = line_count;
+ if (status_on)
+ debug_limit_scroll = lines_total;
+}
+
+
diff --git a/src/router/main.c b/src/router/main.c
new file mode 100644
index 0000000..b3a404e
--- /dev/null
+++ b/src/router/main.c
@@ -0,0 +1,260 @@
+/* osmo-cc-router main
+ *
+ * (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <termios.h>
+#include "../libdebug/debug.h"
+#include "../liboptions/options.h"
+#include "../libg711/g711.h"
+#include "call.h"
+#include "audio.h"
+#include "display.h"
+
+int num_kanal = 1;
+osmo_cc_endpoint_t *cc_ep = NULL;
+
+static char *routing_script = "~/.osmocom/router/routing.sh";
+static char *routing_shell = "bash";
+#define MAX_CC_ARGS 1024
+static int cc_argc = 0;
+static const char *cc_argv[MAX_CC_ARGS];
+
+static void print_usage(const char *app)
+{
+ printf("Usage: %s [<options>]\n", app);
+}
+
+static void print_help()
+{
+ /* - - */
+ printf(" -h --help\n");
+ printf(" This help\n");
+ printf(" -v --verbose <level> | <level>,<category>[,<category>[,...]] | list\n");
+ printf(" Use 'list' to get a list of all levels and categories\n");
+ printf(" Verbose level: digit of debug level (default = '%d')\n", debuglevel);
+ printf(" Verbose level+category: level digit followed by one or more categories\n");
+ printf(" -r --routing-script <script>\n");
+ printf(" Define the script/executable that is executed to perform routing\n");
+ printf(" decision for each call. (default = %s)\n", routing_script);
+ printf(" -s --routing-shell <>\n");
+ printf(" Define the shell to run the routing scrip. (default = %s)\n", routing_shell);
+ printf(" -C --cc \"<osmo-cc arg>\" [--cc ...]\n");
+ printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n");
+}
+
+#define OPT_XXX 256
+
+static void add_options(void)
+{
+ option_add('h', "help", 0);
+ option_add('v', "verbose", 1);
+ option_add('r', "routing-script", 1);
+ option_add('s', "routing-shell", 1);
+ option_add('C', "cc", 1);
+}
+
+static int handle_options(int short_option, int argi, char **argv)
+{
+ int rc;
+
+ switch (short_option) {
+ case 'h':
+ print_usage(argv[0]);
+ print_help();
+ return 0;
+ case 'v':
+ if (!strcasecmp(argv[argi], "list")) {
+ debug_list_cat();
+ return 0;
+ }
+ rc = parse_debug_opt(argv[argi]);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
+ return rc;
+ }
+ break;
+ case 'r':
+ routing_script = strdup(argv[argi]);
+ break;
+ case 's':
+ routing_shell = strdup(argv[argi]);
+ break;
+ case 'C':
+ if (!strcasecmp(argv[argi], "help")) {
+ osmo_cc_help();
+ return 0;
+ }
+ if (cc_argc == MAX_CC_ARGS) {
+ fprintf(stderr, "Too many osmo-cc args!\n");
+ break;
+ }
+ cc_argv[cc_argc++] = strdup(argv[argi]);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 1;
+}
+
+static int quit = 0;
+void sighandler(int sigset)
+{
+ if (sigset == SIGHUP || sigset == SIGPIPE)
+ return;
+
+ fprintf(stderr, "\nSignal %d received.\n", sigset);
+
+ quit = 1;
+}
+
+static int get_char()
+{
+ struct timeval tv = {0, 0};
+ fd_set fds;
+ char c = 0;
+ int __attribute__((__unused__)) rc;
+
+ FD_ZERO(&fds);
+ FD_SET(0, &fds);
+ select(0+1, &fds, NULL, NULL, &tv);
+ if (FD_ISSET(0, &fds)) {
+ rc = read(0, &c, 1);
+ return c;
+ } else
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ double last_time_call = 0, now;
+ int argi, rc;
+ struct termios term, term_orig;
+ int c;
+
+ /* init FM */
+ fm_init(0);
+
+ /* init codecs (for recording) */
+ g711_init();
+
+ /* handle options / config file */
+ add_options();
+ rc = options_config_file("~/.osmocom/router/router.conf", handle_options);
+ if (rc < 0)
+ return 0;
+ argi = options_command_line(argc, argv, handle_options);
+ if (argi <= 0)
+ return argi;
+
+ /* init osmo-cc endpoint */
+ cc_ep = calloc(1, sizeof(*cc_ep));
+ if (!cc_ep)
+ goto error;
+ rc = osmo_cc_new(cc_ep, OSMO_CC_VERSION, NULL, OSMO_CC_LOCATION_PRIV_SERV_LOC_USER, cc_message, NULL, NULL, cc_argc, cc_argv);
+ if (rc < 0)
+ goto error;
+
+ /* init call handling */
+ call_init(cc_ep, routing_script, routing_shell);
+
+ /* prepare terminal */
+ tcgetattr(0, &term_orig);
+ term = term_orig;
+ term.c_lflag &= ~(ISIG|ICANON|ECHO);
+ term.c_cc[VMIN]=1;
+ term.c_cc[VTIME]=2;
+ tcsetattr(0, TCSANOW, &term);
+
+ /* catch signals */
+ signal(SIGINT, sighandler);
+ signal(SIGHUP, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+
+ printf("\nRouter is ready to process calls.\n\n");
+
+ while (!quit) {
+ int w;
+
+ /* send clock calls to play/record audio files */
+ now = get_time();
+ if (now - last_time_call >= 0.1)
+ last_time_call = now;
+ if (now - last_time_call >= 0.020) {
+ last_time_call += 0.020;
+ /* call clock every 20ms */
+ call_clock(160);
+ }
+
+ process_timer();
+ call_media_handle();
+ do {
+ w = 0;
+ w |= osmo_cc_handle();
+ w |= call_handle();
+ } while (w);
+ usleep(1000);
+
+ /* process keyboard input */
+next_char:
+ c = get_char();
+ switch (c) {
+ case 3:
+ printf("CTRL+c received, quitting!\n");
+ quit = 1;
+ goto next_char;
+ case 'c':
+ display_status_on(-1);
+ goto next_char;
+ }
+ }
+
+ /* reset signals */
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTSTP, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+
+ /* reset terminal */
+ tcsetattr(0, TCSANOW, &term_orig);
+
+
+error:
+ if (cc_ep) {
+ /* exit call handling */
+ call_exit();
+
+ /* exit osmo-cc endpoint */
+ osmo_cc_delete(cc_ep);
+ free(cc_ep);
+ }
+
+ /* exit FM */
+ fm_exit();
+
+ return 0;
+}
+
diff --git a/src/router/routing.c b/src/router/routing.c
new file mode 100644
index 0000000..118ef78
--- /dev/null
+++ b/src/router/routing.c
@@ -0,0 +1,490 @@
+/* call routing
+ *
+ * (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <signal.h>
+#include "../libdebug/debug.h"
+#include "call.h"
+
+extern char **environ;
+
+static const char *env_int(const char *name, int value)
+{
+ char *string = malloc(strlen(name) + 20);
+
+ sprintf(string, "CC_%s=%d", name, value);
+
+ return string;
+}
+
+static const char *env_string(const char *name, const char *value)
+{
+ char *string = malloc(strlen(name) + strlen(value) + 8);
+
+ sprintf(string, "CC_%s=%s", name, value);
+
+ return string;
+}
+
+static void enqueue_string(struct string_queue **queue_p, const char *string, const char *suffix)
+{
+ struct string_queue *queue;
+
+ queue = calloc(1, sizeof(*queue));
+ queue->string = malloc(strlen(string) + strlen(suffix) + 1);
+ strcpy(queue->string, string);
+ strcat(queue->string, suffix);
+
+ while (*queue_p)
+ queue_p = &((*queue_p)->next);
+ *queue_p = queue;
+}
+
+static char *dequeue_string(struct string_queue **queue_p)
+{
+ struct string_queue *queue = *queue_p;
+ char *string;
+
+ if (!queue)
+ return NULL;
+
+ string = queue->string;
+ *queue_p = queue->next;
+ free(queue);
+
+ return string;
+}
+
+/* prepare environment with setup info */
+void routing_env_msg(routing_t *routing, osmo_cc_msg_t *msg)
+{
+ uint8_t coding, capability, mode;
+ uint8_t type, plan, present, screen, reason;
+ char number[256];
+ int rc, i;
+
+ for (i = 0; environ[i]; i++) {
+ routing->envp[routing->envc++] = strdup(environ[i]);
+ }
+
+ rc = osmo_cc_get_ie_bearer(msg, 0, &coding, &capability, &mode);
+ if (rc >= 0) {
+ routing->envp[routing->envc++] = env_int("BEARER_CODING", coding);
+ routing->envp[routing->envc++] = env_int("BEARER_CAPABILITY", capability);
+ routing->envp[routing->envc++] = env_int("BEARER_MODE", mode);
+ }
+
+ rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, number, sizeof(number));
+ if (rc >= 0) {
+ routing->envp[routing->envc++] = env_int("CALLING_TYPE", type);
+ routing->envp[routing->envc++] = env_int("CALLING_PLAN", plan);
+ routing->envp[routing->envc++] = env_int("CALLING_PRESENT", present);
+ routing->envp[routing->envc++] = env_int("CALLING_SCREEN", screen);
+ routing->envp[routing->envc++] = env_string("CALLING", number);
+ }
+ rc = osmo_cc_get_ie_calling_interface(msg, 0, number, sizeof(number));
+ if (rc >= 0) {
+ routing->envp[routing->envc++] = env_string("CALLING_INTERFACE", number);
+ }
+
+ rc = osmo_cc_get_ie_calling(msg, 2, &type, &plan, &present, &screen, number, sizeof(number));
+ if (rc >= 0) {
+ routing->envp[routing->envc++] = env_int("CALLING2_TYPE", type);
+ routing->envp[routing->envc++] = env_int("CALLING2_PLAN", plan);
+ routing->envp[routing->envc++] = env_int("CALLING2_PRESENT", present);
+ routing->envp[routing->envc++] = env_int("CALLING2_SCREEN", screen);
+ routing->envp[routing->envc++] = env_string("CALLING2", number);
+ }
+
+ rc = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, number, sizeof(number));
+ if (rc >= 0) {
+ routing->envp[routing->envc++] = env_int("REDIRECTING_TYPE", type);
+ routing->envp[routing->envc++] = env_int("REDIRECTING_PLAN", plan);
+ routing->envp[routing->envc++] = env_int("REDIRECTING_PRESENT", present);
+ routing->envp[routing->envc++] = env_int("REDIRECTING_SCREEN", screen);
+ routing->envp[routing->envc++] = env_int("REDIRECTING_REASON", reason);
+ routing->envp[routing->envc++] = env_string("REDIRECTING", number);
+ }
+
+ rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, number, sizeof(number));
+ if (rc >= 0) {
+ routing->envp[routing->envc++] = env_int("DIALING_TYPE", type);
+ routing->envp[routing->envc++] = env_int("DIALING_PLAN", plan);
+ } else
+ number[0] = 0;
+ /* variable must always be present, so it can be updated when overlap dialing */
+ routing->envc_dialing = routing->envc;
+ routing->envp[routing->envc++] = env_string("DIALING", number);
+
+ rc = osmo_cc_get_ie_keypad(msg, 0, number, sizeof(number));
+ if (rc < 0)
+ number[0] = 0;
+ /* variable must always be present, so it can be updated when overlap dialing */
+ routing->envc_keypad = routing->envc;
+ routing->envp[routing->envc++] = env_string("KEYPAD", number);
+
+ rc = osmo_cc_get_ie_complete(msg, 0);
+ if (rc >= 0)
+ routing->envp[routing->envc++] = env_int("COMPLETE", 1);
+
+ routing->envp[routing->envc++] = NULL;
+}
+
+/* update environment with info message */
+void routing_env_dialing(routing_t *routing, const char *number, const char *keypad)
+{
+ free((char *)routing->envp[routing->envc_dialing]);
+ routing->envp[routing->envc_dialing] = env_string("DIALING", number);
+
+ free((char *)routing->envp[routing->envc_keypad]);
+ routing->envp[routing->envc_keypad] = env_string("KEYPAD", keypad);
+}
+
+void routing_env_free(routing_t *routing)
+{
+ /* remove env */
+ while (routing->envc) {
+ free((char *)routing->envp[--(routing->envc)]);
+ routing->envp[routing->envc] = NULL;
+ }
+}
+
+/* run script */
+void routing_start(routing_t *routing, const char *script, const char *shell)
+{
+ int in_pipe[2], out_pipe[2], err_pipe[2];
+ pid_t pid;
+ char x;
+ int rc, flags;
+
+ rc = pipe(in_pipe);
+ if (rc < 0) {
+ epipe:
+ PDEBUG(DROUTER, DEBUG_ERROR, "pipe() failed: errno=%d\n", errno);
+ abort();
+ }
+ rc = pipe(out_pipe);
+ if (rc < 0)
+ goto epipe;
+ rc = pipe(err_pipe);
+ if (rc < 0)
+ goto epipe;
+
+ pid = fork();
+ if (pid < 0) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "fork() failed: errno=%d\n", errno);
+ abort();
+ }
+ if (pid == 0) {
+ const char *argv[] = { shell, "-c", script, NULL };
+ struct rlimit rlim;
+ int i;
+
+ /* CHILD */
+ /* redirect pipes to stdio */
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ dup2(in_pipe[0], STDIN_FILENO);
+ dup2(out_pipe[1], STDOUT_FILENO);
+ dup2(err_pipe[1], STDERR_FILENO);
+
+ /* close all file descriptors except std* fss */
+ getrlimit(RLIMIT_NOFILE, &rlim);
+ for (i = 3; i < (int)rlim.rlim_cur; i++)
+ close(i);
+
+ /* tell father that we closed all fds */
+ putchar('x');
+ fflush(stdout);
+
+ /* become script */
+ rc = execvpe(argv[0], (char * const*)argv, (char * const*)routing->envp);
+ if (rc < 0) {
+ printf("error \"%s\"\n", strerror(errno));
+ }
+
+ _exit(0);
+ }
+
+ /* PARENT */
+ PDEBUG(DROUTER, DEBUG_DEBUG, "Running script '%s' as child %d\n", script, pid);
+
+ /* wait for clild to complete closing file descriptors */
+ rc = read(out_pipe[0], &x, 1);
+ if (rc != 1 || x != 'x') {
+ PDEBUG(DROUTER, DEBUG_ERROR, "communication with child failed!\n");
+ kill(pid, SIGKILL);
+ abort();
+ }
+
+ /* close unused file descriptors, except the child's side
+ * this is because we need to keep it open to get all data from stdout/stderr
+ * after the child exitted. for some reason the pipe is flused on close.
+ */
+ close(in_pipe[0]);
+
+ /* make nonblocking IO */
+ flags = fcntl(in_pipe[1], F_GETFL);
+ flags |= O_NONBLOCK;
+ fcntl(in_pipe[1], F_SETFL, flags);
+ flags = fcntl(out_pipe[0], F_GETFL);
+ flags |= O_NONBLOCK;
+ fcntl(out_pipe[0], F_SETFL, flags);
+ flags = fcntl(err_pipe[0], F_GETFL);
+ flags |= O_NONBLOCK;
+ fcntl(err_pipe[0], F_SETFL, flags);
+
+ /* attach pipes and pid to routing */
+ routing->script_pid = pid;
+ routing->script_stdin = in_pipe[1];
+ routing->script_stdout = out_pipe[0];
+ routing->script_stdout_child = out_pipe[1];
+ routing->script_stderr = err_pipe[0];
+ routing->script_stderr_child = err_pipe[1];
+
+ routing->routing = 1;
+
+ PDEBUG(DROUTER, DEBUG_DEBUG, "Routing script started.\n");
+}
+
+
+void routing_stop(routing_t *routing)
+{
+ char *string;
+
+ /* kill or at least try to */
+ if (routing->script_pid) {
+ kill(routing->script_pid, SIGTERM);
+ routing->script_pid = 0;
+ }
+
+ /* close files towards script */
+ if (routing->script_stdin) {
+ close(routing->script_stdin);
+ routing->script_stdin = 0;
+ }
+ if (routing->script_stdout) {
+ close(routing->script_stdout);
+ routing->script_stdout = 0;
+ close(routing->script_stdout_child);
+ routing->script_stdout_child = 0;
+ }
+ if (routing->script_stderr) {
+ close(routing->script_stderr);
+ routing->script_stderr = 0;
+ close(routing->script_stderr_child);
+ routing->script_stderr_child = 0;
+ }
+
+ /* flush queues */
+ while (routing->stdin_queue) {
+ string = dequeue_string(&routing->stdin_queue);
+ free(string);
+ }
+ while (routing->stdout_queue) {
+ string = dequeue_string(&routing->stdout_queue);
+ free(string);
+ }
+ routing->stdout_pos = 0;
+ while (routing->stderr_queue) {
+ string = dequeue_string(&routing->stderr_queue);
+ free(string);
+ }
+ routing->stderr_pos = 0;
+
+ routing->routing = 0;
+
+ PDEBUG(DROUTER, DEBUG_DEBUG, "Routing script stopped.\n");
+}
+
+
+void routing_send(routing_t *routing, const char *string)
+{
+ PDEBUG(DROUTER, DEBUG_DEBUG, "Sending line to routing script: '%s'\n", string);
+ enqueue_string(&routing->stdin_queue, string, "\n");
+}
+
+static int routing_handle_stdin(routing_t *routing)
+{
+ char *string;
+ int rc;
+
+ /* write to script */
+ if (routing->stdin_queue && routing->script_stdin) {
+ rc = write(routing->script_stdin, routing->stdin_queue->string, strlen(routing->stdin_queue->string));
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return -EAGAIN;
+ }
+ if (rc <= 0) {
+ close(routing->script_stdin);
+ routing->script_stdin = 0;
+ return 0;
+ }
+ string = dequeue_string(&routing->stdin_queue);
+ free(string);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int routing_handle_stdout(routing_t *routing)
+{
+ int rc, i;
+
+ /* read from script */
+ if (routing->script_stdout) {
+ rc = read(routing->script_stdout, routing->stdout_buffer + routing->stdout_pos, sizeof(routing->stdout_buffer) - routing->stdout_pos);
+ if (rc < 0) {
+ if (errno == EAGAIN) {
+ /* if script has terminated (pid is set to 0) and stdout/stderr queue is empty */
+ if (!routing->script_pid && !routing->stdout_queue && !routing->stderr_queue) {
+ routing_stop(routing);
+ routing_close(routing);
+ return 0;
+ }
+ return -EAGAIN;
+ }
+ }
+ if (rc <= 0) {
+ close(routing->script_stdout);
+ routing->script_stdout = 0;
+ return 0;
+ }
+ routing->stdout_pos += rc;
+ i = 0;
+ while (i < routing->stdout_pos) {
+ if (routing->stdout_buffer[i] != '\n') {
+ i++;
+ continue;
+ }
+ routing->stdout_buffer[i] = '\0';
+ enqueue_string(&routing->stdout_queue, routing->stdout_buffer, "");
+ i++;
+ if (i < routing->stdout_pos)
+ memcpy(routing->stdout_buffer, routing->stdout_buffer + i, routing->stdout_pos - i);
+ routing->stdout_pos -= i;
+ i = 0;
+ }
+ if (i == sizeof(routing->stdout_buffer)) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "Output line from script too long, please fix!\n");
+ routing->stdout_pos = 0;
+ }
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int routing_handle_stderr(routing_t *routing)
+{
+ int rc, i;
+
+ /* read from script */
+ if (routing->script_stderr) {
+ rc = read(routing->script_stderr, routing->stderr_buffer + routing->stderr_pos, sizeof(routing->stderr_buffer) - routing->stderr_pos);
+ if (rc < 0) {
+ if (errno == EAGAIN) {
+ /* if script has terminated (pid is set to 0) and stdout/stderr queue is empty */
+ if (!routing->script_pid && !routing->stdout_queue && !routing->stderr_queue) {
+ routing_stop(routing);
+ routing_close(routing);
+ return 0;
+ }
+ return -EAGAIN;
+ }
+ }
+ if (rc <= 0) {
+ close(routing->script_stderr);
+ routing->script_stderr = 0;
+ return 0;
+ }
+ routing->stderr_pos += rc;
+ i = 0;
+ while (i < routing->stderr_pos) {
+ if (routing->stderr_buffer[i] != '\n') {
+ i++;
+ continue;
+ }
+ routing->stderr_buffer[i] = '\0';
+ enqueue_string(&routing->stderr_queue, routing->stderr_buffer, "");
+ i++;
+ if (i < routing->stderr_pos)
+ memcpy(routing->stderr_buffer, routing->stderr_buffer + i, routing->stderr_pos - i);
+ routing->stderr_pos -= i;
+ i = 0;
+ }
+ if (i == sizeof(routing->stderr_buffer)) {
+ PDEBUG(DROUTER, DEBUG_ERROR, "Output line from script too long, please fix!\n");
+ routing->stderr_pos = 0;
+ }
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* handle everything that has to do with routing
+ * note that work may cause the call to be destroyed
+ */
+int routing_handle(routing_t *routing)
+{
+ char *string;
+ int rc;
+
+ rc = routing_handle_stdin(routing);
+ if (rc == 0)
+ return 1;
+ rc = routing_handle_stdout(routing);
+ if (rc == 0)
+ return 1;
+ rc = routing_handle_stderr(routing);
+ if (rc == 0)
+ return 1;
+
+ if (routing->stdout_queue) {
+ string = dequeue_string(&routing->stdout_queue);
+ PDEBUG(DROUTER, DEBUG_DEBUG, "Routing script returned line from stdout: '%s'\n", string);
+ routing_receive_stdout(routing, string);
+ free(string);
+ return 1;
+ }
+ if (routing->stderr_queue) {
+ string = dequeue_string(&routing->stderr_queue);
+ routing_receive_stderr(routing, string);
+ free(string);
+ return 1;
+ }
+
+ return 0;
+}
+
+#warning achja: beim router darf rtp-proxy und call nur erfolgen, wenn noch kein codec negoitiated wurde. call darf aber nach call erfolgen, wenn rtp-proxy verwendet wird. tones und record darf erfolgen wemm rtp-proxy verwendet wird
diff --git a/src/router/routing.h b/src/router/routing.h
new file mode 100644
index 0000000..bab5502
--- /dev/null
+++ b/src/router/routing.h
@@ -0,0 +1,47 @@
+
+struct string_queue {
+ struct string_queue *next;
+ char *string;
+};
+
+struct call;
+
+typedef struct routing {
+ struct call *call;
+
+ int routing; /* set, if routing is performed (script runs) */
+
+ int envc; /* number of environment variables */
+ const char *envp[256]; /* environment variables */
+ int envc_dialing; /* envc index for dialing number */
+ int envc_keypad; /* envc index for keypad */
+
+ pid_t script_pid; /* pid of routing script */
+ int script_stdin; /* pipe to stdin */
+ int script_stdout; /* pipe from stdout */
+ int script_stdout_child; /* child side of pipe */
+ int script_stderr; /* pipe from stderr */
+ int script_stderr_child; /* child side of pipe */
+
+ struct string_queue *stdin_queue; /* strings to write to script */
+ char stdout_buffer[1024]; /* line buffer when reading from script */
+ int stdout_pos; /* number of characters in buffer */
+ struct string_queue *stdout_queue; /* strings read from script */
+ char stderr_buffer[1024]; /* line buffer when reading from script */
+ int stderr_pos; /* number of characters in buffer */
+ struct string_queue *stderr_queue; /* strings read from script */
+} routing_t;
+
+void routing_env_msg(routing_t *routing, osmo_cc_msg_t *msg);
+void routing_env_dialing(routing_t *routing, const char *number, const char *keypad);
+void routing_env_free(routing_t *routing);
+void routing_start(routing_t *routing, const char *script, const char *shell);
+void routing_stop(routing_t *routing);
+void routing_send(routing_t *routing, const char *string);
+int routing_handle(routing_t *routing);
+
+/* callbacks */
+void routing_receive_stdout(routing_t *routing, const char *string);
+void routing_receive_stderr(routing_t *routing, const char *string);
+void routing_close(routing_t *routing);
+
diff --git a/src/router/rtp_bridge.c b/src/router/rtp_bridge.c
new file mode 100644
index 0000000..f822fef
--- /dev/null
+++ b/src/router/rtp_bridge.c
@@ -0,0 +1,20 @@
+/* RTP bridging and recording
+ *
+ * (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+