From fde7cc2ce319bf294ded54da0822672fe33b1923 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sun, 27 Sep 2020 14:17:11 +0200 Subject: [PATCH] Initial GIT import --- .gitignore | 47 + Makefile.am | 5 + configure.ac | 95 ++ git-version-gen | 151 +++ src/Makefile.am | 16 + src/router/Makefile.am | 26 + src/router/audio.c | 286 ++++++ src/router/audio.h | 6 + src/router/call.c | 1905 +++++++++++++++++++++++++++++++++++ src/router/call.h | 84 ++ src/router/display.h | 9 + src/router/display_status.c | 235 +++++ src/router/main.c | 260 +++++ src/router/routing.c | 490 +++++++++ src/router/routing.h | 47 + src/router/rtp_bridge.c | 20 + 16 files changed, 3682 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile.am create mode 100644 configure.ac create mode 100755 git-version-gen create mode 100644 src/Makefile.am create mode 100644 src/router/Makefile.am create mode 100644 src/router/audio.c create mode 100644 src/router/audio.h create mode 100644 src/router/call.c create mode 100644 src/router/call.h create mode 100644 src/router/display.h create mode 100644 src/router/display_status.c create mode 100644 src/router/main.c create mode 100644 src/router/routing.c create mode 100644 src/router/routing.h create mode 100644 src/router/rtp_bridge.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f50561 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +Makefile +Makefile.in +.deps +.libs +*.o +*.lo +*.la +*.pc +aclocal.m4 +acinclude.m4 +aminclude.am +m4/*.m4 +autom4te.cache +compile +config.h* +config.sub +config.log +config.status +config.guess +configure +depcomp +missing +ltmain.sh +install-sh +stamp-h1 +libtool + +.tarball-version +.version +.dirstamp + +Doxyfile + +.*.sw? + +src/libdebug/libdebug.a +src/libg711/libg711.a +src/libjitter/libjitter.a +src/liboptions/liboptions.a +src/libosmocc/libosmocc.a +src/libsample/libsample.a +src/libtimer/libtimer.a +src/libwave/libwave.a +src/libdtmf/libdtmf.a +src/libfm/libfm.a +src/libfilter/libfilter.a +src/router/osmo-cc-router diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..689d568 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..e220053 --- /dev/null +++ b/configure.ac @@ -0,0 +1,95 @@ +AC_INIT([osmo-cc-router], + m4_esyscmd([./git-version-gen .tarball-version]), + [jolly@eversberg.eu]) + +dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ +AC_CONFIG_AUX_DIR([.]) + +AM_INIT_AUTOMAKE([subdir-objects dist-bzip2]) +AC_CONFIG_TESTDIR(tests) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl include release helper +RELMAKE='-include osmo-release.mk' +AC_SUBST([RELMAKE]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +LT_INIT +AC_PROG_LIBTOOL + +AC_CONFIG_MACRO_DIR([m4]) + +AC_ARG_ENABLE(sanitize, + [AS_HELP_STRING( + [--enable-sanitize], + [Compile with address sanitizer enabled], + )], + [sanitize=$enableval], [sanitize="no"]) +if test x"$sanitize" = x"yes" +then + CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined" + CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" +fi + +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + +CFLAGS="$CFLAGS -Wall" +CPPFLAGS="$CPPFLAGS -Wall" + +dnl checks for header files +AC_HEADER_STDC +AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h) + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden"], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST(SYMBOL_VISIBILITY) + +dnl Generate the output +AM_CONFIG_HEADER(config.h) + +AC_CHECK_LIB([m], [main]) +AC_CHECK_LIB([pthread], [main]) + +AC_OUTPUT( + src/liboptions/Makefile + src/libdebug/Makefile + src/libsample/Makefile + src/libtimer/Makefile + src/libjitter/Makefile + src/libosmocc/Makefile + src/libg711/Makefile + src/libwave/Makefile + src/libdtmf/Makefile + src/libfm/Makefile + src/libfilter/Makefile + src/router/Makefile + src/Makefile + Makefile) diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# 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 . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: 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 + * 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 . + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#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 + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Call forking process + * + * A call can be forwarded to one or multiple endpoints (call forking). + * + * In case of call forking, each terminating endpoint that disconnects or + * releases, is removed from the list of terminating endpoints. The cause is + * collected according to mulitpoint process at Q.931. If all endpoints have + * disconnected or released, the originating endpoint is released with the + * collected cause. + * + * If one terminating endpoint answers the call, all other endpoints are + * released with the cause "non-selected user clearing". + * + * If the originating endpoint disconnectes or releases prior answer, all + * terminating endpoints are released with the same cause. + */ + +/* SDP negotiation process + * + * The originating endpoint sends a CC-SETUP-REQ with SDP included. + * + * If no RTP proxy is enabled, the SDP is forwarded towards terminating + * endpoint or multiple terminating endpoints (in case of call forking). The + * first reply with SDP from the terminating endpoint is stored. In case of + * single terminating endpoint, it is forwarded towards the originating + * endpoint with progress indicator set to 1 or 8. In case of multiple + * terminating endpoints (call forking) the SDP is forwarded as soon a + * CC-SETUP-RSP is received from the first terminating endpoint that answers. + * The SDP negotiation is complete. + * + * If RTP proxy is enabled, the SDP is negotiated with the supported codecs of + * this router. The first reply to the CC-SETUP-REQ message will include the + * SDP reply as well as progress indicator set to 8 (if not a CC-SETUP-CNF + * message) towards originating endpoint. The SDP 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libg711/g711.h" +#include "call.h" +#include "audio.h" +#include "display.h" + +call_t *call_list = NULL; +struct timer status_timer; +static osmo_cc_endpoint_t *cc_ep; +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] : ""); + 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 + * 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 . + */ + +#include +#include +#include +#include +#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 = ""; + + /* 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) { + /* */ + memcpy(line, from_if, strlen(from_if)); + memset(color, 3, strlen(from_if)); + } + if (!to_count) { + /* '' */ + 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) { + /* -> '' */ + 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 + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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 []\n", app); +} + +static void print_help() +{ + /* - - */ + printf(" -h --help\n"); + printf(" This help\n"); + printf(" -v --verbose | ,[,[,...]] | 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