diff --git a/.gitignore b/.gitignore index ce92abd..ad37cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,11 @@ 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/isdn/osmo-cc-misdn-endpoint diff --git a/src/libdebug/Makefile.am b/src/libdebug/Makefile.am new file mode 100644 index 0000000..210a097 --- /dev/null +++ b/src/libdebug/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libdebug.a + +libdebug_a_SOURCES = \ + debug.c + diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c new file mode 100644 index 0000000..946963d --- /dev/null +++ b/src/libdebug/debug.c @@ -0,0 +1,192 @@ +/* Simple debug functions for level and category filtering + * + * (C) 2016 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 "debug.h" + +const char *debug_level[] = { + "debug ", + "info ", + "notice ", + "error ", + NULL, +}; + +struct debug_cat { + const char *name; + const char *color; +} debug_cat[] = { + { "options", "\033[0;33m" }, + { "dsp", "\033[0;31m" }, + { "isdn", "\033[1;35m" }, + { "misdn", "\033[0;34m" }, + { "dss1", "\033[1;34m" }, + { "cc", "\033[1;32m" }, + { NULL, NULL } +}; + +int debuglevel = DEBUG_INFO; +uint64_t debug_mask = ~0; +extern int num_kanal; + +void _printdebug(const char *file, const char __attribute__((unused)) *function, int line, int cat, int level, const char *kanal, const char *fmt, ...) +{ + char buffer[4096], *b = buffer; + int s = sizeof(buffer) - 1; + const char *p; + va_list args; + + if (debuglevel > level) + return; + + buffer[sizeof(buffer) - 1] = '\0'; + + /* if kanal is used, prefix the channel number */ + if (num_kanal > 1 && kanal) { + sprintf(buffer, "(chan %s) ", kanal); + b = strchr(buffer, '\0'); + s -= strlen(buffer); + } + + if (!(debug_mask & ((uint64_t)1 << cat))) + return; + + va_start(args, fmt); + vsnprintf(b, s, fmt, args); + va_end(args); + + while ((p = strchr(file, '/'))) + file = p + 1; + printf("%s%s:%4d %s: %s\033[0;39m", debug_cat[cat].color, file, line, debug_level[level], buffer); + fflush(stdout); +} + +const char *debug_amplitude(double level) +{ + static char text[42]; + + strcpy(text, " : "); + if (level > 1.0) + level = 1.0; + if (level < -1.0) + level = -1.0; + text[20 + (int)(level * 20)] = '*'; + + return text; +} + +#define level2db(level) (20 * log10(level)) + +const char *debug_db(double level_db) +{ + static char text[128]; + int l; + + strcpy(text, ": . : . : . : . : . : . : . : . | . : . : . : . : . : . : . : . :"); + if (level_db <= 0.0) + return text; + l = (int)round(level2db(level_db)); + if (l > 48) + return text; + if (l < -48) + return text; + text[l + 48] = '*'; + + return text; +} + +void debug_list_cat(void) +{ + int i; + + printf("Give number of debug level:\n"); + for (i = 0; debug_level[i]; i++) + printf(" %d = %s\n", i, debug_level[i]); + printf("\n"); + + printf("Give name(s) of debug category:\n"); + for (i = 0; debug_cat[i].name; i++) + printf(" %s%s\033[0;39m\n", debug_cat[i].color, debug_cat[i].name); + printf("\n"); +} + +int parse_debug_opt(const char *optarg) +{ + int i, max_level = 0; + char *dstring, *p; + + for (i = 0; debug_level[i]; i++) + max_level = i; + + dstring = strdup(optarg); + p = strsep(&dstring, ","); + for (i = 0; i < p[i]; i++) { + if (p[i] < '0' || p[i] > '9') { + fprintf(stderr, "Only digits are allowed for debug level!\n"); + return -EINVAL; + } + } + debuglevel = atoi(p); + if (debuglevel > max_level) { + fprintf(stderr, "Debug level too high, use 'list' to show available levels!\n"); + return -EINVAL; + } + if (dstring) + debug_mask = 0; + while((p = strsep(&dstring, ","))) { + for (i = 0; debug_cat[i].name; i++) { + if (!strcasecmp(p, debug_cat[i].name)) + break; + } + if (!debug_cat[i].name) { + fprintf(stderr, "Given debug category '%s' unknown, use 'list' to show available categories!\n", p); + return -EINVAL; + } + debug_mask |= ((uint64_t)1 << i); + } + + return 0; +} + +const char *debug_hex(const uint8_t *data, int len) +{ + static char *text = NULL; + char *p; + int i; + + if (text) + free(text); + p = text = calloc(1, len * 3 + 1); + for (i = 0; i < len; i++) { + sprintf(p, "%02x ", *data++); + p += 3; + } + if (text[0]) + p[-1] = '\0'; + + return text; +} + diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h new file mode 100644 index 0000000..63cd23c --- /dev/null +++ b/src/libdebug/debug.h @@ -0,0 +1,34 @@ + +#define DEBUG_DEBUG 0 /* debug info, not for normal use */ +#define DEBUG_INFO 1 /* all info about process */ +#define DEBUG_NOTICE 2 /* something unexpected happens */ +#define DEBUG_ERROR 3 /* there is an error with this software */ + +#define DOPTIONS 0 +#define DDSP 1 +#define DISDN 2 +#define DMISDN 3 +#define DDSS1 4 +#define DCC 5 + +void get_win_size(int *w, int *h); + +#define PDEBUG(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, NULL, fmt, ## arg) +#define PDEBUG_CHAN(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, CHAN, fmt, ## arg) +void _printdebug(const char *file, const char *function, int line, int cat, int level, const char *chan_str, const char *fmt, ...) __attribute__ ((__format__ (__printf__, 7, 8))); + +const char *debug_amplitude(double level); +const char *debug_db(double level_db); + +void debug_list_cat(void); +int parse_debug_opt(const char *opt); + +extern int debuglevel; + +extern void (*clear_console_text)(void); +extern void (*print_console_text)(void); + +extern int debug_limit_scroll; + +const char *debug_hex(const uint8_t *data, int len); + diff --git a/src/libg711/Makefile.am b/src/libg711/Makefile.am new file mode 100644 index 0000000..15223bf --- /dev/null +++ b/src/libg711/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libg711.a + +libg711_a_SOURCES = \ + g711.c + diff --git a/src/libg711/g711.c b/src/libg711/g711.c new file mode 100644 index 0000000..cd195f9 --- /dev/null +++ b/src/libg711/g711.c @@ -0,0 +1,537 @@ +/*****************************************************************************\ +** ** +** PBX4Linux ** +** ** +**---------------------------------------------------------------------------** +** Copyright: Andreas Eversberg (GPL) ** +** ** +** audio conversions for alaw and ulaw ** +** ** +\*****************************************************************************/ + +#include +#include +#include + +/* ulaw -> signed 16-bit */ +static int16_t g711_ulaw_flipped_to_linear[256] = +{ + 0x8284, 0x8684, 0x8a84, 0x8e84, 0x9284, 0x9684, 0x9a84, 0x9e84, + 0xa284, 0xa684, 0xaa84, 0xae84, 0xb284, 0xb684, 0xba84, 0xbe84, + 0xc184, 0xc384, 0xc584, 0xc784, 0xc984, 0xcb84, 0xcd84, 0xcf84, + 0xd184, 0xd384, 0xd584, 0xd784, 0xd984, 0xdb84, 0xdd84, 0xdf84, + 0xe104, 0xe204, 0xe304, 0xe404, 0xe504, 0xe604, 0xe704, 0xe804, + 0xe904, 0xea04, 0xeb04, 0xec04, 0xed04, 0xee04, 0xef04, 0xf004, + 0xf0c4, 0xf144, 0xf1c4, 0xf244, 0xf2c4, 0xf344, 0xf3c4, 0xf444, + 0xf4c4, 0xf544, 0xf5c4, 0xf644, 0xf6c4, 0xf744, 0xf7c4, 0xf844, + 0xf8a4, 0xf8e4, 0xf924, 0xf964, 0xf9a4, 0xf9e4, 0xfa24, 0xfa64, + 0xfaa4, 0xfae4, 0xfb24, 0xfb64, 0xfba4, 0xfbe4, 0xfc24, 0xfc64, + 0xfc94, 0xfcb4, 0xfcd4, 0xfcf4, 0xfd14, 0xfd34, 0xfd54, 0xfd74, + 0xfd94, 0xfdb4, 0xfdd4, 0xfdf4, 0xfe14, 0xfe34, 0xfe54, 0xfe74, + 0xfe8c, 0xfe9c, 0xfeac, 0xfebc, 0xfecc, 0xfedc, 0xfeec, 0xfefc, + 0xff0c, 0xff1c, 0xff2c, 0xff3c, 0xff4c, 0xff5c, 0xff6c, 0xff7c, + 0xff88, 0xff90, 0xff98, 0xffa0, 0xffa8, 0xffb0, 0xffb8, 0xffc0, + 0xffc8, 0xffd0, 0xffd8, 0xffe0, 0xffe8, 0xfff0, 0xfff8, 0xffff, + 0x7d7c, 0x797c, 0x757c, 0x717c, 0x6d7c, 0x697c, 0x657c, 0x617c, + 0x5d7c, 0x597c, 0x557c, 0x517c, 0x4d7c, 0x497c, 0x457c, 0x417c, + 0x3e7c, 0x3c7c, 0x3a7c, 0x387c, 0x367c, 0x347c, 0x327c, 0x307c, + 0x2e7c, 0x2c7c, 0x2a7c, 0x287c, 0x267c, 0x247c, 0x227c, 0x207c, + 0x1efc, 0x1dfc, 0x1cfc, 0x1bfc, 0x1afc, 0x19fc, 0x18fc, 0x17fc, + 0x16fc, 0x15fc, 0x14fc, 0x13fc, 0x12fc, 0x11fc, 0x10fc, 0x0ffc, + 0x0f3c, 0x0ebc, 0x0e3c, 0x0dbc, 0x0d3c, 0x0cbc, 0x0c3c, 0x0bbc, + 0x0b3c, 0x0abc, 0x0a3c, 0x09bc, 0x093c, 0x08bc, 0x083c, 0x07bc, + 0x075c, 0x071c, 0x06dc, 0x069c, 0x065c, 0x061c, 0x05dc, 0x059c, + 0x055c, 0x051c, 0x04dc, 0x049c, 0x045c, 0x041c, 0x03dc, 0x039c, + 0x036c, 0x034c, 0x032c, 0x030c, 0x02ec, 0x02cc, 0x02ac, 0x028c, + 0x026c, 0x024c, 0x022c, 0x020c, 0x01ec, 0x01cc, 0x01ac, 0x018c, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, + 0x00f4, 0x00e4, 0x00d4, 0x00c4, 0x00b4, 0x00a4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, + 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +/* alaw -> signed 16-bit */ +static int16_t g711_alaw_flipped_to_linear[256] = +{ + 0x13fc, 0xec04, 0x0144, 0xfebc, 0x517c, 0xae84, 0x051c, 0xfae4, + 0x0a3c, 0xf5c4, 0x0048, 0xffb8, 0x287c, 0xd784, 0x028c, 0xfd74, + 0x1bfc, 0xe404, 0x01cc, 0xfe34, 0x717c, 0x8e84, 0x071c, 0xf8e4, + 0x0e3c, 0xf1c4, 0x00c4, 0xff3c, 0x387c, 0xc784, 0x039c, 0xfc64, + 0x0ffc, 0xf004, 0x0104, 0xfefc, 0x417c, 0xbe84, 0x041c, 0xfbe4, + 0x083c, 0xf7c4, 0x0008, 0xfff8, 0x207c, 0xdf84, 0x020c, 0xfdf4, + 0x17fc, 0xe804, 0x018c, 0xfe74, 0x617c, 0x9e84, 0x061c, 0xf9e4, + 0x0c3c, 0xf3c4, 0x0084, 0xff7c, 0x307c, 0xcf84, 0x030c, 0xfcf4, + 0x15fc, 0xea04, 0x0164, 0xfe9c, 0x597c, 0xa684, 0x059c, 0xfa64, + 0x0b3c, 0xf4c4, 0x0068, 0xff98, 0x2c7c, 0xd384, 0x02cc, 0xfd34, + 0x1dfc, 0xe204, 0x01ec, 0xfe14, 0x797c, 0x8684, 0x07bc, 0xf844, + 0x0f3c, 0xf0c4, 0x00e4, 0xff1c, 0x3c7c, 0xc384, 0x03dc, 0xfc24, + 0x11fc, 0xee04, 0x0124, 0xfedc, 0x497c, 0xb684, 0x049c, 0xfb64, + 0x093c, 0xf6c4, 0x0028, 0xffd8, 0x247c, 0xdb84, 0x024c, 0xfdb4, + 0x19fc, 0xe604, 0x01ac, 0xfe54, 0x697c, 0x9684, 0x069c, 0xf964, + 0x0d3c, 0xf2c4, 0x00a4, 0xff5c, 0x347c, 0xcb84, 0x034c, 0xfcb4, + 0x12fc, 0xed04, 0x0134, 0xfecc, 0x4d7c, 0xb284, 0x04dc, 0xfb24, + 0x09bc, 0xf644, 0x0038, 0xffc8, 0x267c, 0xd984, 0x026c, 0xfd94, + 0x1afc, 0xe504, 0x01ac, 0xfe54, 0x6d7c, 0x9284, 0x06dc, 0xf924, + 0x0dbc, 0xf244, 0x00b4, 0xff4c, 0x367c, 0xc984, 0x036c, 0xfc94, + 0x0f3c, 0xf0c4, 0x00f4, 0xff0c, 0x3e7c, 0xc184, 0x03dc, 0xfc24, + 0x07bc, 0xf844, 0x0008, 0xfff8, 0x1efc, 0xe104, 0x01ec, 0xfe14, + 0x16fc, 0xe904, 0x0174, 0xfe8c, 0x5d7c, 0xa284, 0x05dc, 0xfa24, + 0x0bbc, 0xf444, 0x0078, 0xff88, 0x2e7c, 0xd184, 0x02ec, 0xfd14, + 0x14fc, 0xeb04, 0x0154, 0xfeac, 0x557c, 0xaa84, 0x055c, 0xfaa4, + 0x0abc, 0xf544, 0x0058, 0xffa8, 0x2a7c, 0xd584, 0x02ac, 0xfd54, + 0x1cfc, 0xe304, 0x01cc, 0xfe34, 0x757c, 0x8a84, 0x075c, 0xf8a4, + 0x0ebc, 0xf144, 0x00d4, 0xff2c, 0x3a7c, 0xc584, 0x039c, 0xfc64, + 0x10fc, 0xef04, 0x0114, 0xfeec, 0x457c, 0xba84, 0x045c, 0xfba4, + 0x08bc, 0xf744, 0x0018, 0xffe8, 0x227c, 0xdd84, 0x022c, 0xfdd4, + 0x18fc, 0xe704, 0x018c, 0xfe74, 0x657c, 0x9a84, 0x065c, 0xf9a4, + 0x0cbc, 0xf344, 0x0094, 0xff6c, 0x327c, 0xcd84, 0x032c, 0xfcd4 +}; + +/* Xlaw -> signed 16-bit */ +static int16_t g711_alaw_to_linear[256]; +static int16_t g711_ulaw_to_linear[256]; + +/* signed 16-bit -> Xlaw */ +static uint8_t g711_linear_to_alaw_flipped[65536]; +static uint8_t g711_linear_to_ulaw_flipped[65536]; +static uint8_t g711_linear_to_alaw[65536]; +static uint8_t g711_linear_to_ulaw[65536]; + +/* transcode */ +static uint8_t g711_alaw_to_ulaw[256]; +static uint8_t g711_ulaw_to_alaw[256]; +static uint8_t g711_alaw_flipped_to_ulaw[256]; +static uint8_t g711_ulaw_flipped_to_alaw[256]; +static uint8_t g711_alaw_to_ulaw_flipped[256]; +static uint8_t g711_ulaw_to_alaw_flipped[256]; + +/* table is used to generate linear_to_alaw */ +static int16_t g711_alaw_relations[] = +{ + 0x8684, 0x55, 0x8a84, 0xd5, 0x8e84, 0x15, 0x9284, 0x95, + 0x9684, 0x75, 0x9a84, 0xf5, 0x9e84, 0x35, 0xa284, 0xb5, + 0xa684, 0x45, 0xaa84, 0xc5, 0xae84, 0x05, 0xb284, 0x85, + 0xb684, 0x65, 0xba84, 0xe5, 0xbe84, 0x25, 0xc184, 0xa5, + 0xc384, 0x5d, 0xc584, 0xdd, 0xc784, 0x1d, 0xc984, 0x9d, + 0xcb84, 0x7d, 0xcd84, 0xfd, 0xcf84, 0x3d, 0xd184, 0xbd, + 0xd384, 0x4d, 0xd584, 0xcd, 0xd784, 0x0d, 0xd984, 0x8d, + 0xdb84, 0x6d, 0xdd84, 0xed, 0xdf84, 0x2d, 0xe104, 0xad, + 0xe204, 0x51, 0xe304, 0xd1, 0xe404, 0x11, 0xe504, 0x91, + 0xe604, 0x71, 0xe704, 0xf1, 0xe804, 0x31, 0xe904, 0xb1, + 0xea04, 0x41, 0xeb04, 0xc1, 0xec04, 0x01, 0xed04, 0x81, + 0xee04, 0x61, 0xef04, 0xe1, 0xf004, 0x21, 0xf0c4, 0x59, + 0xf0c4, 0xa1, 0xf144, 0xd9, 0xf1c4, 0x19, 0xf244, 0x99, + 0xf2c4, 0x79, 0xf344, 0xf9, 0xf3c4, 0x39, 0xf444, 0xb9, + 0xf4c4, 0x49, 0xf544, 0xc9, 0xf5c4, 0x09, 0xf644, 0x89, + 0xf6c4, 0x69, 0xf744, 0xe9, 0xf7c4, 0x29, 0xf844, 0x57, + 0xf844, 0xa9, 0xf8a4, 0xd7, 0xf8e4, 0x17, 0xf924, 0x97, + 0xf964, 0x77, 0xf9a4, 0xf7, 0xf9e4, 0x37, 0xfa24, 0xb7, + 0xfa64, 0x47, 0xfaa4, 0xc7, 0xfae4, 0x07, 0xfb24, 0x87, + 0xfb64, 0x67, 0xfba4, 0xe7, 0xfbe4, 0x27, 0xfc24, 0x5f, + 0xfc24, 0xa7, 0xfc64, 0x1f, 0xfc64, 0xdf, 0xfc94, 0x9f, + 0xfcb4, 0x7f, 0xfcd4, 0xff, 0xfcf4, 0x3f, 0xfd14, 0xbf, + 0xfd34, 0x4f, 0xfd54, 0xcf, 0xfd74, 0x0f, 0xfd94, 0x8f, + 0xfdb4, 0x6f, 0xfdd4, 0xef, 0xfdf4, 0x2f, 0xfe14, 0x53, + 0xfe14, 0xaf, 0xfe34, 0x13, 0xfe34, 0xd3, 0xfe54, 0x73, + 0xfe54, 0x93, 0xfe74, 0x33, 0xfe74, 0xf3, 0xfe8c, 0xb3, + 0xfe9c, 0x43, 0xfeac, 0xc3, 0xfebc, 0x03, 0xfecc, 0x83, + 0xfedc, 0x63, 0xfeec, 0xe3, 0xfefc, 0x23, 0xff0c, 0xa3, + 0xff1c, 0x5b, 0xff2c, 0xdb, 0xff3c, 0x1b, 0xff4c, 0x9b, + 0xff5c, 0x7b, 0xff6c, 0xfb, 0xff7c, 0x3b, 0xff88, 0xbb, + 0xff98, 0x4b, 0xffa8, 0xcb, 0xffb8, 0x0b, 0xffc8, 0x8b, + 0xffd8, 0x6b, 0xffe8, 0xeb, 0xfff8, 0x2b, 0xfff8, 0xab, + 0x0008, 0x2a, 0x0008, 0xaa, 0x0018, 0xea, 0x0028, 0x6a, + 0x0038, 0x8a, 0x0048, 0x0a, 0x0058, 0xca, 0x0068, 0x4a, + 0x0078, 0xba, 0x0084, 0x3a, 0x0094, 0xfa, 0x00a4, 0x7a, + 0x00b4, 0x9a, 0x00c4, 0x1a, 0x00d4, 0xda, 0x00e4, 0x5a, + 0x00f4, 0xa2, 0x0104, 0x22, 0x0114, 0xe2, 0x0124, 0x62, + 0x0134, 0x82, 0x0144, 0x02, 0x0154, 0xc2, 0x0164, 0x42, + 0x0174, 0xb2, 0x018c, 0x32, 0x018c, 0xf2, 0x01ac, 0x72, + 0x01ac, 0x92, 0x01cc, 0x12, 0x01cc, 0xd2, 0x01ec, 0x52, + 0x01ec, 0xae, 0x020c, 0x2e, 0x022c, 0xee, 0x024c, 0x6e, + 0x026c, 0x8e, 0x028c, 0x0e, 0x02ac, 0xce, 0x02cc, 0x4e, + 0x02ec, 0xbe, 0x030c, 0x3e, 0x032c, 0xfe, 0x034c, 0x7e, + 0x036c, 0x9e, 0x039c, 0x1e, 0x039c, 0xde, 0x03dc, 0x5e, + 0x03dc, 0xa6, 0x041c, 0x26, 0x045c, 0xe6, 0x049c, 0x66, + 0x04dc, 0x86, 0x051c, 0x06, 0x055c, 0xc6, 0x059c, 0x46, + 0x05dc, 0xb6, 0x061c, 0x36, 0x065c, 0xf6, 0x069c, 0x76, + 0x06dc, 0x96, 0x071c, 0x16, 0x075c, 0xd6, 0x07bc, 0x56, + 0x07bc, 0xa8, 0x083c, 0x28, 0x08bc, 0xe8, 0x093c, 0x68, + 0x09bc, 0x88, 0x0a3c, 0x08, 0x0abc, 0xc8, 0x0b3c, 0x48, + 0x0bbc, 0xb8, 0x0c3c, 0x38, 0x0cbc, 0xf8, 0x0d3c, 0x78, + 0x0dbc, 0x98, 0x0e3c, 0x18, 0x0ebc, 0xd8, 0x0f3c, 0x58, + 0x0f3c, 0xa0, 0x0ffc, 0x20, 0x10fc, 0xe0, 0x11fc, 0x60, + 0x12fc, 0x80, 0x13fc, 0x00, 0x14fc, 0xc0, 0x15fc, 0x40, + 0x16fc, 0xb0, 0x17fc, 0x30, 0x18fc, 0xf0, 0x19fc, 0x70, + 0x1afc, 0x90, 0x1bfc, 0x10, 0x1cfc, 0xd0, 0x1dfc, 0x50, + 0x1efc, 0xac, 0x207c, 0x2c, 0x227c, 0xec, 0x247c, 0x6c, + 0x267c, 0x8c, 0x287c, 0x0c, 0x2a7c, 0xcc, 0x2c7c, 0x4c, + 0x2e7c, 0xbc, 0x307c, 0x3c, 0x327c, 0xfc, 0x347c, 0x7c, + 0x367c, 0x9c, 0x387c, 0x1c, 0x3a7c, 0xdc, 0x3c7c, 0x5c, + 0x3e7c, 0xa4, 0x417c, 0x24, 0x457c, 0xe4, 0x497c, 0x64, + 0x4d7c, 0x84, 0x517c, 0x04, 0x557c, 0xc4, 0x597c, 0x44, + 0x5d7c, 0xb4, 0x617c, 0x34, 0x657c, 0xf4, 0x697c, 0x74, + 0x6d7c, 0x94, 0x717c, 0x14, 0x757c, 0xd4, 0x797c, 0x54 +}; + +uint8_t g711_flip[256]; + +static int g711_initialized = 0; + +/* generate tables + */ +void g711_init(void) +{ + int i, j; + + /* flip tables */ + for (i = 0; i < 256; i++) { + g711_flip[i] + = ((i & 1) << 7) + + ((i & 2) << 5) + + ((i & 4) << 3) + + ((i & 8) << 1) + + ((i & 16) >> 1) + + ((i & 32) >> 3) + + ((i & 64) >> 5) + + ((i & 128) >> 7); + g711_alaw_to_linear[i] = g711_alaw_flipped_to_linear[g711_flip[i]]; + g711_ulaw_to_linear[i] = g711_ulaw_flipped_to_linear[g711_flip[i]]; + } + + /* linear to alaw tables */ + i = j = 0; + while(i < 65536) { + if (i - 32768 > g711_alaw_relations[j << 1]) + j++; + if (j > 255) + j = 255; + g711_linear_to_alaw_flipped[(i - 32768) & 0xffff] = g711_alaw_relations[(j << 1) | 1]; + g711_linear_to_alaw[(i - 32768) & 0xffff] = g711_flip[g711_alaw_relations[(j << 1) | 1]]; + i++; + } + + /* linear to ulaw tables */ + i = j = 0; + while(i < 32768) { + if (i - 32768 > g711_ulaw_flipped_to_linear[j]) + j++; + g711_linear_to_ulaw_flipped[(i - 32768) & 0xffff] = j; + g711_linear_to_ulaw[(i - 32768) & 0xffff] = g711_flip[j]; + i++; + } + j = 255; + while(i < 65536) { + if (i - 32768 > g711_alaw_flipped_to_linear[j]) + j--; + g711_linear_to_ulaw_flipped[(i - 32768) & 0xffff] = j; + g711_linear_to_ulaw[(i - 32768) & 0xffff] = g711_flip[j]; + i++; + } + + /* transcode */ + for (i = 0; i < 256; i++) { + g711_alaw_to_ulaw[i] = g711_linear_to_ulaw[(uint16_t)g711_alaw_to_linear[i]]; + g711_ulaw_to_alaw[i] = g711_linear_to_alaw[(uint16_t)g711_ulaw_to_linear[i]]; + g711_alaw_flipped_to_ulaw[i] = g711_linear_to_ulaw[(uint16_t)g711_alaw_to_linear[g711_flip[i]]]; + g711_ulaw_flipped_to_alaw[i] = g711_linear_to_alaw[(uint16_t)g711_ulaw_to_linear[g711_flip[i]]]; + g711_alaw_to_ulaw_flipped[i] = g711_flip[g711_linear_to_ulaw[(uint16_t)g711_alaw_to_linear[i]]]; + g711_ulaw_to_alaw_flipped[i] = g711_flip[g711_linear_to_alaw[(uint16_t)g711_ulaw_to_linear[i]]]; + } + + g711_initialized = 1; +} + +void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + int16_t *src = (int16_t *)src_data; + uint8_t *dst; + int len = src_len / 2, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_linear_to_alaw_flipped[(uint16_t)src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + int16_t *src = (int16_t *)src_data; + uint8_t *dst; + int len = src_len / 2, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_linear_to_ulaw_flipped[(uint16_t)src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data; + int16_t *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len * 2); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_alaw_flipped_to_linear[src[i]]; + *dst_data = (uint8_t *)dst; + *dst_len = len * 2; +} + +void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data; + int16_t *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len * 2); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_ulaw_flipped_to_linear[src[i]]; + *dst_data = (uint8_t *)dst; + *dst_len = len * 2; +} + +void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + int16_t *src = (int16_t *)src_data; + uint8_t *dst; + int len = src_len / 2, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_linear_to_alaw[(uint16_t)src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + int16_t *src = (int16_t *)src_data; + uint8_t *dst; + int len = src_len / 2, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_linear_to_ulaw[(uint16_t)src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data; + int16_t *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len * 2); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_alaw_to_linear[src[i]]; + *dst_data = (uint8_t *)dst; + *dst_len = len * 2; +} + +void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data; + int16_t *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len * 2); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_ulaw_to_linear[src[i]]; + *dst_data = (uint8_t *)dst; + *dst_len = len * 2; +} + +void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data, *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_alaw_to_ulaw[src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data, *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_alaw_flipped_to_ulaw[src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data, *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_alaw_to_ulaw_flipped[src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data, *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_ulaw_to_alaw[src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data, *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_ulaw_flipped_to_alaw[src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data, *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_ulaw_to_alaw_flipped[src[i]]; + *dst_data = dst; + *dst_len = len; +} + +void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len) +{ + uint8_t *src = src_data, *dst; + int len = src_len, i; + + if (!g711_initialized) { + fprintf(stderr, "G711 codec not initialized! Please fix!\n"); + abort(); + } + + dst = malloc(len); + if (!dst) + return; + for (i = 0; i < len; i++) + dst[i] = g711_flip[src[i]]; + *dst_data = dst; + *dst_len = len; +} + diff --git a/src/libg711/g711.h b/src/libg711/g711.h new file mode 100644 index 0000000..795f60b --- /dev/null +++ b/src/libg711/g711.h @@ -0,0 +1,17 @@ +void g711_init(void); +void g711_encode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_encode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_decode_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_decode_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_encode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_encode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_decode_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_decode_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_transcode_alaw_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_transcode_alaw_flipped_to_ulaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_transcode_alaw_to_ulaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_transcode_ulaw_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_transcode_ulaw_flipped_to_alaw(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_transcode_ulaw_to_alaw_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +void g711_transcode_flipped(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); + diff --git a/src/libjitter/Makefile.am b/src/libjitter/Makefile.am new file mode 100644 index 0000000..506ef87 --- /dev/null +++ b/src/libjitter/Makefile.am @@ -0,0 +1,6 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libjitter.a + +libjitter_a_SOURCES = \ + jitter.c diff --git a/src/libjitter/jitter.c b/src/libjitter/jitter.c new file mode 100644 index 0000000..c3af715 --- /dev/null +++ b/src/libjitter/jitter.c @@ -0,0 +1,125 @@ +/* Jitter buffering functions + * + * (C) 2016 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 "../libsample/sample.h" +#include "../libdebug/debug.h" +#include "jitter.h" + +/* create jitter buffer */ +int jitter_create(jitter_t *jitter, int length) +{ + memset(jitter, 0, sizeof(*jitter)); + jitter->spl = malloc(length * sizeof(sample_t)); + if (!jitter->spl) { + PDEBUG(DDSP, DEBUG_ERROR, "No memory for jitter buffer.\n"); + return -ENOMEM; + } + jitter->len = length; + + jitter_reset(jitter); + + return 0; +} + +void jitter_reset(jitter_t *jitter) +{ + memset(jitter->spl, 0, jitter->len * sizeof(sample_t)); + + /* put write pointer ahead by half of the buffer length */ + jitter->inptr = jitter->len / 2; +} + +void jitter_destroy(jitter_t *jitter) +{ + if (jitter->spl) { + free(jitter->spl); + jitter->spl = NULL; + } +} + +/* store audio in jitterbuffer + * + * stop if buffer is completely filled + */ +void jitter_save(jitter_t *jb, sample_t *samples, int length) +{ + sample_t *spl; + int inptr, outptr, len, space; + int i; + + spl = jb->spl; + inptr = jb->inptr; + outptr = jb->outptr; + len = jb->len; + space = (outptr - inptr + len - 1) % len; + + if (space < length) + length = space; + for (i = 0; i < length; i++) { + spl[inptr++] = *samples++; + if (inptr == len) + inptr = 0; + } + + jb->inptr = inptr; +} + +/* get audio from jitterbuffer + */ +void jitter_load(jitter_t *jb, sample_t *samples, int length) +{ + sample_t *spl; + int inptr, outptr, len, fill; + int i, ii; + + spl = jb->spl; + inptr = jb->inptr; + outptr = jb->outptr; + len = jb->len; + fill = (inptr - outptr + len) % len; + + if (fill < length) + ii = fill; + else + ii = length; + + /* fill what we got */ + for (i = 0; i < ii; i++) { + *samples++ = spl[outptr++]; + if (outptr == len) + outptr = 0; + } + /* on underrun, fill with silence */ + for (; i < length; i++) { + *samples++ = 0; + } + + jb->outptr = outptr; +} + +void jitter_clear(jitter_t *jb) +{ + jb->inptr = jb->outptr = 0; +} + diff --git a/src/libjitter/jitter.h b/src/libjitter/jitter.h new file mode 100644 index 0000000..e344d8a --- /dev/null +++ b/src/libjitter/jitter.h @@ -0,0 +1,14 @@ + +typedef struct jitter { + sample_t *spl; /* pointer to sample buffer */ + int len; /* buffer size: number of samples */ + int inptr, outptr; /* write pointer and read pointer */ +} jitter_t; + +int jitter_create(jitter_t *jitter, int length); +void jitter_reset(jitter_t *jitter); +void jitter_destroy(jitter_t *jitter); +void jitter_save(jitter_t *jb, sample_t *samples, int length); +void jitter_load(jitter_t *jb, sample_t *samples, int length); +void jitter_clear(jitter_t *jb); + diff --git a/src/liboptions/Makefile.am b/src/liboptions/Makefile.am new file mode 100644 index 0000000..0d10fa7 --- /dev/null +++ b/src/liboptions/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = liboptions.a + +liboptions_a_SOURCES = \ + options.c + diff --git a/src/liboptions/options.c b/src/liboptions/options.c new file mode 100644 index 0000000..4b8f062 --- /dev/null +++ b/src/liboptions/options.c @@ -0,0 +1,275 @@ +/* command line options and config file parsing + * + * (C) 2018 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 "options.h" +#include "../libdebug/debug.h" + +typedef struct option { + struct option *next; + int short_option; + const char *long_option; + int parameter_count; +} option_t; + +static option_t *option_head = NULL; +static option_t **option_tailp = &option_head; +static int first_option = 1; + +void option_add(int short_option, const char *long_option, int parameter_count) +{ + option_t *option; + + /* check if option already exists */ + for (option = option_head; option; option = option->next) { + if (option->short_option == short_option + || !strcmp(option->long_option, long_option)) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Option '%s' added twice, please fix!\n", option->long_option); + abort(); + } + } + + option = calloc(1, sizeof(*option)); + if (!option) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "No mem!\n"); + abort(); + } + + option->short_option = short_option; + option->long_option = long_option; + option->parameter_count = parameter_count; + *option_tailp = option; + option_tailp = &(option->next); +} + +int options_config_file(const char *config_file, int (*handle_options)(int short_option, int argi, char *argv[])) +{ + static const char *home; + char config[256]; + FILE *fp; + char buffer[256], opt[256], param[256], *p, *argv[16]; + char params[1024]; + int line; + int rc = 1; + int i, j, quote; + option_t *option; + + /* open config file */ + home = getenv("HOME"); + if (home == NULL) + return 1; + sprintf(config, "%s/%s", home, config_file + 2); + + fp = fopen(config, "r"); + if (!fp) { + PDEBUG(DOPTIONS, DEBUG_INFO, "Config file '%s' seems not to exist, using command line options only.\n", config); + return 1; + } + + /* parse config file */ + line = 0; + while((fgets(buffer, sizeof(buffer), fp))) { + line++; + /* prevent buffer overflow */ + buffer[sizeof(buffer) - 1] = '\0'; + /* cut away new-line and white spaces */ + while (buffer[0] && buffer[strlen(buffer) - 1] <= ' ') + buffer[strlen(buffer) - 1] = '\0'; + p = buffer; + /* remove white spaces in front of first keyword */ + while (*p > '\0' && *p <= ' ') + p++; + /* ignore '#' lines */ + if (*p == '#') + continue; + /* get option form line */ + i = 0; + while (*p > ' ') + opt[i++] = *p++; + opt[i] = '\0'; + if (opt[0] == '\0') + continue; + /* skip white spaces behind option */ + while (*p > '\0' && *p <= ' ') + p++; + /* get param from line */ + params[0] = '\0'; + i = 0; + while (*p) { + /* copy parameter */ + j = 0; + quote = 0; + while (*p) { + /* escape allows all following characters */ + if (*p == '\\') { + p++; + if (*p) + param[j++] = *p++; + continue; + } + /* no quote, check for them or break on white space */ + if (quote == 0) { + if (*p == '\'') { + quote = 1; + p++; + continue; + } + if (*p == '\"') { + quote = 2; + p++; + continue; + } + if (*p <= ' ') + break; + } + /* single quote, check for unquote */ + if (quote == 1 && *p == '\'') { + quote = 0; + p++; + continue; + } + /* double quote, check for unquote */ + if (quote == 2 && *p == '\"') { + quote = 0; + p++; + continue; + } + /* copy character */ + param[j++] = *p++; + } + param[j] = '\0'; + argv[i] = strdup(param); + sprintf(strchr(params, '\0'), " '%s'", param); + /* skip white spaces behind option */ + while (*p > '\0' && *p <= ' ') + p++; + i++; + } + /* search option */ + for (option = option_head; option; option = option->next) { + if (opt[0] == option->short_option && opt[1] == '\0') { + PDEBUG(DOPTIONS, DEBUG_INFO, "Config file option '%s' ('%s'), parameter%s\n", opt, option->long_option, params); + break; + } + if (!strcmp(opt, option->long_option)) { + PDEBUG(DOPTIONS, DEBUG_INFO, "Config file option '%s', parameter%s\n", opt, params); + break; + } + } + if (!option) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given option '%s' in config file '%s' at line %d is not a valid option, use '-h' for help!\n", opt, config_file, line); + rc = -EINVAL; + goto done; + } + if (option->parameter_count != i) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given option '%s' in config file '%s' at line %d requires %d parameter(s), use '-h' for help!\n", opt, config_file, line, option->parameter_count); + return -EINVAL; + } + rc = handle_options(option->short_option, 0, argv); + if (rc <= 0) + goto done; + first_option = 0; + } + +done: + /* close config file */ + fclose(fp); + + return rc; +} + +int options_command_line(int argc, char *argv[], int (*handle_options)(int short_option, int argi, char *argv[])) +{ + option_t *option; + char params[1024]; + int argi, i; + int rc; + + for (argi = 1; argi < argc; argi++) { + if (argv[argi][0] == '-') { + if (argv[argi][1] != '-') { + if (strlen(argv[argi]) != 2) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' exceeds one character, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + /* -x */ + for (option = option_head; option; option = option->next) { + if (argv[argi][1] == option->short_option) { + if (option->parameter_count && argi + option->parameter_count < argc) { + params[0] = '\0'; + for (i = 0; i < option->parameter_count; i++) + sprintf(strchr(params, '\0'), " '%s'", argv[argi + 1 + i]); + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s' ('--%s'), parameter%s\n", argv[argi], option->long_option, params); + } else + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s' ('--%s')\n", argv[argi], option->long_option); + break; + } + } + } else { + /* --xxxxxx */ + for (option = option_head; option; option = option->next) { + if (!strcmp(argv[argi] + 2, option->long_option)) { + if (option->parameter_count && argi + option->parameter_count < argc) { + params[0] = '\0'; + for (i = 0; i < option->parameter_count; i++) + sprintf(strchr(params, '\0'), " '%s'", argv[argi + 1 + i]); + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s', parameter%s\n", argv[argi], params); + } else + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s'\n", argv[argi]); + break; + } + } + } + if (!option) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' is not a valid option, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + if (argi + option->parameter_count >= argc) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' requires %d parameter(s), use '-h' for help!\n", argv[argi], option->parameter_count); + return -EINVAL; + } + rc = handle_options(option->short_option, argi + 1, argv); + if (rc <= 0) + return rc; + first_option = 0; + argi += option->parameter_count; + } else + break; + } + + /* no more options, so we check if there is an option after a non-option parameter */ + for (i = argi; i < argc; i++) { + if (argv[i][0] == '-') { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' behind command line parameter '%s' not allowed! Please put all command line options before command line parameter(s).\n", argv[i], argv[argi]); + return -EINVAL; + } + } + + return argi; +} + +int option_is_first(void) +{ + return first_option; +} + diff --git a/src/liboptions/options.h b/src/liboptions/options.h new file mode 100644 index 0000000..6f7e41a --- /dev/null +++ b/src/liboptions/options.h @@ -0,0 +1,6 @@ + +void option_add(int short_option, const char *long_option, int parameter_count); +int options_config_file(const char *config_file, int (*handle_options)(int short_option, int argi, char *argv[])); +int options_command_line(int argc, char *argv[], int (*handle_options)(int short_option, int argi, char *argv[])); +int option_is_first(void); + diff --git a/src/libosmocc/Makefile.am b/src/libosmocc/Makefile.am new file mode 100644 index 0000000..27c0f6c --- /dev/null +++ b/src/libosmocc/Makefile.am @@ -0,0 +1,15 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libosmocc.a + +libosmocc_a_SOURCES = \ + message.c \ + socket.c \ + cause.c \ + screen.c \ + endpoint.c \ + session.c \ + sdp.c \ + rtp.c \ + helper.c + diff --git a/src/libosmocc/cause.c b/src/libosmocc/cause.c new file mode 100644 index 0000000..4a3a41e --- /dev/null +++ b/src/libosmocc/cause.c @@ -0,0 +1,252 @@ +/* OSMO-CC Processing: convert causes + * + * (C) 2019 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 "message.h" +#include "cause.h" + +/* stolen from freeswitch */ +/* map sip responses to QSIG cause codes ala RFC4497 section 8.4.4 */ +static uint8_t status2isdn_cause(uint16_t status) +{ + switch (status) { + case 200: + return 16; //SWITCH_CAUSE_NORMAL_CLEARING; + case 401: + case 402: + case 403: + case 407: + case 603: + return 21; //SWITCH_CAUSE_CALL_REJECTED; + case 404: + return 1; //SWITCH_CAUSE_UNALLOCATED_NUMBER; + case 485: + case 604: + return 3; //SWITCH_CAUSE_NO_ROUTE_DESTINATION; + case 408: + case 504: + return 102; //SWITCH_CAUSE_RECOVERY_ON_TIMER_EXPIRE; + case 410: + return 22; //SWITCH_CAUSE_NUMBER_CHANGED; + case 413: + case 414: + case 416: + case 420: + case 421: + case 423: + case 505: + case 513: + return 127; //SWITCH_CAUSE_INTERWORKING; + case 480: + return 180; //SWITCH_CAUSE_NO_USER_RESPONSE; + case 400: + case 481: + case 500: + case 503: + return 41; //SWITCH_CAUSE_NORMAL_TEMPORARY_FAILURE; + case 486: + case 600: + return 17; //SWITCH_CAUSE_USER_BUSY; + case 484: + return 28; //SWITCH_CAUSE_INVALID_NUMBER_FORMAT; + case 488: + case 606: + return 88; //SWITCH_CAUSE_INCOMPATIBLE_DESTINATION; + case 502: + return 38; //SWITCH_CAUSE_NETWORK_OUT_OF_ORDER; + case 405: + return 63; //SWITCH_CAUSE_SERVICE_UNAVAILABLE; + case 406: + case 415: + case 501: + return 79; //SWITCH_CAUSE_SERVICE_NOT_IMPLEMENTED; + case 482: + case 483: + return 25; //SWITCH_CAUSE_EXCHANGE_ROUTING_ERROR; + case 487: + return 31; //??? SWITCH_CAUSE_ORIGINATOR_CANCEL; + default: + return 31; //SWITCH_CAUSE_NORMAL_UNSPECIFIED; + } +} + +static uint16_t isdn2status_cause(uint8_t cause, uint8_t location) +{ + switch (cause) { + case 1: + return 404; + case 2: + return 404; + case 3: + return 404; + case 17: + return 486; + case 18: + return 408; + case 19: + return 480; + case 20: + return 480; + case 21: + if (location == OSMO_CC_LOCATION_USER) + return 603; + return 403; + case 22: + //return 301; + return 410; + case 23: + return 410; + case 26: + return 404; + case 27: + return 502; + case 28: + return 484; + case 29: + return 501; + case 31: + return 480; + case 34: + return 503; + case 38: + return 503; + case 41: + return 503; + case 42: + return 503; + case 47: + return 503; + case 55: + return 403; + case 57: + return 403; + case 58: + return 503; + case 65: + return 488; + case 69: + return 501; + case 70: + return 488; + case 79: + return 501; + case 87: + return 403; + case 88: + return 503; + case 102: + return 504; + case 111: + return 500; + case 127: + return 500; + default: + return 468; + } +} + +static uint8_t socket2isdn_cause(uint8_t sock) +{ + switch (sock) { + case OSMO_CC_SOCKET_CAUSE_FAILED: + return 47; + case OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE: + return 41; + case OSMO_CC_SOCKET_CAUSE_VERSION_MISMATCH: + return 38; + case OSMO_CC_SOCKET_CAUSE_TIMEOUT: + return 41; + default: + return 31; + } +} + +void osmo_cc_convert_cause(struct osmo_cc_ie_cause *cause) +{ + /* complete cause, from socket cause */ + if (cause->socket_cause && cause->isdn_cause == 0 && ntohs(cause->sip_cause_networkorder) == 0) + cause->isdn_cause = socket2isdn_cause(cause->socket_cause); + + /* convert ISDN cause to SIP cause */ + if (cause->isdn_cause && ntohs(cause->sip_cause_networkorder) == 0) { + cause->sip_cause_networkorder = htons(isdn2status_cause(cause->isdn_cause, cause->location)); + } + + /* convert SIP cause to ISDN cause */ + if (ntohs(cause->sip_cause_networkorder) && cause->isdn_cause == 0) { + cause->isdn_cause = status2isdn_cause(ntohs(cause->sip_cause_networkorder)); + } + + /* no cause at all: use Normal Call Clearing */ + if (cause->isdn_cause == 0 && ntohs(cause->sip_cause_networkorder) == 0) { + cause->isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; + cause->sip_cause_networkorder = htons(486); + } +} + +void osmo_cc_convert_cause_msg(osmo_cc_msg_t *msg) +{ + void *ie; + uint8_t type; + uint16_t length; + void *value; + + /* search for (all) cause IE and convert the values, if needed */ + ie = msg->data; + while ((value = osmo_cc_msg_sep_ie(msg, &ie, &type, &length))) { + if (type == OSMO_CC_IE_CAUSE && length >= sizeof(struct osmo_cc_ie_cause)) { + osmo_cc_convert_cause(value); + } + } +} + +uint8_t osmo_cc_collect_cause(uint8_t old_cause, uint8_t new_cause) +{ + /* first cause */ + if (old_cause == 0) + return new_cause; + + /* first prio: return 17 */ + if (old_cause == OSMO_CC_ISDN_CAUSE_USER_BUSY + || new_cause == OSMO_CC_ISDN_CAUSE_USER_BUSY) + return OSMO_CC_ISDN_CAUSE_USER_BUSY; + + /* second prio: return 21 */ + if (old_cause == OSMO_CC_ISDN_CAUSE_CALL_REJECTED + || new_cause == OSMO_CC_ISDN_CAUSE_CALL_REJECTED) + return OSMO_CC_ISDN_CAUSE_CALL_REJECTED; + + /* third prio: return other than 88 and 18 (what ever was first) */ + if (old_cause != OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST + && old_cause != OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND) + return old_cause; + if (new_cause != OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST + && new_cause != OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND) + return new_cause; + + /* fourth prio: return 88 */ + if (old_cause == OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST + || new_cause == OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST) + return OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST; + + /* fith prio: return 18 */ + return OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND; +} + diff --git a/src/libosmocc/cause.h b/src/libosmocc/cause.h new file mode 100644 index 0000000..22319f4 --- /dev/null +++ b/src/libosmocc/cause.h @@ -0,0 +1,5 @@ + +void osmo_cc_convert_cause(struct osmo_cc_ie_cause *cause); +void osmo_cc_convert_cause_msg(osmo_cc_msg_t *msg); +uint8_t osmo_cc_collect_cause(uint8_t old_cause, uint8_t new_cause); + diff --git a/src/libosmocc/endpoint.c b/src/libosmocc/endpoint.c new file mode 100644 index 0000000..83dd475 --- /dev/null +++ b/src/libosmocc/endpoint.c @@ -0,0 +1,1476 @@ +/* Endpoint and call process handling + * + * (C) 2019 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 "../libtimer/timer.h" +#include "../libdebug/debug.h" +#include "endpoint.h" + +osmo_cc_endpoint_t *osmo_cc_endpoint_list = NULL; + +static osmo_cc_call_t *call_new(osmo_cc_endpoint_t *ep, uint32_t callref) +{ + osmo_cc_call_t *call, **cp; + + call = calloc(1, sizeof(*call)); + if (!call) { + PDEBUG(DCC, DEBUG_ERROR, "No memory for call process instance.\n"); + abort(); + } + + PDEBUG(DCC, DEBUG_DEBUG, "Creating new call with callref %u.\n", callref); + + call->ep = ep; + call->callref = callref; + + /* attach to call process list */ + cp = &ep->call_list; + while (*cp) + cp = &((*cp)->next); + *cp = call; + + /* return new entry */ + return call; +} + +static void call_delete(osmo_cc_call_t *call) +{ + osmo_cc_call_t **cp; + + PDEBUG(DCC, DEBUG_DEBUG, "Destroying call with callref %u.\n", call->callref); + + /* detach from call process list */ + cp = &call->ep->call_list; + while (*cp != call) + cp = &((*cp)->next); + *cp = call->next; + + /* flush message queue */ + while (call->sock_queue) { + osmo_cc_msg_t *msg = osmo_cc_msg_list_dequeue(&call->sock_queue, NULL); + osmo_cc_free_msg(msg); + } + + /* free remote peer */ + free((char *)call->attached_name); + free((char *)call->attached_host); + + free(call); +} + +static const char *state_names[] = { + "IDLE", + "INIT-OUT", + "INIT-IN", + "OVERLAP-OUT", + "OVERLAP-IN", + "PROCEEDING-OUT", + "PROCEEDING-IN", + "ALERTING-OUT", + "ALERTING-IN", + "CONNECTING-OUT", + "CONNECTING-IN", + "ACTIVE", + "DISCONNECTING-OUT", + "DISCONNECTING-IN", + "DISCONNECT-COLLISION", + "RELEASING-OUT", + "ATTACH-SENT", + "ATTACH-OUT", + "ATTACH-WAIT", + "ATTACH-IN", +}; + +static void new_call_state(osmo_cc_call_t *call, enum osmo_cc_state new_state) +{ + PDEBUG(DCC, DEBUG_DEBUG, "Changing call state with callref %u from %s to %s.\n", call->callref, state_names[call->state], state_names[new_state]); + call->state = new_state; +} + +/* helper to forward message to lower layer */ +static void forward_to_ll(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + if (call->lower_layer_released) + return; + + if (msg->type == OSMO_CC_MSG_SETUP_REQ + || msg->type == OSMO_CC_MSG_SETUP_RSP) { + /* screen towards lower layer */ + msg = osmo_cc_screen_msg(call->ep, msg, 0, NULL); + } + + osmo_cc_msg_list_enqueue(&call->ep->ll_queue, msg, call->callref); +} + +static void sock_reject_msg(osmo_cc_socket_t *os, uint32_t callref, uint8_t location, uint8_t socket_cause, uint8_t isdn_cause, uint16_t sip_cause) +{ + osmo_cc_msg_t *msg; + + /* create message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); + + /* add cause */ + osmo_cc_add_ie_cause(msg, location, isdn_cause, sip_cause, socket_cause); + osmo_cc_convert_cause_msg(msg); + + /* message to socket */ + osmo_cc_sock_send_msg(os, callref, msg, NULL, 0); +} + +static void ll_reject_msg(osmo_cc_call_t *call, uint8_t location, uint8_t socket_cause, uint8_t isdn_cause, uint16_t sip_cause) +{ + osmo_cc_msg_t *msg; + + /* create message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_REQ); + + /* add cause */ + osmo_cc_add_ie_cause(msg, location, isdn_cause, sip_cause, socket_cause); + osmo_cc_convert_cause_msg(msg); + + /* message to lower layer */ + forward_to_ll(call, msg); +} + +static int split_address(const char *address, const char **host_p, uint16_t *port_p) +{ + const char *portstring; + + *host_p = osmo_cc_host_of_address(address); + if (!(*host_p)) { + PDEBUG(DCC, DEBUG_ERROR, "Host IP in given address '%s' is invalid.\n", address); + return -EINVAL; + } + portstring = osmo_cc_port_of_address(address); + if (!portstring) { + PDEBUG(DCC, DEBUG_ERROR, "Port number in given address '%s' is not specified or invalid.\n", address); + return -EINVAL; + } + *port_p = atoi(portstring); + + return 0; +} + +/* helper to forward message to upper layer */ +static void forward_to_ul(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + const char *address = NULL, *host = NULL; + uint16_t port; + int rc; + + if (call->upper_layer_released) + return; + + if (msg->type == OSMO_CC_MSG_SETUP_IND + || msg->type == OSMO_CC_MSG_SETUP_CNF) { + /* screen towards upper layer */ + msg = osmo_cc_screen_msg(call->ep, msg, 1, &address); + } + + /* no socket: forward message to upper layer */ + if (call->ep->ul_msg_cb) { + call->ep->ul_msg_cb(call, msg); + return; + } + + /* if remote peer is included in the setup message */ + if (address && msg->type == OSMO_CC_MSG_SETUP_IND) { + rc = split_address(address, &host, &port); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Given remote peer's address '%s' in setup message is invalid, rejecting call.\n", address); +reject: + /* reject, due to error */ + osmo_cc_free_msg(msg); + new_call_state(call, OSMO_CC_STATE_IDLE); + ll_reject_msg(call, call->ep->serving_location, 0, OSMO_CC_ISDN_CAUSE_DEST_OOO, 0); + call_delete(call); + return; + } + PDEBUG(DCC, DEBUG_DEBUG, "Using host IP '%s' and port '%d' from setup message.\n", host, port); + } + + /* for attach message, use remote peer */ + if (msg->type == OSMO_CC_MSG_ATTACH_IND) { + host = call->ep->remote_host; + port = call->ep->remote_port; + PDEBUG(DCC, DEBUG_DEBUG, "Using host IP '%s' and port '%d' from remote address for attach message.\n", host, port); + } + + /* if there is no remote peer in the setup message, use remote peer */ + if (!address && msg->type == OSMO_CC_MSG_SETUP_IND && call->ep->remote_host) { + host = call->ep->remote_host; + port = call->ep->remote_port; + PDEBUG(DCC, DEBUG_DEBUG, "Using host IP '%s' and port '%d' from remote address for setup message.\n", host, port); + } + + /* if there is no remote peer set, try to use the interface name */ + if (!host && msg->type == OSMO_CC_MSG_SETUP_IND) { + char interface[256]; + osmo_cc_call_t *att; + + rc = osmo_cc_get_ie_called_interface(msg, 0, interface, sizeof(interface)); + if (rc < 0) + interface[0] = '\0'; + /* check for incoming attachment */ + for (att = call->ep->call_list; att; att = att->next) { + if (att->state != OSMO_CC_STATE_ATTACH_IN) + continue; + /* no interface given, just use the attached peer */ + if (!interface[0]) + break; + /* no interface name given on attached peer, ignore it */ + if (!att->attached_name || !att->attached_name[0]) + continue; + /* interface given, use the attached peer with the same interface name */ + if (!strcmp(interface, att->attached_name)) + break; + } + if (!att && !interface[0]) { + PDEBUG(DCC, DEBUG_ERROR, "No remote peer attached, rejecting call.\n"); + goto reject; + } + if (!att) { + PDEBUG(DCC, DEBUG_ERROR, "No remote peer attached for given interface '%s', rejecting call.\n", interface); + goto reject; + } + host = att->attached_host; + port = att->attached_port; + PDEBUG(DCC, DEBUG_DEBUG, "Using host IP '%s' and port '%d' from attached peer for setup message.\n", host, port); + } + + /* add local interface name to setup message */ + // FIXME: should we do that if there is already an interface name given? + if (msg->type == OSMO_CC_MSG_SETUP_IND && call->ep->local_name) + osmo_cc_add_ie_calling_interface(msg, call->ep->local_name); + + /* forward message to socket */ + osmo_cc_sock_send_msg(&call->ep->os, call->callref, msg, host, port); +} + +/* send attach indication to socket */ +void send_attach_ind(struct timer *timer) +{ + osmo_cc_endpoint_t *ep = (osmo_cc_endpoint_t *)timer->priv; + osmo_cc_call_t *call; + osmo_cc_msg_t *msg; + + PDEBUG(DCC, DEBUG_DEBUG, "Trying to attach to remote peer \"%s\".\n", ep->remote_host); + + /* create new call for attachment */ + call = osmo_cc_call_new(ep); + + /* create attach message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_ATTACH_IND); + + /* set interface name and address */ + osmo_cc_add_ie_calling_interface(msg, ep->local_name); + osmo_cc_add_ie_socket_address(msg, ep->local_address); + + /* message to socket */ + forward_to_ul(call, msg); + + /* set state */ + new_call_state(call, OSMO_CC_STATE_ATTACH_SENT); +} + +void attach_rsp(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + PDEBUG(DCC, DEBUG_INFO, "Attached to remote peer \"%s\".\n", call->ep->remote_address); + + /* set state */ + new_call_state(call, OSMO_CC_STATE_ATTACH_OUT); + + /* drop message */ + osmo_cc_free_msg(msg); +} + +void attach_rel(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* (re-)start timer for next attachment */ + if (call->state == OSMO_CC_STATE_ATTACH_SENT + || call->state == OSMO_CC_STATE_ATTACH_OUT) { + timer_start(&call->ep->attach_timer, OSMO_CC_ATTACH_TIMER); + PDEBUG(DCC, DEBUG_INFO, "Attachment to remote peer \"%s\" failed, retrying.\n", call->ep->remote_address); + } + + if (call->attached_name) + PDEBUG(DCC, DEBUG_INFO, "Peer with remote interface \"%s\" detached from us.\n", call->attached_name); + + /* change state */ + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* unset interface */ + free((char *)call->attached_name); + call->attached_name = NULL; + free((char *)call->attached_host); + call->attached_host = NULL; + + /* drop message */ + osmo_cc_free_msg(msg); + + /* destroy */ + call_delete(call); +} + +void attach_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + char address[256]; + char interface[256]; + const char *host; + uint16_t port; + int rc; + + /* get peer from message */ + rc = osmo_cc_get_ie_socket_address(msg, 0, address, sizeof(address)); + if (rc < 0) + address[0] = '\0'; + if (!address[0]) { + PDEBUG(DCC, DEBUG_ERROR, "Attachment request from remote peer has no remote address set, rejecting.\n"); + +rel: + /* change to REL_REQ */ + msg->type = OSMO_CC_MSG_REL_IND; + PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); + + /* message to socket */ + forward_to_ul(call, msg); + + /* destroy */ + call_delete(call); + + return; + } + rc = split_address(address, &host, &port); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Given remote peer's address '%s' in attach message is invalid, rejecting call.\n", address); + goto rel; + } + free((char *)call->attached_host); + call->attached_host = strdup(host); + call->attached_port = port; + + rc = osmo_cc_get_ie_calling_interface(msg, 0, interface, sizeof(interface)); + if (rc < 0) + interface[0] = '\0'; + if (interface[0]) { + free((char *)call->attached_name); + call->attached_name = strdup(interface); + } + + PDEBUG(DCC, DEBUG_INFO, "Remote peer with socket address '%s' and port '%d' and interface '%s' attached to us.\n", call->attached_host, call->attached_port, call->attached_name); + + /* changing to confirm message */ + msg->type = OSMO_CC_MSG_ATTACH_CNF; + PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); + + /* message to socket */ + forward_to_ul(call, msg); + + /* set state */ + new_call_state(call, OSMO_CC_STATE_ATTACH_IN); +} + +static void setup_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_INIT_OUT); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void setup_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_INIT_IN); + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void rej_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* to lower layer */ + forward_to_ll(call, msg); + + /* destroy */ + call_delete(call); +} + +static void rej_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* to upper layer */ + forward_to_ul(call, msg); + + /* destroy */ + call_delete(call); +} + +static void setup_ack_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_OVERLAP_IN); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void setup_ack_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_OVERLAP_OUT); + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void proc_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_PROCEEDING_IN); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void proc_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_PROCEEDING_OUT); + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void alert_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_ALERTING_IN); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void alert_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_ALERTING_OUT); + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void setup_rsp(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_CONNECTING_IN); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void setup_cnf(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_CONNECTING_OUT); + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void setup_comp_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_ACTIVE); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void setup_comp_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_ACTIVE); + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void info_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void info_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void progress_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void progress_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void notify_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void notify_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void disc_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_DISCONNECTING_OUT); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void disc_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_DISCONNECTING_IN); + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void rel_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* terminate process, if there is no lower layer anmore */ + if (call->lower_layer_released) { + /* change state */ + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* drop message */ + osmo_cc_free_msg(msg); + + /* destroy */ + call_delete(call); + + return; + } + + /* change state */ + new_call_state(call, OSMO_CC_STATE_RELEASING_OUT); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void rel_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* to upper layer */ + forward_to_ul(call, msg); + + /* destroy */ + call_delete(call); +} + +static void rel_cnf(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* drop message */ + osmo_cc_free_msg(msg); + + /* destroy */ + call_delete(call); +} + +static void disc_collision_ind(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* release to lower layer wheen there is no upper layer */ + if (call->upper_layer_released) { + /* change state */ + new_call_state(call, OSMO_CC_STATE_RELEASING_OUT); + + /* change to REL_REQ */ + msg->type = OSMO_CC_MSG_REL_REQ; + PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); + + /* to lower layer */ + forward_to_ll(call, msg); + + return; + } + + /* change state */ + new_call_state(call, OSMO_CC_STATE_DISC_COLLISION); + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void disc_collision_req(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* release to upper layer wheen there is no lower layer */ + if (call->lower_layer_released) { + /* change to REL_REQ */ + msg->type = OSMO_CC_MSG_REL_IND; + PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); + + /* to upper layer */ + forward_to_ul(call, msg); + + /* destroy */ + call_delete(call); + + return; + } + + /* change state */ + new_call_state(call, OSMO_CC_STATE_DISC_COLLISION); + + /* to lower layer */ + forward_to_ll(call, msg); +} + +static void rel_collision(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + if (call->state != OSMO_CC_STATE_IDLE) + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* drop message */ + osmo_cc_free_msg(msg); + + /* destroy */ + call_delete(call); +} + +static void rej_ind_disc(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* change to REL_IND */ + msg->type = OSMO_CC_MSG_REL_IND; + PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); + + /* to upper layer */ + forward_to_ul(call, msg); + + /* destroy */ + call_delete(call); +} + +static void rej_req_disc(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + /* change state */ + new_call_state(call, OSMO_CC_STATE_IDLE); + + /* change to REL_REQ */ + msg->type = OSMO_CC_MSG_REL_REQ; + PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); + + /* to lower layer */ + forward_to_ll(call, msg); + + /* destroy */ + call_delete(call); +} + +static void rel_ind_other(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + // FIXME: does this event really happens in this state? + // just to be safe we handle it + /* if thereis no upper layer, we are done */ + if (call->upper_layer_released) { + /* drop message */ + osmo_cc_free_msg(msg); + + /* destroy */ + call_delete(call); + + return; + } + + /* change state */ + new_call_state(call, OSMO_CC_STATE_DISCONNECTING_IN); + + /* change to DISC_IND */ + msg->type = OSMO_CC_MSG_DISC_IND; + PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); + call->lower_layer_released = 1; + + /* to upper layer */ + forward_to_ul(call, msg); +} + +static void rel_req_other(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + // FIXME: does this event really happens in this state? + // just to be safe we handle it + /* if thereis no lower layer, we are done */ + if (call->lower_layer_released) { + /* drop message */ + osmo_cc_free_msg(msg); + + /* destroy */ + call_delete(call); + + return; + } + + /* change state */ + new_call_state(call, OSMO_CC_STATE_DISCONNECTING_OUT); + + /* change to DISC_REQ */ + msg->type = OSMO_CC_MSG_DISC_REQ; + PDEBUG(DCC, DEBUG_INFO, "Changing message to %s.\n", osmo_cc_msg_name(msg->type)); + call->upper_layer_released = 1; + + /* to lower layer */ + forward_to_ll(call, msg); +} + +#define SBIT(a) (1 << a) +#define ALL_STATES (~0) + +static struct statemachine { + uint32_t states; + int type; + void (*action)(osmo_cc_call_t *call, osmo_cc_msg_t *msg); +} statemachine_list[] = { + /* attachment states */ + {SBIT(OSMO_CC_STATE_ATTACH_SENT), + OSMO_CC_MSG_ATTACH_RSP, attach_rsp}, + {SBIT(OSMO_CC_STATE_ATTACH_OUT) | SBIT(OSMO_CC_STATE_ATTACH_SENT), + OSMO_CC_MSG_REL_REQ, attach_rel}, + {SBIT(OSMO_CC_STATE_IDLE), + OSMO_CC_MSG_ATTACH_REQ, attach_req}, + {SBIT(OSMO_CC_STATE_ATTACH_IN), + OSMO_CC_MSG_REL_REQ, attach_rel}, + + /* call setup toward lower layer protocol */ + {SBIT(OSMO_CC_STATE_IDLE), + OSMO_CC_MSG_SETUP_REQ, setup_req}, + {SBIT(OSMO_CC_STATE_INIT_OUT), + OSMO_CC_MSG_SETUP_ACK_IND, setup_ack_ind}, + {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_OUT), + OSMO_CC_MSG_PROC_IND, proc_ind}, + {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_OUT) | + SBIT(OSMO_CC_STATE_PROCEEDING_OUT), + OSMO_CC_MSG_ALERT_IND, alert_ind}, + {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_OUT) | + SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_OUT), + OSMO_CC_MSG_SETUP_CNF, setup_cnf}, + {SBIT(OSMO_CC_STATE_OVERLAP_OUT) | SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | + SBIT(OSMO_CC_STATE_ALERTING_OUT), + OSMO_CC_MSG_PROGRESS_IND, progress_ind}, + {SBIT(OSMO_CC_STATE_OVERLAP_OUT), + OSMO_CC_MSG_INFO_REQ, info_req}, + {SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_OUT), + OSMO_CC_MSG_NOTIFY_IND, notify_ind}, + {SBIT(OSMO_CC_STATE_CONNECTING_OUT), + OSMO_CC_MSG_SETUP_COMP_REQ, setup_comp_req}, + + /* call setup from lower layer protocol */ + {SBIT(OSMO_CC_STATE_IDLE), + OSMO_CC_MSG_SETUP_IND, setup_ind}, + {SBIT(OSMO_CC_STATE_INIT_IN), + OSMO_CC_MSG_SETUP_ACK_REQ, setup_ack_req}, + {SBIT(OSMO_CC_STATE_INIT_IN) | SBIT(OSMO_CC_STATE_OVERLAP_IN), + OSMO_CC_MSG_PROC_REQ, proc_req}, + {SBIT(OSMO_CC_STATE_INIT_IN) | SBIT(OSMO_CC_STATE_OVERLAP_IN) | + SBIT(OSMO_CC_STATE_PROCEEDING_IN), + OSMO_CC_MSG_ALERT_REQ, alert_req}, + {SBIT(OSMO_CC_STATE_INIT_IN) | SBIT(OSMO_CC_STATE_OVERLAP_IN) | + SBIT(OSMO_CC_STATE_PROCEEDING_IN) | SBIT(OSMO_CC_STATE_ALERTING_IN), + OSMO_CC_MSG_SETUP_RSP, setup_rsp}, + {SBIT(OSMO_CC_STATE_OVERLAP_IN) | SBIT(OSMO_CC_STATE_PROCEEDING_IN) | + SBIT(OSMO_CC_STATE_ALERTING_IN), + OSMO_CC_MSG_PROGRESS_REQ, progress_req}, + {SBIT(OSMO_CC_STATE_OVERLAP_IN), + OSMO_CC_MSG_INFO_IND, info_ind}, + {SBIT(OSMO_CC_STATE_PROCEEDING_IN) | SBIT(OSMO_CC_STATE_ALERTING_IN), + OSMO_CC_MSG_NOTIFY_REQ, notify_req}, + {SBIT(OSMO_CC_STATE_CONNECTING_IN), + OSMO_CC_MSG_SETUP_COMP_IND, setup_comp_ind}, + + /* active state */ + {SBIT(OSMO_CC_STATE_ACTIVE), + OSMO_CC_MSG_NOTIFY_IND, notify_ind}, + {SBIT(OSMO_CC_STATE_ACTIVE), + OSMO_CC_MSG_NOTIFY_REQ, notify_req}, + {SBIT(OSMO_CC_STATE_ACTIVE), + OSMO_CC_MSG_INFO_IND, info_ind}, + {SBIT(OSMO_CC_STATE_ACTIVE), + OSMO_CC_MSG_INFO_REQ, info_req}, + + /* call release */ + {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_INIT_IN) | + SBIT(OSMO_CC_STATE_OVERLAP_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_IN) | + SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_PROCEEDING_IN) | + SBIT(OSMO_CC_STATE_ALERTING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_IN) | + SBIT(OSMO_CC_STATE_CONNECTING_OUT) | SBIT(OSMO_CC_STATE_CONNECTING_IN) | + SBIT(OSMO_CC_STATE_ACTIVE), + OSMO_CC_MSG_DISC_REQ, disc_req}, + {SBIT(OSMO_CC_STATE_INIT_OUT) | SBIT(OSMO_CC_STATE_INIT_IN) | + SBIT(OSMO_CC_STATE_OVERLAP_OUT) | SBIT(OSMO_CC_STATE_OVERLAP_IN) | + SBIT(OSMO_CC_STATE_PROCEEDING_OUT) | SBIT(OSMO_CC_STATE_PROCEEDING_IN) | + SBIT(OSMO_CC_STATE_ALERTING_OUT) | SBIT(OSMO_CC_STATE_ALERTING_IN) | + SBIT(OSMO_CC_STATE_CONNECTING_OUT) | SBIT(OSMO_CC_STATE_CONNECTING_IN) | + SBIT(OSMO_CC_STATE_ACTIVE), + OSMO_CC_MSG_DISC_IND, disc_ind}, + {SBIT(OSMO_CC_STATE_INIT_OUT), + OSMO_CC_MSG_REJ_IND, rej_ind}, + {SBIT(OSMO_CC_STATE_INIT_IN), + OSMO_CC_MSG_REJ_REQ, rej_req}, + {SBIT(OSMO_CC_STATE_DISCONNECTING_OUT), + OSMO_CC_MSG_REL_IND, rel_ind}, + {SBIT(OSMO_CC_STATE_DISCONNECTING_IN), + OSMO_CC_MSG_REL_REQ, rel_req}, + {SBIT(OSMO_CC_STATE_RELEASING_OUT), + OSMO_CC_MSG_REL_CNF, rel_cnf}, + + /* race condition where disconnect is received after disconnecting (disconnect collision) */ + {SBIT(OSMO_CC_STATE_DISCONNECTING_OUT), + OSMO_CC_MSG_DISC_IND, disc_collision_ind}, + {SBIT(OSMO_CC_STATE_DISCONNECTING_IN), + OSMO_CC_MSG_DISC_REQ, disc_collision_req}, + {SBIT(OSMO_CC_STATE_DISC_COLLISION), + OSMO_CC_MSG_REL_IND, rel_ind}, + {SBIT(OSMO_CC_STATE_DISC_COLLISION), + OSMO_CC_MSG_REL_REQ, rel_req}, + + /* race condition where release is received after releasing (release collision) */ + {SBIT(OSMO_CC_STATE_RELEASING_OUT), + OSMO_CC_MSG_REL_IND, rel_collision}, + {SBIT(OSMO_CC_STATE_IDLE), + OSMO_CC_MSG_REL_REQ, rel_collision}, + + /* race condition where reject is received after disconnecting */ + {SBIT(OSMO_CC_STATE_DISCONNECTING_OUT), + OSMO_CC_MSG_REJ_IND, rej_ind_disc}, + {SBIT(OSMO_CC_STATE_DISCONNECTING_IN), + OSMO_CC_MSG_REJ_REQ, rej_req_disc}, + + /* turn release into disconnect, so release is possible in any state */ + {ALL_STATES, + OSMO_CC_MSG_REL_IND, rel_ind_other}, + {ALL_STATES, + OSMO_CC_MSG_REL_REQ, rel_req_other}, +}; + +#define STATEMACHINE_LEN \ + (sizeof(statemachine_list) / sizeof(struct statemachine)) + +static void handle_msg(osmo_cc_call_t *call, osmo_cc_msg_t *msg) +{ + int i; + + /* Find function for current state and message */ + for (i = 0; i < (int)STATEMACHINE_LEN; i++) + if ((msg->type == statemachine_list[i].type) + && ((1 << call->state) & statemachine_list[i].states)) + break; + if (i == STATEMACHINE_LEN) { + PDEBUG(DCC, DEBUG_INFO, "Message %s unhandled at state %s (callref %d)\n", + osmo_cc_msg_name(msg->type), state_names[call->state], call->callref); + osmo_cc_free_msg(msg); + return; + } + + PDEBUG(DCC, DEBUG_INFO, "Handle message %s at state %s (callref %d)\n", + osmo_cc_msg_name(msg->type), state_names[call->state], call->callref); + statemachine_list[i].action(call, msg); +} + +static int handle_call(osmo_cc_call_t *call) +{ + /* may handle only one message, since call may be destroyed when handling */ + if (call->sock_queue) { + osmo_cc_msg_t *msg = osmo_cc_msg_list_dequeue(&call->sock_queue, NULL); + handle_msg(call, msg); + return 1; + } + + return 0; +} + +static int osmo_cc_handle_endpoint(osmo_cc_endpoint_t *ep) +{ + int work = 0; + uint32_t callref; + osmo_cc_call_t *call; + + /* may handle only one message, since call may be destroyed when handling */ + if (ep->ll_queue) { + osmo_cc_msg_t *msg = osmo_cc_msg_list_dequeue(&ep->ll_queue, &callref); + ep->ll_msg_cb(ep, callref, msg); + work |= 1; + } + + /* handle only one call, because it might have been removed */ + for (call = ep->call_list; call; call = call->next) { + work |= handle_call(call); + if (work) + break; + } + + return work; +} + +/* main handler + * note that it must be called in a loop (with ohter handlers) until no work was done + */ +int osmo_cc_handle(void) +{ + int work = 0; + osmo_cc_endpoint_t *ep; + + for (ep = osmo_cc_endpoint_list; ep; ep = ep->next) { + work |= osmo_cc_handle_endpoint(ep); + work |= osmo_cc_handle_socket(&ep->os); + } + + return work; +} + +osmo_cc_call_t *osmo_cc_call_by_callref(osmo_cc_endpoint_t *ep, uint32_t callref) +{ + osmo_cc_call_t *call; + + if (!callref) + return NULL; + + for (call = ep->call_list; call; call = call->next) { + if (call->callref == callref) { + return call; + } + } + + return NULL; +} + + +void osmo_cc_ll_msg(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg) +{ + osmo_cc_call_t *call; + + if (!(msg->type & 1)) { + PDEBUG(DCC, DEBUG_ERROR, "Received message from lower layer that is not an _IND nor _CNF, please fix!\n"); + osmo_cc_free_msg(msg); + return; + } + + call = osmo_cc_call_by_callref(ep, callref); + if (call) { + /* complete cause */ + osmo_cc_convert_cause_msg(msg); + handle_msg(call, msg); + return; + } + + /* if no ref exists */ +} + +/* message from upper layer (socket) */ +void osmo_cc_ul_msg(void *priv, uint32_t callref, osmo_cc_msg_t *msg) +{ + osmo_cc_endpoint_t *ep = priv; + osmo_cc_call_t *call; + + if ((msg->type & 1)) { + PDEBUG(DCC, DEBUG_ERROR, "Received message from socket that is not an _REQ nor _RSP, please fix!\n"); + osmo_cc_free_msg(msg); + return; + } + + call = osmo_cc_call_by_callref(ep, callref); + if (call) { + /* if we are not in INIT-IN state, we change a CC-REJ-REQ into CC-REL_REQ. + * this happens, if the socket fails. + */ + if (call->state != OSMO_CC_STATE_INIT_IN + && msg->type == OSMO_CC_MSG_REJ_REQ) + msg->type = OSMO_CC_MSG_REL_REQ; + + osmo_cc_msg_list_enqueue(&call->sock_queue, msg, call->callref); + return; + } + + /* if no ref exists */ + + /* reject and release are ignored */ + if (msg->type == OSMO_CC_MSG_REJ_REQ + || msg->type == OSMO_CC_MSG_REL_REQ) { + osmo_cc_free_msg(msg); + return; + } + + /* reject if not a setup/attach or release message */ + if (msg->type != OSMO_CC_MSG_SETUP_REQ + && msg->type != OSMO_CC_MSG_ATTACH_REQ) { + sock_reject_msg(&ep->os, callref, ep->serving_location, 0, OSMO_CC_ISDN_CAUSE_INVAL_CALLREF, 0); + osmo_cc_free_msg(msg); + return; + } + + /* create call instance with one socket reference */ + call = call_new(ep, callref); + + osmo_cc_msg_list_enqueue(&call->sock_queue, msg, call->callref); +} + +static void osmo_cc_help_address(void) +{ + printf("Address options:\n\n"); + + printf("local :\n"); + printf("local []:\n"); + printf("remote :\n"); + printf("remote []:\n\n"); + + printf("These options can be used to define local and remote IP and port for the socket\n"); + printf("interface. Note that IPv6 adresses must be enclosed by '[' and ']'.\n\n"); + + printf("If no local address was given, the IPv4 loopback IP and port %d is used. If\n", OSMO_CC_DEFAULT_PORT); + printf("this port is already in use, the first free higher port is used.\n\n"); + + printf("If no remote address is given, the local IP is used. If the local port is %d,\n", OSMO_CC_DEFAULT_PORT); + printf("the remote port will be %d. If not, the remote port will be %d. This way it is\n", OSMO_CC_DEFAULT_PORT + 1, OSMO_CC_DEFAULT_PORT); + printf("possible to link two interfaces without any IP configuration required.\n\n"); +} + +static int osmo_cc_set_address(osmo_cc_endpoint_t *ep, const char *text) +{ + const char **address_p, **host_p; + uint16_t *port_p; + int rc; + + if (!strncasecmp(text, "local", 5)) { + text += 5; + /* remove spaces after keyword */ + while (*text) { + if (*text > 32) + break; + text++; + } + address_p = &ep->local_address; + host_p = &ep->local_host; + port_p = &ep->local_port; + } else if (!strncasecmp(text, "remote", 6)) { + text += 6; + /* remove spaces after keyword */ + while (*text) { + if (*text > 32) + break; + text++; + } + if (!strcasecmp(text, "auto")) { + PDEBUG(DCC, DEBUG_DEBUG, "setting automatic remote peer selection\n"); + ep->remote_auto = 1; + return 0; + } + ep->remote_auto = 0; + address_p = &ep->remote_address; + host_p = &ep->remote_host; + port_p = &ep->remote_port; + } else { + PDEBUG(DCC, DEBUG_ERROR, "Invalid local or remote address definition '%s'\n", text); + return -EINVAL; + } + + if (*address_p) { + free((char *)*address_p); + *address_p = NULL; + } + if (*host_p) { + free((char *)*host_p); + *host_p = NULL; + } + rc = split_address(text, host_p, port_p); + if (rc < 0) { + /* unset, so that this is not treated with free() */ + *host_p = NULL; + return rc; + } + *address_p = strdup(text); + *host_p = strdup(*host_p); + + return 0; +} + +static void osmo_cc_help_rtp(void) +{ + printf("RTP options:\n\n"); + + printf("rtp-peer \n"); + printf("rtp-peer \n"); + printf("rtp-ports \n\n"); + + printf("These options can be used to alter the local IP and port range for RTP traffic.\n"); + printf("By default the local IPv4 loopback address is used. To connect interfaces\n"); + printf("between machines, local machine's IP must be given.\n\n"); +} + +static int osmo_cc_set_rtp(const char *text) +{ + int peer = 0, ports = 0; + + if (!strncasecmp(text, "rtp-peer", 8)) { + text += 8; + peer = 1; + } else if (!strncasecmp(text, "rtp-ports", 9)) { + text += 9; + ports = 1; + } else { + PDEBUG(DCC, DEBUG_ERROR, "Invalid RTP definition '%s'\n", text); + return -EINVAL; + } + + /* remove spaces after keyword */ + while (*text) { + if (*text > 32) + break; + text++; + } + + if (peer) { + enum osmo_cc_session_addrtype addrtype; + addrtype = osmo_cc_address_type(text); + if (addrtype == osmo_cc_session_addrtype_unknown) { + PDEBUG(DCC, DEBUG_ERROR, "Given RTP address '%s' is invalid.\n", text); + return -EINVAL; + } + osmo_cc_set_local_peer(osmo_cc_session_nettype_inet, addrtype, text); + return 0; + } + + if (ports) { + int from = 0, to = 0; + + /* from port */ + while (*text > ' ') { + if (*text < '0' || *text > '9') { + PDEBUG(DCC, DEBUG_ERROR, "Given 'from' port in '%s' is invalid.\n", text); + return -EINVAL; + } + from = from * 10 + *text - '0'; + } + + /* remove spaces after keyword */ + while (*text) { + if (*text > 32) + break; + text++; + } + + /* to port */ + while (*text > ' ') { + if (*text < '0' || *text > '9') { + PDEBUG(DCC, DEBUG_ERROR, "Given 'to' port in '%s' is invalid.\n", text); + return -EINVAL; + } + from = from * 10 + *text - '0'; + } + + osmo_cc_set_rtp_ports(from, to); + return 0; + } + + return -EINVAL; +} + +void osmo_cc_help(void) +{ + osmo_cc_help_screen(); + osmo_cc_help_address(); + osmo_cc_help_rtp(); +} + +/* create a new endpoint instance */ +int osmo_cc_new(osmo_cc_endpoint_t *ep, const char *version, const char *name, uint8_t serving_location, void (*ll_msg_cb)(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg), void (*ul_msg_cb)(osmo_cc_call_t *call, osmo_cc_msg_t *msg), void *priv, int argc, const char *argv[]) +{ + osmo_cc_endpoint_t **epp; + int rc; + int i; + + PDEBUG(DCC, DEBUG_DEBUG, "Creating new endpoint instance.\n"); + + if (!!strcmp(version, OSMO_CC_VERSION)) { + PDEBUG(DCC, DEBUG_ERROR, "Application was compiled for different Osmo-CC version.\n"); + return OSMO_CC_RC_VERSION_MISMATCH; + } + + memset(ep, 0, sizeof(*ep)); + + /* attach to list */ + epp = &osmo_cc_endpoint_list; + while (*epp) + epp = &((*epp)->next); + *epp = ep; + + if (name) + ep->local_name = strdup(name); + ep->ll_msg_cb = ll_msg_cb; + ep->ul_msg_cb = ul_msg_cb; + ep->serving_location = serving_location; + ep->priv = priv; + + /* apply args */ + for (i = 0; i < argc; i++) { + if (!strncasecmp(argv[i], "local", 5)) { + rc = osmo_cc_set_address(ep, argv[i]); + if (rc < 0) { + return rc; + } + } else + if (!strncasecmp(argv[i], "remote", 6)) { + rc = osmo_cc_set_address(ep, argv[i]); + if (rc < 0) { + return rc; + } + } else + if (!strncasecmp(argv[i], "rtp", 3)) { + rc = osmo_cc_set_rtp(argv[i]); + if (rc < 0) { + return rc; + } + } else + if (!strncasecmp(argv[i], "screen", 6)) { + rc = osmo_cc_add_screen(ep, argv[i]); + if (rc < 0) { + return rc; + } + } else { + PDEBUG(DCC, DEBUG_ERROR, "Unknown osmo-cc argument \"%s\"\n", argv[i]); + return -EINVAL; + } + } + + /* open socket */ + if (!ul_msg_cb) { + char address[256]; + const char *host; + uint16_t port; + enum osmo_cc_session_addrtype addrtype; + + host = ep->local_host; + port = ep->local_port; + if (!host) { + host = "127.0.0.1"; + PDEBUG(DCC, DEBUG_DEBUG, "No local peer set, using default \"%s\"\n", host); + } + rc = osmo_cc_open_socket(&ep->os, host, port, ep, osmo_cc_ul_msg, serving_location); + if (rc < 0) { + return rc; + } + port = rc; + if (!ep->local_host) { + ep->local_host = strdup(host); + /* create address string */ + addrtype = osmo_cc_address_type(host); + if (addrtype == osmo_cc_session_addrtype_ipv6) + sprintf(address, "[%s]:%d", host, port); + else + sprintf(address, "%s:%d", host, port); + ep->local_address = strdup(address); + } + ep->local_port = port; + /* auto configure */ + if (ep->remote_auto) { + free((char *)ep->remote_host); + ep->remote_host = strdup(ep->local_host); + PDEBUG(DCC, DEBUG_DEBUG, "Remote peer set to auto, using local peer's host \"%s\" for remote peer.\n", ep->remote_host); + if (rc == OSMO_CC_DEFAULT_PORT) + ep->remote_port = OSMO_CC_DEFAULT_PORT + 1; + else + ep->remote_port = OSMO_CC_DEFAULT_PORT; + PDEBUG(DCC, DEBUG_DEBUG, " -> Using remote port %d.\n", ep->remote_port); + /* create address string */ + free((char *)ep->remote_address); + addrtype = osmo_cc_address_type(ep->remote_host); + if (addrtype == osmo_cc_session_addrtype_ipv6) + sprintf(address, "[%s]:%d", ep->remote_host, ep->remote_port); + else + sprintf(address, "%s:%d", ep->remote_host, ep->remote_port); + ep->remote_address = strdup(address); + } + /* attach to remote host */ + timer_init(&ep->attach_timer, send_attach_ind, ep); + if (ep->remote_host) { + send_attach_ind(&ep->attach_timer); + } + } + + return 0; +} + +/* destroy an endpoint instance */ +void osmo_cc_delete(osmo_cc_endpoint_t *ep) +{ + osmo_cc_endpoint_t **epp; + + PDEBUG(DCC, DEBUG_DEBUG, "Destroying endpoint instance.\n"); + + /* detach from list >*/ + epp = &osmo_cc_endpoint_list; + while (*epp && *epp != ep) + epp = &((*epp)->next); + if (*epp) + *epp = ep->next; + + /* remove timer */ + timer_exit(&ep->attach_timer); + + /* flush screen lists */ + osmo_cc_flush_screen(ep->screen_calling_in); + osmo_cc_flush_screen(ep->screen_called_in); + osmo_cc_flush_screen(ep->screen_calling_out); + osmo_cc_flush_screen(ep->screen_called_out); + + /* free local and remote peer */ + free((char *)ep->local_name); + free((char *)ep->local_address); + free((char *)ep->local_host); + free((char *)ep->remote_address); + free((char *)ep->remote_host); + + /* destroying all child callesses (calls) */ + while(ep->call_list) + call_delete(ep->call_list); + + /* flush message queue */ + while(ep->ll_queue) { + osmo_cc_msg_t *msg = osmo_cc_msg_list_dequeue(&ep->ll_queue, NULL); + osmo_cc_free_msg(msg); + } + + /* remove socket */ + osmo_cc_close_socket(&ep->os); + + memset(ep, 0, sizeof(*ep)); +} + +/* create new call instance */ +osmo_cc_call_t *osmo_cc_call_new(osmo_cc_endpoint_t *ep) +{ + return call_new(ep, osmo_cc_new_callref()); +} + +/* destroy call instance */ +void osmo_cc_call_delete(osmo_cc_call_t *call) +{ + call_delete(call); +} + +/* check valid IP and return address type (protocol) */ +enum osmo_cc_session_addrtype osmo_cc_address_type(const char *address) +{ + struct sockaddr_storage sa; + int rc; + + rc = inet_pton(AF_INET, address, &sa); + if (rc > 0) + return osmo_cc_session_addrtype_ipv4; + rc = inet_pton(AF_INET6, address, &sa); + if (rc > 0) + return osmo_cc_session_addrtype_ipv6; + + return osmo_cc_session_addrtype_unknown; +} + +/* get host from address */ +const char *osmo_cc_host_of_address(const char *address) +{ + static char host[256]; + char *p; + + if (strlen(address) >= sizeof(host)) { + PDEBUG(DCC, DEBUG_ERROR, "String way too long!\n"); + return NULL; + } + + if (address[0] == '[' && (p = strchr(address, ']'))) { + memcpy(host, address + 1, p - address - 1); + host[p - address - 1] = '\0'; + return host; + } + + strcpy(host, address); + if ((p = strchr(host, ':'))) + *p = '\0'; + + return host; +} + +/* get port from address */ +const char *osmo_cc_port_of_address(const char *address) +{ + const char *p; + int i; + + if (address[0] == '[' && (p = strchr(address, ']'))) + address = p + 1; + + if (!(p = strchr(address, ':'))) + return NULL; + p++; + + /* check for zero */ + if (p[0] == '0') + return NULL; + + /* check for digits */ + for (i = 0; i < (int)strlen(p); i++) { + if (p[i] < '0' || p[i] > '9') + return NULL; + } + + /* check for magnitude */ + if (atoi(p) > 65535) + return NULL; + + return p; +} + diff --git a/src/libosmocc/endpoint.h b/src/libosmocc/endpoint.h new file mode 100644 index 0000000..7145726 --- /dev/null +++ b/src/libosmocc/endpoint.h @@ -0,0 +1,128 @@ +#ifndef OSMO_CC_ENDPOINT_H +#define OSMO_CC_ENDPOINT_H + +#include "message.h" +#include "socket.h" +#include "cause.h" + +/* special osmo-cc error codes */ +#define OSMO_CC_RC_SEE_ERRNO -1 +#define OSMO_CC_RC_VERSION_MISMATCH 1 + +#define OSMO_CC_ATTACH_TIMER 2 + +/* call control state */ +enum osmo_cc_state { + OSMO_CC_STATE_IDLE = 0, + /* call states */ + OSMO_CC_STATE_INIT_OUT, /* outgoing CC-SETUP-REQ sent */ + OSMO_CC_STATE_INIT_IN, /* incoming CC-SETUP-IND received */ + OSMO_CC_STATE_OVERLAP_OUT, /* received CC-SETUP-ACK-IND on outgoing call */ + OSMO_CC_STATE_OVERLAP_IN, /* sent CC-SETUP-ACK-REQ on incoming call */ + OSMO_CC_STATE_PROCEEDING_OUT, /* received CC-PROC-IND on outgoing call */ + OSMO_CC_STATE_PROCEEDING_IN, /* sent CC-PROC-REQ on incoming call */ + OSMO_CC_STATE_ALERTING_OUT, /* received CC-ALERT-IND on outgoing call */ + OSMO_CC_STATE_ALERTING_IN, /* sent CC-ALERT-REQ on incoming call */ + OSMO_CC_STATE_CONNECTING_OUT, /* received CC-SETUP-CNF on outgoing call */ + OSMO_CC_STATE_CONNECTING_IN, /* sent CC-SETUP-RSP on incoming call */ + OSMO_CC_STATE_ACTIVE, /* received or sent CC-SETUP-COMPL-* */ + OSMO_CC_STATE_DISCONNECTING_OUT, /* sent CC-DISC-REQ */ + OSMO_CC_STATE_DISCONNECTING_IN, /* received CC-DISC-IND */ + OSMO_CC_STATE_DISC_COLLISION, /* received CC-DISC-IND after sending CC-DISC_REQ */ + OSMO_CC_STATE_RELEASING_OUT, /* sent CC-REL-REQ */ + /* attachment states */ + OSMO_CC_STATE_ATTACH_SENT, /* outgoing CC-ATT-REQ sent to socket */ + OSMO_CC_STATE_ATTACH_OUT, /* received CC-ATT-RSP on outgoing socket */ + OSMO_CC_STATE_ATTACH_WAIT, /* wait for outgoing attachment after failure */ + OSMO_CC_STATE_ATTACH_IN, /* incoming CC-ATT-REQ received from socket*/ +}; + +/* sample type */ +typedef int16_t osmo_cc_sample_t; + +#define OSMO_CC_SAMPLE_MILLIWATT 23170 /* peak sine at -3 dB of full sample range */ +#define OSMO_CC_SAMPLE_SPEECH 3672 /* peak speech at -16 dB of milliwatt */ +#define OSMO_CC_SAMPLE_MIN -32768 /* lowest level */ +#define OSMO_CC_SAMPLE_MAX 32767 /* highest level */ + +struct osmo_cc_call; + +typedef struct osmo_cc_screen_list { + struct osmo_cc_screen_list *next; + int has_from_type; + uint8_t from_type; + int has_from_present; + uint8_t from_present; + char from[128]; + int has_to_type; + uint8_t to_type; + int has_to_present; + uint8_t to_present; + char to[128]; +} osmo_cc_screen_list_t; + +/* endpoint instance */ +typedef struct osmo_cc_endpoint { + struct osmo_cc_endpoint *next; + void *priv; + void (*ll_msg_cb)(struct osmo_cc_endpoint *ep, uint32_t callref, osmo_cc_msg_t *msg); + void (*ul_msg_cb)(struct osmo_cc_call *call, osmo_cc_msg_t *msg); + osmo_cc_msg_list_t *ll_queue; /* messages towards lower layer */ + struct osmo_cc_call *call_list; + const char *local_name; /* name of interface */ + const char *local_address; /* host+port */ + const char *local_host; + uint16_t local_port; + const char *remote_address; /* host+port */ + const char *remote_host; + uint16_t remote_port; + uint8_t serving_location; + osmo_cc_socket_t os; + osmo_cc_screen_list_t *screen_calling_in; + osmo_cc_screen_list_t *screen_called_in; + osmo_cc_screen_list_t *screen_calling_out; + osmo_cc_screen_list_t *screen_called_out; + int remote_auto; /* automatic remote address */ + struct timer attach_timer; /* timer to retry attachment */ +} osmo_cc_endpoint_t; + +extern osmo_cc_endpoint_t *osmo_cc_endpoint_list; + +/* call process */ +typedef struct osmo_cc_call { + struct osmo_cc_call *next; + osmo_cc_endpoint_t *ep; + enum osmo_cc_state state; + int lower_layer_released; /* when lower layer sent release, while upper layer gets a disconnect */ + int upper_layer_released; /* when upper layer sent release, while lower layer gets a disconnect */ + uint32_t callref; + osmo_cc_msg_list_t *sock_queue; /* messages from socket */ + const char *attached_host; /* host and port from remote peer that attached to us */ + uint16_t attached_port; + const char *attached_name; /* interface name from remote peer that attached to us */ +} osmo_cc_call_t; + +/* returns 0 if ok + * returns <0 for error as indicated + * returns >=1 to indicate osmo-cc error code + */ + +void osmo_cc_help(void); +int osmo_cc_new(osmo_cc_endpoint_t *ep, const char *version, const char *name, uint8_t serving_location, void (*ll_msg_cb)(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg), void (*ul_msg_cb)(osmo_cc_call_t *call, osmo_cc_msg_t *msg), void *priv, int argc, const char *argv[]); +void osmo_cc_delete(struct osmo_cc_endpoint *ep); +int osmo_cc_handle(void); +osmo_cc_call_t *osmo_cc_call_by_callref(osmo_cc_endpoint_t *ep, uint32_t callref); +void osmo_cc_ll_msg(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg); +void osmo_cc_ul_msg(void *priv, uint32_t callref, osmo_cc_msg_t *msg); +osmo_cc_call_t *osmo_cc_call_new(osmo_cc_endpoint_t *ep); +void osmo_cc_call_delete(struct osmo_cc_call *call); +enum osmo_cc_session_addrtype osmo_cc_address_type(const char *address); +const char *osmo_cc_host_of_address(const char *address); +const char *osmo_cc_port_of_address(const char *address); + +#include "session.h" +#include "rtp.h" +#include "sdp.h" +#include "screen.h" + +#endif /* OSMO_CC_ENDPOINT_H */ diff --git a/src/libosmocc/helper.c b/src/libosmocc/helper.c new file mode 100644 index 0000000..cde8b27 --- /dev/null +++ b/src/libosmocc/helper.c @@ -0,0 +1,171 @@ +/* Osmo-CC: helpers to simplify Osmo-CC usage + * + * (C) 2016 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 "../libtimer/timer.h" +#include "../libdebug/debug.h" +#include "endpoint.h" +#include "helper.h" + +osmo_cc_session_t *osmo_cc_helper_audio_offer(void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, int debug) +{ + osmo_cc_session_t *session; + osmo_cc_session_media_t *media; + const char *sdp; + int i; + + session = osmo_cc_new_session(priv, NULL, NULL, NULL, 0, 0, NULL, NULL, debug); + if (!session) + return NULL; + + media = osmo_cc_add_media(session, 0, 0, NULL, osmo_cc_session_media_type_audio, 0, osmo_cc_session_media_proto_rtp, 1, 1, receiver, debug); + osmo_cc_rtp_open(media); + + for (i = 0; codecs[i].payload_name; i++) + osmo_cc_add_codec(media, codecs[i].payload_name, codecs[i].payload_rate, codecs[i].payload_channels, codecs[i].encoder, codecs[i].decoder, debug); + + sdp = osmo_cc_session_send_offer(session); + osmo_cc_add_ie_sdp(msg, sdp); + + return session; +} + +const char *osmo_cc_helper_audio_accept(void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p, int force_our_codec) +{ + char offer_sdp[65536]; + const char *accept_sdp; + osmo_cc_session_media_t *media, *selected_media = NULL; + osmo_cc_session_codec_t *codec, *selected_codec = NULL; + int rc; + int i, selected_i; + + if (*session_p) { + PDEBUG(DCC, DEBUG_ERROR, "Session already set, please fix!\n"); + abort(); + } + if (*codec_p) { + PDEBUG(DCC, DEBUG_ERROR, "Codec already set, please fix!\n"); + abort(); + } + + /* SDP IE */ + rc = osmo_cc_get_ie_sdp(msg, 0, offer_sdp, sizeof(offer_sdp)); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "There is no SDP included in setup request.\n"); + return NULL; + } + + *session_p = osmo_cc_session_receive_offer(priv, offer_sdp); + if (!*session_p) { + PDEBUG(DCC, DEBUG_ERROR, "Failed to parse SDP.\n"); + return NULL; + } + + selected_i = -1; + osmo_cc_session_for_each_media((*session_p)->media_list, media) { + /* only audio */ + if (media->description.type != osmo_cc_session_media_type_audio) + continue; + osmo_cc_session_for_each_codec(media->codec_list, codec) { + for (i = 0; codecs[i].payload_name; i++) { + if (osmo_cc_session_if_codec(codec, codecs[i].payload_name, codecs[i].payload_rate, codecs[i].payload_channels)) { + /* select the first matchting codec or the one we prefer */ + if (selected_i < 0 || i < selected_i) { + selected_codec = codec; + selected_media = media; + selected_i = i; + } + /* if we don't force our preferred codec, use the preferred one from the remote */ + if (!force_our_codec) + break; + } + } + } + } + if (!selected_codec) { + PDEBUG(DCC, DEBUG_ERROR, "No codec found in setup message that we support.\n"); + osmo_cc_free_session(*session_p); + return NULL; + } + osmo_cc_session_accept_codec(selected_codec, codecs[selected_i].encoder, codecs[selected_i].decoder); + osmo_cc_session_accept_media(selected_media, 0, 0, NULL, 1, 1, receiver); + osmo_cc_rtp_open(selected_media); + osmo_cc_rtp_connect(selected_media); + *codec_p = selected_codec; + + accept_sdp = osmo_cc_session_send_answer(*session_p); + if (!accept_sdp) { + osmo_cc_free_session(*session_p); + return NULL; + } + + return accept_sdp; +} + +int osmo_cc_helper_audio_negotiate(osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p) +{ + char sdp[65536]; + osmo_cc_session_media_t *media; + int rc; + + if (!(*session_p)) { + PDEBUG(DCC, DEBUG_ERROR, "Session not set, please fix!\n"); + abort(); + } + + /* once done, just ignore further messages that reply to setup */ + if (*codec_p) + return 0; + + /* SDP IE */ + rc = osmo_cc_get_ie_sdp(msg, 0, sdp, sizeof(sdp)); + if (rc < 0) + return 0; // no reply in this message + + rc = osmo_cc_session_receive_answer(*session_p, sdp); + if (rc < 0) + return rc; + + osmo_cc_session_for_each_media((*session_p)->media_list, media) { + /* only audio */ + if (media->description.type != osmo_cc_session_media_type_audio) + continue; + /* select first codec, if one was accpeted */ + if (media->codec_list) + *codec_p = media->codec_list; + if (*codec_p) { + osmo_cc_rtp_connect(media); + /* no more media streams */ + break; + } + } + if (!(*codec_p)) { + PDEBUG(DCC, DEBUG_ERROR, "No codec found in setup reply message that we support.\n"); + return -EIO; + } + + return 0; +} + diff --git a/src/libosmocc/helper.h b/src/libosmocc/helper.h new file mode 100644 index 0000000..e3acf0e --- /dev/null +++ b/src/libosmocc/helper.h @@ -0,0 +1,13 @@ + +struct osmo_cc_helper_audio_codecs { + const char *payload_name; + uint32_t payload_rate; + int payload_channels; + void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); + void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); +}; + +osmo_cc_session_t *osmo_cc_helper_audio_offer(void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, int debug); +const char *osmo_cc_helper_audio_accept(void *priv, struct osmo_cc_helper_audio_codecs *codecs, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p, int force_our_codec); +int osmo_cc_helper_audio_negotiate(osmo_cc_msg_t *msg, osmo_cc_session_t **session_p, osmo_cc_session_codec_t **codec_p); + diff --git a/src/libosmocc/message.c b/src/libosmocc/message.c new file mode 100644 index 0000000..126f684 --- /dev/null +++ b/src/libosmocc/message.c @@ -0,0 +1,879 @@ +/* Osmo-CC: Message handling + * + * (C) 2016 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 "../libdebug/debug.h" +#include "message.h" + +static uint32_t new_callref = 0; + +uint32_t osmo_cc_new_callref(void) +{ + return (++new_callref); +} + +const char *osmo_cc_msg_name(uint8_t msg_type) +{ + switch (msg_type) { + case OSMO_CC_MSG_SETUP_REQ: + return "CC-SETUP-REQ"; + case OSMO_CC_MSG_SETUP_IND: + return "CC-SETUP-IND"; + case OSMO_CC_MSG_REJ_REQ: + return "CC-REJ-REQ"; + case OSMO_CC_MSG_REJ_IND: + return "CC-REJ-IND"; + case OSMO_CC_MSG_SETUP_ACK_REQ: + return "CC-SETUP-ACK-REQ"; + case OSMO_CC_MSG_SETUP_ACK_IND: + return "CC-SETUP-ACK-IND"; + case OSMO_CC_MSG_PROC_REQ: + return "CC-PROC-REQ"; + case OSMO_CC_MSG_PROC_IND: + return "CC-PROC-IND"; + case OSMO_CC_MSG_ALERT_REQ: + return "CC-ALERT-REQ"; + case OSMO_CC_MSG_ALERT_IND: + return "CC-ALERT-IND"; + case OSMO_CC_MSG_SETUP_RSP: + return "CC-SETUP-RSP"; + case OSMO_CC_MSG_SETUP_CNF: + return "CC-SETUP-CNF"; + case OSMO_CC_MSG_SETUP_COMP_REQ: + return "CC-SETUP-COMP-REQ"; + case OSMO_CC_MSG_SETUP_COMP_IND: + return "CC-SETUP-COMP-IND"; + case OSMO_CC_MSG_DISC_REQ: + return "CC-DISC-REQ"; + case OSMO_CC_MSG_DISC_IND: + return "CC-DISC-IND"; + case OSMO_CC_MSG_REL_REQ: + return "CC-REL-REQ"; + case OSMO_CC_MSG_REL_CNF: + return "CC-REL-CNF"; + case OSMO_CC_MSG_REL_IND: + return "CC-REL-IND"; + case OSMO_CC_MSG_PROGRESS_REQ: + return "CC-PROGRESS-REQ"; + case OSMO_CC_MSG_PROGRESS_IND: + return "CC-PROGRESS-IND"; + case OSMO_CC_MSG_NOTIFY_REQ: + return "CC-NOTIFY-REQ"; + case OSMO_CC_MSG_NOTIFY_IND: + return "CC-NOTIFY-IND"; + case OSMO_CC_MSG_INFO_REQ: + return "CC-INFO-REQ"; + case OSMO_CC_MSG_INFO_IND: + return "CC-INFO-IND"; + case OSMO_CC_MSG_ATTACH_REQ: + return "CC-ATTACH-REQ"; + case OSMO_CC_MSG_ATTACH_IND: + return "CC-ATTACH-IND"; + case OSMO_CC_MSG_ATTACH_RSP: + return "CC-ATTACH-RSP"; + case OSMO_CC_MSG_ATTACH_CNF: + return "CC-ATTACH-CNF"; + default: + return ""; + } +} + +/* create message with maximum size */ +osmo_cc_msg_t *osmo_cc_new_msg(uint8_t msg_type) +{ + osmo_cc_msg_t *msg; + + /* allocate message */ + msg = calloc(1, sizeof(*msg) + 65535); + if (!msg) { + PDEBUG(DCC, DEBUG_ERROR, "No memory\n"); + abort(); + } + /* set message type and zero lentgh */ + msg->type = msg_type; + msg->length_networkorder = htons(0); + + return msg; +} + +/* clone message */ +osmo_cc_msg_t *osmo_cc_clone_msg(osmo_cc_msg_t *msg) +{ + osmo_cc_msg_t *new_msg; + + new_msg = osmo_cc_new_msg(msg->type); + new_msg->length_networkorder = msg->length_networkorder; + memcpy(new_msg->data, msg->data, ntohs(msg->length_networkorder)); + + return new_msg; +} + +osmo_cc_msg_t *osmo_cc_msg_list_dequeue(osmo_cc_msg_list_t **mlp, uint32_t *callref_p) +{ + osmo_cc_msg_list_t *ml; + osmo_cc_msg_t *msg; + + ml = *mlp; + msg = ml->msg; + if (callref_p) + *callref_p = ml->callref; + *mlp = ml->next; + free(ml); + + return msg; +} + +osmo_cc_msg_list_t *osmo_cc_msg_list_enqueue(osmo_cc_msg_list_t **mlp, osmo_cc_msg_t *msg, uint32_t callref) +{ + osmo_cc_msg_list_t *ml; + + ml = calloc(1, sizeof(*ml)); + ml->msg = msg; + ml->callref = callref; + while (*mlp) + mlp = &((*mlp)->next); + *mlp = ml; + + return ml; +} + +/* destroy message */ +void osmo_cc_free_msg(osmo_cc_msg_t *msg) +{ + free(msg); +} + +static void osmo_cc_debug_ie(osmo_cc_msg_t *msg, int level) +{ + uint16_t msg_len, len; + uint8_t *p; + osmo_cc_ie_t *ie; + + msg_len = ntohs(msg->length_networkorder); + p = msg->data; + + PDEBUG(DCC, level, "Debugging Message: type=0x%02x length=%d value=%s\n", msg->type, msg_len, debug_hex(p, msg_len)); + while (msg_len) { + ie = (osmo_cc_ie_t *)p; + /* check for minimum IE length */ + if (msg_len < sizeof(*ie)) { + PDEBUG(DCC, level, "****** Rest of message is too short for an IE: value=%s\n", debug_hex(p, msg_len)); + return; + } + /* get actual IE length */ + len = ntohs(ie->length_networkorder); + /* check if IE length does not exceed message */ + if (msg_len < sizeof(*ie) + len) { + PDEBUG(DCC, level, "****** IE: type=0x%02x length=%d would exceed the rest length of message (%d bytes left)\n", ie->type, len, msg_len - (int)sizeof(*ie)); + return; + } + PDEBUG(DCC, level, "IE: type=0x%02x length=%d value=%s\n", ie->type, len, debug_hex(ie->data, len)); + p += sizeof(*ie) + len; + msg_len -= sizeof(*ie) + len; + } +} + +/* search and return information element + * we give the IE type we are searching for + * we also give the repetition, to find IE that is repeated + * the result is stored in *ie_data + * the return length is the length that exceeds the given ie_len + * if there is an error, a value < 0 is returned + */ +int osmo_cc_get_ie_struct(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat, int ie_len, const osmo_cc_ie_t **ie_struct) +{ + uint16_t msg_len, len; + uint8_t *p; + osmo_cc_ie_t *ie; + + msg_len = ntohs(msg->length_networkorder); + p = msg->data; + + while (msg_len) { + ie = (osmo_cc_ie_t *)p; + /* check for minimum IE length */ + if (msg_len < sizeof(*ie)) { + PDEBUG(DCC, DEBUG_ERROR, "MSG short read\n"); + osmo_cc_debug_ie(msg, DEBUG_ERROR); + return -EINVAL; + } + /* get actual IE length */ + len = ntohs(ie->length_networkorder); + /* check if IE length does not exceed message */ + if (msg_len < sizeof(*ie) + len) { + PDEBUG(DCC, DEBUG_ERROR, "MSG short read\n"); + osmo_cc_debug_ie(msg, DEBUG_ERROR); + return -EINVAL; + } + /* check if IE matches the one that is searched for */ + if (ie->type != ie_type) { + p += sizeof(*ie) + len; + msg_len -= sizeof(*ie) + len; + continue; + } + /* check if IE repetition exists */ + if (ie_repeat) { + --ie_repeat; + p += sizeof(*ie) + len; + msg_len -= sizeof(*ie) + len; + continue; + } + /* return IE and indicate how many bytes we have more than the given length*/ + if (ntohs(ie->length_networkorder) < ie_len) { + PDEBUG(DCC, DEBUG_ERROR, "IE 0x%02d has length of %d, but we expect it to have at least %d!\n", ie_type, ntohs(ie->length_networkorder), ie_len); + return -EINVAL; + } + *ie_struct = ie; + return ntohs(ie->length_networkorder) - ie_len; + } + + /* IE not found */ + return -EINVAL; +} + +/* as above, but return data of IE only */ +int osmo_cc_get_ie_data(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat, int ie_len, const void **ie_data) +{ + const osmo_cc_ie_t *ie; + int rc; + + rc = osmo_cc_get_ie_struct(msg, ie_type, ie_repeat, ie_len, &ie); + if (rc >= 0) + *ie_data = ie->data; + + return rc; +} + +/* as above, but return 1 if IE exists */ +int osmo_cc_has_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat) +{ + const osmo_cc_ie_t *ie; + int rc; + + rc = osmo_cc_get_ie_struct(msg, ie_type, ie_repeat, 0, &ie); + if (rc >= 0) + return 1; + + return 0; +} + +/* remove IE from message */ +int osmo_cc_remove_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat) +{ + const osmo_cc_ie_t *ie; + int rc; + int msg_len, before_ie, ie_size, after_ie; + + rc = osmo_cc_get_ie_struct(msg, ie_type, ie_repeat, 0, &ie); + if (rc < 0) + return rc; + + msg_len = ntohs(msg->length_networkorder); + before_ie = (void *)ie - (void *)msg->data; + ie_size = sizeof(*ie) + ntohs(ie->length_networkorder); + after_ie = msg_len - ie_size - before_ie; + if (after_ie) + memcpy(msg->data + before_ie, msg->data + before_ie + ie_size, after_ie); + msg->length_networkorder = htons(msg_len - ie_size); + + return 0; +} + +/* add information element + * the type is given by ie_type and length is given by ie_len + * the return value is a pointer to the data of the IE + */ +void *osmo_cc_add_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_len) +{ + uint16_t msg_len; + int new_msg_len; + uint8_t *p; + osmo_cc_ie_t *ie; + + /* get pointer to first IE, if any */ + p = msg->data; + /* expand messasge */ + msg_len = ntohs(msg->length_networkorder); + new_msg_len = msg_len + sizeof(*ie) + ie_len; + if (new_msg_len > 65535) { + PDEBUG(DCC, DEBUG_ERROR, "MSG overflow\n"); + return NULL; + } + msg->length_networkorder = htons(new_msg_len); + /* go to end of (unexpanded) message */ + ie = (osmo_cc_ie_t *)(p + msg_len); + /* add ie */ + ie->type = ie_type; + ie->length_networkorder = htons(ie_len); + memset(ie->data, 0, ie_len); /* just in case there is something, but it shouldn't */ + + return ie->data; +} + +/* gets the information element's data that *iep points to and returns that ie. + * if *iep points to msg->data, the first IE's data is returned. (must be set before first call.) + * if *iep points to the end of the message, NULL is returned. + * if there is no next IE, *iep is set to point to the end of message. + */ +void *osmo_cc_msg_sep_ie(osmo_cc_msg_t *msg, void **iep, uint8_t *ie_type, uint16_t *ie_length) +{ + uint16_t msg_len; + osmo_cc_ie_t *ie; + + /* in case that *iep points to start of message, make it point to first IE */ + if (*iep == msg) + *iep = msg->data; + /* case IE */ + ie = *iep; + /* check if it is NULL */ + if (ie == NULL) + return NULL; + /* check if it points to the end of message or there is not at least an IE header */ + msg_len = ntohs(msg->length_networkorder); + if ((int)((uint8_t *)ie - msg->data) > (int)(msg_len - sizeof(*ie))) + return NULL; + /* increment iep and return IE */ + *ie_type = ie->type; + *ie_length = ntohs(ie->length_networkorder); + *iep = (uint8_t *)ie + sizeof(*ie) + *ie_length; + return ie->data; +} + +/* copy given block to given string with given size */ +static void _ie2string(char *string, size_t string_size, const char *ie_string, int ie_size) +{ + int copy_size; + + copy_size = string_size - 1; + if (ie_size < copy_size) + copy_size = ie_size; + memcpy(string, ie_string, copy_size); + string[copy_size] = '\0'; +} + +/* helper to encode called party number (dialing) */ +void osmo_cc_add_ie_called(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, const char *dialing) +{ + struct osmo_cc_ie_called *ie_called; + + ie_called = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLED, sizeof(*ie_called) + strlen(dialing)); + ie_called->type = type; + ie_called->plan = plan; + memcpy(ie_called->digits, dialing, strlen(dialing)); +} + +/* helper to decode called party number (dialing) */ +int osmo_cc_get_ie_called(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, char *dialing, size_t dialing_size) +{ + struct osmo_cc_ie_called *ie_called; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLED, ie_repeat, sizeof(*ie_called), (const void **)&ie_called); + if (rc < 0) + return rc; + *type = ie_called->type; + *plan = ie_called->plan; + _ie2string(dialing, dialing_size, ie_called->digits, rc); + return rc; +} + +/* helper to encode called party sub address (dialing) */ +void osmo_cc_add_ie_called_sub(osmo_cc_msg_t *msg, uint8_t type, const char *dialing) +{ + struct osmo_cc_ie_called_sub *ie_called_sub; + + ie_called_sub = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLED_SUB, sizeof(*ie_called_sub) + strlen(dialing)); + ie_called_sub->type = type; + memcpy(ie_called_sub->digits, dialing, strlen(dialing)); +} + +/* helper to decode called party sub address (dialing) */ +int osmo_cc_get_ie_called_sub(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *dialing, size_t dialing_size) +{ + struct osmo_cc_ie_called_sub *ie_called_sub; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLED_SUB, ie_repeat, sizeof(*ie_called_sub), (const void **)&ie_called_sub); + if (rc < 0) + return rc; + *type = ie_called_sub->type; + _ie2string(dialing, dialing_size, ie_called_sub->digits, rc); + return rc; +} + +/* helper to encode called party name (dialing) */ +void osmo_cc_add_ie_called_name(osmo_cc_msg_t *msg, const char *name) +{ + struct osmo_cc_ie_called_name *ie_called_name; + + ie_called_name = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLED_NAME, sizeof(*ie_called_name) + strlen(name)); + memcpy(ie_called_name->name, name, strlen(name)); +} + +/* helper to decode called party name (dialing) */ +int osmo_cc_get_ie_called_name(osmo_cc_msg_t *msg, int ie_repeat, char *name, size_t name_size) +{ + struct osmo_cc_ie_called_name *ie_called_name; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLED_NAME, ie_repeat, sizeof(*ie_called_name), (const void **)&ie_called_name); + if (rc < 0) + return rc; + _ie2string(name, name_size, ie_called_name->name, rc); + return rc; +} + +/* helper to encode called interface name */ +void osmo_cc_add_ie_called_interface(osmo_cc_msg_t *msg, const char *interface) +{ + struct osmo_cc_ie_called_interface *ie_interface; + + ie_interface = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLED_INTERFACE, sizeof(*ie_interface) + strlen(interface)); + memcpy(ie_interface->name, interface, strlen(interface)); +} + +/* helper to decode called interface name */ +int osmo_cc_get_ie_called_interface(osmo_cc_msg_t *msg, int ie_repeat, char *interface, size_t interface_size) +{ + struct osmo_cc_ie_called_interface *ie_interface; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLED_INTERFACE, ie_repeat, sizeof(*ie_interface), (const void **)&ie_interface); + if (rc < 0) + return rc; + _ie2string(interface, interface_size, ie_interface->name, rc); + return rc; +} + +/* helper to encode complete IE */ +void osmo_cc_add_ie_complete(osmo_cc_msg_t *msg) +{ + osmo_cc_add_ie(msg, OSMO_CC_IE_COMPLETE, 0); +} + +/* helper to decode complete IE */ +int osmo_cc_get_ie_complete(osmo_cc_msg_t *msg, int ie_repeat) +{ + int rc; + void *ie_complete; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_COMPLETE, ie_repeat, 0, (const void **)&ie_complete); + return rc; +} + +/* helper to encode calling/connected party number (caller ID or connected ID) */ +void osmo_cc_add_ie_calling(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, uint8_t present, uint8_t screen, const char *callerid) +{ + struct osmo_cc_ie_calling *ie_calling; + + ie_calling = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLING, sizeof(*ie_calling) + strlen(callerid)); + ie_calling->type = type; + ie_calling->plan = plan; + ie_calling->present = present; + ie_calling->screen = screen; + memcpy(ie_calling->digits, callerid, strlen(callerid)); +} + +/* helper to decode calling/connected party number (caller ID or connected ID) */ +int osmo_cc_get_ie_calling(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, uint8_t *present, uint8_t *screen, char *callerid, size_t callerid_size) +{ + struct osmo_cc_ie_calling *ie_calling; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLING, ie_repeat, sizeof(*ie_calling), (const void **)&ie_calling); + if (rc < 0) + return rc; + *type = ie_calling->type; + *plan = ie_calling->plan; + *present = ie_calling->present; + *screen = ie_calling->screen; + _ie2string(callerid, callerid_size, ie_calling->digits, rc); + return rc; +} + +/* helper to encode calling/connected sub address (caller ID or connected ID) */ +void osmo_cc_add_ie_calling_sub(osmo_cc_msg_t *msg, uint8_t type, const char *callerid) +{ + struct osmo_cc_ie_calling_sub *ie_calling_sub; + + ie_calling_sub = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLING_SUB, sizeof(*ie_calling_sub) + strlen(callerid)); + ie_calling_sub->type = type; + memcpy(ie_calling_sub->digits, callerid, strlen(callerid)); +} + +/* helper to decode calling/connected sub address (caller ID or connected ID) */ +int osmo_cc_get_ie_calling_sub(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *callerid, size_t callerid_size) +{ + struct osmo_cc_ie_calling_sub *ie_calling_sub; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLING_SUB, ie_repeat, sizeof(*ie_calling_sub), (const void **)&ie_calling_sub); + if (rc < 0) + return rc; + *type = ie_calling_sub->type; + _ie2string(callerid, callerid_size, ie_calling_sub->digits, rc); + return rc; +} + +/* helper to encode calling/connected name (caller ID or connected ID) */ +void osmo_cc_add_ie_calling_name(osmo_cc_msg_t *msg, const char *name) +{ + struct osmo_cc_ie_calling_name *ie_calling_name; + + ie_calling_name = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLING_NAME, sizeof(*ie_calling_name) + strlen(name)); + memcpy(ie_calling_name->name, name, strlen(name)); +} + +/* helper to decode calling/connected name address (caller ID or connected ID) */ +int osmo_cc_get_ie_calling_name(osmo_cc_msg_t *msg, int ie_repeat, char *name, size_t name_size) +{ + struct osmo_cc_ie_calling_name *ie_calling_name; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLING_NAME, ie_repeat, sizeof(*ie_calling_name), (const void **)&ie_calling_name); + if (rc < 0) + return rc; + _ie2string(name, name_size, ie_calling_name->name, rc); + return rc; +} + +/* helper to encode calling interface name */ +void osmo_cc_add_ie_calling_interface(osmo_cc_msg_t *msg, const char *interface) +{ + struct osmo_cc_ie_calling_interface *ie_interface; + + ie_interface = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLING_INTERFACE, sizeof(*ie_interface) + strlen(interface)); + memcpy(ie_interface->name, interface, strlen(interface)); +} + +/* helper to decode calling interface name */ +int osmo_cc_get_ie_calling_interface(osmo_cc_msg_t *msg, int ie_repeat, char *interface, size_t interface_size) +{ + struct osmo_cc_ie_calling_interface *ie_interface; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLING_INTERFACE, ie_repeat, sizeof(*ie_interface), (const void **)&ie_interface); + if (rc < 0) + return rc; + _ie2string(interface, interface_size, ie_interface->name, rc); + return rc; +} + +/* helper to encode network specific caller/connected ID */ +void osmo_cc_add_ie_calling_network(osmo_cc_msg_t *msg, uint8_t type, const char *networkid) +{ + struct osmo_cc_ie_network *ie_network; + + ie_network = osmo_cc_add_ie(msg, OSMO_CC_IE_CALLING_NETWORK, sizeof(*ie_network) + strlen(networkid)); + ie_network->type = type; + memcpy(ie_network->id, networkid, strlen(networkid)); +} + +/* helper to encode network specific caller/connected ID */ +int osmo_cc_get_ie_calling_network(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *networkid, size_t networkid_size) +{ + struct osmo_cc_ie_network *ie_network; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CALLING_NETWORK, ie_repeat, sizeof(*ie_network), (const void **)&ie_network); + if (rc < 0) + return rc; + *type = ie_network->type; + _ie2string(networkid, networkid_size, ie_network->id, rc); + return rc; +} + +/* helper to encode bearer capability */ +void osmo_cc_add_ie_bearer(osmo_cc_msg_t *msg, uint8_t coding, uint8_t capability, uint8_t mode) +{ + struct osmo_cc_ie_bearer *ie_bearer; + + ie_bearer = osmo_cc_add_ie(msg, OSMO_CC_IE_BEARER, sizeof(*ie_bearer)); + ie_bearer->coding = coding; + ie_bearer->capability = capability; + ie_bearer->mode = mode; +} + +/* helper to decode bearer capability */ +int osmo_cc_get_ie_bearer(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *coding, uint8_t *capability, uint8_t *mode) +{ + struct osmo_cc_ie_bearer *ie_bearer; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_BEARER, ie_repeat, sizeof(*ie_bearer), (const void **)&ie_bearer); + if (rc < 0) + return rc; + *coding = ie_bearer->coding; + *capability = ie_bearer->capability; + *mode = ie_bearer->mode; + return rc; +} + +/* helper to encode redirection and redirecting number */ +void osmo_cc_add_ie_redir(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, uint8_t present, uint8_t screen, uint8_t redir_reason, const char *callerid) +{ + struct osmo_cc_ie_redir *ie_redir; + + ie_redir = osmo_cc_add_ie(msg, OSMO_CC_IE_REDIR, sizeof(*ie_redir) + strlen(callerid)); + ie_redir->type = type; + ie_redir->plan = plan; + ie_redir->present = present; + ie_redir->screen = screen; + ie_redir->redir_reason = redir_reason; + memcpy(ie_redir->digits, callerid, strlen(callerid)); +} + +/* helper to decode redirection and redirecting number */ +int osmo_cc_get_ie_redir(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, uint8_t *present, uint8_t *screen, uint8_t *reason, char *callerid, size_t callerid_size) +{ + struct osmo_cc_ie_redir *ie_redir; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_REDIR, ie_repeat, sizeof(*ie_redir), (const void **)&ie_redir); + if (rc < 0) + return rc; + *type = ie_redir->type; + *plan = ie_redir->plan; + *present = ie_redir->present; + *screen = ie_redir->screen; + *reason = ie_redir->redir_reason; + _ie2string(callerid, callerid_size, ie_redir->digits, rc); + return rc; +} + +/* helper to encode DTMF tones */ +void osmo_cc_add_ie_dtmf(osmo_cc_msg_t *msg, uint8_t duration_ms, uint8_t pause_ms, uint8_t dtmf_mode, const char *digits) +{ + struct osmo_cc_ie_dtmf *ie_dtmf; + + ie_dtmf = osmo_cc_add_ie(msg, OSMO_CC_IE_DTMF, sizeof(*ie_dtmf) + strlen(digits)); + ie_dtmf->duration_ms = duration_ms; + ie_dtmf->pause_ms = pause_ms; + ie_dtmf->dtmf_mode = dtmf_mode; + memcpy(ie_dtmf->digits, digits, strlen(digits)); +} + +/* helper to decode DTMF tones */ +int osmo_cc_get_ie_dtmf(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *duration_ms, uint8_t *pause_ms, uint8_t *dtmf_mode, char *digits, size_t digits_size) +{ + struct osmo_cc_ie_dtmf *ie_dtmf; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_DTMF, ie_repeat, sizeof(*ie_dtmf), (const void **)&ie_dtmf); + if (rc < 0) + return rc; + *duration_ms = ie_dtmf->duration_ms; + *pause_ms = ie_dtmf->pause_ms; + *dtmf_mode = ie_dtmf->dtmf_mode; + _ie2string(digits, digits_size, ie_dtmf->digits, rc); + return rc; +} + +/* helper to encode keypad press */ +void osmo_cc_add_ie_keypad(osmo_cc_msg_t *msg, const char *digits) +{ + struct osmo_cc_ie_keypad *ie_keypad; + + ie_keypad = osmo_cc_add_ie(msg, OSMO_CC_IE_KEYPAD, sizeof(*ie_keypad) + strlen(digits)); + memcpy(ie_keypad->digits, digits, strlen(digits)); +} + +/* helper to decode keypad press */ +int osmo_cc_get_ie_keypad(osmo_cc_msg_t *msg, int ie_repeat, char *digits, size_t digits_size) +{ + struct osmo_cc_ie_keypad *ie_keypad; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_KEYPAD, ie_repeat, sizeof(*ie_keypad), (const void **)&ie_keypad); + if (rc < 0) + return rc; + _ie2string(digits, digits_size, ie_keypad->digits, rc); + return rc; +} + +/* helper to encode call progress information */ +void osmo_cc_add_ie_progress(osmo_cc_msg_t *msg, uint8_t coding, uint8_t location, uint8_t progress) +{ + struct osmo_cc_ie_progress *ie_progress; + + ie_progress = osmo_cc_add_ie(msg, OSMO_CC_IE_PROGRESS, sizeof(*ie_progress)); + ie_progress->coding = coding; + ie_progress->location = location; + ie_progress->progress = progress; +} + +/* helper to decode call progress information */ +int osmo_cc_get_ie_progress(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *coding, uint8_t *location, uint8_t *progress) +{ + struct osmo_cc_ie_progress *ie_progress; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_PROGRESS, ie_repeat, sizeof(*ie_progress), (const void **)&ie_progress); + if (rc < 0) + return rc; + *coding = ie_progress->coding; + *location = ie_progress->location; + *progress = ie_progress->progress; + return rc; +} + +/* helper to encode notification */ +void osmo_cc_add_ie_notify(osmo_cc_msg_t *msg, uint8_t notify) +{ + struct osmo_cc_ie_notify *ie_notify; + + ie_notify = osmo_cc_add_ie(msg, OSMO_CC_IE_NOTIFY, sizeof(*ie_notify)); + ie_notify->notify = notify; +} + +/* helper to decode notification */ +int osmo_cc_get_ie_notify(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *notify) +{ + struct osmo_cc_ie_notify *ie_notify; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_NOTIFY, ie_repeat, sizeof(*ie_notify), (const void **)&ie_notify); + if (rc < 0) + return rc; + *notify = ie_notify->notify; + return rc; +} + +/* helper to encode cause */ +void osmo_cc_add_ie_cause(osmo_cc_msg_t *msg, uint8_t location, uint8_t isdn_cause, uint16_t sip_cause, uint8_t socket_cause) +{ + struct osmo_cc_ie_cause *ie_cause; + + ie_cause = osmo_cc_add_ie(msg, OSMO_CC_IE_CAUSE, sizeof(*ie_cause)); + ie_cause->location = location; + ie_cause->isdn_cause = isdn_cause; + ie_cause->sip_cause_networkorder = htons(sip_cause); + ie_cause->socket_cause = socket_cause; +} + +/* helper to deccode cause */ +int osmo_cc_get_ie_cause(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *location, uint8_t *isdn_cause, uint16_t *sip_cause, uint8_t *socket_cause) +{ + struct osmo_cc_ie_cause *ie_cause; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_CAUSE, ie_repeat, sizeof(*ie_cause), (const void **)&ie_cause); + if (rc < 0) + return rc; + *location = ie_cause->location; + *isdn_cause = ie_cause->isdn_cause; + *sip_cause = ntohs(ie_cause->sip_cause_networkorder); + *socket_cause = ie_cause->socket_cause; + return rc; +} + +/* helper to encode DISPLAY information */ +void osmo_cc_add_ie_display(osmo_cc_msg_t *msg, const char *text) +{ + struct osmo_cc_ie_display *ie_display; + + ie_display = osmo_cc_add_ie(msg, OSMO_CC_IE_DISPLAY, sizeof(*ie_display) + strlen(text)); + memcpy(ie_display->text, text, strlen(text)); +} + +/* helper to decode DISPLAY information */ +int osmo_cc_get_ie_display(osmo_cc_msg_t *msg, int ie_repeat, char *text, size_t text_size) +{ + struct osmo_cc_ie_display *ie_display; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_DISPLAY, ie_repeat, sizeof(*ie_display), (const void **)&ie_display); + if (rc < 0) + return rc; + _ie2string(text, text_size, ie_display->text, rc); + return rc; +} + +/* helper to encode SDP */ +void osmo_cc_add_ie_sdp(osmo_cc_msg_t *msg, const char *sdp) +{ + struct osmo_cc_ie_sdp *ie_sdp; + + ie_sdp = osmo_cc_add_ie(msg, OSMO_CC_IE_SDP, sizeof(*ie_sdp) + strlen(sdp)); + memcpy(ie_sdp->sdp, sdp, strlen(sdp)); +} + +/* helper to decode SDP */ +int osmo_cc_get_ie_sdp(osmo_cc_msg_t *msg, int ie_repeat, char *sdp, size_t sdp_size) +{ + struct osmo_cc_ie_sdp *ie_sdp; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_SDP, ie_repeat, sizeof(*ie_sdp), (const void **)&ie_sdp); + if (rc < 0) + return rc; + _ie2string(sdp, sdp_size, ie_sdp->sdp, rc); + return rc; +} + +/* helper to encode socket addresss */ +void osmo_cc_add_ie_socket_address(osmo_cc_msg_t *msg, const char *address) +{ + struct osmo_cc_ie_socket_address *ie_socket_address; + + ie_socket_address = osmo_cc_add_ie(msg, OSMO_CC_IE_SOCKET_ADDRESS, sizeof(*ie_socket_address) + strlen(address)); + memcpy(ie_socket_address->address, address, strlen(address)); +} + +/* helper to decode socket addresss */ +int osmo_cc_get_ie_socket_address(osmo_cc_msg_t *msg, int ie_repeat, char *address, size_t address_size) +{ + struct osmo_cc_ie_socket_address *ie_socket_address; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_SOCKET_ADDRESS, ie_repeat, sizeof(*ie_socket_address), (const void **)&ie_socket_address); + if (rc < 0) + return rc; + _ie2string(address, address_size, ie_socket_address->address, rc); + return rc; +} + +/* helper to encode private information element */ +void osmo_cc_add_ie_private(osmo_cc_msg_t *msg, uint32_t unique, const uint8_t *data, size_t data_size) +{ + struct osmo_cc_ie_private *ie_private; + + ie_private = osmo_cc_add_ie(msg, OSMO_CC_IE_PRIVATE, sizeof(*ie_private) + data_size); + ie_private->unique_networkorder = htonl(unique); + memcpy(ie_private->data, data, data_size); +} + +/* helper to decode private information element */ +int osmo_cc_get_ie_private(osmo_cc_msg_t *msg, int ie_repeat, uint32_t *unique, uint8_t *data, size_t data_size) +{ + struct osmo_cc_ie_private *ie_private; + int rc; + + rc = osmo_cc_get_ie_data(msg, OSMO_CC_IE_PRIVATE, ie_repeat, sizeof(*ie_private), (const void **)&ie_private); + if (rc < 0) + return rc; + *unique = ntohl(ie_private->unique_networkorder); + memcpy(data, ie_private->data, (rc < (int)data_size) ? rc : (int)data_size); + return rc; +} + diff --git a/src/libosmocc/message.h b/src/libosmocc/message.h new file mode 100644 index 0000000..9dee2c9 --- /dev/null +++ b/src/libosmocc/message.h @@ -0,0 +1,437 @@ +#ifndef OSMO_CC_MSG_H +#define OSMO_CC_MSG_H + +#define OSMO_CC_VERSION "OSMOCCv1" + +/* call control messages types */ +enum osmo_cc_msg_type { + OSMO_CC_MSG_SETUP_REQ = 0x00, + OSMO_CC_MSG_SETUP_IND = 0x01, + OSMO_CC_MSG_REJ_REQ = 0x10, + OSMO_CC_MSG_REJ_IND = 0x11, + OSMO_CC_MSG_SETUP_ACK_REQ = 0x20, + OSMO_CC_MSG_SETUP_ACK_IND = 0x21, + OSMO_CC_MSG_PROC_REQ = 0x30, + OSMO_CC_MSG_PROC_IND = 0x31, + OSMO_CC_MSG_ALERT_REQ = 0x40, + OSMO_CC_MSG_ALERT_IND = 0x41, + OSMO_CC_MSG_SETUP_RSP = 0x02, + OSMO_CC_MSG_SETUP_CNF = 0x03, + OSMO_CC_MSG_SETUP_COMP_REQ = 0x50, + OSMO_CC_MSG_SETUP_COMP_IND = 0x51, + OSMO_CC_MSG_DISC_REQ = 0x60, + OSMO_CC_MSG_DISC_IND = 0x61, + OSMO_CC_MSG_REL_REQ = 0x70, + OSMO_CC_MSG_REL_CNF = 0x73, + OSMO_CC_MSG_REL_IND = 0x71, + OSMO_CC_MSG_PROGRESS_REQ = 0x80, + OSMO_CC_MSG_PROGRESS_IND = 0x81, + OSMO_CC_MSG_NOTIFY_REQ = 0x84, + OSMO_CC_MSG_NOTIFY_IND = 0x85, + OSMO_CC_MSG_INFO_REQ = 0x88, + OSMO_CC_MSG_INFO_IND = 0x89, + OSMO_CC_MSG_ATTACH_REQ = 0xf8, + OSMO_CC_MSG_ATTACH_IND = 0xf9, + OSMO_CC_MSG_ATTACH_RSP = 0xfa, + OSMO_CC_MSG_ATTACH_CNF = 0xfb, + OSMO_CC_MSG_DUMMY_REQ = 0xfc, +}; + +#define OSMO_CC_MSG_MASK 0x03, +#define OSMO_CC_MSG_REQ 0x00, +#define OSMO_CC_MSG_IND 0x01, +#define OSMO_CC_MSG_RSP 0x02, +#define OSMO_CC_MSG_CNF 0x03, + +/* information elements */ +enum osmo_cc_ie_type { + OSMO_CC_IE_CALLED = 0x11, + OSMO_CC_IE_CALLED_SUB = 0x12, + OSMO_CC_IE_CALLED_NAME = 0x13, + OSMO_CC_IE_CALLED_INTERFACE = 0x14, + OSMO_CC_IE_DTMF = 0x1d, + OSMO_CC_IE_KEYPAD = 0x1e, + OSMO_CC_IE_COMPLETE = 0x1f, + OSMO_CC_IE_CALLING = 0x21, + OSMO_CC_IE_CALLING_SUB = 0x22, + OSMO_CC_IE_CALLING_NAME = 0x23, + OSMO_CC_IE_CALLING_INTERFACE = 0x24, + OSMO_CC_IE_CALLING_NETWORK = 0x2f, + OSMO_CC_IE_REDIR = 0x31, + OSMO_CC_IE_PROGRESS = 0x32, + OSMO_CC_IE_NOTIFY = 0x33, + OSMO_CC_IE_DISPLAY = 0x34, + OSMO_CC_IE_CAUSE = 0x41, + OSMO_CC_IE_BEARER = 0x51, + OSMO_CC_IE_SDP = 0x52, + OSMO_CC_IE_SOCKET_ADDRESS = 0x5e, + OSMO_CC_IE_PRIVATE = 0x5f, +}; + +/* type of number, see ITU-T Rec. Q.931 */ +#define OSMO_CC_TYPE_UNKNOWN 0 +#define OSMO_CC_TYPE_INTERNATIONAL 1 +#define OSMO_CC_TYPE_NATIONAL 2 +#define OSMO_CC_TYPE_NETWORK 3 +#define OSMO_CC_TYPE_SUBSCRIBER 4 +#define OSMO_CC_TYPE_ABBREVIATED 5 +#define OSMO_CC_TYPE_RESERVED 7 + +/* numbering plan, see ITU-T Rec. Q.931 */ +#define OSMO_CC_PLAN_UNKNOWN 0 +#define OSMO_CC_PLAN_TELEPHONY 1 +#define OSMO_CC_PLAN_DATA 3 +#define OSMO_CC_PLAN_TTY 4 +#define OSMO_CC_PLAN_NATIONAL_STANDARD 8 +#define OSMO_CC_PLAN_PRIVATE 9 +#define OSMO_CC_PLAN_RESERVED 15 + +/* presentation indicator, see ITU-T Rec. Q.931 */ +#define OSMO_CC_PRESENT_ALLOWED 0 +#define OSMO_CC_PRESENT_RESTRICTED 1 +#define OSMO_CC_PRESENT_NOT_AVAIL 2 +#define OSMO_CC_PRESENT_RESERVED 3 + +/* screening indicator, see ITU-T Rec. Q.931 */ +#define OSMO_CC_SCREEN_USER_UNSCREENED 0 +#define OSMO_CC_SCREEN_USER_VERIFIED_PASSED 1 +#define OSMO_CC_SCREEN_USER_VERIFIED_FAILED 2 +#define OSMO_CC_SCREEN_NETWORK 3 + +/* screening indicator, see ITU-T Rec. Q.931 */ +#define OSMO_CC_REDIR_REASON_UNKNOWN 0 +#define OSMO_CC_REDIR_REASON_CFB 1 +#define OSMO_CC_REDIR_REASON_CFNR 2 +#define OSMO_CC_REDIR_REASON_CD 4 +#define OSMO_CC_REDIR_REASON_CF_OUTOFORDER 9 +#define OSMO_CC_REDIR_REASON_CF_BY_DTE 10 +#define OSMO_CC_REDIR_REASON_CFU 15 + +/* notification indicator, see ITU-T Rec. Q.931 ff. */ +#define OSMO_CC_NOTIFY_USER_SUSPENDED 0x00 +#define OSMO_CC_NOTIFY_USER_RESUMED 0x01 +#define OSMO_CC_NOTIFY_BEARER_SERVICE_CHANGE 0x02 +#define OSMO_CC_NOTIFY_CALL_COMPLETION_DELAY 0x03 +#define OSMO_CC_NOTIFY_CONFERENCE_ESTABLISHED 0x42 +#define OSMO_CC_NOTIFY_CONFERENCE_DISCONNECTED 0x43 +#define OSMO_CC_NOTIFY_OTHER_PARTY_ADDED 0x44 +#define OSMO_CC_NOTIFY_ISOLATED 0x45 +#define OSMO_CC_NOTIFY_REATTACHED 0x46 +#define OSMO_CC_NOTIFY_OTHER_PARTY_ISOLATED 0x47 +#define OSMO_CC_NOTIFY_OTHER_PARTY_REATTACHED 0x48 +#define OSMO_CC_NOTIFY_OTHER_PARTY_SPLIT 0x49 +#define OSMO_CC_NOTIFY_OTHER_PARTY_DISCONNECTED 0x4a +#define OSMO_CC_NOTIFY_CONFERENCE_FLOATING 0x4b +#define OSMO_CC_NOTIFY_CONFERENCE_DISC_PREEMPT 0x4c /* disconnect preemted */ +#define OSMO_CC_NOTIFY_CONFERENCE_FLOATING_SUP 0x4f /* served user preemted */ +#define OSMO_CC_NOTIFY_CALL_IS_A_WAITING_CALL 0x60 +#define OSMO_CC_NOTIFY_DIVERSION_ACTIVATED 0x68 +#define OSMO_CC_NOTIFY_RESERVED_CT_1 0x69 +#define OSMO_CC_NOTIFY_RESERVED_CT_2 0x6a +#define OSMO_CC_NOTIFY_REVERSE_CHARGING 0x6e +#define OSMO_CC_NOTIFY_REMOTE_HOLD 0x79 +#define OSMO_CC_NOTIFY_REMOTE_RETRIEVAL 0x7a +#define OSMO_CC_NOTIFY_CALL_IS_DIVERTING 0x7b + +/* coding standard, see ITU-T Rec. Q.931 */ +#define OSMO_CC_CODING_ITU_T 0 +#define OSMO_CC_CODING_ISO_IEC 1 +#define OSMO_CC_CODING_NATIONAL 2 +#define OSMO_CC_CODING_STANDARD_SPECIFIC 3 + +/* cause, see ITU-T Rec. Q.850 */ +#define OSMO_CC_ISDN_CAUSE_UNASSIGNED_NR 1 +#define OSMO_CC_ISDN_CAUSE_NO_ROUTE 3 +#define OSMO_CC_ISDN_CAUSE_CHAN_UNACCEPT 6 +#define OSMO_CC_ISDN_CAUSE_OP_DET_BARRING 8 +#define OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR 16 +#define OSMO_CC_ISDN_CAUSE_USER_BUSY 17 +#define OSMO_CC_ISDN_CAUSE_USER_NOTRESPOND 18 +#define OSMO_CC_ISDN_CAUSE_USER_ALERTING_NA 19 +#define OSMO_CC_ISDN_CAUSE_CALL_REJECTED 21 +#define OSMO_CC_ISDN_CAUSE_NUMBER_CHANGED 22 +#define OSMO_CC_ISDN_CAUSE_PRE_EMPTION 25 +#define OSMO_CC_ISDN_CAUSE_NONSE_USER_CLR 26 +#define OSMO_CC_ISDN_CAUSE_DEST_OOO 27 +#define OSMO_CC_ISDN_CAUSE_INV_NR_FORMAT 28 +#define OSMO_CC_ISDN_CAUSE_FACILITY_REJ 29 +#define OSMO_CC_ISDN_CAUSE_RESP_STATUS_INQ 30 +#define OSMO_CC_ISDN_CAUSE_NORMAL_UNSPEC 31 +#define OSMO_CC_ISDN_CAUSE_NO_CIRCUIT_CHAN 34 +#define OSMO_CC_ISDN_CAUSE_NETWORK_OOO 38 +#define OSMO_CC_ISDN_CAUSE_TEMP_FAILURE 41 +#define OSMO_CC_ISDN_CAUSE_SWITCH_CONG 42 +#define OSMO_CC_ISDN_CAUSE_ACC_INF_DISCARD 43 +#define OSMO_CC_ISDN_CAUSE_REQ_CHAN_UNAVAIL 44 +#define OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL 47 +#define OSMO_CC_ISDN_CAUSE_QOS_UNAVAIL 49 +#define OSMO_CC_ISDN_CAUSE_REQ_FAC_NOT_SUBSC 50 +#define OSMO_CC_ISDN_CAUSE_INC_BARRED_CUG 55 +#define OSMO_CC_ISDN_CAUSE_BEARER_CAP_UNAUTH 57 +#define OSMO_CC_ISDN_CAUSE_BEARER_CA_UNAVAIL 58 +#define OSMO_CC_ISDN_CAUSE_SERV_OPT_UNAVAIL 63 +#define OSMO_CC_ISDN_CAUSE_BEARERSERV_UNIMPL 65 +#define OSMO_CC_ISDN_CAUSE_ACM_GE_ACM_MAX 68 +#define OSMO_CC_ISDN_CAUSE_REQ_FAC_NOTIMPL 69 +#define OSMO_CC_ISDN_CAUSE_RESTR_BCAP_AVAIL 70 +#define OSMO_CC_ISDN_CAUSE_SERV_OPT_UNIMPL 79 +#define OSMO_CC_ISDN_CAUSE_INVAL_CALLREF 81 +#define OSMO_CC_ISDN_CAUSE_USER_NOT_IN_CUG 87 +#define OSMO_CC_ISDN_CAUSE_INCOMPAT_DEST 88 +#define OSMO_CC_ISDN_CAUSE_INVAL_TRANS_NET 91 +#define OSMO_CC_ISDN_CAUSE_SEMANTIC_INCORR 95 +#define OSMO_CC_ISDN_CAUSE_INVAL_MAND_INF 96 +#define OSMO_CC_ISDN_CAUSE_MSGTYPE_NOTEXIST 97 +#define OSMO_CC_ISDN_CAUSE_MSGTYPE_INCOMPAT 98 +#define OSMO_CC_ISDN_CAUSE_IE_NOTEXIST 99 +#define OSMO_CC_ISDN_CAUSE_COND_IE_ERR 100 +#define OSMO_CC_ISDN_CAUSE_MSG_INCOMP_STATE 101 +#define OSMO_CC_ISDN_CAUSE_RECOVERY_TIMER 102 +#define OSMO_CC_ISDN_CAUSE_PROTO_ERR 111 +#define OSMO_CC_ISDN_CAUSE_INTERWORKING 127 + +/* location, see ITU-T Rec. Q.931 */ +#define OSMO_CC_LOCATION_USER 0 +#define OSMO_CC_LOCATION_PRIV_SERV_LOC_USER 1 +#define OSMO_CC_LOCATION_PUB_SERV_LOC_USER 2 +#define OSMO_CC_LOCATION_TRANSIT 3 +#define OSMO_CC_LOCATION_PUB_SERV_REM_USER 4 +#define OSMO_CC_LOCATION_PRIV_SERV_REM_USER 5 +#define OSMO_CC_LOCATION_BEYOND_INTERWORKING 10 + +/* progress description, see ITU-T Rec. Q.931 */ +#define OSMO_CC_PROGRESS_NOT_END_TO_END_ISDN 1 +#define OSMO_CC_PROGRESS_DEST_NOT_ISDN 2 +#define OSMO_CC_PROGRESS_ORIG_NOT_ISDN 3 +#define OSMO_CC_PROGRESS_RETURN_TO_ISDN 4 +#define OSMO_CC_PROGRESS_INTERWORKING 5 +#define OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE 8 + +/* information transfer capability, see ITU-T Rec. Q.931 */ +#define OSMO_CC_CAPABILITY_SPEECH 0 +#define OSMO_CC_CAPABILITY_DATA 8 +#define OSMO_CC_CAPABILITY_DATA_RESTRICTED 9 +#define OSMO_CC_CAPABILITY_AUDIO 16 +#define OSMO_CC_CAPABILITY_DATA_WITH_TONES 17 +#define OSMO_CC_CAPABILITY_VIDEO 24 + +/* transfer mode, see ITU-T Rec. Q.931 */ +#define OSMO_CC_MODE_CIRCUIT 0 +#define OSMO_CC_MODE_PACKET 2 + +#define OSMO_CC_DTMF_MODE_OFF 0 /* stop tone */ +#define OSMO_CC_DTMF_MODE_ON 1 /* start tone */ +#define OSMO_CC_DTMF_MODE_DIGITS 2 /* play tone(s) with duration and pauses */ + +#define OSMO_CC_SOCKET_CAUSE_VERSION_MISMATCH 1 /* version missmatch */ +#define OSMO_CC_SOCKET_CAUSE_FAILED 2 /* connection failed */ +#define OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE 3 /* connected socket failed */ +#define OSMO_CC_SOCKET_CAUSE_TIMEOUT 4 /* keepalive packets timeout */ +// if you add causes here, add them in process_cause.c also! + +/* network type (network IE) and meaning of 'id' */ +#define OSMO_CC_NETWORK_UNDEFINED 0x00 +#define OSMO_CC_NETWORK_ALSA_NONE 0x01 +#define OSMO_CC_NETWORK_POTS_NONE 0x02 +#define OSMO_CC_NETWORK_ISDN_NONE 0x03 +#define OSMO_CC_NETWORK_SIP_NONE 0x04 +#define OSMO_CC_NETWORK_GSM_IMSI 0x05 /* id has decimal IMSI */ +#define OSMO_CC_NETWORK_GSM_IMEI 0x06 /* id has decimal IMEI */ +#define OSMO_CC_NETWORK_WEB_NONE 0x07 +#define OSMO_CC_NETWORK_DECT_NONE 0x08 +#define OSMO_CC_NETWORK_BLUETOOTH_NONE 0x09 +#define OSMO_CC_NETWORK_SS5_NONE 0x0a +#define OSMO_CC_NETWORK_ANETZ_NONE 0x80 +#define OSMO_CC_NETWORK_BNETZ_MUENZ 0x81 /* id starts with 'M' */ +#define OSMO_CC_NETWORK_CNETZ_NONE 0x82 +#define OSMO_CC_NETWORK_NMT_NONE 0x83 /* id has decimal password */ +#define OSMO_CC_NETWORK_R2000_NONE 0x84 +#define OSMO_CC_NETWORK_AMPS_ESN 0x85 /* if has decimal ESN (TACS also) */ +#define OSMO_CC_NETWORK_MTS_NONE 0x86 +#define OSMO_CC_NETWORK_IMTS_NONE 0x87 +#define OSMO_CC_NETWORK_EUROSIGNAL_NONE 0x88 +#define OSMO_CC_NETWORK_JOLLYCOM_NONE 0x89 /* call from JollyCom... */ + +typedef struct osmo_cc_msg { + uint8_t type; + uint16_t length_networkorder; + uint8_t data[0]; +} __attribute__((packed)) osmo_cc_msg_t; + +typedef struct osmo_cc_msg_list { + struct osmo_cc_msg_list *next; + struct osmo_cc_msg *msg; + uint32_t callref; + char host[128]; + uint16_t port; +} osmo_cc_msg_list_t; + +typedef struct osmo_cc_ie { + uint8_t type; + uint16_t length_networkorder; + uint8_t data[0]; +} __attribute__((packed)) osmo_cc_ie_t; + +struct osmo_cc_ie_called { + uint8_t type; + uint8_t plan; + char digits[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_called_sub { + uint8_t type; + char digits[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_called_name { + char name[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_called_interface { + char name[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_calling { + uint8_t type; + uint8_t plan; + uint8_t present; + uint8_t screen; + char digits[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_calling_sub { + uint8_t type; + char digits[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_calling_name { + char name[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_calling_interface { + char name[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_network { + uint8_t type; + char id[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_bearer { + uint8_t coding; + uint8_t capability; + uint8_t mode; +} __attribute__((packed)); + +struct osmo_cc_ie_redir { + uint8_t type; + uint8_t plan; + uint8_t present; + uint8_t screen; + uint8_t redir_reason; + char digits[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_dtmf { + uint8_t duration_ms; + uint8_t pause_ms; + uint8_t dtmf_mode; + char digits[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_keypad { + char digits[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_progress { + uint8_t coding; + uint8_t location; + uint8_t progress; +} __attribute__((packed)); + +struct osmo_cc_ie_notify { + uint8_t notify; +} __attribute__((packed)); + +struct osmo_cc_ie_cause { + uint8_t location; + uint8_t isdn_cause; + uint16_t sip_cause_networkorder; + uint8_t socket_cause; +} __attribute__((packed)); + +struct osmo_cc_ie_display { + char text[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_sdp { + char sdp[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_socket_address { + char address[0]; +} __attribute__((packed)); + +struct osmo_cc_ie_private { + uint32_t unique_networkorder; + uint8_t data[0]; +} __attribute__((packed)); + +uint32_t osmo_cc_new_callref(void); +const char *osmo_cc_msg_name(uint8_t msg_type); +osmo_cc_msg_t *osmo_cc_new_msg(uint8_t msg_type); +osmo_cc_msg_t *osmo_cc_clone_msg(osmo_cc_msg_t *msg); +osmo_cc_msg_t *osmo_cc_msg_list_dequeue(osmo_cc_msg_list_t **mlp, uint32_t *callref_p); +osmo_cc_msg_list_t *osmo_cc_msg_list_enqueue(osmo_cc_msg_list_t **mlp, osmo_cc_msg_t *msg, uint32_t callref); +void osmo_cc_free_msg(osmo_cc_msg_t *msg); +int osmo_cc_get_ie_struct(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat, int ie_len, const osmo_cc_ie_t **ie_struct); +int osmo_cc_get_ie_data(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat, int ie_len, const void **ie_data); +int osmo_cc_has_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat); +int osmo_cc_remove_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_repeat); +void *osmo_cc_add_ie(osmo_cc_msg_t *msg, uint8_t ie_type, int ie_len); +void *osmo_cc_msg_sep_ie(osmo_cc_msg_t *msg, void **iep, uint8_t *ie_type, uint16_t *ie_length); + +void osmo_cc_add_ie_called(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, const char *dialing); +int osmo_cc_get_ie_called(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, char *dialing, size_t dialing_size); +void osmo_cc_add_ie_called_sub(osmo_cc_msg_t *msg, uint8_t type, const char *dialing); +int osmo_cc_get_ie_called_sub(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *dialing, size_t dialing_size); +void osmo_cc_add_ie_called_name(osmo_cc_msg_t *msg, const char *name); +int osmo_cc_get_ie_called_name(osmo_cc_msg_t *msg, int ie_repeat, char *name, size_t name_size); +void osmo_cc_add_ie_called_interface(osmo_cc_msg_t *msg, const char *interface); +int osmo_cc_get_ie_called_interface(osmo_cc_msg_t *msg, int ie_repeat, char *interface, size_t interface_size); +void osmo_cc_add_ie_complete(osmo_cc_msg_t *msg); +int osmo_cc_get_ie_complete(osmo_cc_msg_t *msg, int ie_repeat); +void osmo_cc_add_ie_calling(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, uint8_t present, uint8_t screen, const char *callerid); +int osmo_cc_get_ie_calling(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, uint8_t *present, uint8_t *screen, char *callerid, size_t callerid_size); +void osmo_cc_add_ie_calling_sub(osmo_cc_msg_t *msg, uint8_t type, const char *callerid); +int osmo_cc_get_ie_calling_sub(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *callerid, size_t callerid_size); +void osmo_cc_add_ie_calling_name(osmo_cc_msg_t *msg, const char *name); +int osmo_cc_get_ie_calling_name(osmo_cc_msg_t *msg, int ie_repeat, char *name, size_t name_size); +void osmo_cc_add_ie_calling_interface(osmo_cc_msg_t *msg, const char *interface); +int osmo_cc_get_ie_calling_interface(osmo_cc_msg_t *msg, int ie_repeat, char *interface, size_t interface_size); +void osmo_cc_add_ie_calling_network(osmo_cc_msg_t *msg, uint8_t type, const char *networkid); +int osmo_cc_get_ie_calling_network(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, char *networkid, size_t networkid_size); +void osmo_cc_add_ie_bearer(osmo_cc_msg_t *msg, uint8_t coding, uint8_t capability, uint8_t mode); +int osmo_cc_get_ie_bearer(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *coding, uint8_t *capability, uint8_t *mode); +void osmo_cc_add_ie_redir(osmo_cc_msg_t *msg, uint8_t type, uint8_t plan, uint8_t present, uint8_t screen, uint8_t redir_reason, const char *callerid); +int osmo_cc_get_ie_redir(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *type, uint8_t *plan, uint8_t *present, uint8_t *screen, uint8_t *reason, char *callerid, size_t callerid_size); +void osmo_cc_add_ie_dtmf(osmo_cc_msg_t *msg, uint8_t duration_ms, uint8_t pause_ms, uint8_t dtmf_mode, const char *digits); +int osmo_cc_get_ie_dtmf(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *duration_ms, uint8_t *pause_ms, uint8_t *dtmf_mode, char *digits, size_t digits_size); +void osmo_cc_add_ie_keypad(osmo_cc_msg_t *msg, const char *digits); +int osmo_cc_get_ie_keypad(osmo_cc_msg_t *msg, int ie_repeat, char *digits, size_t digits_size); +void osmo_cc_add_ie_progress(osmo_cc_msg_t *msg, uint8_t coding, uint8_t location, uint8_t progress); +int osmo_cc_get_ie_progress(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *coding, uint8_t *location, uint8_t *progress); +void osmo_cc_add_ie_notify(osmo_cc_msg_t *msg, uint8_t notify); +int osmo_cc_get_ie_notify(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *notify); +void osmo_cc_add_ie_cause(osmo_cc_msg_t *msg, uint8_t location, uint8_t isdn_cause, uint16_t sip_cause, uint8_t socket_cause); +int osmo_cc_get_ie_cause(osmo_cc_msg_t *msg, int ie_repeat, uint8_t *location, uint8_t *isdn_cause, uint16_t *sip_cause, uint8_t *socket_cause); +void osmo_cc_add_ie_display(osmo_cc_msg_t *msg, const char *text); +int osmo_cc_get_ie_display(osmo_cc_msg_t *msg, int ie_repeat, char *text, size_t text_size); +void osmo_cc_add_ie_sdp(osmo_cc_msg_t *msg, const char *sdp); +int osmo_cc_get_ie_sdp(osmo_cc_msg_t *msg, int ie_repeat, char *sdp, size_t sdp_size); +void osmo_cc_add_ie_socket_address(osmo_cc_msg_t *msg, const char *address); +int osmo_cc_get_ie_socket_address(osmo_cc_msg_t *msg, int ie_repeat, char *address, size_t address_size); +void osmo_cc_add_ie_private(osmo_cc_msg_t *msg, uint32_t unique, const uint8_t *data, size_t data_size); +int osmo_cc_get_ie_private(osmo_cc_msg_t *msg, int ie_repeat, uint32_t *unique, uint8_t *data, size_t data_size); + +#endif /* OSMO_CC_MSG_H */ diff --git a/src/libosmocc/rtp.c b/src/libosmocc/rtp.c new file mode 100644 index 0000000..a6de25a --- /dev/null +++ b/src/libosmocc/rtp.c @@ -0,0 +1,399 @@ +/* Osmo-CC: RTP handling + * + * (C) 2016 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 +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "endpoint.h" + +#define RTP_VERSION 2 + +static uint16_t rtp_port_next = 16384; +static uint16_t rtp_port_from = 16384; +static uint16_t rtp_port_to = 32767; + +void osmo_cc_set_rtp_ports(uint16_t from, uint16_t to) +{ + rtp_port_next = from; + rtp_port_from = from; + rtp_port_to = to; +} + +struct rtp_hdr { + uint8_t byte0; + uint8_t byte1; + uint16_t sequence; + uint32_t timestamp; + uint32_t ssrc; +} __attribute__((packed)); + +struct rtp_x_hdr { + uint16_t by_profile; + uint16_t length; +} __attribute__((packed)); + +static int rtp_receive(int sock, uint8_t **payload_p, int *payload_len_p, uint8_t *marker_p, uint8_t *pt_p, uint16_t *sequence_p, uint32_t *timestamp_p) +{ + static uint8_t data[2048]; + int len; + struct rtp_hdr *rtph = (struct rtp_hdr *)data; + uint8_t version, padding, extension, csrc_count, marker, payload_type; + struct rtp_x_hdr *rtpxh; + uint8_t *payload; + int payload_len; + int x_len; + + len = read(sock, data, sizeof(data)); + if (len < 0) { + if (errno == EAGAIN) + return -EAGAIN; + PDEBUG(DCC, DEBUG_DEBUG, "Read errno = %d (%s)\n", errno, strerror(errno)); + return -EIO; + } + if (len < 12) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short (len = %d).\n", len); + return -EINVAL; + } + + version = rtph->byte0 >> 6; + padding = (rtph->byte0 >> 5) & 1; + extension = (rtph->byte0 >> 4) & 1; + csrc_count = rtph->byte0 & 0x0f; + marker = rtph->byte1 >> 7; + payload_type = rtph->byte1 & 0x7f; + *sequence_p = ntohs(rtph->sequence); + *timestamp_p = ntohl(rtph->timestamp); + + if (version != RTP_VERSION) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP version %d not supported.\n", version); + return -EINVAL; + } + + payload = data + sizeof(*rtph) + (csrc_count << 2); + payload_len = len - sizeof(*rtph) - (csrc_count << 2); + if (payload_len < 0) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short (len = %d, csrc count = %d).\n", len, csrc_count); + return -EINVAL; + } + + if (extension) { + if (payload_len < (int)sizeof(*rtpxh)) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short for extension header.\n"); + return -EINVAL; + } + rtpxh = (struct rtp_x_hdr *)payload; + x_len = ntohs(rtpxh->length) * 4 + sizeof(*rtpxh); + payload += x_len; + payload_len -= x_len; + if (payload_len < (int)sizeof(*rtpxh)) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short, extension header exceeds frame length.\n"); + return -EINVAL; + } + } + + if (padding) { + if (payload_len < 1) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame too short for padding length.\n"); + return -EINVAL; + } + payload_len -= payload[payload_len - 1]; + if (payload_len < 0) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame padding is greater than payload.\n"); + return -EINVAL; + } + } + + *payload_p = payload; + *payload_len_p = payload_len; + *marker_p = marker; + *pt_p = payload_type; + + return 0; +} + +static void rtp_send(int sock, uint8_t *payload, int payload_len, uint8_t pt, uint16_t sequence, uint32_t timestamp, uint32_t ssrc) +{ + struct rtp_hdr *rtph; + char data[sizeof(*rtph) + payload_len]; + int len, rc; + + rtph = (struct rtp_hdr *)data; + len = sizeof(*rtph); + rtph->byte0 = RTP_VERSION << 6; + rtph->byte1 = pt; + rtph->sequence = htons(sequence); + rtph->timestamp = htonl(timestamp); + rtph->ssrc = htonl(ssrc); + len += payload_len; + if (len > (int)sizeof(data)) { + PDEBUG(DCC, DEBUG_NOTICE, "Buffer overflow, please fix!.\n"); + abort(); + } + memcpy(data + sizeof(*rtph), payload, payload_len); + + rc = write(sock, data, len); + if (rc < 0) + PDEBUG(DCC, DEBUG_DEBUG, "Write errno = %d (%s)\n", errno, strerror(errno)); +} + +/* open and bind RTP + * set local port to what we bound + */ +int osmo_cc_rtp_open(osmo_cc_session_media_t *media) +{ + int domain = 0; // make GCC happy + uint16_t start_port; + struct sockaddr_storage sa; + int slen = 0; // make GCC happy + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + uint16_t *sport; + int flags; + int rc; + + media->rtp_ssrc = rand(); + + osmo_cc_rtp_close(media); + + switch (media->connection_data_local.addrtype) { + case osmo_cc_session_addrtype_ipv4: + domain = AF_INET; + memset(&sa, 0, sizeof(sa)); + sa4 = (struct sockaddr_in *)&sa; + sa4->sin_family = domain; + sa4->sin_addr.s_addr = INADDR_ANY; + sport = &sa4->sin_port; + slen = sizeof(*sa4); + break; + case osmo_cc_session_addrtype_ipv6: + domain = AF_INET6; + memset(&sa, 0, sizeof(sa)); + sa6 = (struct sockaddr_in6 *)&sa; + sa6->sin6_family = domain; + sa6->sin6_addr = in6addr_any; + sport = &sa6->sin6_port; + slen = sizeof(*sa6); + break; + case osmo_cc_session_addrtype_unknown: + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s'.\n", media->connection_data_local.addrtype_name); + return -EINVAL; + } + + /* rtp_port_from/rtp_port_to may be changed at run time, so rtp_port_next can become out of range. */ + if (rtp_port_next < rtp_port_from || rtp_port_next > rtp_port_to) + rtp_port_next = rtp_port_from; + start_port = rtp_port_next; + while (1) { + /* open sockets */ + rc = socket(domain, SOCK_DGRAM, IPPROTO_UDP); + if (rc < 0) { +socket_error: + PDEBUG(DCC, DEBUG_ERROR, "Cannot create socket (domain=%d, errno=%d(%s))\n", domain, errno, strerror(errno)); + osmo_cc_rtp_close(media); + return -EIO; + } + media->rtp_socket = rc; + rc = socket(domain, SOCK_DGRAM, IPPROTO_UDP); + if (rc < 0) + goto socket_error; + media->rtcp_socket = rc; + + /* bind sockets */ + *sport = htons(rtp_port_next); + rc = bind(media->rtp_socket, (struct sockaddr *)&sa, slen); + if (rc < 0) { +bind_error: + osmo_cc_rtp_close(media); + rtp_port_next = (rtp_port_next + 2 > rtp_port_to) ? rtp_port_from : rtp_port_next + 2; + if (rtp_port_next == start_port) { + PDEBUG(DCC, DEBUG_ERROR, "Cannot bind socket (errno=%d(%s))\n", errno, strerror(errno)); + return -EIO; + } + continue; + } + *sport = htons(rtp_port_next + 1); + rc = bind(media->rtcp_socket, (struct sockaddr *)&sa, slen); + if (rc < 0) + goto bind_error; + media->description.port_local = rtp_port_next; + rtp_port_next = (rtp_port_next + 2 > rtp_port_to) ? rtp_port_from : rtp_port_next + 2; + /* set nonblocking io */ + flags = fcntl(media->rtp_socket, F_GETFL); + flags |= O_NONBLOCK; + fcntl(media->rtp_socket, F_SETFL, flags); + flags = fcntl(media->rtcp_socket, F_GETFL); + flags |= O_NONBLOCK; + fcntl(media->rtcp_socket, F_SETFL, flags); + break; + } + + PDEBUG(DCC, DEBUG_DEBUG, "Opening media port %d\n", media->description.port_local); + + return 0; +} + +/* connect RTP + * use remote port to connect to + */ +int osmo_cc_rtp_connect(osmo_cc_session_media_t *media) +{ + struct sockaddr_storage sa; + int slen = 0; // make GCC happy + struct sockaddr_in6 *sa6; + struct sockaddr_in *sa4; + uint16_t *sport; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Connecting media port %d->%d\n", media->description.port_local, media->description.port_remote); + + switch (media->connection_data_remote.addrtype) { + case osmo_cc_session_addrtype_ipv4: + memset(&sa, 0, sizeof(sa)); + sa4 = (struct sockaddr_in *)&sa; + sa4->sin_family = AF_INET; + rc = inet_pton(AF_INET, media->connection_data_remote.address, &sa4->sin_addr); + if (rc < 1) { +pton_error: + PDEBUG(DCC, DEBUG_NOTICE, "Cannot connect to address '%s'.\n", media->connection_data_remote.address); + return -EINVAL; + } + sport = &sa4->sin_port; + slen = sizeof(*sa4); + break; + case osmo_cc_session_addrtype_ipv6: + memset(&sa, 0, sizeof(sa)); + sa6 = (struct sockaddr_in6 *)&sa; + sa6->sin6_family = AF_INET6; + rc = inet_pton(AF_INET6, media->connection_data_remote.address, &sa6->sin6_addr); + if (rc < 1) + goto pton_error; + sport = &sa6->sin6_port; + slen = sizeof(*sa6); + break; + case osmo_cc_session_addrtype_unknown: + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s'.\n", media->connection_data_local.addrtype_name); + return -EINVAL; + } + + *sport = htons(media->description.port_remote); + rc = connect(media->rtp_socket, (struct sockaddr *)&sa, slen); + if (rc < 0) { +connect_error: + PDEBUG(DCC, DEBUG_NOTICE, "Cannot connect to address '%s'.\n", media->connection_data_remote.address); + osmo_cc_rtp_close(media); + return -EIO; + } + *sport = htons(media->description.port_remote + 1); + rc = connect(media->rtcp_socket, (struct sockaddr *)&sa, slen); + if (rc < 0) + goto connect_error; + + return 0; +} + +/* send rtp data with given codec */ +void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, int inc_sequence, int inc_timestamp) +{ + uint8_t *payload = NULL; + int payload_len = 0; + + if (!codec || !codec->media->rtp_socket) + return; + + if (codec->encoder) + codec->encoder(data, len, &payload, &payload_len); + else { + payload = data; + payload_len = len; + } + + rtp_send(codec->media->rtp_socket, payload, payload_len, codec->payload_type_remote, codec->media->tx_sequence, codec->media->tx_timestamp, codec->media->rtp_ssrc); + codec->media->tx_sequence += inc_sequence; + codec->media->tx_timestamp += inc_timestamp; + + if (codec->encoder) + free(payload); +} + +/* receive rtp data for given media, return < 0, if there is nothing this time */ +int osmo_cc_rtp_receive(osmo_cc_session_media_t *media) +{ + int rc; + uint8_t *payload = NULL; + int payload_len = 0; + uint8_t marker; + uint8_t payload_type; + osmo_cc_session_codec_t *codec; + uint8_t *data; + int len; + + if (!media || media->rtp_socket <= 0) + return -EIO; + + rc = rtp_receive(media->rtp_socket, &payload, &payload_len, &marker, &payload_type, &media->rx_sequence, &media->rx_timestamp); + if (rc < 0) + return rc; + + /* search for codec */ + for (codec = media->codec_list; codec; codec = codec->next) { + + if (codec->payload_type_local == payload_type) + break; + } + if (!codec) { + PDEBUG(DCC, DEBUG_NOTICE, "Received RTP frame for unknown codec (payload_type = %d).\n", payload_type); + return 0; + } + + if (codec->decoder) + codec->decoder(payload, payload_len, &data, &len); + else { + data = payload; + len = payload_len; + } + + if (codec->media->receive) + codec->media->receiver(codec, media->rx_sequence, media->rx_timestamp, data, len); + + if (codec->decoder) + free(data); + + return 0; +} + +void osmo_cc_rtp_close(osmo_cc_session_media_t *media) +{ + if (media->rtp_socket) { + close(media->rtp_socket); + media->rtp_socket = 0; + } + if (media->rtcp_socket) { + close(media->rtcp_socket); + media->rtcp_socket = 0; + } +} + diff --git a/src/libosmocc/rtp.h b/src/libosmocc/rtp.h new file mode 100644 index 0000000..47d748c --- /dev/null +++ b/src/libosmocc/rtp.h @@ -0,0 +1,8 @@ + +void osmo_cc_set_rtp_ports(uint16_t from, uint16_t to); +int osmo_cc_rtp_open(osmo_cc_session_media_t *media); +int osmo_cc_rtp_connect(osmo_cc_session_media_t *media); +void osmo_cc_rtp_send(osmo_cc_session_codec_t *codec, uint8_t *data, int len, int inc_sequence, int inc_timestamp); +int osmo_cc_rtp_receive(osmo_cc_session_media_t *media); +void osmo_cc_rtp_close(osmo_cc_session_media_t *media); + diff --git a/src/libosmocc/screen.c b/src/libosmocc/screen.c new file mode 100644 index 0000000..c8e17dc --- /dev/null +++ b/src/libosmocc/screen.c @@ -0,0 +1,684 @@ +/* Endpoint and call process handling + * + * (C) 2019 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 "../libtimer/timer.h" +#include "../libdebug/debug.h" +#include "endpoint.h" +#include "message.h" + +#define SCREEN_QUESTIONMARK 1 +#define SCREEN_STAR 2 +#define SCREEN_AT 3 + +void osmo_cc_help_screen(void) +{ + printf("Screening options:\n\n"); + + printf("screen-calling-in [attrs] [attrs] \n"); + printf("screen-called-in [attrs] [attrs] \n"); + printf("screen-calling-out [attrs] [attrs] \n"); + printf("screen-called-out [attrs] [attrs] \n\n"); + + printf("These options allow to screen an incoming or outgoing caller ID or dialed\n"); + printf("number. If 'the current caller ID' or 'current dialed number' matches, it will\n"); + printf("be replaced by 'new caller ID' or 'new dialed number'. 'incoming' means from\n"); + printf(" the interface and 'outgoing' means towards the interface.\n\n"); + + printf("Attributes prior 'current caller ID' or 'new dialed number' may be used to\n"); + printf("perform screening only if the attribute match. Attributes prior\n"); + printf("'new caller ID' or 'new dialed number' may be used to alter them. Attribute to\n"); + printf("define the type of number can be: 'unknown', 'international', 'national',\n"); + printf("'network', 'subscriber', 'abbreviated' Attribute to define the restriction of a\n"); + printf("caller ID: 'allowed', 'restricted'\n\n"); + + printf("The current caller ID or dialed number may contain one or more '?', to allow\n"); + printf("any digit to match. The current caller ID or dialed number may contain a '*',\n"); + printf("to allow any suffix to match from now on. The new caller ID or dialed number\n"); + printf("may contain a '*', to append the suffix from the current caller ID or dialed\n"); + printf("number.\n\n"); +} + +char *osmo_cc_strtok_quotes(const char **text_p) +{ + static char token[1024]; + const char *text = *text_p; + int i, quote; + + /* skip spaces */ + while (*text) { + if (*text > 32) + break; + text++; + } + + /* if eol, return NULL */ + if (!(*text)) + return NULL; + + i = 0; + quote = 0; + while (*text) { + /* escape allows all following characters */ + if (*text == '\\') { + text++; + if (*text) + token[i++] = *text++; + continue; + } + /* no quote, check for them or break on white space */ + if (quote == 0) { + if (*text == '\'') { + quote = 1; + text++; + continue; + } + if (*text == '\"') { + quote = 2; + text++; + continue; + } + if (*text <= ' ') + break; + } + /* single quote, check for unquote */ + if (quote == 1 && *text == '\'') { + quote = 0; + text++; + continue; + } + /* double quote, check for unquote */ + if (quote == 2 && *text == '\"') { + quote = 0; + text++; + continue; + } + /* copy character */ + token[i++] = *text++; + } + token[i] = '\0'; + + *text_p = text; + return token; +} + +int osmo_cc_add_screen(osmo_cc_endpoint_t *ep, const char *text) +{ + osmo_cc_screen_list_t **list_p = NULL, *list; + const char *token; + int no_present = 0, calling_in = 0, star_used, at_used; + int i, j; + + star_used = 0; + if (!strncasecmp(text, "screen-calling-in", 17)) { + text += 17; + list_p = &ep->screen_calling_in; + no_present = 1; + calling_in = 1; + } else if (!strncasecmp(text, "screen-called-in", 16)) { + text += 16; + list_p = &ep->screen_called_in; + } else if (!strncasecmp(text, "screen-calling-out", 18)) { + text += 18; + list_p = &ep->screen_calling_out; + no_present = 1; + } else if (!strncasecmp(text, "screen-called-out", 17)) { + text += 17; + list_p = &ep->screen_called_out; + } else { + PDEBUG(DCC, DEBUG_ERROR, "Invalid screening definition \"%s\". It must start with 'screen-calling-in' or 'screen-called-in' or 'screen-calling-out' or 'screen-called-out'\n", text); + return -EINVAL; + } + + /* skip space behind screen list string */ + while (*text) { + if (*text > 32) + break; + text++; + } + + list = calloc(1, sizeof(*list)); + if (!list) + return -ENOMEM; + +next_from: + token = osmo_cc_strtok_quotes(&text); + if (!token) { + free(list); + PDEBUG(DCC, DEBUG_ERROR, "Missing 'from' string in screening definition \"%s\". If the string shall be empty, use double quotes. (\'\' or \"\")\n", text); + return -EINVAL; + } + if (!strcasecmp(token, "unknown")) { + list->has_from_type = 1; + list->from_type = OSMO_CC_TYPE_UNKNOWN; + goto next_from; + } else + if (!strcasecmp(token, "international")) { + list->has_from_type = 1; + list->from_type = OSMO_CC_TYPE_INTERNATIONAL; + goto next_from; + } else + if (!strcasecmp(token, "national")) { + list->has_from_type = 1; + list->from_type = OSMO_CC_TYPE_NATIONAL; + goto next_from; + } else + if (!strcasecmp(token, "network")) { + list->has_from_type = 1; + list->from_type = OSMO_CC_TYPE_NETWORK; + goto next_from; + } else + if (!strcasecmp(token, "subscriber")) { + list->has_from_type = 1; + list->from_type = OSMO_CC_TYPE_SUBSCRIBER; + goto next_from; + } else + if (!strcasecmp(token, "abbreviated")) { + list->has_from_type = 1; + list->from_type = OSMO_CC_TYPE_ABBREVIATED; + goto next_from; + } else + if (!strcasecmp(token, "allowed")) { + if (no_present) { +no_present_error: + free(list); + PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text); + PDEBUG(DCC, DEBUG_ERROR, "Keyword '%s' not allowed in screen entry for called number\n", token); + return -EINVAL; + } + list->has_from_present = 1; + list->from_present = OSMO_CC_PRESENT_ALLOWED; + goto next_from; + } else + if (!strcasecmp(token, "restricted")) { + if (no_present) + goto no_present_error; + list->has_from_present = 1; + list->from_present = OSMO_CC_PRESENT_RESTRICTED; + goto next_from; + } else { + for (i = j = 0; token[i] && j < (int)sizeof(list->from) - 1; i++, j++) { + if (token[i] == '?') + list->from[j] = SCREEN_QUESTIONMARK; + else + if (token[i] == '*') { + if (star_used) { + free(list); + PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text); + PDEBUG(DCC, DEBUG_ERROR, "The '*' may be used only once.\n"); + return -EINVAL; + } + list->from[j] = SCREEN_STAR; + star_used = 1; + } else + if (token[i] == '\\' && token[i + 1] != '\0') + list->from[j] = token[++i]; + else + list->from[j] = token[i]; + } + list->from[j] = '\0'; + } + + star_used = 0; +next_to: + token = osmo_cc_strtok_quotes(&text); + if (!token) { + free(list); + PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text); + PDEBUG(DCC, DEBUG_ERROR, "Missing screening result. If the string shall be empty, use double quotes. (\'\' or \"\")\n"); + return -EINVAL; + } + if (!strcasecmp(token, "unknown")) { + list->has_to_type = 1; + list->to_type = OSMO_CC_TYPE_UNKNOWN; + goto next_to; + } else + if (!strcasecmp(token, "international")) { + list->has_to_type = 1; + list->to_type = OSMO_CC_TYPE_INTERNATIONAL; + goto next_to; + } else + if (!strcasecmp(token, "national")) { + list->has_to_type = 1; + list->to_type = OSMO_CC_TYPE_NATIONAL; + goto next_to; + } else + if (!strcasecmp(token, "network")) { + list->has_to_type = 1; + list->to_type = OSMO_CC_TYPE_NETWORK; + goto next_to; + } else + if (!strcasecmp(token, "subscriber")) { + list->has_to_type = 1; + list->to_type = OSMO_CC_TYPE_SUBSCRIBER; + goto next_to; + } else + if (!strcasecmp(token, "abbreviated")) { + list->has_to_type = 1; + list->to_type = OSMO_CC_TYPE_ABBREVIATED; + goto next_to; + } else + if (!strcasecmp(token, "allowed")) { + if (no_present) + goto no_present_error; + list->has_to_present = 1; + list->to_present = OSMO_CC_PRESENT_ALLOWED; + goto next_to; + } else + if (!strcasecmp(token, "restricted")) { + if (no_present) + goto no_present_error; + list->has_to_present = 1; + list->to_present = OSMO_CC_PRESENT_RESTRICTED; + goto next_to; + } else { + for (i = j = 0; token[i] && j < (int)sizeof(list->to) - 1; i++, j++) { + if (token[i] == '*') { + if (star_used) { + free(list); + PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text); + PDEBUG(DCC, DEBUG_ERROR, "The '*' may be used only once.\n"); + return -EINVAL; + } + list->to[j] = SCREEN_STAR; + star_used = 1; + } else + if (token[i] == '@') { + if (!calling_in) { + free(list); + PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text); + PDEBUG(DCC, DEBUG_ERROR, "The '@' may be used only for incoming calls from interface.\n"); + return -EINVAL; + } + if (at_used) { + free(list); + PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text); + PDEBUG(DCC, DEBUG_ERROR, "The '@' may be used only once.\n"); + return -EINVAL; + } + list->to[j] = SCREEN_AT; + at_used = 1; + } else + if (token[i] == '\\' && token[i + 1] != '\0') + list->to[j] = token[++i]; + else + list->to[j] = token[i]; + } + list->to[j] = '\0'; + } + + token = osmo_cc_strtok_quotes(&text); + if (token) { + free(list); + PDEBUG(DCC, DEBUG_ERROR, "Error in screening definition '%s'.\n", text); + PDEBUG(DCC, DEBUG_ERROR, "Got garbage behind screening result.\n"); + return -EINVAL; + } + + /* attach screen entry to list */ + while (*list_p) + list_p = &((*list_p)->next); + *list_p = list; + + return 0; +} + +void osmo_cc_flush_screen(osmo_cc_screen_list_t *list) +{ + osmo_cc_screen_list_t *temp; + + while (list) { + temp = list; + list = list->next; + free(temp); + } +} + +const char *print_rule_string(const char *input) +{ + static char output[256]; + int i; + + for (i = 0; *input && i < (int)sizeof(output) - 1; i++, input++) { + switch (*input) { + case SCREEN_QUESTIONMARK: + output[i] = '?'; + break; + case SCREEN_STAR: + output[i] = '*'; + break; + case SCREEN_AT: + output[i] = '@'; + break; + default: + output[i] = *input; + } + } + + output[i] = '\0'; + return output; +} + +static int osmo_cc_screen(const char *what, osmo_cc_screen_list_t *list, uint8_t *type, uint8_t *present, char *id_to, int id_to_size, const char *id_from, const char **routing_p) +{ + const char *suffix; + int i, j, rule; + + PDEBUG(DCC, DEBUG_INFO, "Screening %s '%s':\n", what, id_from); + switch (*type) { + case OSMO_CC_TYPE_UNKNOWN: + PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n"); + break; + case OSMO_CC_TYPE_INTERNATIONAL: + PDEBUG(DCC, DEBUG_INFO, " -> type = international\n"); + break; + case OSMO_CC_TYPE_NATIONAL: + PDEBUG(DCC, DEBUG_INFO, " -> type = national\n"); + break; + case OSMO_CC_TYPE_NETWORK: + PDEBUG(DCC, DEBUG_INFO, " -> type = network\n"); + break; + case OSMO_CC_TYPE_SUBSCRIBER: + PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n"); + break; + case OSMO_CC_TYPE_ABBREVIATED: + PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n"); + break; + } + if (present) switch (*present) { + case OSMO_CC_PRESENT_ALLOWED: + PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n"); + break; + case OSMO_CC_PRESENT_RESTRICTED: + PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n"); + break; + } + + rule = 0; + while (list) { + rule++; + PDEBUG(DCC, DEBUG_INFO, "Comparing with rule #%d: '%s':\n", rule, print_rule_string(list->from)); + if (list->has_from_type) switch (list->from_type) { + case OSMO_CC_TYPE_UNKNOWN: + PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n"); + break; + case OSMO_CC_TYPE_INTERNATIONAL: + PDEBUG(DCC, DEBUG_INFO, " -> type = international\n"); + break; + case OSMO_CC_TYPE_NATIONAL: + PDEBUG(DCC, DEBUG_INFO, " -> type = national\n"); + break; + case OSMO_CC_TYPE_NETWORK: + PDEBUG(DCC, DEBUG_INFO, " -> type = network\n"); + break; + case OSMO_CC_TYPE_SUBSCRIBER: + PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n"); + break; + case OSMO_CC_TYPE_ABBREVIATED: + PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n"); + break; + } + if (list->has_from_present) switch (list->from_present) { + case OSMO_CC_PRESENT_ALLOWED: + PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n"); + break; + case OSMO_CC_PRESENT_RESTRICTED: + PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n"); + break; + } + suffix = NULL; + /* attributes do not match */ + if (list->has_from_type && list->from_type != *type) { + PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because 'type' is different.\n"); + continue; + } + if (present && list->has_from_present && list->from_present != *present) { + PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because 'present' is different.\n"); + continue; + } + for (i = 0; list->from[i] && id_from[i]; i++) { + /* '?' means: any digit, so it machtes */ + if (list->from[i] == SCREEN_QUESTIONMARK) { + continue; + } + /* '*' means: anything may follow, so it machtes */ + if (list->from[i] == SCREEN_STAR) { + suffix = id_from + i; + break; + } + /* check if digit doesn't matches */ + if (list->from[i] != id_from[i]) + break; + } + /* if last checked digit is '*', we have a match */ + /* also if we hit EOL at id_from and next check digit is '*' */ + if (list->from[i] == SCREEN_STAR) + break; + /* if all digits have matched */ + if (list->from[i] == '\0' && id_from[i] == '\0') + break; + PDEBUG(DCC, DEBUG_INFO, "Rule does not match, because %s is different.\n", what); + list = list->next; + } + + /* if no list entry matches */ + if (!list) + return -1; + + /* replace ID */ + if (list->has_to_type) { + *type = list->to_type; + } + if (present && list->has_to_present) { + *present = list->to_present; + } + for (i = j = 0; list->to[i]; i++) { + if (j == id_to_size - 1) + break; + /* '*' means to use suffix of input string */ + if (list->to[i] == SCREEN_STAR && suffix) { + while (*suffix) { + id_to[j++] = *suffix++; + if (j == id_to_size - 1) + break; + } + continue; + /* '@' means to stop and return routing also */ + } else if (list->to[i] == SCREEN_AT) { + *routing_p = &list->to[i]; + break; + } + /* copy output digit */ + id_to[j++] = list->to[i]; + } + id_to[j] = '\0'; + + PDEBUG(DCC, DEBUG_INFO, "Rule matches, changing %s to '%s'.\n", what, print_rule_string(id_to)); + if (list->has_to_type) switch (list->to_type) { + case OSMO_CC_TYPE_UNKNOWN: + PDEBUG(DCC, DEBUG_INFO, " -> type = unknown\n"); + break; + case OSMO_CC_TYPE_INTERNATIONAL: + PDEBUG(DCC, DEBUG_INFO, " -> type = international\n"); + break; + case OSMO_CC_TYPE_NATIONAL: + PDEBUG(DCC, DEBUG_INFO, " -> type = national\n"); + break; + case OSMO_CC_TYPE_NETWORK: + PDEBUG(DCC, DEBUG_INFO, " -> type = network\n"); + break; + case OSMO_CC_TYPE_SUBSCRIBER: + PDEBUG(DCC, DEBUG_INFO, " -> type = subscriber\n"); + break; + case OSMO_CC_TYPE_ABBREVIATED: + PDEBUG(DCC, DEBUG_INFO, " -> type = abbreviated\n"); + break; + } + if (list->has_to_present) switch (list->to_present) { + case OSMO_CC_PRESENT_ALLOWED: + PDEBUG(DCC, DEBUG_INFO, " -> present = allowed\n"); + break; + case OSMO_CC_PRESENT_RESTRICTED: + PDEBUG(DCC, DEBUG_INFO, " -> present = restricted\n"); + break; + } + + return 0; +} + +osmo_cc_msg_t *osmo_cc_screen_msg(osmo_cc_endpoint_t *ep, osmo_cc_msg_t *old_msg, int in, const char **routing_p) +{ + osmo_cc_msg_t *new_msg; + char id[256], calling[256], called[256], redir[256]; + uint8_t calling_type, calling_plan, calling_present, calling_screen; + uint8_t called_type, called_plan; + uint8_t redir_type, redir_plan, redir_present, redir_screen, redir_reason; + int calling_status = 0, called_status = 0, redir_status = 0; + int rc; + void *ie, *to_ie; + uint8_t ie_type; + uint16_t ie_length; + void *ie_value; + + if (in && ep->screen_calling_in) { + rc = osmo_cc_get_ie_calling(old_msg, 0, &calling_type, &calling_plan, &calling_present, &calling_screen, id, sizeof(id)); + if (rc >= 0) { + rc = osmo_cc_screen("incoming caller ID", ep->screen_calling_in, &calling_type, &calling_present, calling, sizeof(calling), id, routing_p); + if (rc >= 0) + calling_status = 1; + } else { + calling_type = OSMO_CC_TYPE_UNKNOWN; + calling_plan = OSMO_CC_PLAN_TELEPHONY; + calling_present = OSMO_CC_PRESENT_ALLOWED; + calling_screen = OSMO_CC_SCREEN_NETWORK; + rc = osmo_cc_screen("incoming caller ID", ep->screen_calling_in, &calling_type, &calling_present, calling, sizeof(calling), "", routing_p); + if (rc >= 0) + calling_status = 1; + } + rc = osmo_cc_get_ie_redir(old_msg, 0, &redir_type, &redir_plan, &redir_present, &redir_screen, &redir_reason, id, sizeof(id)); + if (rc >= 0) { + rc = osmo_cc_screen("incoming redirecting number", ep->screen_calling_in, &redir_type, &redir_present, redir, sizeof(redir), id, routing_p); + if (rc >= 0) + redir_status = 1; + } + } + if (in && ep->screen_called_in) { + rc = osmo_cc_get_ie_called(old_msg, 0, &called_type, &called_plan, id, sizeof(id)); + if (rc >= 0) { + rc = osmo_cc_screen("incoming dialed number", ep->screen_called_in, &called_type, NULL, called, sizeof(called), id, NULL); + if (rc >= 0) + called_status = 1; + } else { + called_type = OSMO_CC_TYPE_UNKNOWN; + called_plan = OSMO_CC_PLAN_TELEPHONY; + rc = osmo_cc_screen("incoming dialed number", ep->screen_called_in, &called_type, NULL, called, sizeof(called), "", NULL); + if (rc >= 0) + called_status = 1; + } + } + if (!in && ep->screen_calling_out) { + rc = osmo_cc_get_ie_calling(old_msg, 0, &calling_type, &calling_plan, &calling_present, &calling_screen, id, sizeof(id)); + if (rc >= 0) { + rc = osmo_cc_screen("outgoing caller ID", ep->screen_calling_out, &calling_type, &calling_present, calling, sizeof(calling), id, NULL); + if (rc >= 0) + calling_status = 1; + } else { + calling_type = OSMO_CC_TYPE_UNKNOWN; + calling_plan = OSMO_CC_PLAN_TELEPHONY; + calling_present = OSMO_CC_PRESENT_ALLOWED; + calling_screen = OSMO_CC_SCREEN_NETWORK; + rc = osmo_cc_screen("outgoing caller ID", ep->screen_calling_out, &calling_type, &calling_present, calling, sizeof(calling), "", NULL); + if (rc >= 0) + calling_status = 1; + } + rc = osmo_cc_get_ie_redir(old_msg, 0, &redir_type, &redir_plan, &redir_present, &redir_screen, &redir_reason, id, sizeof(id)); + if (rc >= 0) { + rc = osmo_cc_screen("outgoing redirecting number", ep->screen_calling_out, &redir_type, &redir_present, redir, sizeof(redir), id, NULL); + if (rc >= 0) + redir_status = 1; + } + } + if (!in && ep->screen_called_out) { + rc = osmo_cc_get_ie_called(old_msg, 0, &called_type, &called_plan, id, sizeof(id)); + if (rc >= 0) { + rc = osmo_cc_screen("outgoing dialed number", ep->screen_called_out, &called_type, NULL, called, sizeof(called), id, NULL); + if (rc >= 0) + called_status = 1; + } else { + called_type = OSMO_CC_TYPE_UNKNOWN; + called_plan = OSMO_CC_PLAN_TELEPHONY; + rc = osmo_cc_screen("outgoing dialed number", ep->screen_called_out, &called_type, NULL, called, sizeof(called), "", NULL); + if (rc >= 0) + called_status = 1; + } + } + + /* nothing screened */ + if (!calling_status && !called_status && !redir_status) + return old_msg; + + new_msg = osmo_cc_new_msg(old_msg->type); + + /* copy and replace */ + ie = old_msg->data; + while ((ie_value = osmo_cc_msg_sep_ie(old_msg, &ie, &ie_type, &ie_length))) { + switch (ie_type) { + case OSMO_CC_IE_CALLING: + if (calling_status) { + osmo_cc_add_ie_calling(new_msg, calling_type, calling_plan, calling_present, calling_screen, calling); + calling_status = 0; + break; + } + goto copy; + case OSMO_CC_IE_CALLED: + if (called_status) { + osmo_cc_add_ie_called(new_msg, called_type, called_plan, called); + called_status = 0; + break; + } + goto copy; + case OSMO_CC_IE_REDIR: + if (redir_status) { + osmo_cc_add_ie_redir(new_msg, redir_type, redir_plan, redir_present, redir_screen, redir_reason, redir); + redir_status = 0; + break; + } + goto copy; + default: + copy: + to_ie = osmo_cc_add_ie(new_msg, ie_type, ie_length); + memcpy(to_ie, ie_value, ie_length); + } + } + + /* applend, if not yet in message (except redir, since it must exist) */ + if (calling_status) + osmo_cc_add_ie_calling(new_msg, calling_type, calling_plan, calling_present, calling_screen, calling); + if (called_status) + osmo_cc_add_ie_called(new_msg, called_type, called_plan, called); + + free(old_msg); + return new_msg; +} + diff --git a/src/libosmocc/screen.h b/src/libosmocc/screen.h new file mode 100644 index 0000000..29f4515 --- /dev/null +++ b/src/libosmocc/screen.h @@ -0,0 +1,7 @@ + +void osmo_cc_help_screen(void); +char *osmo_cc_strtok_quotes(const char **text_p); +int osmo_cc_add_screen(osmo_cc_endpoint_t *ep, const char *text); +void osmo_cc_flush_screen(osmo_cc_screen_list_t *list); +osmo_cc_msg_t *osmo_cc_screen_msg(osmo_cc_endpoint_t *ep, osmo_cc_msg_t *old_msg, int in, const char **routing_p); + diff --git a/src/libosmocc/sdp.c b/src/libosmocc/sdp.c new file mode 100644 index 0000000..3fba8af --- /dev/null +++ b/src/libosmocc/sdp.c @@ -0,0 +1,539 @@ +/* Session Description Protocol parsing and generator + * This shall be simple and is incomplete. + * + * (C) 2019 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 "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "endpoint.h" +#include "sdp.h" + +#define strncat_printf(sdp, fmt, arg...) \ + { \ + snprintf(sdp + strlen(sdp), sizeof(sdp) - strlen(sdp), fmt, ## arg); \ + sdp[sizeof(sdp) - 1] = '\0'; \ + } + +/* generate SDP from session structure */ +char *osmo_cc_session_gensdp(osmo_cc_session_t *session) +{ + /* calc max size of SDP: quick an dirty (close to max UDP payload size) */ + static char sdp[65000]; + const char *username, *sess_id, *sess_version, *nettype, *addrtype, *unicast_address; + const char *session_name; + int individual_connection_data = 1; /* in case there is no media, there is no connection data */ + int individual_send_receive = 1; /* in case there is no media, there is no send/receive attribute */ + struct osmo_cc_session_media *media; + struct osmo_cc_session_codec *codec; + + sdp[0] = 0; + + /* Version */ + strncat_printf(sdp, "v=0\r\n"); + + /* Origin */ + username = session->origin_local.username; + sess_id = session->origin_local.sess_id; + sess_version = session->origin_local.sess_version; + nettype = session->origin_local.nettype; + addrtype = session->origin_local.addrtype; + unicast_address = session->origin_local.unicast_address; + strncat_printf(sdp, "o=%s %s %s %s %s %s\r\n", username, sess_id, sess_version, nettype, addrtype, unicast_address); + + /* Session */ + session_name = session->name; + strncat_printf(sdp, "s=%s\r\n", session_name); + + /* Connection Data (if all media have the same data) */ + if (session->media_list) { + osmo_cc_session_for_each_media(session->media_list->next, media) { + if (session->media_list->connection_data_local.nettype != media->connection_data_local.nettype) + break; + if (session->media_list->connection_data_local.addrtype != media->connection_data_local.addrtype) + break; + if (!!strcmp(session->media_list->connection_data_local.address, media->connection_data_local.address)) + break; + } + if (!media) + individual_connection_data = 0; + } + if (!individual_connection_data) + strncat_printf(sdp, "c=%s %s %s\r\n", osmo_cc_session_nettype2string(session->media_list->connection_data_local.nettype), osmo_cc_session_addrtype2string(session->media_list->connection_data_local.addrtype), session->media_list->connection_data_local.address); + + /* timestamp */ + strncat_printf(sdp, "t=0 0\r\n"); + + /* sendonly /recvonly (if all media have the same data) */ + if (session->media_list) { + osmo_cc_session_for_each_media(session->media_list->next, media) { + if (session->media_list->send != media->send) + break; + if (session->media_list->receive != media->receive) + break; + } + if (!media) + individual_send_receive = 0; + } + if (!individual_send_receive) { + if (session->media_list->send && !session->media_list->receive) + strncat_printf(sdp, "a=sendonly\r\n"); + if (!session->media_list->send && session->media_list->receive) + strncat_printf(sdp, "a=recvonly\r\n"); + if (!session->media_list->send && !session->media_list->receive) + strncat_printf(sdp, "a=inactive\r\n"); + } + + /* media */ + osmo_cc_session_for_each_media(session->media_list, media) { + strncat_printf(sdp, "m=%s %u %s", + osmo_cc_session_media_type2string(media->description.type) ? : media->description.type_name, + media->description.port_local, + osmo_cc_session_media_proto2string(media->description.proto) ? : media->description.proto_name); + osmo_cc_session_for_each_codec(media->codec_list, codec) + strncat_printf(sdp, " %u", codec->payload_type_local); + strncat_printf(sdp, "\r\n"); + /* don't list rtpmap when session was canceled by setting port to 0 */ + if (media->description.port_local == 0) + continue; + if (individual_connection_data) + strncat_printf(sdp, "c=%s %s %s\r\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype), osmo_cc_session_addrtype2string(media->connection_data_local.addrtype), media->connection_data_local.address); + osmo_cc_session_for_each_codec(media->codec_list, codec) { + strncat_printf(sdp, "a=rtpmap:%u %s/%d", codec->payload_type_local, codec->payload_name, codec->payload_rate); + if (codec->payload_channels >= 2) + strncat_printf(sdp, "/%d", codec->payload_channels); + strncat_printf(sdp, "\r\n"); + } + if (individual_send_receive) { + if (media->send && !media->receive) + strncat_printf(sdp, "a=sendonly\r\n"); + if (!media->send && media->receive) + strncat_printf(sdp, "a=recvonly\r\n"); + if (!media->send && !media->receive) + strncat_printf(sdp, "a=inactive\r\n"); + } + } + + /* check for overflow and return */ + if (strlen(sdp) == sizeof(sdp) - 1) { + PDEBUG(DCC, DEBUG_ERROR, "Fatal error: Allocated SDP buffer with %d bytes is too small, please fix!\n", (int)sizeof(sdp)); + return NULL; + } + return sdp; +} + +/* seperate a word from string that is delimited with one or more space characters */ +static char *wordsep(char **text_p) +{ + char *text = *text_p; + static char word[256]; + int i; + + /* no text */ + if (text == NULL || *text == '\0') + return NULL; + /* skip spaces before text */ + while (*text && *text <= ' ') + text++; + /* copy content */ + i = 0; + while (*text > ' ' && i < (int)sizeof(word)) + word[i++] = *text++; + word[i] = '\0'; + /* set next */ + *text_p = text; + return word; +} + +/* + * codecs and their default values + * + * if format is -1, payload type is dynamic + * if rate is 0, rate may be any rate + */ +struct codec_defaults { + int fmt; + char *name; + uint32_t rate; + int channels; +} codec_defaults[] = { + { 0, "PCMU", 8000, 1 }, + { 3, "GSM", 8000, 1 }, + { 4, "G723", 8000, 1 }, + { 5, "DVI4", 8000, 1 }, + { 6, "DVI4", 16000, 1 }, + { 7, "LPC", 8000, 1 }, + { 8, "PCMA", 8000, 1 }, + { 9, "G722", 8000, 1 }, + { 10, "L16", 44100, 2 }, + { 11, "L16", 44100, 1 }, + { 12, "QCELP", 8000, 1 }, + { 13, "CN", 8000, 1 }, + { 14, "MPA", 90000, 1 }, + { 15, "G728", 8000, 1 }, + { 16, "DVI4", 11025, 1 }, + { 17, "DVI4", 22050, 1 }, + { 18, "G729", 8000, 1 }, + { 25, "CELB", 90000, 0 }, + { 26, "JPEG", 90000, 0 }, + { 28, "nv", 90000, 0 }, + { 31, "H261", 90000, 0 }, + { 32, "MPV", 90000, 0 }, + { 33, "MP2T", 90000, 0 }, + { 34, "H263", 90000, 0 }, + { -1, NULL, 0, 0 }, +}; + +static void complete_codec_by_fmt(uint8_t fmt, const char **name, uint32_t *rate, int *channels) +{ + int i; + + for (i = 0; codec_defaults[i].name; i++) { + if (codec_defaults[i].fmt == fmt) + break; + } + if (!codec_defaults[i].name) + return; + + free((char *)*name); + *name = strdup(codec_defaults[i].name); + *rate = codec_defaults[i].rate; + *channels = codec_defaults[i].channels; +} + +int osmo_cc_payload_type_by_attrs(uint8_t *fmt, const char *name, uint32_t *rate, int *channels) +{ + int i; + + for (i = 0; codec_defaults[i].name; i++) { + if (!strcmp(codec_defaults[i].name, name) + && (*rate == 0 || codec_defaults[i].rate == *rate) + && (*channels == 0 || codec_defaults[i].channels == *channels)) + break; + } + if (!codec_defaults[i].name) + return -EINVAL; + + *fmt = codec_defaults[i].fmt; + *rate = codec_defaults[i].rate; + *channels = codec_defaults[i].channels; + + return 0; +} + +/* parses data and codec list from SDP + * + * sdp = given SDP text + * return: SDP session description structure */ +struct osmo_cc_session *osmo_cc_session_parsesdp(void *priv, const char *_sdp) +{ + char buffer[strlen(_sdp) + 1], *sdp = buffer; + char *line, *p, *word, *next_word; + int line_no = 0; + struct osmo_cc_session_connection_data ccd, *cd; + int csend = 1, creceive = 1; /* common default */ + struct osmo_cc_session *session = NULL; + struct osmo_cc_session_media *media = NULL; + struct osmo_cc_session_codec *codec = NULL; + + /* prepare data */ + strcpy(sdp, _sdp); + memset(&ccd, 0, sizeof(ccd)); + + /* create SDP session description */ + session = osmo_cc_new_session(priv, NULL, NULL, NULL, osmo_cc_session_nettype_inet, osmo_cc_session_addrtype_ipv4, "127.0.0.1", NULL, 0); // values will be replaced by local definitions during negotiation + + /* check every line of SDP and parse its data */ + while(*sdp) { + if ((p = strchr(sdp, '\r'))) { + *p++ = '\0'; + if (*p == '\n') + p++; + line = sdp; + sdp = p; + } else if ((p = strchr(sdp, '\n'))) { + *p++ = '\0'; + line = sdp; + sdp = p; + } else { + line = sdp; + sdp = strchr(sdp, '\0'); + } + next_word = line + 2; + line_no++; + + if (line[0] == '\0') + continue; + + if (line[1] != '=') { + PDEBUG(DCC, DEBUG_NOTICE, "SDP line %d = '%s' is garbage, expecting '=' as second character.\n", line_no, line); + continue; + } + + switch(line[0]) { + case 'v': + PDEBUG(DCC, DEBUG_DEBUG, " -> Version: %s\n", next_word); + if (atoi(next_word) != 0) { + PDEBUG(DCC, DEBUG_NOTICE, "SDP line %d = '%s' describes unsupported version.\n", line_no, line); + osmo_cc_free_session(session); + return NULL; + } + break; + case 'o': + PDEBUG(DCC, DEBUG_DEBUG, " -> Originator: %s\n", next_word); + /* Originator */ + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.username); // if already set + session->origin_remote.username = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.sess_id); // if already set + session->origin_remote.sess_id = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.sess_version); // if already set + session->origin_remote.sess_version = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.nettype); // if already set + session->origin_remote.nettype = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.addrtype); // if already set + session->origin_remote.addrtype = strdup(word); + word = wordsep(&next_word); + if (!word) + break; + free((char *)session->origin_remote.unicast_address); // if already set + session->origin_remote.unicast_address = strdup(word); + break; + case 's': + /* Session Name */ + PDEBUG(DCC, DEBUG_DEBUG, " -> Session Name: %s\n", next_word); + free((char *)session->name); // if already set + session->name = strdup(next_word); + break; + case 'c': /* Connection Data */ + PDEBUG(DCC, DEBUG_DEBUG, " -> Connection Data: %s\n", next_word); + if (media) + cd = &media->connection_data_remote; + else + cd = &ccd; + /* network type */ + if (!(word = wordsep(&next_word))) + break; + if (!strcmp(word, "IN")) + cd->nettype = osmo_cc_session_nettype_inet; + else { + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported network type '%s' in SDP line %d = '%s'\n", word, line_no, line); + break; + } + /* address type */ + if (!(word = wordsep(&next_word))) + break; + if (!strcmp(word, "IP4")) { + cd->addrtype = osmo_cc_session_addrtype_ipv4; + PDEBUG(DCC, DEBUG_DEBUG, " -> Address Type = IPv4\n"); + } else + if (!strcmp(word, "IP6")) { + cd->addrtype = osmo_cc_session_addrtype_ipv6; + PDEBUG(DCC, DEBUG_DEBUG, " -> Address Type = IPv6\n"); + } else { + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported address type '%s' in SDP line %d = '%s'\n", word, line_no, line); + break; + } + /* connection address */ + if (!(word = wordsep(&next_word))) + break; + if ((p = strchr(word, '/'))) + *p++ = '\0'; + free((char *)cd->address); // in case of multiple lines of 'c' + cd->address = strdup(word); + PDEBUG(DCC, DEBUG_DEBUG, " -> Address = %s\n", word); + break; + case 'm': /* Media Description */ + PDEBUG(DCC, DEBUG_DEBUG, " -> Media Description: %s\n", next_word); + /* add media description */ + media = osmo_cc_add_media(session, 0, 0, NULL, 0, 0, 0, csend, creceive, NULL, 0); + /* copy common connection data from common connection, if exists */ + cd = &media->connection_data_remote; + memcpy(cd, &ccd, sizeof(*cd)); + /* media type */ + if (!(word = wordsep(&next_word))) + break; + if (!strcmp(word, "audio")) + media->description.type = osmo_cc_session_media_type_audio; + else + if (!strcmp(word, "video")) + media->description.type = osmo_cc_session_media_type_video; + else { + media->description.type = osmo_cc_session_media_type_unknown; + media->description.type_name = strdup(word); + PDEBUG(DCC, DEBUG_DEBUG, "Unsupported media type in SDP line %d = '%s'\n", line_no, line); + } + /* port */ + if (!(word = wordsep(&next_word))) + break; + media->description.port_remote = atoi(word); + /* proto */ + if (!(word = wordsep(&next_word))) + break; + if (!strcmp(word, "RTP/AVP")) + media->description.proto = osmo_cc_session_media_proto_rtp; + else { + media->description.proto = osmo_cc_session_media_proto_unknown; + media->description.proto_name = strdup(word); + PDEBUG(DCC, DEBUG_NOTICE, "Unsupported protocol type in SDP line %d = '%s'\n", line_no, line); + break; + } + /* create codec description for each codec and link */ + while ((word = wordsep(&next_word))) { + /* create codec */ + codec = osmo_cc_add_codec(media, NULL, 0, 1, NULL, NULL, 0); + /* fmt */ + codec->payload_type_remote = atoi(word); + complete_codec_by_fmt(codec->payload_type_remote, &codec->payload_name, &codec->payload_rate, &codec->payload_channels); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_remote); + if (codec->payload_name) + PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name); + if (codec->payload_rate) + PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate); + if (codec->payload_channels) + PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels); + } + break; + case 'a': + PDEBUG(DCC, DEBUG_DEBUG, " -> Attribute: %s\n", next_word); + word = wordsep(&next_word); + if (!strcmp(word, "sendrecv")) { + if (media) { + media->receive = 1; + media->send = 1; + } else { + creceive = 1; + csend = 1; + } + break; + } else + if (!strcmp(word, "recvonly")) { + if (media) { + media->receive = 1; + media->send = 0; + } else { + creceive = 1; + csend = 0; + } + break; + } else + if (!strcmp(word, "sendonly")) { + if (media) { + media->receive = 0; + media->send = 1; + } else { + creceive = 0; + csend = 1; + } + break; + } else + if (!strcmp(word, "inactive")) { + if (media) { + media->receive = 0; + media->send = 0; + } else { + creceive = 0; + csend = 0; + } + break; + } else + if (!media) { + PDEBUG(DCC, DEBUG_NOTICE, "Attribute without previously defined media in SDP line %d = '%s'\n", line_no, line); + break; + } + if (!strncmp(word, "rtpmap:", 7)) { + int fmt = atoi(word + 7); + osmo_cc_session_for_each_codec(media->codec_list, codec) { + if (codec->payload_type_remote == fmt) + break; + } + if (!codec) { + PDEBUG(DCC, DEBUG_NOTICE, "Attribute without previously defined codec in SDP line %d = '%s'\n", line_no, line); + break; + } + PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload type = %d\n", codec->payload_type_remote); + if (!(word = wordsep(&next_word))) + break; + if ((p = strchr(word, '/'))) + *p++ = '\0'; + free((char *)codec->payload_name); // in case it is already set above + codec->payload_name = strdup(word); + PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload name = %s\n", codec->payload_name); + if (!(word = p)) + break; + if ((p = strchr(word, '/'))) + *p++ = '\0'; + codec->payload_rate = atoi(word); + PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload rate = %d\n", codec->payload_rate); + if (!(word = p)) { + /* if no channel is given and no default was specified, we must set 1 channel */ + if (!codec->payload_channels) + codec->payload_channels = 1; + break; + } + codec->payload_channels = atoi(word); + PDEBUG(DCC, DEBUG_DEBUG, " -> (rtpmap) payload channels = %d\n", codec->payload_channels); + } + break; + } + } + + /* if something is incomplete, abort here */ + if (osmo_cc_session_check(session, 1)) { + PDEBUG(DCC, DEBUG_NOTICE, "Parsing SDP failed.\n"); + osmo_cc_free_session(session); + return NULL; + } + + return session; +} + +void osmo_cc_debug_sdp(const char *_sdp) +{ + const unsigned char *sdp = (const unsigned char *)_sdp; + char text[256]; + int i; + + while (*sdp) { + for (i = 0; *sdp > 0 && *sdp >= 32 && i < (int)sizeof(text) - 1; i++) + text[i] = *sdp++; + text[i] = '\0'; + PDEBUG(DCC, DEBUG_DEBUG, " | %s\n", text); + while (*sdp > 0 && *sdp < 32) + sdp++; + } +} + diff --git a/src/libosmocc/sdp.h b/src/libosmocc/sdp.h new file mode 100644 index 0000000..4aa6f60 --- /dev/null +++ b/src/libosmocc/sdp.h @@ -0,0 +1,6 @@ + +char *osmo_cc_session_gensdp(struct osmo_cc_session *session); +struct osmo_cc_session *osmo_cc_session_parsesdp(void *priv, const char *_sdp); +int osmo_cc_payload_type_by_attrs(uint8_t *fmt, const char *name, uint32_t *rate, int *channels); +void osmo_cc_debug_sdp(const char *sdp); + diff --git a/src/libosmocc/session.c b/src/libosmocc/session.c new file mode 100644 index 0000000..72e805a --- /dev/null +++ b/src/libosmocc/session.c @@ -0,0 +1,639 @@ +/* Osmo-CC: Media Session handling + * + * (C) 2016 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 "../libtimer/timer.h" +#include "../libdebug/debug.h" +#include "endpoint.h" + +#define NTP_OFFSET 2208988800 + +enum osmo_cc_session_nettype default_nettype = osmo_cc_session_nettype_inet; +enum osmo_cc_session_addrtype default_addrtype = osmo_cc_session_addrtype_ipv4; +const char *default_unicast_address = "127.0.0.1"; + +void osmo_cc_set_local_peer(enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address) +{ + default_nettype = nettype; + default_addrtype = addrtype; + default_unicast_address = strdup(address); +} + +osmo_cc_session_t *osmo_cc_new_session(void *priv, const char *username, const char *sess_id, const char *sess_version, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *unicast_address, const char *session_name, int debug) +{ + osmo_cc_session_t *session; + + if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Creating session structure.\n"); + + session = calloc(1, sizeof(*session)); + if (!session) { + PDEBUG(DCC, DEBUG_ERROR, "No mem!\n"); + abort(); + } + session->priv = priv; + if (username) { + int i; + for (i = 0; username[i]; i++) { + if ((uint8_t)username[i] < 33) { + PDEBUG(DCC, DEBUG_ERROR, "Fatal error: SDP's originator (username) uses invalid characters, please fix!\n"); + abort(); + } + } + session->origin_local.username = strdup(username); + } + if (!username) + session->origin_local.username = strdup("-"); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> user name = %s\n", session->origin_local.username); + if (sess_id) + session->origin_local.sess_id = strdup(sess_id); + if (sess_version) + session->origin_local.sess_version = strdup(sess_version); + if (!sess_id || !sess_version) { + struct timeval tv; + char ntp_timestamp[32]; + /* get time NTP format time stamp (time since 1900) */ + gettimeofday(&tv, NULL); + sprintf(ntp_timestamp, "%" PRIu64, (uint64_t)tv.tv_sec + NTP_OFFSET); + if (!sess_id) + session->origin_local.sess_id = strdup(ntp_timestamp); + if (!sess_version) + session->origin_local.sess_version = strdup(ntp_timestamp); + } + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session ID = %s\n", session->origin_local.sess_id); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session version = %s\n", session->origin_local.sess_version); + if (nettype) + session->origin_local.nettype = strdup(osmo_cc_session_nettype2string(nettype)); + else + session->origin_local.nettype = strdup(osmo_cc_session_nettype2string(default_nettype)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", session->origin_local.nettype); + if (addrtype) + session->origin_local.addrtype = strdup(osmo_cc_session_addrtype2string(addrtype)); + else + session->origin_local.addrtype = strdup(osmo_cc_session_addrtype2string(default_addrtype)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", session->origin_local.addrtype); + if (unicast_address) + session->origin_local.unicast_address = strdup(unicast_address); + else + session->origin_local.unicast_address = strdup(default_unicast_address); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> unicast address = %s\n", session->origin_local.unicast_address); + if (session_name) + session->name = strdup(session_name); + if (!session_name) + session->name = strdup("-"); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session name = %s\n", session->name); + + return session; +} + +void osmo_cc_free_session(osmo_cc_session_t *session) +{ + PDEBUG(DCC, DEBUG_DEBUG, "Free session structure.\n"); + + free((char *)session->origin_local.username); + free((char *)session->origin_local.sess_id); + free((char *)session->origin_local.sess_version); + free((char *)session->origin_local.nettype); + free((char *)session->origin_local.addrtype); + free((char *)session->origin_local.unicast_address); + free((char *)session->origin_remote.username); + free((char *)session->origin_remote.sess_id); + free((char *)session->origin_remote.sess_version); + free((char *)session->origin_remote.nettype); + free((char *)session->origin_remote.addrtype); + free((char *)session->origin_remote.unicast_address); + free((char *)session->name); + while (session->media_list) + osmo_cc_free_media(session->media_list); + free(session); +} + +osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), int debug) +{ + osmo_cc_session_media_t *media, **mediap; + + media = calloc(1, sizeof(*media)); + if (!media) { + PDEBUG(DCC, DEBUG_ERROR, "No mem!\n"); + abort(); + } + media->session = session; + if (nettype) + media->connection_data_local.nettype = nettype; + else + media->connection_data_local.nettype = default_nettype; + if (addrtype) + media->connection_data_local.addrtype = addrtype; + else + media->connection_data_local.addrtype = default_addrtype; + if (address) + media->connection_data_local.address = strdup(address); + else + media->connection_data_local.address = strdup(default_unicast_address); + media->description.type = type; + media->description.port_local = port; + media->description.proto = proto; + media->send = send; + media->receive = receive; + media->receiver = receiver; + media->tx_sequence = random(); + media->tx_timestamp = random(); + mediap = &media->session->media_list; + while (*mediap) + mediap = &((*mediap)->next); + *mediap = media; + + if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Adding session media.\n"); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", osmo_cc_session_addrtype2string(media->connection_data_local.addrtype)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address = %s\n", media->connection_data_local.address); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media type = %s\n", osmo_cc_session_media_type2string(media->description.type)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media port = %d\n", media->description.port_local); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media proto = %s\n", osmo_cc_session_media_proto2string(media->description.proto)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Opening and binding media port %d\n", media->description.port_local); + + return media; +} + +void osmo_cc_free_media(osmo_cc_session_media_t *media) +{ + osmo_cc_session_media_t **mediap; + + PDEBUG(DCC, DEBUG_DEBUG, "Free session media.\n"); + + osmo_cc_rtp_close(media); + free((char *)media->connection_data_local.nettype_name); + free((char *)media->connection_data_local.addrtype_name); + free((char *)media->connection_data_local.address); + free((char *)media->connection_data_remote.nettype_name); + free((char *)media->connection_data_remote.addrtype_name); + free((char *)media->connection_data_remote.address); + while (media->codec_list) + osmo_cc_free_codec(media->codec_list); + mediap = &media->session->media_list; + while (*mediap != media) + mediap = &((*mediap)->next); + *mediap = media->next; + free(media); +} + +osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *payload_name, uint32_t payload_rate, int payload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), int debug) +{ + osmo_cc_session_codec_t *codec, **codecp; + int rc; + + codec = calloc(1, sizeof(*codec)); + if (!codec) { + PDEBUG(DCC, DEBUG_ERROR, "No mem!\n"); + abort(); + } + codec->media = media; + if (payload_name) { + codec->payload_name = strdup(payload_name); + codec->payload_rate = payload_rate; + codec->payload_channels = payload_channels; + rc = osmo_cc_payload_type_by_attrs(&codec->payload_type_local, payload_name, &payload_rate, &payload_channels); + if (rc < 0) { + /* hunt for next free dynamic payload type */ + uint8_t fmt = 96; + osmo_cc_session_codec_t *c; + osmo_cc_session_for_each_codec(media->codec_list, c) { + if (c->payload_type_local >= fmt) + fmt = c->payload_type_local + 1; + } + codec->payload_type_local = fmt; + } + } + codec->encoder = encoder; + codec->decoder = decoder; + codecp = &codec->media->codec_list; + while (*codecp) + codecp = &((*codecp)->next); + *codecp = codec; + + if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Adding session codec.\n"); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_local); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels); + + return codec; +} + +void osmo_cc_free_codec(osmo_cc_session_codec_t *codec) +{ + osmo_cc_session_codec_t **codecp; + + PDEBUG(DCC, DEBUG_DEBUG, "Free session codec.\n"); + + free((char *)codec->payload_name); + codecp = &codec->media->codec_list; + while (*codecp != codec) + codecp = &((*codecp)->next); + *codecp = codec->next; + free(codec); +} + +int osmo_cc_session_check(osmo_cc_session_t *session, int remote) +{ + struct osmo_cc_session_origin *orig; + struct osmo_cc_session_media *media; + struct osmo_cc_session_connection_data *cd; + struct osmo_cc_session_media_description *md; + struct osmo_cc_session_codec *codec; + int i, j; + + if (remote) + orig = &session->origin_remote; + else + orig = &session->origin_local; + if (!orig->username + || !orig->sess_id + || !orig->sess_version + || !orig->nettype + || !orig->addrtype + || !orig->unicast_address) { + PDEBUG(DCC, DEBUG_NOTICE, "Missing data in session origin\n"); + return -EINVAL; + } + if (!session->name) { + PDEBUG(DCC, DEBUG_NOTICE, "Missing data in session origin\n"); + return -EINVAL; + } + if (!session->media_list) { + PDEBUG(DCC, DEBUG_NOTICE, "Missing media session\n"); + return -EINVAL; + } + i = 0; + osmo_cc_session_for_each_media(session->media_list, media) { + i++; + if (remote) + cd = &media->connection_data_remote; + else + cd = &media->connection_data_local; + if (!cd->nettype && !cd->nettype_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection network type\n", i); + return -EINVAL; + } + if (!cd->addrtype && !cd->addrtype_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection address type\n", i); + return -EINVAL; + } + if (!cd->address) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection address\n", i); + return -EINVAL; + } + md = &media->description; + if (!md->type && !md->type_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing media type\n", i); + return -EINVAL; + } + if (!md->proto && !md->proto_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing protocol\n", i); + return -EINVAL; + } + j = 0; + osmo_cc_session_for_each_codec(media->codec_list, codec) { + j++; + if (!codec->payload_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing name\n", i, j); + return -EINVAL; + } + if (!codec->payload_rate) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing rate\n", i, j); + return -EINVAL; + } + if (!codec->payload_channels) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing channel count\n", i, j); + return -EINVAL; + } + } + } + + return 0; +} + +/* check session description and generate SDP */ +const char *osmo_cc_session_send_offer(osmo_cc_session_t *session) +{ + const char *sdp; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Generating session offer and opening RTP stream.\n"); + + rc = osmo_cc_session_check(session, 0); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Please fix!\n"); + abort(); + } + + sdp = osmo_cc_session_gensdp(session); + osmo_cc_debug_sdp(sdp); + + return sdp; +} + +osmo_cc_session_t *osmo_cc_session_receive_offer(void *priv, const char *sdp) +{ + osmo_cc_session_t *session; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Parsing session offer.\n"); + + osmo_cc_debug_sdp(sdp); + session = osmo_cc_session_parsesdp(priv, sdp); + if (!session) + return NULL; + + rc = osmo_cc_session_check(session, 0); + if (rc < 0) { + osmo_cc_free_session(session); + return NULL; + } + + return session; +} + +void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len)) +{ + media->accepted = 1; + if (nettype) + media->connection_data_local.nettype = nettype; + else + media->connection_data_local.nettype = default_nettype; + if (addrtype) + media->connection_data_local.addrtype = addrtype; + else + media->connection_data_local.addrtype = default_addrtype; + free((char *)media->connection_data_local.address); + if (address) + media->connection_data_local.address = strdup(address); + else + media->connection_data_local.address = strdup(default_unicast_address); + media->send = send; + media->receive = receive; + media->receiver = receiver; + + PDEBUG(DCC, DEBUG_DEBUG, "Accepting session media.\n"); + PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype)); + PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", osmo_cc_session_addrtype2string(media->connection_data_local.addrtype)); + PDEBUG(DCC, DEBUG_DEBUG, " -> address = %s\n", media->connection_data_local.address); +} + + +void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)) +{ + codec->accepted = 1; + codec->encoder = encoder; + codec->decoder = decoder; + /* when we accept a codec, we just use the same payload type as the remote */ + codec->payload_type_local = codec->payload_type_remote; + + PDEBUG(DCC, DEBUG_DEBUG, "Accepting session codec.\n"); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_local); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels); +} + +/* remove codecs/media that have not been accepted and generate SDP */ +const char *osmo_cc_session_send_answer(osmo_cc_session_t *session) +{ + osmo_cc_session_media_t *media; + osmo_cc_session_codec_t *codec, **codec_p; + const char *sdp; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Generating session answer.\n"); + + /* loop all media */ + osmo_cc_session_for_each_media(session->media_list, media) { + /* remove unaccepted codecs */ + codec_p = &media->codec_list; + codec = *codec_p; + while (codec) { + if (!codec->accepted) { + osmo_cc_free_codec(codec); + codec = *codec_p; + continue; + } + codec_p = &codec->next; + codec = *codec_p; + } + /* mark media as unused, if no codec or not accepted */ + if (!media->accepted || !media->codec_list) + media->description.port_local = 0; + } + + rc = osmo_cc_session_check(session, 0); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Please fix!\n"); + abort(); + } + + sdp = osmo_cc_session_gensdp(session); + osmo_cc_debug_sdp(sdp); + + return sdp; +} + +/* Apply remote session description to local session description. + * If remote media's port is 0, remove from local session description. + * If codecs in the remote session description are missing, remove from local session description. + */ +static int osmo_cc_session_negotiate(osmo_cc_session_t *session_local, struct osmo_cc_session *session_remote) +{ + osmo_cc_session_media_t *media_local, *media_remote, **media_local_p; + osmo_cc_session_codec_t *codec_local, *codec_remote, **codec_local_p; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Negotiating session.\n"); + + /* copy remote session infromation */ + session_local->origin_remote.username = strdup(session_remote->origin_remote.username); + session_local->origin_remote.sess_id = strdup(session_remote->origin_remote.sess_id); + session_local->origin_remote.sess_version = strdup(session_remote->origin_remote.sess_version); + session_local->origin_remote.nettype = strdup(session_remote->origin_remote.nettype); + session_local->origin_remote.addrtype = strdup(session_remote->origin_remote.addrtype); + session_local->origin_remote.unicast_address = strdup(session_remote->origin_remote.unicast_address); + + /* loop all media */ + for (media_local = session_local->media_list, media_remote = session_remote->media_list; media_local && media_remote; media_local = media_local->next, media_remote = media_remote->next) { + /* copy remote media information */ + media_local->connection_data_remote.nettype = media_remote->connection_data_remote.nettype; + if (media_remote->connection_data_remote.nettype_name) + media_local->connection_data_remote.nettype_name = strdup(media_remote->connection_data_remote.nettype_name); + media_local->connection_data_remote.addrtype = media_remote->connection_data_remote.addrtype; + if (media_remote->connection_data_remote.addrtype_name) + media_local->connection_data_remote.addrtype_name = strdup(media_remote->connection_data_remote.addrtype_name); + if (media_remote->connection_data_remote.address) + media_local->connection_data_remote.address = strdup(media_remote->connection_data_remote.address); + media_local->description.port_remote = media_remote->description.port_remote; + media_local->send = media_remote->send; + media_local->receive = media_remote->receive; + /* loop all codecs and remove if they are not found in local session description */ + codec_local_p = &media_local->codec_list; + codec_local = *codec_local_p; + while (codec_local) { + /* search for equal codec, payload type may differe for each direction */ + osmo_cc_session_for_each_codec(media_remote->codec_list, codec_remote) { + if (!strcmp(codec_local->payload_name, codec_remote->payload_name) + && codec_local->payload_rate == codec_remote->payload_rate + && codec_local->payload_channels == codec_remote->payload_channels) + break; + } + if (!codec_remote) { + osmo_cc_free_codec(codec_local); + codec_local = *codec_local_p; + continue; + } + /* copy remote codec information */ + codec_local->payload_type_remote = codec_remote->payload_type_remote; + codec_local_p = &codec_local->next; + codec_local = *codec_local_p; + } + } + if (media_local) { + PDEBUG(DCC, DEBUG_NOTICE, "Negotiation failed, because remote endpoint returns less media streams than we offered.\n"); + return -EINVAL; + } + if (media_remote) { + PDEBUG(DCC, DEBUG_NOTICE, "Negotiation failed, because remote endpoint returns more media streams than we offered.\n"); + return -EINVAL; + } + + /* remove media with port == 0 or no codec at all */ + media_local_p = &session_local->media_list; + media_local = *media_local_p; + while (media_local) { + if (media_local->description.port_remote == 0 || !media_local->codec_list) { + osmo_cc_free_media(media_local); + media_local = *media_local_p; + continue; + } + media_local_p = &media_local->next; + media_local = *media_local_p; + } + + rc = osmo_cc_session_check(session_local, 1); + if (rc < 0) + return rc; + + return 0; +} + +int osmo_cc_session_receive_answer(osmo_cc_session_t *session, const char *sdp) +{ + osmo_cc_session_t *session_remote; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Parsing session answer.\n"); + + osmo_cc_debug_sdp(sdp); + session_remote = osmo_cc_session_parsesdp(NULL, sdp); + if (!session_remote) + return -EINVAL; + + rc = osmo_cc_session_check(session_remote, 1); + if (rc < 0) { + osmo_cc_free_session(session_remote); + return rc; + } + rc = osmo_cc_session_negotiate(session, session_remote); + if (rc < 0) { + osmo_cc_free_session(session_remote); + return rc; + } + osmo_cc_free_session(session_remote); + + return 0; +} + +const char *osmo_cc_session_nettype2string(enum osmo_cc_session_nettype nettype) +{ + switch (nettype) { + case osmo_cc_session_nettype_inet: + return "IN"; + default: + return NULL; + } +} + +const char *osmo_cc_session_addrtype2string(enum osmo_cc_session_addrtype addrtype) +{ + switch (addrtype) { + case osmo_cc_session_addrtype_ipv4: + return "IP4"; + case osmo_cc_session_addrtype_ipv6: + return "IP6"; + default: + return NULL; + } +} + +const char *osmo_cc_session_media_type2string(enum osmo_cc_session_media_type media_type) +{ + switch (media_type) { + case osmo_cc_session_media_type_audio: + return "audio"; + case osmo_cc_session_media_type_video: + return "video"; + default: + return NULL; + } +} + +const char *osmo_cc_session_media_proto2string(enum osmo_cc_session_media_proto media_proto) +{ + switch (media_proto) { + case osmo_cc_session_media_proto_rtp: + return "RTP/AVP"; + default: + return NULL; + } +} + +int osmo_cc_session_if_codec(osmo_cc_session_codec_t *codec, const char *name, uint32_t rate, int channels) +{ + return (!strcmp(codec->payload_name, name) + && codec->payload_rate == rate + && codec->payload_channels == channels); +} + +int osmo_cc_session_handle(osmo_cc_session_t *session) +{ + osmo_cc_session_media_t *media; + int w = 0, rc; + + osmo_cc_session_for_each_media(session->media_list, media) { + do { + rc = osmo_cc_rtp_receive(media); + if (rc >= 0) + w = 1; + } while (rc >= 0); + } + + return w; +} + diff --git a/src/libosmocc/session.h b/src/libosmocc/session.h new file mode 100644 index 0000000..cab8fea --- /dev/null +++ b/src/libosmocc/session.h @@ -0,0 +1,119 @@ + +/* session description, global part: */ + +typedef struct osmo_cc_session_origin { + const char *username; + const char *sess_id; + const char *sess_version; + const char *nettype; + const char *addrtype; + const char *unicast_address; +} osmo_cc_session_origin_t; + +/* session instance */ +typedef struct osmo_cc_session { + void *priv; + osmo_cc_session_origin_t origin_local, origin_remote; + const char *name; + struct osmo_cc_session_media *media_list; +} osmo_cc_session_t; + +/* connection description: */ + +enum osmo_cc_session_nettype { + osmo_cc_session_nettype_unknown = 0, + osmo_cc_session_nettype_inet, +}; + +enum osmo_cc_session_addrtype { + osmo_cc_session_addrtype_unknown = 0, + osmo_cc_session_addrtype_ipv4, + osmo_cc_session_addrtype_ipv6, +}; + +typedef struct osmo_cc_session_connection_data { + enum osmo_cc_session_nettype nettype; + const char *nettype_name; + enum osmo_cc_session_addrtype addrtype; + const char *addrtype_name; + const char *address; +} osmo_cc_session_connection_data_t; + +/* one media of session description: */ + +enum osmo_cc_session_media_type { + osmo_cc_session_media_type_unknown, + osmo_cc_session_media_type_audio, + osmo_cc_session_media_type_video, +}; + +enum osmo_cc_session_media_proto { + osmo_cc_session_media_proto_unknown, + osmo_cc_session_media_proto_rtp, +}; + +typedef struct osmo_cc_session_media_description { + enum osmo_cc_session_media_type type; + const char *type_name; + uint16_t port_local, port_remote; + enum osmo_cc_session_media_proto proto; + const char *proto_name; +} osmo_cc_session_media_description_t; + +/* media entry */ +typedef struct osmo_cc_session_media { + struct osmo_cc_session_media *next; + osmo_cc_session_t *session; + osmo_cc_session_media_description_t description; + osmo_cc_session_connection_data_t connection_data_local, connection_data_remote; + struct osmo_cc_session_codec *codec_list; + int send, receive; + void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len); + int rtp_socket; + int rtcp_socket; + uint32_t rtp_ssrc; + uint16_t tx_sequence, rx_sequence; + uint32_t tx_timestamp, rx_timestamp; + int accepted; +} osmo_cc_session_media_t; + +/* codec entry */ +typedef struct osmo_cc_session_codec { + struct osmo_cc_session_codec *next; + osmo_cc_session_media_t *media; + uint8_t payload_type_local, payload_type_remote; /* local = towards local, remote = toward remote */ + const char *payload_name; + uint32_t payload_rate; + int payload_channels; + void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); + void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len); + int accepted; +} osmo_cc_session_codec_t; + +#define osmo_cc_session_for_each_media(head, m) \ + for (m = (head); m; m = m->next) + +#define osmo_cc_session_for_each_codec(head, c) \ + for (c = (head); c; c = c->next) + +void osmo_cc_set_local_peer(enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address); +osmo_cc_session_t *osmo_cc_new_session(void *priv, const char *username, const char *sess_id, const char *sess_version, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *unicast_address, const char *session_name, int debug); +void osmo_cc_free_session(osmo_cc_session_t *session); +osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), int debug); +void osmo_cc_free_media(osmo_cc_session_media_t *media); +osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *playload_name, uint32_t playload_rate, int playload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), int debug); +void osmo_cc_free_codec(osmo_cc_session_codec_t *codec); +int osmo_cc_session_check(struct osmo_cc_session *session, int remote); +const char *osmo_cc_session_send_offer(osmo_cc_session_t *session); +osmo_cc_session_t *osmo_cc_session_receive_offer(void *priv, const char *sdp); +void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len)); +void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)); +const char *osmo_cc_session_send_answer(osmo_cc_session_t *session); +int osmo_cc_session_receive_answer(osmo_cc_session_t *session, const char *sdp); +const char *osmo_cc_session_nettype2string(enum osmo_cc_session_nettype nettype); +const char *osmo_cc_session_addrtype2string(enum osmo_cc_session_addrtype addrtype); +const char *osmo_cc_session_media_type2string(enum osmo_cc_session_media_type media_type); +const char *osmo_cc_session_media_proto2string(enum osmo_cc_session_media_proto media_proto); +int osmo_cc_session_if_codec(osmo_cc_session_codec_t *codec, const char *name, uint32_t rate, int channels); +int osmo_cc_session_handle(osmo_cc_session_t *session); + diff --git a/src/libosmocc/socket.c b/src/libosmocc/socket.c new file mode 100644 index 0000000..d4eb12e --- /dev/null +++ b/src/libosmocc/socket.c @@ -0,0 +1,583 @@ +/* Osmo-CC: Socket handling + * + * (C) 2016 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 +#include "../libdebug/debug.h" +#include "../libtimer/timer.h" +#include "message.h" +#include "cause.h" +#include "socket.h" + +static const char version_string[] = OSMO_CC_VERSION; + +static int _getaddrinfo(const char *host, uint16_t port, struct addrinfo **result) +{ + char portstr[8]; + struct addrinfo hints; + int rc; + + sprintf(portstr, "%d", port); + + /* bind socket */ + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + rc = getaddrinfo(host, portstr, &hints, result); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Failed to create socket for host '%s', port '%d': %s.\n", host, port, gai_strerror(rc)); + return rc; + } + return rc; +} + +/* send a reject message toward CC process. + * the CC process will change the reject message to a release message when not in INIT_IN state + */ +static void rej_msg(osmo_cc_socket_t *os, uint32_t callref, uint8_t socket_cause, uint8_t isdn_cause, uint16_t sip_cause) +{ + osmo_cc_msg_t *msg; + + /* create message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_REQ); + if (!msg) + abort(); + + /* add cause */ + osmo_cc_add_ie_cause(msg, os->location, isdn_cause, sip_cause, socket_cause); + osmo_cc_convert_cause_msg(msg); + + /* message down */ + os->recv_msg_cb(os->priv, callref, msg); +} + +void tx_keepalive_timeout(struct timer *timer) +{ + osmo_cc_conn_t *conn = (osmo_cc_conn_t *)timer->priv; + osmo_cc_msg_t *msg; + + /* send keepalive message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_DUMMY_REQ); + osmo_cc_msg_list_enqueue(&conn->os->write_list, msg, conn->callref); + timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE); +} + +static void close_conn(osmo_cc_conn_t *conn, uint8_t socket_cause); + +void rx_keepalive_timeout(struct timer *timer) +{ + osmo_cc_conn_t *conn = (osmo_cc_conn_t *)timer->priv; + + PDEBUG(DCC, DEBUG_ERROR, "OsmoCC-Socket failed due to timeout.\n"); + close_conn(conn, OSMO_CC_SOCKET_CAUSE_TIMEOUT); +} + +/* create socket process and bind socket */ +int osmo_cc_open_socket(osmo_cc_socket_t *os, const char *host, uint16_t port, void *priv, void (*recv_msg_cb)(void *priv, uint32_t callref, osmo_cc_msg_t *msg), uint8_t location) +{ + int try = 0, auto_port = 0; + struct addrinfo *result, *rp; + int rc, sock, flags; + + memset(os, 0, sizeof(*os)); + +try_again: + /* check for given port, if NULL, autoselect port */ + if (!port || auto_port) { + port = OSMO_CC_DEFAULT_PORT + try; + try++; + auto_port = 1; + } + + PDEBUG(DCC, DEBUG_DEBUG, "Create socket for host %s port %d.\n", host, port); + + rc = _getaddrinfo(host, port, &result); + if (rc < 0) + return rc; + for (rp = result; rp; rp = rp->ai_next) { + int on = 1; + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock < 0) + continue; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (unsigned char *)&on, sizeof(on)); + rc = bind(sock, rp->ai_addr, rp->ai_addrlen); + if (rc == 0) + break; + close(sock); + } + freeaddrinfo(result); + if (rp == NULL) { + if (auto_port && port < OSMO_CC_DEFAULT_PORT_MAX) { + PDEBUG(DCC, DEBUG_DEBUG, "Failed to bind host %s port %d, trying again.\n", host, port); + goto try_again; + } + PDEBUG(DCC, DEBUG_ERROR, "Failed to bind given host %s port %d.\n", host, port); + return -EIO; + } + + /* listen to socket */ + rc = listen(sock, 10); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Failed to listen on socket.\n"); + return rc; + } + + /* set nonblocking io */ + flags = fcntl(sock, F_GETFL); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + + os->socket = sock; + os->recv_msg_cb = recv_msg_cb; + os->priv = priv; + os->location = location; + + return port; +} + +/* create a connection */ +static osmo_cc_conn_t *open_conn(osmo_cc_socket_t *os, int sock, uint32_t callref, int read_setup) +{ + osmo_cc_conn_t *conn, **connp; + + /* create connection */ + conn = calloc(1, sizeof(*conn)); + if (!conn) { + PDEBUG(DCC, DEBUG_ERROR, "No mem!\n"); + abort(); + } + conn->os = os; + conn->socket = sock; + conn->read_version = 1; + conn->write_version = 1; + conn->read_setup = read_setup; + if (callref) + conn->callref = callref; + else + conn->callref = osmo_cc_new_callref(); + + timer_init(&conn->tx_keepalive_timer, tx_keepalive_timeout, conn); + timer_init(&conn->rx_keepalive_timer, rx_keepalive_timeout, conn); + timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE); + timer_start(&conn->rx_keepalive_timer, OSMO_CC_SOCKET_RX_KEEPALIVE); + + PDEBUG(DCC, DEBUG_DEBUG, "New socket connection (callref %d).\n", conn->callref); + + /* attach to list */ + connp = &os->conn_list; + while (*connp) + connp = &((*connp)->next); + *connp = conn; + + return conn; +} + +/* remove a connection */ +static void close_conn(osmo_cc_conn_t *conn, uint8_t socket_cause) +{ + osmo_cc_conn_t **connp; + osmo_cc_msg_list_t *ml; + + /* detach connection first, to prevent a destruction during message handling (double free) */ + connp = &conn->os->conn_list; + while (*connp != conn) + connp = &((*connp)->next); + *connp = conn->next; + /* send reject message, if socket_cause is set */ + if (socket_cause && !conn->read_setup) { + /* receive a release or reject (depending on state), but only if we sent a setup */ + rej_msg(conn->os, conn->callref, socket_cause, 0, 0); + } + + PDEBUG(DCC, DEBUG_DEBUG, "Destroy socket connection (callref %d).\n", conn->callref); + + /* close socket */ + if (conn->socket) + close(conn->socket); + /* free partly received message */ + if (conn->read_msg) + osmo_cc_free_msg(conn->read_msg); + /* free send queue */ + while ((ml = conn->write_list)) { + osmo_cc_free_msg(ml->msg); + conn->write_list = ml->next; + free(ml); + } + /* free timers */ + timer_exit(&conn->tx_keepalive_timer); + timer_exit(&conn->rx_keepalive_timer); + /* free connection (already detached above) */ + free(conn); +} + +/* close socket and remove */ +void osmo_cc_close_socket(osmo_cc_socket_t *os) +{ + osmo_cc_msg_list_t *ml; + + PDEBUG(DCC, DEBUG_DEBUG, "Destroy socket.\n"); + + /* free all connections */ + while (os->conn_list) + close_conn(os->conn_list, 0); + /* close socket */ + if (os->socket > 0) { + close(os->socket); + os->socket = 0; + } + /* free send queue */ + while ((ml = os->write_list)) { + osmo_cc_free_msg(ml->msg); + os->write_list = ml->next; + free(ml); + } +} + +/* send message to send_queue of sock instance */ +int osmo_cc_sock_send_msg(osmo_cc_socket_t *os, uint32_t callref, osmo_cc_msg_t *msg, const char *host, uint16_t port) +{ + osmo_cc_msg_list_t *ml; + + /* turn _IND into _REQ and _CNF into _RSP */ + msg->type &= ~1; + + /* create list entry */ + ml = osmo_cc_msg_list_enqueue(&os->write_list, msg, callref); + if (host) + strncpy(ml->host, host, sizeof(ml->host) - 1); + ml->port = port; + + return 0; +} + +/* receive message + * return 1 if work was done. + */ +static int receive_conn(osmo_cc_conn_t *conn) +{ + uint8_t socket_cause = OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE; + int rc; + osmo_cc_msg_t *msg; + uint8_t msg_type; + int len; + int work = 0; + + /* get version from remote */ + if (conn->read_version) { + rc = recv(conn->socket, conn->read_version_string + conn->read_version_pos, strlen(version_string) - conn->read_version_pos, 0); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + conn->read_version_pos += rc; + if (conn->read_version_pos == strlen(version_string)) { + conn->read_version = 0; + if (!!memcmp(conn->read_version_string, version_string, strlen(version_string) - 1)) { + PDEBUG(DCC, DEBUG_NOTICE, "Remote does not seem to be an Osmo-CC socket, rejecting!\n"); + socket_cause = OSMO_CC_SOCKET_CAUSE_FAILED; + goto close; + } + if (conn->read_version_string[strlen(version_string) - 1] != version_string[strlen(version_string) - 1]) { + PDEBUG(DCC, DEBUG_NOTICE, "Remote Osmo-CC socket has wrong version (local=%s, remote=%s), rejecting!\n", version_string, conn->read_version_string); + socket_cause = OSMO_CC_SOCKET_CAUSE_VERSION_MISMATCH; + goto close; + } + } else + return work; + } + +try_next_message: + /* read message header from remote */ + if (!conn->read_msg) { + rc = recv(conn->socket, ((uint8_t *)&conn->read_hdr) + conn->read_pos, sizeof(conn->read_hdr) - conn->read_pos, 0); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + conn->read_pos += rc; + if (conn->read_pos == sizeof(conn->read_hdr)) { + conn->read_msg = osmo_cc_new_msg(conn->read_hdr.type); + if (!conn->read_msg) + abort(); + conn->read_msg->length_networkorder = conn->read_hdr.length_networkorder; + /* prepare for reading message */ + conn->read_pos = 0; + } else + return work; + } + + /* read message data from remote */ + msg = conn->read_msg; + len = ntohs(msg->length_networkorder); + if (len == 0) + goto empty_message; + rc = recv(conn->socket, msg->data + conn->read_pos, len - conn->read_pos, 0); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + conn->read_pos += rc; + if (conn->read_pos == len) { +empty_message: + /* start RX keepalive timeer, if not already */ + timer_start(&conn->rx_keepalive_timer, OSMO_CC_SOCKET_RX_KEEPALIVE); + /* we got our setup message, so we clear the flag */ + conn->read_setup = 0; + /* prepare for reading header */ + conn->read_pos = 0; + /* detach message first, because the connection might be destroyed during message handling */ + msg_type = conn->read_msg->type; + conn->read_msg = NULL; + /* drop dummy or forward message */ + if (msg_type == OSMO_CC_MSG_DUMMY_REQ) + osmo_cc_free_msg(msg); + else + conn->os->recv_msg_cb(conn->os->priv, conn->callref, msg); + if (msg_type == OSMO_CC_MSG_REL_REQ || msg_type == OSMO_CC_MSG_REJ_REQ) { + PDEBUG(DCC, DEBUG_DEBUG, "closing socket because we received a release or reject message.\n"); + close_conn(conn, 0); + return 1; /* conn removed */ + } + goto try_next_message; + } + return work; + +close: + PDEBUG(DCC, DEBUG_ERROR, "OsmoCC-Socket failed, socket cause %d.\n", socket_cause); + close_conn(conn, socket_cause); + return work; /* conn removed */ +} + +/* transmit message + * return 1 if work was done. + */ +static int transmit_conn(osmo_cc_conn_t *conn) +{ + uint8_t socket_cause = OSMO_CC_SOCKET_CAUSE_BROKEN_PIPE; + int rc; + osmo_cc_msg_t *msg; + int len; + osmo_cc_msg_list_t *ml; + int work = 0; + + /* send socket version to remote */ + if (conn->write_version) { + rc = write(conn->socket, version_string, strlen(version_string)); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + if (rc != strlen(version_string)) { + PDEBUG(DCC, DEBUG_ERROR, "short write, please fix handling!\n"); + abort(); + } + conn->write_version = 0; + } + + /* send message to remote */ + while (conn->write_list) { + timer_stop(&conn->tx_keepalive_timer); + msg = conn->write_list->msg; + len = sizeof(*msg) + ntohs(msg->length_networkorder); + rc = write(conn->socket, msg, len); + if (rc < 0 && errno == EAGAIN) + return work; + work = 1; + if (rc <= 0) { + goto close; + } + if (rc != len) { + PDEBUG(DCC, DEBUG_ERROR, "short write, please fix handling!\n"); + abort(); + } + /* close socket after sending release/reject message */ + if (msg->type == OSMO_CC_MSG_REL_REQ || msg->type == OSMO_CC_MSG_REJ_REQ) { + PDEBUG(DCC, DEBUG_DEBUG, "closing socket because we sent a release or reject message.\n"); + close_conn(conn, 0); + return work; /* conn removed */ + } + /* free message after sending */ + ml = conn->write_list; + conn->write_list = ml->next; + osmo_cc_free_msg(msg); + free(ml); + } + + /* start TX keepalive timeer, if not already + * because we stop at every message above, we actually restart the timer here. + * only if there is no message for the amout of time, the timer fires. + */ + if (!timer_running(&conn->tx_keepalive_timer)) + timer_start(&conn->tx_keepalive_timer, OSMO_CC_SOCKET_TX_KEEPALIVE); + + return work; + +close: + PDEBUG(DCC, DEBUG_NOTICE, "OsmoCC-Socket failed.\n"); + close_conn(conn, socket_cause); + return work; /* conn removed */ +} + +/* handle all sockets of a socket interface + * return 1 if work was done. + */ +int osmo_cc_handle_socket(osmo_cc_socket_t *os) +{ + struct sockaddr_storage sa; + socklen_t slen = sizeof(sa); + int sock; + osmo_cc_conn_t *conn; + osmo_cc_msg_list_t *ml, **mlp; + int flags; + struct addrinfo *result, *rp; + int rc; + int work = 0; + + /* handle messages in send queue */ + while ((ml = os->write_list)) { + work = 1; + /* detach list entry */ + os->write_list = ml->next; + ml->next = NULL; + /* search for socket connection */ + for (conn = os->conn_list; conn; conn=conn->next) { + if (conn->callref == ml->callref) + break; + } + if (conn) { + /* attach to list */ + mlp = &conn->write_list; + while (*mlp) + mlp = &((*mlp)->next); + *mlp = ml; + /* done with message */ + continue; + } + + /* reject and release are ignored */ + if (ml->msg->type == OSMO_CC_MSG_REJ_REQ + || ml->msg->type == OSMO_CC_MSG_REL_REQ) { + /* drop message */ + osmo_cc_free_msg(ml->msg); + free(ml); + /* done with message */ + continue; + } + + /* reject, if this is not a setup message */ + if (ml->msg->type != OSMO_CC_MSG_SETUP_REQ + && ml->msg->type != OSMO_CC_MSG_ATTACH_REQ) { + PDEBUG(DCC, DEBUG_ERROR, "Message with unknown callref.\n"); + rej_msg(os, ml->callref, 0, OSMO_CC_ISDN_CAUSE_INVAL_CALLREF, 0); + /* drop message */ + osmo_cc_free_msg(ml->msg); + free(ml); + /* done with message */ + continue; + } + /* connect to remote */ + rc = _getaddrinfo(ml->host, ml->port, &result); + if (rc < 0) { + rej_msg(os, ml->callref, OSMO_CC_SOCKET_CAUSE_FAILED, 0, 0); + /* drop message */ + osmo_cc_free_msg(ml->msg); + free(ml); + /* done with message */ + continue; + } + for (rp = result; rp; rp = rp->ai_next) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sock < 0) + continue; + /* set nonblocking io */ + flags = fcntl(sock, F_GETFL); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + /* connect */ + rc = connect(sock, rp->ai_addr, rp->ai_addrlen); + if (rc == 0 || errno == EINPROGRESS) + break; + close(sock); + } + freeaddrinfo(result); + if (rp == NULL) { + PDEBUG(DCC, DEBUG_ERROR, "Failed to connect to given host %s port %d.\n", ml->host, ml->port); + rej_msg(os, ml->callref, OSMO_CC_SOCKET_CAUSE_FAILED, 0, 0); + /* drop message */ + osmo_cc_free_msg(ml->msg); + free(ml); + /* done with message */ + continue; + } + /* create connection */ + conn = open_conn(os, sock, ml->callref, 0); + /* attach to list */ + conn->write_list = ml; + /* done with (setup) message */ + } + + /* handle new socket connection */ + while ((sock = accept(os->socket, (struct sockaddr *)&sa, &slen)) > 0) { + work = 1; + /* set nonblocking io */ + flags = fcntl(sock, F_GETFL); + flags |= O_NONBLOCK; + fcntl(sock, F_SETFL, flags); + /* create connection */ + open_conn(os, sock, 0, 1); + } + + /* start with list after each read/write, because while handling (the message), one or more connections may be destroyed */ + for (conn = os->conn_list; conn; conn=conn->next) { + /* check for rx */ + work = receive_conn(conn); + /* if "change" is set, connection list might have changed, so we restart processing the list */ + if (work) + break; + /* check for tx */ + work = transmit_conn(conn); + /* if "change" is set, connection list might have changed, so we restart processing the list */ + if (work) + break; + } + + return work; +} + diff --git a/src/libosmocc/socket.h b/src/libosmocc/socket.h new file mode 100644 index 0000000..a0f96ed --- /dev/null +++ b/src/libosmocc/socket.h @@ -0,0 +1,44 @@ +#ifndef OSMO_CC_SOCKET_H +#define OSMO_CC_SOCKET_H + +#define OSMO_CC_DEFAULT_PORT 4200 +#define OSMO_CC_DEFAULT_PORT_MAX 4299 + +#define OSMO_CC_SOCKET_TX_KEEPALIVE 10.0 +#define OSMO_CC_SOCKET_RX_KEEPALIVE 20.0 + +struct osmo_cc_socket; + +typedef struct osmo_cc_conn { + struct osmo_cc_conn *next; + struct osmo_cc_socket *os; + int socket; + uint32_t callref; + int read_setup; + int read_version; + char read_version_string[sizeof(OSMO_CC_VERSION)]; /* must include 0-termination */ + int read_version_pos; + int write_version; + osmo_cc_msg_t read_hdr; + osmo_cc_msg_t *read_msg; + int read_pos; + osmo_cc_msg_list_t *write_list; + struct timer tx_keepalive_timer; + struct timer rx_keepalive_timer; +} osmo_cc_conn_t; + +typedef struct osmo_cc_socket { + int socket; + osmo_cc_conn_t *conn_list; + osmo_cc_msg_list_t *write_list; + void (*recv_msg_cb)(void *priv, uint32_t callref, osmo_cc_msg_t *msg); + void *priv; + uint8_t location; +} osmo_cc_socket_t; + +int osmo_cc_open_socket(osmo_cc_socket_t *os, const char *host, uint16_t port, void *priv, void (*recv_msg_cb)(void *priv, uint32_t callref, osmo_cc_msg_t *msg), uint8_t location); +void osmo_cc_close_socket(osmo_cc_socket_t *os); +int osmo_cc_sock_send_msg(osmo_cc_socket_t *os, uint32_t callref, osmo_cc_msg_t *msg, const char *host, uint16_t port); +int osmo_cc_handle_socket(osmo_cc_socket_t *os); + +#endif /* OSMO_CC_SOCKET_H */ diff --git a/src/libsample/Makefile.am b/src/libsample/Makefile.am new file mode 100644 index 0000000..5ae865c --- /dev/null +++ b/src/libsample/Makefile.am @@ -0,0 +1,6 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libsample.a + +libsample_a_SOURCES = \ + sample.c diff --git a/src/libsample/sample.c b/src/libsample/sample.c new file mode 100644 index 0000000..a084b66 --- /dev/null +++ b/src/libsample/sample.c @@ -0,0 +1,64 @@ +/* Sample definition + * + * (C) 2017 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 "sample.h" + +/* + * A regular voice conversation takes place at this factor below the full range + * of 16 bits signed value: + */ +static double int_16_speech_level = SPEECH_LEVEL * 0.7079; /* 16 dBm below dBm0, which is about 3dBm below full 16 bit range */ + +/* A sample_t is a value that has virtually infinite precision but will also + * support high numbers. 'double' or 'float' types are sufficient. + * + * When using sample_t inside signal processing of each base station, the + * level of +- 1 is relative to the normal speech evenlope. + * + * When converting sample_t to int16_t, the level of +- 1 is reduced by factor. + * This way the speech may be louder before clipping happens. + * + * When using sample_t to modulate (SDR or sound card), the level is changed, + * so it represents the frequency deviation in Hz. The deviation of speech + * envelope is network dependent. + */ + +void samples_to_int16(int16_t *spl, sample_t *samples, int length) +{ + int32_t value; + + while (length--) { + value = *samples++ * int_16_speech_level * 32768.0; + if (value > 32767.0) + *spl++ = 32767; + else if (value < -32767.0) + *spl++ = -32767; + else + *spl++ = (uint16_t)value; + } +} + +void int16_to_samples(sample_t *samples, int16_t *spl, int length) +{ + while (length--) { + *samples++ = (double)(*spl++) / 32767.0 / int_16_speech_level; + } +} + diff --git a/src/libsample/sample.h b/src/libsample/sample.h new file mode 100644 index 0000000..01a17df --- /dev/null +++ b/src/libsample/sample.h @@ -0,0 +1,8 @@ + +typedef double sample_t; + +#define SPEECH_LEVEL 0.1585 + +void samples_to_int16(int16_t *spl, sample_t *samples, int length); +void int16_to_samples(sample_t *samples, int16_t *spl, int length); + diff --git a/src/libtimer/Makefile.am b/src/libtimer/Makefile.am new file mode 100644 index 0000000..538670a --- /dev/null +++ b/src/libtimer/Makefile.am @@ -0,0 +1,6 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = libtimer.a + +libtimer_a_SOURCES = \ + timer.c diff --git a/src/libtimer/timer.c b/src/libtimer/timer.c new file mode 100644 index 0000000..955d790 --- /dev/null +++ b/src/libtimer/timer.c @@ -0,0 +1,121 @@ +/* Timer handling + * + * (C) 2016 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 "timer.h" + +static struct timer *timer_head = NULL; +static struct timer **timer_tail_p = &timer_head; + +double get_time(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0; +} + +void timer_init(struct timer *timer, void (*fn)(struct timer *timer), void *priv) +{ + if (timer->linked) { + fprintf(stderr, "Timer is already initialized, aborting!\n"); + abort(); + } + + timer->timeout = 0; + timer->fn = fn; + timer->priv = priv; + timer->next = NULL; + *timer_tail_p = timer; + timer_tail_p = &timer->next; + timer->linked = 1; +} + +void timer_exit(struct timer *timer) +{ + timer_tail_p = &timer_head; + while (*timer_tail_p) { + if (timer == *timer_tail_p) + *timer_tail_p = (*timer_tail_p)->next; + else + timer_tail_p = &((*timer_tail_p)->next); + } + timer->linked = 0; +} + +void timer_start(struct timer *timer, double duration) +{ + struct timeval tv; + + if (!timer->linked) { + fprintf(stderr, "Timer is not initialized, aborting!\n"); + abort(); + } + + gettimeofday(&tv, NULL); + + timer->duration = duration; + timer->timeout = get_time() + duration; +} + +void timer_stop(struct timer *timer) +{ + if (!timer->linked) { + fprintf(stderr, "Timer is not initialized, aborting!\n"); + abort(); + } + + timer->timeout = 0; +} + +int timer_running(struct timer *timer) +{ + if (!timer->linked) { + fprintf(stderr, "Timer is not initialized, aborting!\n"); + abort(); + } + + return (timer->timeout != 0); +} + +void process_timer(void) +{ + struct timer *timer; + double now; + + now = get_time(); + +again: + timer = timer_head; + + while (timer) { + if (timer->linked && timer->timeout > 0 && now >= timer->timeout) { + timer->timeout = 0; + timer->fn(timer); + goto again; + } + timer = timer->next; + } +} + diff --git a/src/libtimer/timer.h b/src/libtimer/timer.h new file mode 100644 index 0000000..2073a8f --- /dev/null +++ b/src/libtimer/timer.h @@ -0,0 +1,18 @@ + +struct timer { + struct timer *next; + int linked; /* set is timer is initialized and linked */ + double duration; + double timeout; + void (*fn)(struct timer *timer); + void *priv; +}; + +double get_time(void); +void timer_init(struct timer *timer, void (*fn)(struct timer *timer), void *priv); +void timer_exit(struct timer *timer); +void timer_start(struct timer *timer, double duration); +void timer_stop(struct timer *timer); +int timer_running(struct timer *timer); +void process_timer(void); +