aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Eversberg <jolly@eversberg.eu>2020-08-01 09:36:09 +0200
committerAndreas Eversberg <jolly@eversberg.eu>2020-12-29 11:11:41 +0100
commitad8a7be345dbca5e3102b4776c65c12435c93545 (patch)
tree3e7fd93a23efadc25872abd55ba050871578b923
parent59119f380f3129c4113c9d4bb8de3a64db42620f (diff)
Add Osmo-CC library to replace MNCC
-rw-r--r--.gitignore1
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile.am3
-rwxr-xr-xsrc/libdebug/debug.c2
-rw-r--r--src/libdebug/debug.h1
-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
24 files changed, 6410 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 5f42b6e..d1d4596 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,7 @@ src/libclipper/libclipper.a
src/libserial/libserial.a
src/libv27/libv27.a
src/libmtp/libmtp.a
+src/libosmocc/libosmocc.a
src/anetz/libgermanton.a
src/anetz/anetz
src/bnetz/bnetz
diff --git a/configure.ac b/configure.ac
index ca537ae..188a548 100644
--- a/configure.ac
+++ b/configure.ac
@@ -87,6 +87,7 @@ AC_OUTPUT(
src/libserial/Makefile
src/libv27/Makefile
src/libmtp/Makefile
+ src/libosmocc/Makefile
src/anetz/Makefile
src/bnetz/Makefile
src/cnetz/Makefile
diff --git a/src/Makefile.am b/src/Makefile.am
index 5f2bad4..b342db1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -28,7 +28,8 @@ SUBDIRS = \
libclipper \
libserial \
libv27 \
- libmtp
+ libmtp \
+ libosmocc
if HAVE_ALSA
SUBDIRS += \
diff --git a/src/libdebug/debug.c b/src/libdebug/debug.c
index 43fb56c..334b2b4 100755
--- a/src/libdebug/debug.c
+++ b/src/libdebug/debug.c
@@ -56,7 +56,7 @@ struct debug_cat {
{ "eurosignal", "\033[1;34m" },
{ "frame", "\033[0;36m" },
{ "call", "\033[0;37m" },
- { "mncc", "\033[1;32m" },
+ { "cc", "\033[1;32m" },
{ "database", "\033[0;33m" },
{ "transaction", "\033[0;32m" },
{ "dms", "\033[0;33m" },
diff --git a/src/libdebug/debug.h b/src/libdebug/debug.h
index 2061ce2..ff8cba7 100644
--- a/src/libdebug/debug.h
+++ b/src/libdebug/debug.h
@@ -20,6 +20,7 @@
#define DFRAME 13
#define DCALL 14
#define DMNCC 15
+#define DCC 15
#define DDB 16
#define DTRANS 17
#define DDMS 18
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 */