Initial GIT import

This commit is contained in:
Andreas Eversberg 2020-09-27 14:17:11 +02:00
commit fde7cc2ce3
16 changed files with 3682 additions and 0 deletions

47
.gitignore vendored Normal file
View File

@ -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

5
Makefile.am Normal file
View File

@ -0,0 +1,5 @@
AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = src

95
configure.ac Normal file
View File

@ -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)

151
git-version-gen Executable file
View File

@ -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 <http://www.gnu.org/licenses/>.
# 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:

16
src/Makefile.am Normal file
View File

@ -0,0 +1,16 @@
AUTOMAKE_OPTIONS = foreign
SUBDIRS = \
liboptions \
libdebug \
libsample \
libtimer \
libjitter \
libosmocc \
libg711 \
libwave \
libdtmf \
libfm \
libfilter \
router

26
src/router/Makefile.am Normal file
View File

@ -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

286
src/router/audio.c Normal file
View File

@ -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;
}

6
src/router/audio.h Normal file
View File

@ -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);

1905
src/router/call.c Normal file

File diff suppressed because it is too large Load Diff

84
src/router/call.h Normal file
View File

@ -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);

9
src/router/display.h Normal file
View File

@ -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);

235
src/router/display_status.c Normal file
View File

@ -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;
}

260
src/router/main.c Normal file
View File

@ -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;
}

490
src/router/routing.c Normal file
View File

@ -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

47
src/router/routing.h Normal file
View File

@ -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);

20
src/router/rtp_bridge.c Normal file
View File

@ -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/>.
*/