summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2020-11-14 15:45:58 +0100
committerAndreas Eversberg <jolly@eversberg.eu>2020-12-29 11:18:53 +0100
commit0bafcfa52a150bd8aade474ba4351ae9c662745d (patch)
tree89233a0ea7093afc42319d4e7d5371a5a522010f
parentc354814c97bd206e872c7dd91c3bb9f1363fc1eb (diff)
Add libs from Osmocom-Analog
-rw-r--r--src/libdebug/Makefile.am7
-rw-r--r--src/libdebug/debug.c223
-rw-r--r--src/libdebug/debug.h33
-rw-r--r--src/libfilter/Makefile.am8
-rw-r--r--src/libfilter/fir_filter.c197
-rw-r--r--src/libfilter/fir_filter.h21
-rw-r--r--src/libfilter/iir_filter.c204
-rw-r--r--src/libfilter/iir_filter.h17
-rw-r--r--src/libg711/Makefile.am7
-rw-r--r--src/libg711/g711.c537
-rw-r--r--src/libg711/g711.h17
-rw-r--r--src/libjitter/Makefile.am6
-rw-r--r--src/libjitter/jitter.c125
-rw-r--r--src/libjitter/jitter.h14
-rw-r--r--src/liboptions/Makefile.am7
-rw-r--r--src/liboptions/options.c275
-rw-r--r--src/liboptions/options.h6
-rw-r--r--src/libosmocc/Makefile.am15
-rw-r--r--src/libosmocc/cause.c252
-rw-r--r--src/libosmocc/cause.h5
-rw-r--r--src/libosmocc/endpoint.c1476
-rw-r--r--src/libosmocc/endpoint.h128
-rw-r--r--src/libosmocc/helper.c171
-rw-r--r--src/libosmocc/helper.h13
-rw-r--r--src/libosmocc/message.c879
-rw-r--r--src/libosmocc/message.h437
-rw-r--r--src/libosmocc/rtp.c399
-rw-r--r--src/libosmocc/rtp.h8
-rw-r--r--src/libosmocc/screen.c684
-rw-r--r--src/libosmocc/screen.h7
-rw-r--r--src/libosmocc/sdp.c539
-rw-r--r--src/libosmocc/sdp.h6
-rw-r--r--src/libosmocc/session.c639
-rw-r--r--src/libosmocc/session.h119
-rw-r--r--src/libosmocc/socket.c583
-rw-r--r--src/libosmocc/socket.h44
-rw-r--r--src/libsample/Makefile.am6
-rw-r--r--src/libsample/sample.c64
-rw-r--r--src/libsample/sample.h8
-rw-r--r--src/libtimer/Makefile.am6
-rw-r--r--src/libtimer/timer.c121
-rw-r--r--src/libtimer/timer.h18
42 files changed, 8331 insertions, 0 deletions
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..c9b8b27
--- /dev/null
+++ b/src/libdebug/debug.c
@@ -0,0 +1,223 @@
+/* Simple debug functions for level and category filtering
+ *
+ * (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <math.h>
+#include <sys/ioctl.h>
+#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" },
+ { "ss5", "\033[1;34m" },
+ { "cc", "\033[1;32m" },
+ { "stderr", "\033[1;37m" },
+ { NULL, NULL }
+};
+
+int debuglevel = DEBUG_INFO;
+uint64_t debug_mask = ~0;
+extern int num_kanal;
+
+void (*clear_console_text)(void) = NULL;
+void (*print_console_text)(void) = NULL;
+
+int debug_limit_scroll = 0;
+
+void get_win_size(int *w, int *h)
+{
+ struct winsize win;
+ int rc;
+
+ rc = ioctl(0, TIOCGWINSZ, &win);
+ if (rc) {
+ *w = 80;
+ *h = 25;
+ return;
+ }
+
+ *h = win.ws_row;
+ *w = win.ws_col;
+}
+
+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;
+ int w, h;
+
+ 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;
+ if (clear_console_text)
+ clear_console_text();
+ if (debug_limit_scroll) {
+ get_win_size(&w, &h);
+ printf("\0337\033[%d;%dr\0338", debug_limit_scroll + 1, h);
+ }
+ printf("%s%s:%4d %s: %s\033[0;39m", debug_cat[cat].color, file, line, debug_level[level], buffer);
+ if (debug_limit_scroll)
+ printf("\0337\033[%d;%dr\0338", 1, h);
+ if (print_console_text)
+ print_console_text();
+ 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..65e00df
--- /dev/null
+++ b/src/libdebug/debug.h
@@ -0,0 +1,33 @@
+
+#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 DSS5 2
+#define DCC 3
+#define DSTDERR 4
+
+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/libfilter/Makefile.am b/src/libfilter/Makefile.am
new file mode 100644
index 0000000..45d2ec2
--- /dev/null
+++ b/src/libfilter/Makefile.am
@@ -0,0 +1,8 @@
+AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
+
+noinst_LIBRARIES = libfilter.a
+
+libfilter_a_SOURCES = \
+ iir_filter.c \
+ fir_filter.c
+
diff --git a/src/libfilter/fir_filter.c b/src/libfilter/fir_filter.c
new file mode 100644
index 0000000..5df4f40
--- /dev/null
+++ b/src/libfilter/fir_filter.c
@@ -0,0 +1,197 @@
+/* FIR filter
+ *
+ * (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include "../libsample/sample.h"
+#include "fir_filter.h"
+
+//#define DEBUG_TAPS
+
+static void kernel(double *taps, int M, double cutoff, int invert)
+{
+ int i;
+ double sum;
+
+ for (i = 0; i <= M; i++) {
+ /* gen sinc */
+ if (i == M / 2)
+ taps[i] = 2.0 * M_PI * cutoff;
+ else
+ taps[i] = sin(2.0 * M_PI * cutoff * (double)(i - M / 2))
+ / (double)(i - M / 2);
+ /* blackman window */
+ taps[i] *= 0.42 - 0.50 * cos(2 * M_PI * (double)(i / M))
+ + 0.08 * cos(4 * M_PI * (double)(i / M));
+ }
+
+ /* normalize */
+ sum = 0;
+ for (i = 0; i <= M; i++)
+ sum += taps[i];
+ for (i = 0; i <= M; i++)
+ taps[i] /= sum;
+
+ /* invert */
+ if (invert) {
+ for (i = 0; i <= M; i++)
+ taps[i] = -taps[i];
+ taps[M / 2] += 1.0;
+ }
+
+#ifdef DEBUG_TAPS
+ puts("start");
+ for (i = 0; i <= M; i++)
+ puts(debug_amplitude(taps[i]));
+#endif
+}
+
+static fir_filter_t *fir_init(double samplerate, double transition_bandwidth)
+{
+ fir_filter_t *fir;
+ int M;
+
+ /* alloc struct */
+ fir = calloc(1, sizeof(*fir));
+ if (!fir) {
+ fprintf(stderr, "No memory creating FIR filter!\n");
+ return NULL;
+ }
+
+ /* transition bandwidth */
+ M = ceil(1.0 / (transition_bandwidth / samplerate));
+ if ((M & 1))
+ M++;
+
+// printf("cutoff=%.4f\n", cutoff / samplerate);
+// printf("tb=%.4f\n", transition_bandwidth / samplerate);
+ fir->ntaps = M + 1;
+ fir->delay = M / 2;
+
+ /* alloc taps */
+ fir->taps = calloc(fir->ntaps, sizeof(*fir->taps));
+ if (!fir->taps) {
+ fprintf(stderr, "No memory creating FIR filter!\n");
+ fir_exit(fir);
+ return NULL;
+ }
+
+ /* alloc ring buffer */
+ fir->buffer = calloc(fir->ntaps, sizeof(*fir->buffer));
+ if (!fir->buffer) {
+ fprintf(stderr, "No memory creating FIR filter!\n");
+ fir_exit(fir);
+ return NULL;
+ }
+
+
+ return fir;
+}
+
+fir_filter_t *fir_lowpass_init(double samplerate, double cutoff, double transition_bandwidth)
+{
+ /* calculate kernel */
+ fir_filter_t *fir = fir_init(samplerate, transition_bandwidth);
+ if (!fir)
+ return NULL;
+ kernel(fir->taps, fir->ntaps - 1, cutoff / samplerate, 0);
+ return fir;
+}
+
+fir_filter_t *fir_highpass_init(double samplerate, double cutoff, double transition_bandwidth)
+{
+ fir_filter_t *fir = fir_init(samplerate, transition_bandwidth);
+ if (!fir)
+ return NULL;
+ kernel(fir->taps, fir->ntaps - 1, cutoff / samplerate, 1);
+ return fir;
+}
+
+fir_filter_t *fir_allpass_init(double samplerate, double transition_bandwidth)
+{
+ fir_filter_t *fir = fir_init(samplerate, transition_bandwidth);
+ if (!fir)
+ return NULL;
+ fir->taps[(fir->ntaps - 1) / 2] = 1.0;
+ return fir;
+}
+
+fir_filter_t *fir_twopass_init(double samplerate, double cutoff_low, double cutoff_high, double transition_bandwidth)
+{
+ int i;
+ double sum;
+ fir_filter_t *fir = fir_init(samplerate, transition_bandwidth);
+ if (!fir)
+ return NULL;
+ double lp_taps[fir->ntaps], hp_taps[fir->ntaps];
+ kernel(lp_taps, fir->ntaps - 1, cutoff_low / samplerate, 0);
+ kernel(hp_taps, fir->ntaps - 1, cutoff_high / samplerate, 1);
+ sum = 0;
+ printf("#warning does not work as expected\n");
+ abort();
+ for (i = 0; i < fir->ntaps; i++) {
+ fir->taps[i] = lp_taps[i] + hp_taps[i];
+ sum += fir->taps[i];
+ }
+ /* hp will die */
+// for (i = 0; i < fir->ntaps; i++)
+ // fir->taps[i] /= sum;
+ return fir;
+}
+
+void fir_exit(fir_filter_t *fir)
+{
+ if (!fir)
+ return;
+ free(fir->taps);
+ free(fir->buffer);
+ free(fir);
+}
+
+void fir_process(fir_filter_t *fir, sample_t *samples, int num)
+{
+ int i, j;
+ double y;
+
+ for (i = 0; i < num; i++) {
+ /* put sample in ring buffer */
+ fir->buffer[fir->buffer_pos] = samples[i];
+ if (++fir->buffer_pos == fir->ntaps)
+ fir->buffer_pos = 0;
+
+ /* convolve samples */
+ y = 0;
+ for (j = 0; j < fir->ntaps; j++) {
+ /* convolve sample from ring buffer, starting with oldes */
+ y += fir->buffer[fir->buffer_pos] * fir->taps[j];
+ if (++fir->buffer_pos == fir->ntaps)
+ fir->buffer_pos = 0;
+ }
+ samples[i] = y;
+ }
+}
+
+int fir_get_delay(fir_filter_t *fir)
+{
+ return fir->delay;
+}
+
diff --git a/src/libfilter/fir_filter.h b/src/libfilter/fir_filter.h
new file mode 100644
index 0000000..7d94091
--- /dev/null
+++ b/src/libfilter/fir_filter.h
@@ -0,0 +1,21 @@
+#ifndef _FIR_FILTER_H
+#define _FIR_FILTER_H
+
+typedef struct fir_filter {
+ int ntaps;
+ int delay;
+ double *taps;
+ double *buffer;
+ int buffer_pos;
+} fir_filter_t;
+
+fir_filter_t *fir_lowpass_init(double samplerate, double cutoff, double transition_bandwidth);
+fir_filter_t *fir_highpass_init(double samplerate, double cutoff, double transition_bandwidth);
+fir_filter_t *fir_allpass_init(double samplerate, double transition_bandwidth);
+fir_filter_t *fir_twopass_init(double samplerate, double cutoff_low, double cutoff_high, double transition_bandwidth);
+void fir_exit(fir_filter_t *fir);
+void fir_process(fir_filter_t *fir, sample_t *samples, int num);
+int fir_get_delay(fir_filter_t *fir);
+
+#endif /* _FIR_FILTER_H */
+
diff --git a/src/libfilter/iir_filter.c b/src/libfilter/iir_filter.c
new file mode 100644
index 0000000..5872d61
--- /dev/null
+++ b/src/libfilter/iir_filter.c
@@ -0,0 +1,204 @@
+/* cut-off filter (biquad) based on Nigel Redmon (www.earlevel.com)
+ *
+ * (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include "../libsample/sample.h"
+#include "iir_filter.h"
+
+//#define DEBUG_NAN
+
+#define PI M_PI
+
+void iir_lowpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations)
+{
+ double Fc, Q, K, norm;
+
+ if (iterations > 64) {
+ fprintf(stderr, "%s failed: too many iterations, please fix!\n", __func__);
+ abort();
+ }
+
+ memset(filter, 0, sizeof(*filter));
+ filter->iter = iterations;
+ Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */
+ Fc = frequency / (double)samplerate;
+ K = tan(PI * Fc);
+ norm = 1 / (1 + K / Q + K * K);
+ filter->a0 = K * K * norm;
+ filter->a1 = 2 * filter->a0;
+ filter->a2 = filter->a0;
+ filter->b1 = 2 * (K * K - 1) * norm;
+ filter->b2 = (1 - K / Q + K * K) * norm;
+#ifdef DEBUG_NAN
+ printf("%p\n", filter);
+#endif
+}
+
+void iir_highpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations)
+{
+ double Fc, Q, K, norm;
+
+ memset(filter, 0, sizeof(*filter));
+ filter->iter = iterations;
+ Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */
+ Fc = frequency / (double)samplerate;
+ K = tan(PI * Fc);
+ norm = 1 / (1 + K / Q + K * K);
+ filter->a0 = 1 * norm;
+ filter->a1 = -2 * filter->a0;
+ filter->a2 = filter->a0;
+ filter->b1 = 2 * (K * K - 1) * norm;
+ filter->b2 = (1 - K / Q + K * K) * norm;
+}
+
+void iir_bandpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations)
+{
+ double Fc, Q, K, norm;
+
+ memset(filter, 0, sizeof(*filter));
+ filter->iter = iterations;
+ Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */
+ Fc = frequency / (double)samplerate;
+ K = tan(PI * Fc);
+ norm = 1 / (1 + K / Q + K * K);
+ filter->a0 = K / Q * norm;
+ filter->a1 = 0;
+ filter->a2 = -filter->a0;
+ filter->b1 = 2 * (K * K - 1) * norm;
+ filter->b2 = (1 - K / Q + K * K) * norm;
+}
+
+void iir_notch_init(iir_filter_t *filter, double frequency, int samplerate, int iterations)
+{
+ double Fc, Q, K, norm;
+
+ memset(filter, 0, sizeof(*filter));
+ filter->iter = iterations;
+ Q = pow(sqrt(0.5), 1.0 / (double)iterations); /* 0.7071 @ 1 iteration */
+ Fc = frequency / (double)samplerate;
+ K = tan(PI * Fc);
+ norm = 1 / (1 + K / Q + K * K);
+ filter->a0 = (1 + K * K) * norm;
+ filter->a1 = 2 * (K * K - 1) * norm;
+ filter->a2 = filter->a0;
+ filter->b1 = filter->a1;
+ filter->b2 = (1 - K / Q + K * K) * norm;
+}
+
+void iir_process(iir_filter_t *filter, sample_t *samples, int length)
+{
+ double a0, a1, a2, b1, b2;
+ double *z1, *z2;
+ double in, out;
+ int iterations = filter->iter;
+ int i, j;
+
+ /* get states */
+ a0 = filter->a0;
+ a1 = filter->a1;
+ a2 = filter->a2;
+ b1 = filter->b1;
+ b2 = filter->b2;
+
+ /* these are state pointers, so no need to write back */
+ z1 = filter->z1;
+ z2 = filter->z2;
+
+ /* process filter */
+ for (i = 0; i < length; i++) {
+ /* add a small value, otherwise this loop will perform really bad on my 'nuedel' machine!!! */
+ in = *samples + 0.000000001;
+ for (j = 0; j < iterations; j++) {
+ out = in * a0 + z1[j];
+ z1[j] = in * a1 + z2[j] - b1 * out;
+ z2[j] = in * a2 - b2 * out;
+ in = out;
+ }
+ *samples++ = in;
+ }
+}
+
+#ifdef DEBUG_NAN
+#pragma GCC push_options
+//#pragma GCC optimize ("O0")
+#endif
+
+void iir_process_baseband(iir_filter_t *filter, float *baseband, int length)
+{
+ double a0, a1, a2, b1, b2;
+ double *z1, *z2;
+ double in, out;
+ int iterations = filter->iter;
+ int i, j;
+
+ /* get states */
+ a0 = filter->a0;
+ a1 = filter->a1;
+ a2 = filter->a2;
+ b1 = filter->b1;
+ b2 = filter->b2;
+
+ /* these are state pointers, so no need to write back */
+ z1 = filter->z1;
+ z2 = filter->z2;
+
+ /* process filter */
+ for (i = 0; i < length; i++) {
+ /* add a small value, otherwise this loop will perform really bad on my 'nuedel' machine!!! */
+ in = *baseband + 0.000000001;
+ for (j = 0; j < iterations; j++) {
+ out = in * a0 + z1[j];
+#ifdef DEBUG_NAN
+ if (!(out > -100 && out < 100)) {
+ printf("%p\n", filter);
+ printf("1. i=%d j=%d z=%.5f in=%.5f a0=%.5f out=%.5f\n", i, j, z1[j], in, a0, out);
+ abort();
+ }
+#endif
+ z1[j] = in * a1 + z2[j] - b1 * out;
+#ifdef DEBUG_NAN
+ if (!(z1[j] > -100 && z1[j] < 100)) {
+ printf("%p\n", filter);
+ printf("2. i=%d j=%d z1=%.5f z2=%.5f in=%.5f a1=%.5f out=%.5f b1=%.5f\n", i, j, z1[j], z2[j], in, a1, out, b1);
+ abort();
+ }
+#endif
+ z2[j] = in * a2 - b2 * out;
+#ifdef DEBUG_NAN
+ if (!(z2[j] > -100 && z2[j] < 100)) {
+ printf("%p\n", filter);
+ printf("%.5f\n", (in * a2) - (b2 * out));
+ printf("3. i=%d j=%d z2=%.5f in=%.5f a2=%.5f b2=%.5f out=%.5f\n", i, j, z2[j], in, a2, b2, out);
+ abort();
+ }
+#endif
+ in = out;
+ }
+ *baseband = in;
+ baseband += 2;
+ }
+}
+
+#ifdef DEBUG_NAN
+#pragma GCC pop_options
+#endif
diff --git a/src/libfilter/iir_filter.h b/src/libfilter/iir_filter.h
new file mode 100644
index 0000000..a5956c8
--- /dev/null
+++ b/src/libfilter/iir_filter.h
@@ -0,0 +1,17 @@
+#ifndef _FILTER_H
+#define _FILTER_H
+
+typedef struct iir_filter {
+ int iter;
+ double a0, a1, a2, b1, b2;
+ double z1[64], z2[64];
+} iir_filter_t;
+
+void iir_lowpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations);
+void iir_highpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations);
+void iir_bandpass_init(iir_filter_t *filter, double frequency, int samplerate, int iterations);
+void iir_notch_init(iir_filter_t *filter, double frequency, int samplerate, int iterations);
+void iir_process(iir_filter_t *filter, sample_t *samples, int length);
+void iir_process_baseband(iir_filter_t *filter, float *baseband, int length);
+
+#endif /* _FILTER_H */
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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+/* 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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <arpa/inet.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#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 <IPv4 address>:<port>\n");
+ printf("local [<IPv6 address>]:<port>\n");
+ printf("remote <IPv4 address>:<port>\n");
+ printf("remote [<IPv6 address>]:<port>\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 <IPv4 address>\n");
+ printf("rtp-peer <IPv6 address>\n");
+ printf("rtp-ports <first> <last>\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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <inttypes.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#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 "<unknown>";
+ }
+}
+
+/* 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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#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] <current caller ID> [attrs] <new caller ID>\n");
+ printf("screen-called-in [attrs] <current dialed number> [attrs] <new dialed number>\n");
+ printf("screen-calling-out [attrs] <current caller ID> [attrs] <new caller ID>\n");
+ printf("screen-called-out [attrs] <current dialed number> [attrs] <new dialed number>\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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <inttypes.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#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 <jolly@eversberg.eu>
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#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);
+