commit 22dab51ab177a91451c9ac2f2d122da2bddc3188 Author: Andreas Eversberg Date: Sat Jan 25 08:50:20 2020 +0100 Initial GIT import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce92abd --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +Makefile +Makefile.in +.deps +.libs +*.o +*.lo +*.la +*.pc +aclocal.m4 +acinclude.m4 +aminclude.am +m4/*.m4 +autom4te.cache +compile +config.h* +config.sub +config.log +config.status +config.guess +configure +depcomp +missing +ltmain.sh +install-sh +stamp-h1 +libtool + +.tarball-version +.version +.dirstamp + +Doxyfile + +.*.sw? + +src/isdn/osmo-cc-misdn-endpoint diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..689d568 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,5 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..c0038b7 --- /dev/null +++ b/configure.ac @@ -0,0 +1,93 @@ +AC_INIT([osmo-cc-misdn-endpoint], + m4_esyscmd([./git-version-gen .tarball-version]), + [jolly@eversberg.eu]) + +dnl *This* is the root dir, even if an install-sh exists in ../ or ../../ +AC_CONFIG_AUX_DIR([.]) + +AM_INIT_AUTOMAKE([subdir-objects dist-bzip2]) +AC_CONFIG_TESTDIR(tests) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +dnl include release helper +RELMAKE='-include osmo-release.mk' +AC_SUBST([RELMAKE]) + +dnl checks for programs +AC_PROG_MAKE_SET +AC_PROG_CC +AC_PROG_INSTALL +LT_INIT +AC_PROG_LIBTOOL + +AC_CONFIG_MACRO_DIR([m4]) + +AC_ARG_ENABLE(sanitize, + [AS_HELP_STRING( + [--enable-sanitize], + [Compile with address sanitizer enabled], + )], + [sanitize=$enableval], [sanitize="no"]) +if test x"$sanitize" = x"yes" +then + CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined" + CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined" +fi + +AC_ARG_ENABLE(werror, + [AS_HELP_STRING( + [--enable-werror], + [Turn all compiler warnings into errors, with exceptions: + a) deprecation (allow upstream to mark deprecation without breaking builds); + b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds) + ] + )], + [werror=$enableval], [werror="no"]) +if test x"$werror" = x"yes" +then + WERROR_FLAGS="-Werror" + WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations" + WERROR_FLAGS+=" -Wno-error=cpp" # "#warning" + CFLAGS="$CFLAGS $WERROR_FLAGS" + CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" +fi + +CFLAGS="$CFLAGS -Wall" +CPPFLAGS="$CPPFLAGS -Wall" + +dnl checks for header files +AC_HEADER_STDC +AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h) + +# The following test is taken from WebKit's webkit.m4 +saved_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -fvisibility=hidden " +AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])], + [ AC_MSG_RESULT([yes]) + SYMBOL_VISIBILITY="-fvisibility=hidden"], + AC_MSG_RESULT([no])) +CFLAGS="$saved_CFLAGS" +AC_SUBST(SYMBOL_VISIBILITY) + +dnl Generate the output +AM_CONFIG_HEADER(config.h) + +AC_CHECK_HEADERS([mISDN/mbuffer.h], , [AC_MSG_FAILURE(Missing mISDN user library. Please install mISDNuser!)]) + +AC_CHECK_LIB([m], [main]) +AC_CHECK_LIB([pthread], [main]) + +AC_OUTPUT( + src/liboptions/Makefile + src/libdebug/Makefile + src/libsample/Makefile + src/libtimer/Makefile + src/libjitter/Makefile + src/libosmocc/Makefile + src/libg711/Makefile + src/isdn/Makefile + src/Makefile + Makefile) diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /dev/null +++ b/git-version-gen @@ -0,0 +1,151 @@ +#!/bin/sh +# Print a version string. +scriptversion=2010-01-28.01 + +# Copyright (C) 2007-2010 Free Software Foundation, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/. +# It may be run two ways: +# - from a git repository in which the "git describe" command below +# produces useful output (thus requiring at least one signed tag) +# - from a non-git-repo directory containing a .tarball-version file, which +# presumes this script is invoked like "./git-version-gen .tarball-version". + +# In order to use intra-version strings in your project, you will need two +# separate generated version string files: +# +# .tarball-version - present only in a distribution tarball, and not in +# a checked-out repository. Created with contents that were learned at +# the last time autoconf was run, and used by git-version-gen. Must not +# be present in either $(srcdir) or $(builddir) for git-version-gen to +# give accurate answers during normal development with a checked out tree, +# but must be present in a tarball when there is no version control system. +# Therefore, it cannot be used in any dependencies. GNUmakefile has +# hooks to force a reconfigure at distribution time to get the value +# correct, without penalizing normal development with extra reconfigures. +# +# .version - present in a checked-out repository and in a distribution +# tarball. Usable in dependencies, particularly for files that don't +# want to depend on config.h but do want to track version changes. +# Delete this file prior to any autoconf run where you want to rebuild +# files to pick up a version string change; and leave it stale to +# minimize rebuild time after unrelated changes to configure sources. +# +# It is probably wise to add these two files to .gitignore, so that you +# don't accidentally commit either generated file. +# +# Use the following line in your configure.ac, so that $(VERSION) will +# automatically be up-to-date each time configure is run (and note that +# since configure.ac no longer includes a version string, Makefile rules +# should not depend on configure.ac for version updates). +# +# AC_INIT([GNU project], +# m4_esyscmd([build-aux/git-version-gen .tarball-version]), +# [bug-project@example]) +# +# Then use the following lines in your Makefile.am, so that .version +# will be present for dependencies, and so that .tarball-version will +# exist in distribution tarballs. +# +# BUILT_SOURCES = $(top_srcdir)/.version +# $(top_srcdir)/.version: +# echo $(VERSION) > $@-t && mv $@-t $@ +# dist-hook: +# echo $(VERSION) > $(distdir)/.tarball-version + +case $# in + 1) ;; + *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;; +esac + +tarball_version_file=$1 +nl=' +' + +# First see if there is a tarball-only version file. +# then try "git describe", then default. +if test -f $tarball_version_file +then + v=`cat $tarball_version_file` || exit 1 + case $v in + *$nl*) v= ;; # reject multi-line output + [0-9]*) ;; + *) v= ;; + esac + test -z "$v" \ + && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2 +fi + +if test -n "$v" +then + : # use $v +elif + v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \ + || git describe --abbrev=4 HEAD 2>/dev/null` \ + && case $v in + [0-9]*) ;; + v[0-9]*) ;; + *) (exit 1) ;; + esac +then + # Is this a new git that lists number of commits since the last + # tag or the previous older version that did not? + # Newer: v6.10-77-g0f8faeb + # Older: v6.10-g0f8faeb + case $v in + *-*-*) : git describe is okay three part flavor ;; + *-*) + : git describe is older two part flavor + # Recreate the number of commits and rewrite such that the + # result is the same as if we were using the newer version + # of git describe. + vtag=`echo "$v" | sed 's/-.*//'` + numcommits=`git rev-list "$vtag"..HEAD | wc -l` + v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`; + ;; + esac + + # Change the first '-' to a '.', so version-comparing tools work properly. + # Remove the "g" in git describe's output string, to save a byte. + v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`; +else + v=UNKNOWN +fi + +v=`echo "$v" |sed 's/^v//'` + +# Don't declare a version "dirty" merely because a time stamp has changed. +git status > /dev/null 2>&1 + +dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty= +case "$dirty" in + '') ;; + *) # Append the suffix only if there isn't one already. + case $v in + *-dirty) ;; + *) v="$v-dirty" ;; + esac ;; +esac + +# Omit the trailing newline, so that m4_esyscmd can use the result directly. +echo "$v" | tr -d '\012' + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-end: "$" +# End: diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..c2cd6e4 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,12 @@ +AUTOMAKE_OPTIONS = foreign + +SUBDIRS = \ + liboptions \ + libdebug \ + libsample \ + libtimer \ + libjitter \ + libosmocc \ + libg711 \ + isdn + diff --git a/src/isdn/Makefile.am b/src/isdn/Makefile.am new file mode 100644 index 0000000..d319da0 --- /dev/null +++ b/src/isdn/Makefile.am @@ -0,0 +1,22 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +bin_PROGRAMS = \ + osmo-cc-misdn-endpoint + +osmo_cc_misdn_endpoint_SOURCES = \ + ie.c \ + dss1.c \ + isdn.c \ + main.c + +osmo_cc_misdn_endpoint_LDADD = \ + $(COMMON_LA) \ + ../libdebug/libdebug.a \ + ../liboptions/liboptions.a \ + ../libsample/libsample.a \ + ../libtimer/libtimer.a \ + ../libjitter/libjitter.a \ + ../libosmocc/libosmocc.a \ + ../libg711/libg711.a \ + -lmisdn + diff --git a/src/isdn/dss1.c b/src/isdn/dss1.c new file mode 100644 index 0000000..0e6ffb1 --- /dev/null +++ b/src/isdn/dss1.c @@ -0,0 +1,2413 @@ +#warning warum kommt kein ton, wenn ich bei watson ins timeout komme?: +/* layer3 handling + * + * (C) 2020 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libg711/g711.h" +#include "isdn.h" +#include "dss1.h" +#include "ie.h" +#ifndef u_char +#define u_char unsigned char +#endif +#include +#include +#include + +static struct osmo_cc_helper_audio_codecs codecs_alaw[] = { + { "PCMA", 8000, 1, g711_transcode_flipped, g711_transcode_flipped }, + { "PCMU", 8000, 1, g711_transcode_alaw_flipped_to_ulaw, g711_transcode_ulaw_to_alaw_flipped }, + { NULL, 0, 0, NULL, NULL}, +}; + +static struct osmo_cc_helper_audio_codecs codecs_ulaw[] = { + { "PCMU", 8000, 1, g711_transcode_flipped, g711_transcode_flipped }, + { "PCMA", 8000, 1, g711_transcode_ulaw_flipped_to_alaw, g711_transcode_alaw_to_ulaw_flipped }, + { NULL, 0, 0, NULL, NULL}, +}; + +static struct l3_msg *create_l3msg(void) +{ + struct l3_msg *l3m; + + l3m = alloc_l3_msg(); + if (!l3m) { + PDEBUG(DDSS1, DEBUG_ERROR, "No MEM!\n"); + abort(); + } + return l3m; +} + +static void free_l3msg(struct l3_msg *l3m) +{ + free_l3_msg(l3m); +} + +static const char *state_names[] = { + "IDLE", + "IN-SETUP", + "OUT-SETUP", + "IN-OVERLAP", + "OUT-OVERLAP", + "IN-PROCEEDING", + "OUT-PROCEEDING", + "IN-ALERTING", + "OUT-ALERTING", + "IN-CONNECTING", + "OUT-CONNECTING", + "CONNECT", + "IN-DISCONNECT", + "OUT-DISCONNECT", + "OUT-RELEASE", + "SUSPENDED", +}; + +static void new_state(call_t *call, enum isdn_state state) +{ + if (call->state == state) + return; + PDEBUG(DDSS1, DEBUG_DEBUG, "Changing state %s -> %s\n", state_names[call->state], state_names[state]); + call->state = state; +} + +static void release_and_destroy(call_t *call, uint8_t cc_isdn_cause, uint16_t cc_sip_cause, uint8_t isdn_cause) +{ + osmo_cc_msg_t *msg; + struct l3_msg *l3m; + + if (cc_isdn_cause || cc_sip_cause) { + /* create osmo-cc message */ + if (call->state == ISDN_STATE_OUT_SETUP) + msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); + else + msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); + + /* cause */ + osmo_cc_add_ie_cause(msg, call->isdn_ep->serving_location, cc_isdn_cause, cc_sip_cause, 0); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + } + + if (isdn_cause) { + PDEBUG(DDSS1, DEBUG_INFO, "REJECT REQUEST (pid = 0x%x callref = %d)\n", call->l3_pid, call->cc_callref); + + /* creating release complete */ + l3m = create_l3msg(); + + /* cause */ + enc_ie_cause(l3m, call->isdn_ep->serving_location, isdn_cause); + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_RELEASE_COMPLETE, call->l3_pid, l3m); + } + + /* call terminated */ + new_state(call, ISDN_STATE_IDLE); + call_destroy(call); +} + +static void split_3pty(call_t *call) +{ + call_t *other; + osmo_cc_msg_t *msg; + + /* call must be a conference call */ + if (!call->conference_3pty) + return; + + /* search for other 3pty party on same terminal */ + other = call->isdn_ep->call_list; + while (other) { + if (other != call + && other->l3_ces == call->l3_ces + && other->conference_3pty) + break; + other = other->next; + } + + /* remove conference state */ + if (other) { + other->conference_3pty = 0; + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + /* notify the facility */ + osmo_cc_add_ie_notify(msg, OSMO_CC_NOTIFY_CONFERENCE_DISCONNECTED); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&other->isdn_ep->cc_ep, call->cc_callref, msg); + } +} + +/* + * handles all indications from ISDN stack + */ + +/* CC-SETUP INDICATION */ +void setup_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t type, plan, present, screen, reason; + int has_mode, has_multi, has_user, has_present, has_reason; + uint8_t coding, capability, mode, rate, multi, user; + int exclusive, channel; + int sending_complete; + char callerid[33]; + char called[33]; + char keypad[33]; + char redir[33]; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "SETUP INDICATION (pid = 0x%x)\n", pid); + + /* assing pid */ + PDEBUG(DDSS1, DEBUG_DEBUG, " -> new L3ID assigned (l3id = 0x%x)\n", pid); + call->l3_pid = pid; + call->l3_ces = pid >> 16; + + msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_IND); + + /* newtwork + interface */ + osmo_cc_add_ie_calling_network(msg, OSMO_CC_NETWORK_ISDN_NONE, ""); + osmo_cc_add_ie_calling_interface(msg, call->isdn_ep->portname); + + /* caller info */ + rc = dec_ie_calling_pn(l3m, 0, &type, &plan, &has_present, &present, &screen, callerid, sizeof(callerid)); + if (rc >= 0) { + if (!has_present) + present = screen = 0; + /* check MSN */ + struct msn_list *m = call->isdn_ep->msn_list; + if (m) { + /* we have an MSN list */ + while (m) { + if (!strcmp(callerid, m->msn)) + break; + } + /* not found, so we use first MSN */ + if (!m) { + strncpy(callerid, call->isdn_ep->msn_list->msn, sizeof(callerid) - 1); + callerid[sizeof(callerid) - 1] = '\0'; + } + } + osmo_cc_add_ie_calling(msg, type, plan, present, screen, callerid); + /* secondary caller info */ + rc = dec_ie_calling_pn(l3m, 1, &type, &plan, &has_present, &present, &screen, callerid, sizeof(callerid)); + if (rc >= 0) { + if (!has_present) + present = screen = 0; + osmo_cc_add_ie_calling(msg, type, plan, present, screen, callerid); + } + } else { + /* no caller ID, use MSN, if exists */ + if (call->isdn_ep->msn_list) { + strncpy(callerid, call->isdn_ep->msn_list->msn, sizeof(callerid) - 1); + callerid[sizeof(callerid) - 1] = '\0'; + type = OSMO_CC_TYPE_UNKNOWN; + plan = OSMO_CC_PLAN_TELEPHONY; + present = OSMO_CC_PRESENT_ALLOWED; + screen = OSMO_CC_SCREEN_NETWORK; + osmo_cc_add_ie_calling(msg, type, plan, present, screen, callerid); + } + } + + /* dialing information */ + rc = dec_ie_called_pn(l3m, &type, &plan, called, sizeof(called)); + if (rc >= 0) { + osmo_cc_add_ie_called(msg, type, plan, called); + if (called[0]) + call->any_dialing = 1; + } + + /* keypad */ + rc = dec_ie_keypad(l3m, keypad, sizeof(keypad)); + if (rc >= 0) + osmo_cc_add_ie_keypad(msg, keypad); + + /* redirecting number */ + rc = dec_ie_redirecting(l3m, &type, &plan, &has_present, &present, &screen, &has_reason, &reason, redir, sizeof(redir)); + if (rc >= 0) { + if (!has_present) + present = screen = 0; + if (!has_reason) + reason = 0; + osmo_cc_add_ie_redir(msg, type, plan, present, screen, reason, redir); + } + + /* bearer capability FIXME: clearmode */ + rc = dec_ie_bearer(l3m, &coding, &capability, &has_mode, &mode, &rate, &has_multi, &multi, &has_user, &user); + if (rc >= 0) { + if (!has_mode) + mode = 0; + osmo_cc_add_ie_bearer(msg, coding, capability, mode); + /* set bchannel mode */ + if (capability==OSMO_CC_CAPABILITY_DATA + || capability==OSMO_CC_CAPABILITY_DATA_RESTRICTED + || capability==OSMO_CC_CAPABILITY_VIDEO) + call->b_mode = B_MODE_HDLC; + } + + /* sdp offer */ + call->cc_session = osmo_cc_helper_audio_offer(call, (call->isdn_ep->law == 'a') ? codecs_alaw : codecs_ulaw, bchannel_send, msg, 1); + + /* dialing complete */ + dec_ie_complete(l3m, &sending_complete); + if (sending_complete) { + call->sending_complete = 1; + osmo_cc_add_ie_complete(msg); + } + + /* display */ + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* hunt channel */ + dec_ie_channel_id(l3m, call->isdn_ep->pri, &exclusive, &channel); + rc = channel = hunt_bchannel_in(call->isdn_ep, channel, exclusive); + if (rc < 0) { + no_channel: + osmo_cc_free_msg(msg); + /* send MT_RELEASE_COMPLETE to "REJECT" the channel */ + PDEBUG(DDSS1, DEBUG_INFO, "RELEASE-COMPLETE REQUEST (pid = 0x%x)\n", pid); + l3m = create_l3msg(); + enc_ie_cause(l3m, call->isdn_ep->serving_location, -rc); + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_RELEASE_COMPLETE, call->l3_pid, l3m); + new_state(call, ISDN_STATE_IDLE); + call_destroy(call); + return; + } + + /* open channel */ + rc = open_bchannel_in(call, channel, 1); + if (rc < 0) + goto no_channel; + + /* create endpoint */ + osmo_cc_call_t *cc_call = osmo_cc_call_new(&call->isdn_ep->cc_ep); + call->cc_callref = cc_call->callref; + PDEBUG(DDSS1, DEBUG_DEBUG, " -> new callref assigned (callref = %d)\n", call->cc_callref); + + new_state(call, ISDN_STATE_IN_SETUP); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* CC-SETUP-ACKNOWLEDGE INDICATION */ +void setup_ack_ind(call_t *call, uint32_t cmd, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + int exclusive, channel; + uint8_t coding, location, progress; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "SETUP-ACKNOWLEDGE INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_ACK_IND); + + /* progress indicator */ + rc = dec_ie_progress(l3m, &coding, &location, &progress); + if (rc >= 0) + osmo_cc_add_ie_progress(msg, coding, location, progress); + else { + coding = OSMO_CC_CODING_ITU_T; + progress = 0; + } + + /* display */ + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* complete channel negotiation */ + rc = dec_ie_channel_id(l3m, call->isdn_ep->pri, &exclusive, &channel); + rc = open_bchannel_out(call, cmd, (rc < 0) ? -1 : channel, (rc < 0) ? -1 : exclusive); + if (rc < 0) { + PDEBUG(DDSS1, DEBUG_NOTICE, "Channel negotiation failed.\n"); + osmo_cc_free_msg(msg); + release_and_destroy(call, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL, 0, -rc); + return; + } + + /* send SDP answer */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8) && !call->codec_negotiated) { + call->codec_negotiated = 1; + if (call->sdp) + osmo_cc_add_ie_sdp(msg, call->sdp); + } + + /* the audio path is throughconnected */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + call->audio_path = 1; + + new_state(call, ISDN_STATE_OUT_OVERLAP); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* CC-PROCEEDING INDICATION */ +void proc_ind(call_t *call, uint32_t cmd, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + int exclusive, channel; + uint8_t coding, location, progress; + uint8_t notify, type, plan, present; + int has_present; + char redir[32]; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "PREOCEEDING INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_PROC_IND); + + /* progress indicator */ + rc = dec_ie_progress(l3m, &coding, &location, &progress); + if (rc >= 0) + osmo_cc_add_ie_progress(msg, coding, location, progress); + else { + coding = OSMO_CC_CODING_ITU_T; + progress = 0; + } + + /* display */ + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* notify */ + rc = dec_ie_notify(l3m, ¬ify); + if (rc >= 0) + osmo_cc_add_ie_notify(msg, notify); + + /* redirection info */ + rc = dec_ie_redirection(l3m, &type, &plan, &has_present, &present, redir, sizeof(redir)); + if (rc >= 0) { + if (!has_present) + present = 0; + osmo_cc_add_ie_redir(msg, type, plan, present, 0, 0, redir); + } + + /* complete channel negotiation */ + rc = dec_ie_channel_id(l3m, call->isdn_ep->pri, &exclusive, &channel); + rc = open_bchannel_out(call, cmd, (rc < 0) ? -1 : channel, (rc < 0) ? -1 : exclusive); + if (rc < 0) { + PDEBUG(DDSS1, DEBUG_NOTICE, "Channel negotiation failed.\n"); + osmo_cc_free_msg(msg); + release_and_destroy(call, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL, 0, -rc); + return; + } + + /* send SDP answer */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8) && !call->codec_negotiated) { + call->codec_negotiated = 1; + if (call->sdp) + osmo_cc_add_ie_sdp(msg, call->sdp); + } + + /* the audio path is throughconnected */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + call->audio_path = 1; + + new_state(call, ISDN_STATE_OUT_PROCEEDING); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* CC-ALERTING INDICATION */ +void alert_ind(call_t *call, uint32_t cmd, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + int exclusive, channel; + uint8_t coding, location, progress; + uint8_t notify, type, plan, present; + int has_present; + char redir[32]; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "ALERTING INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_ALERT_IND); + + /* progress indicator */ + rc = dec_ie_progress(l3m, &coding, &location, &progress); + if (rc >= 0) + osmo_cc_add_ie_progress(msg, coding, location, progress); + else { + coding = OSMO_CC_CODING_ITU_T; + progress = 0; + } + + /* display */ + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* notify */ + rc = dec_ie_notify(l3m, ¬ify); + if (rc >= 0) + osmo_cc_add_ie_notify(msg, notify); + + /* redir info */ + rc = dec_ie_redirection(l3m, &type, &plan, &has_present, &present, redir, sizeof(redir)); + if (rc >= 0) { + if (!has_present) + present = 0; + osmo_cc_add_ie_redir(msg, type, plan, present, 0, 0, redir); + } + + /* complete channel negotiation */ + rc = dec_ie_channel_id(l3m, call->isdn_ep->pri, &exclusive, &channel); + rc = open_bchannel_out(call, cmd, (rc < 0) ? -1 : channel, (rc < 0) ? -1 : exclusive); + if (rc < 0) { + PDEBUG(DDSS1, DEBUG_NOTICE, "Channel negotiation failed.\n"); + osmo_cc_free_msg(msg); + release_and_destroy(call, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL, 0, -rc); + return; + } + + /* send SDP answer */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8) && !call->codec_negotiated) { + call->codec_negotiated = 1; + if (call->sdp) + osmo_cc_add_ie_sdp(msg, call->sdp); + } + + /* the audio path is throughconnected */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + call->audio_path = 1; + + new_state(call, ISDN_STATE_OUT_ALERTING); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* CC-CONNECT INDICATION */ +void setup_cnf(call_t *call, uint32_t cmd, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + int exclusive, channel; + uint8_t coding, location, progress; + uint8_t type, plan, present, screen; + int has_present; + char connected[33]; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "CONNECT INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_CNF); + + /* progress indicator */ + rc = dec_ie_progress(l3m, &coding, &location, &progress); + if (rc >= 0) + osmo_cc_add_ie_progress(msg, coding, location, progress); + else { + coding = OSMO_CC_CODING_ITU_T; + progress = 0; + } + + /* display */ + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* connected info */ + rc = dec_ie_connected_pn(l3m, &type, &plan, &has_present, &present, &screen, connected, sizeof(connected)); + if (rc >= 0) { + if (!has_present) + present = screen = 0; + osmo_cc_add_ie_calling(msg, type, plan, present, screen, connected); + /* secondary connected info */ + rc = dec_ie_connected_pn(l3m, &type, &plan, &has_present, &present, &screen, connected, sizeof(connected)); + if (rc >= 0) { + if (!has_present) + present = screen = 0; + osmo_cc_add_ie_calling(msg, type, plan, present, screen, connected); + } + } + + /* if we have no channel (answer call with no channel) we use this flag to assign later */ + rc = dec_ie_channel_id(l3m, call->isdn_ep->pri, &exclusive, &channel); + if (rc < 0) { + channel = -1; + exclusive = -1; + } + if (channel < 0) + call->setup_comp_req_channel_assignment = 1; + /* complete channel negotiation */ + rc = open_bchannel_out(call, cmd, channel, exclusive); /* channel and exclusive may be -1 */ + if (rc < 0) { + PDEBUG(DDSS1, DEBUG_NOTICE, "Channel negotiation failed.\n"); + osmo_cc_free_msg(msg); + release_and_destroy(call, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL, 0, -rc); + return; + } + + /* send SDP answer */ + if (!call->codec_negotiated) { + call->codec_negotiated = 1; + if (call->sdp) + osmo_cc_add_ie_sdp(msg, call->sdp); + } + + /* the audio path is throughconnected */ + call->audio_path = 1; + + new_state(call, ISDN_STATE_OUT_CONNECTING); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* CC-CONNECT-ACKNOWLEDGE INDICATION */ +void setup_comp_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "CONNECT-ACKNOWLEDGE INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* only send in TE mode, because we automatically reply in NT mode */ + if (call->isdn_ep->ntmode) + return; + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_IND); + + /* display */ + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + new_state(call, ISDN_STATE_CONNECT); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* CC-INFORMATION INDICATION */ +void info_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t type, plan; + int sending_complete; + char called[33]; + char keypad[33]; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "INFO INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_INFO_IND); + + /* dialing information */ + rc = dec_ie_called_pn(l3m, &type, &plan, called, sizeof(called)); + if (rc >= 0) { + osmo_cc_add_ie_called(msg, type, plan, called); + if (called[0]) + call->any_dialing = 1; + } + + /* keypad */ + rc = dec_ie_keypad(l3m, keypad, sizeof(keypad)); + if (rc >= 0) + osmo_cc_add_ie_keypad(msg, keypad); + + /* display */ + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* dialing complete */ + dec_ie_complete(l3m, &sending_complete); + if (sending_complete) { + call->sending_complete = 1; + osmo_cc_add_ie_complete(msg); + } + + /* reset overlap timeout */ // FIXME: is this still required? + new_state(call, call->state); + + /* stop tone, if something has been dialled */ + if (call->send_local_tones && call->any_dialing && call->state == ISDN_STATE_IN_OVERLAP) { + PDEBUG(DDSS1, DEBUG_DEBUG, "Stop sending locally generated dial tone. (if not already)\n"); + bchannel_tone(call, 0); + } + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* CC-DISCONNECT INDICATION */ +void disconnect_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t location, cause; + uint8_t coding, proglocation, progress; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "DISCONNECT INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_DISC_IND); + + /* progress indicator */ + rc = dec_ie_progress(l3m, &coding, &proglocation, &progress); + if (rc >= 0) + osmo_cc_add_ie_progress(msg, coding, proglocation, progress); + else { + coding = OSMO_CC_CODING_ITU_T; + progress = 0; + } + + /* cause */ + rc = dec_ie_cause(l3m, &location, &cause); + if (rc < 0) { + cause = 0; + location = call->isdn_ep->serving_location; + } + osmo_cc_add_ie_cause(msg, location, cause, 0, 0); + + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* send SDP answer */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8) && !call->codec_negotiated) { + call->codec_negotiated = 1; + if (call->sdp) + osmo_cc_add_ie_sdp(msg, call->sdp); + } + + /* the audio path is throughconnected */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + call->audio_path = 1; + else + call->audio_path = 0; + + new_state(call, ISDN_STATE_IN_DISCONNECT); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* CC-DISCONNECT INDICATION of child instance */ +void disconnect_ind_i(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + uint8_t location, cause; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "DISCONNECT INDICATION of child (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* collect cause */ + rc = dec_ie_cause(l3m, &location, &cause); + if (rc >= 0) { + call->collect_cause = osmo_cc_collect_cause(call->collect_cause, cause); + call->collect_location = location; + } +} + +/* CC-RELEASE INDICATION */ +void release_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t location, cause; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "RELEASE INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); + + /* cause */ + rc = dec_ie_cause(l3m, &location, &cause); + if (rc < 0) { + cause = 0; + location = call->isdn_ep->serving_location; + } + osmo_cc_add_ie_cause(msg, location, cause, 0, 0); + + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + + /* call terminated */ + new_state(call, ISDN_STATE_IDLE); + split_3pty(call); + call_destroy(call); +} + +/* CC-RELEASE-COMPLETE INDICATION (a reject) */ +void release_complete_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t location, cause; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "RELEASE-COMPLETE INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + switch (call->state) { + case ISDN_STATE_OUT_SETUP: + msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); + break; + case ISDN_STATE_OUT_RELEASE: + msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_CNF); + break; + default: + msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); + } + + /* cause */ + /* in case layer 2 is down during setup, we send cause 27 */ + if (call->state == ISDN_STATE_OUT_SETUP && call->isdn_ep->l1link == 0) { + cause = 27; + location = call->isdn_ep->serving_location; + } else { + rc = dec_ie_cause(l3m, &location, &cause); + if (rc < 0) { + cause = 0; + location = call->isdn_ep->serving_location; + } + } + osmo_cc_add_ie_cause(msg, location, cause, 0, 0); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + + /* call terminated */ + new_state(call, ISDN_STATE_IDLE); + split_3pty(call); + call_destroy(call); +} + +/* CC-RESTART INDICATION */ +void restart_ind(uint32_t pid) +{ + PDEBUG(DDSS1, DEBUG_INFO, "RESTART INDICATION (pid = 0x%x)\n", pid); + + // L3 process is not touched. (not even by network stack) +} + +/* T312 timeout */ +void t312_timeout_ind(void) +{ + // not required, release is performed with MT_FREE +} + +/* CC-NOTIFY INDICATION */ +void notify_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t notify, type, plan, present; + int has_present; + char redir[33]; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "NOTIFY INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + + /* notify */ + rc = dec_ie_notify(l3m, ¬ify); + if (rc >= 0) + osmo_cc_add_ie_notify(msg, notify); + else { + osmo_cc_free_msg(msg); + return; + } + + /* redirection number */ + rc = dec_ie_redirection(l3m, &type, &plan, &has_present, &present, redir, sizeof(redir)); + if (rc >= 0) { + if (!has_present) + present = 0; + osmo_cc_add_ie_redir(msg, type, plan, present, 0, 0, redir); + } + + /* display */ + rc = dec_ie_display(l3m, display, sizeof(display)); + if (rc >= 0) + osmo_cc_add_ie_display(msg, display); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + + +/* CC-HOLD INDICATION */ +void hold_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + + PDEBUG(DDSS1, DEBUG_INFO, "HOLD INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* reject, if we are on hold */ + if (call->hold) { + PDEBUG(DDSS1, DEBUG_INFO, "HOLD-REJECT REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + l3m = create_l3msg(); + enc_ie_cause(l3m, call->isdn_ep->serving_location, call->hold?101:31); /* normal unspecified / incompatible state */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_HOLD_REJECT, call->l3_pid, l3m); + return; + } + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + + /* notify the hold of call */ + osmo_cc_add_ie_notify(msg, OSMO_CC_NOTIFY_REMOTE_HOLD); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + + /* deactivate bchannel */ + drop_bchannel(call); + + /* set hold state */ + call->hold = 1; +#warning use clock to send empty packets/holdmusic + + /* acknowledge hold */ + PDEBUG(DDSS1, DEBUG_INFO, "HOLD-ACKNOWLEDGE REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + l3m = create_l3msg(); + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_HOLD_ACKNOWLEDGE, call->l3_pid, l3m); +} + + +/* CC-RETRIEVE INDICATION */ +void retrieve_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + int channel, exclusive; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "RETRIEVE INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + if (!call->hold) { + rc = -101; /* incompatible state */ + no_channel: + /* reject retrieve */ + PDEBUG(DDSS1, DEBUG_INFO, "RETRIEVE-REJECT REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + l3m = create_l3msg(); + enc_ie_cause(l3m, call->isdn_ep->serving_location, rc); + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_RETRIEVE_REJECT, call->l3_pid, l3m); + return; + } + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + + /* notify the retrieve of call */ + osmo_cc_add_ie_notify(msg, OSMO_CC_NOTIFY_REMOTE_RETRIEVAL); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + + /* hunt channel */ + dec_ie_channel_id(l3m, call->isdn_ep->pri, &exclusive, &channel); + rc = channel = hunt_bchannel_in(call->isdn_ep, channel, exclusive); + if (rc < 0) + goto no_channel; + + /* open channel */ + rc = open_bchannel_in(call, channel, 1); + if (rc < 0) + goto no_channel; + + /* set hold state */ + call->hold = 0; + + /* acknowledge retrieve */ + PDEBUG(DDSS1, DEBUG_INFO, "RETRIEVE-ACKNOWLEDGE REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + l3m = create_l3msg(); + enc_ie_channel_id(l3m, call->isdn_ep->pri, 1, call->b_channel); + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_RETRIEVE_ACKNOWLEDGE, call->l3_pid, l3m); +} + +/* CC-SUSPEND INDICATION */ +void suspend_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t callid[8]; + int len; + int rc = -31; /* normal, unspecified */ + call_t *check; + + PDEBUG(DDSS1, DEBUG_INFO, "SUSPEND INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + if (0) { + reject: + PDEBUG(DDSS1, DEBUG_INFO, "SUSPEND-REJECT REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + l3m = create_l3msg(); + enc_ie_cause(l3m, call->isdn_ep->serving_location, -rc); + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_SUSPEND_REJECT, call->l3_pid, l3m); + return; + } + + /* call id */ + rc = dec_ie_call_id(l3m, callid, &len); + if (rc < 0) + len = 0; + + /* check if call id is in use */ + check = call->isdn_ep->call_list; + while (check) { + if (check->state == ISDN_STATE_SUSPENDED + && check->park_len == len + && !memcmp(check->park_callid, callid, len)) { + PDEBUG(DDSS1, DEBUG_INFO, "Given Park ID is already in use, rejecting!\n"); + rc = -84; /* call id in use */ + goto reject; + } + check = check->next; + } + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + + /* notify the suspension of call */ + osmo_cc_add_ie_notify(msg, OSMO_CC_NOTIFY_USER_SUSPENDED); + + new_state(call, ISDN_STATE_SUSPENDED); +#warning use clock to send empty packets/holdmusic + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + + /* deactivate bchannel */ + drop_bchannel(call); + + /* sending SUSPEND_ACKNOWLEDGE */ + PDEBUG(DDSS1, DEBUG_INFO, "SUSPEND-ACKNOWLEDGE REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + l3m = create_l3msg(); + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_SUSPEND_ACKNOWLEDGE, call->l3_pid, l3m); +} + +/* CC-RESUME INDICATION */ +void resume_ind(isdn_t *isdn_ep, uint32_t pid, struct l3_msg *l3m) +{ + call_t *call; + osmo_cc_msg_t *msg; + uint8_t callid[8]; + int len; + int channel, exclusive; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "RESUME INDICATION (pid = 0x%x)\n", pid); + + /* call id */ + rc = dec_ie_call_id(l3m, callid, &len); + if (rc < 0) + len = 0; + + /* check if call id is in use */ + call = isdn_ep->call_list; + while (call) { + if (call->state == ISDN_STATE_SUSPENDED + && call->park_len == len + && !memcmp(call->park_callid, callid, len)) + break; + call = call->next; + } + + /* process given callref */ + if (!call) { + rc = -85; + no_channel: + PDEBUG(DDSS1, DEBUG_INFO, "RESUME-REJECT REQUEST (pid = 0x%x)\n", pid); + l3m = create_l3msg(); + enc_ie_cause(l3m, call->isdn_ep->serving_location, -rc); + isdn_ep->ml3->to_layer3(isdn_ep->ml3, MT_RESUME_REJECT, pid, l3m); + return; + } + + /* assing pid */ + PDEBUG(DDSS1, DEBUG_DEBUG, "new L3ID assigned (l3id = 0x%x callref = %d)\n", pid, call->cc_callref); + call->l3_pid = pid; + call->l3_ces = pid >> 16; + + /* channel_id (no channel is possible in message) */ + exclusive = 0; + channel = -1; /* any channel */ + + /* hunt channel */ + rc = channel = hunt_bchannel_in(call->isdn_ep, channel, exclusive); + if (rc < 0) + goto no_channel; + + /* open channel */ + rc = open_bchannel_in(call, channel, 1); + if (rc < 0) + goto no_channel; + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + + /* notify the resume of call */ + osmo_cc_add_ie_notify(msg, OSMO_CC_NOTIFY_USER_RESUMED); + + new_state(call, ISDN_STATE_CONNECT); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + + /* sending RESUME_ACKNOWLEDGE */ + PDEBUG(DDSS1, DEBUG_INFO, "RESUME-ACKNOWLEDGE REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + l3m = create_l3msg(); + enc_ie_channel_id(l3m, call->isdn_ep->pri, 1, call->b_channel); + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_RESUME_ACKNOWLEDGE, call->l3_pid, l3m); +} + +/* CC-FACILITY INDICATION */ +void facility_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t fac_ie[256]; + struct asn1_parm fac; + int fac_len; + uint8_t invokeid = 0; + int set_3pty = -1; + uint8_t notify = 0; + call_t *other; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "FACILITY INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* facility */ + rc = dec_ie_facility(l3m, fac_ie + 1, &fac_len); + if (rc < 0) + return; + fac_ie[0] = fac_len; + if (fac_len <= 0) + return; + + decodeFac(fac_ie, &fac); + switch (fac.comp) { + case CompInvoke: + switch(fac.u.inv.operationValue) { + case Fac_Begin3PTY: + notify = OSMO_CC_NOTIFY_CONFERENCE_ESTABLISHED; + invokeid = fac.u.inv.invokeId; + set_3pty = 1; + jitter_clear(&call->dejitter); + break; + + case Fac_End3PTY: + notify = OSMO_CC_NOTIFY_CONFERENCE_DISCONNECTED; + invokeid = fac.u.inv.invokeId; + set_3pty = 0; + break; + default: + ; + } + break; + default: + ; + } + + /* find other terminal on hold */ + other = call->isdn_ep->call_list; + while (other) { +// printf("check: call=%p other=%p call_ces=%x other_ces=%x other_hold=%d\n", call, other, call->l3_ces, other->l3_ces, other->hold); + if (other != call + && other->l3_ces == call->l3_ces) { + /* if we got facility on active call */ + if (other->hold && !call->hold) + break; + /* if we got facility on active call */ + if (!other->hold && call->hold) + break; + } + other = other->next; + } + if (set_3pty >= 0 && other) { + other->conference_3pty = call->conference_3pty = set_3pty; + jitter_clear(&other->dejitter); + } else { + PDEBUG(DDSS1, DEBUG_NOTICE, "Phone requests conference, but no call on hold!\n"); + notify = 0; + } + + /* encode 3PTY facility */ + memset(&fac, 0, sizeof(fac)); + fac.Valid = 1; + if (notify) { + fac.comp = CompReturnResult; + fac.u.retResult.invokeId = invokeid; + fac.u.retResult.operationValuePresent = 1; + if (notify == OSMO_CC_NOTIFY_CONFERENCE_ESTABLISHED) + fac.u.retResult.operationValue = Fac_Begin3PTY; + if (notify == OSMO_CC_NOTIFY_CONFERENCE_DISCONNECTED) + fac.u.retResult.operationValue = Fac_End3PTY; + } else { + fac.comp = CompReturnError; + fac.u.retError.invokeId = invokeid; + fac.u.retError.errorValue = FacError_Gen_InvalidCallState; + } + encodeFac(fac_ie, &fac); + + + /* sending facility */ + l3m = create_l3msg(); + enc_ie_facility(l3m, fac_ie + 2, fac_ie[1]); + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_FACILITY, call->l3_pid, l3m); + + if (notify) { + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + /* notify the facility */ + osmo_cc_add_ie_notify(msg, notify); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_NOTIFY_IND); + /* notify the facility */ + osmo_cc_add_ie_notify(msg, notify); + /* send message to osmo-cc */ + osmo_cc_ll_msg(&other->isdn_ep->cc_ep, call->cc_callref, msg); + } +} + +/* CC-PROGRESS INDICATION */ +void progress_ind(call_t *call, uint32_t pid, struct l3_msg *l3m) +{ + osmo_cc_msg_t *msg; + uint8_t coding, location, progress; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "PROGRESS INDICATION (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_PROGRESS_IND); + + /* progress indicator */ + rc = dec_ie_progress(l3m, &coding, &location, &progress); + if (rc >= 0) + osmo_cc_add_ie_progress(msg, coding, location, progress); + else { + coding = OSMO_CC_CODING_ITU_T; + progress = 0; + } + + /* send SDP answer */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8) && !call->codec_negotiated) { + call->codec_negotiated = 1; + if (call->sdp) + osmo_cc_add_ie_sdp(msg, call->sdp); + } + + /* the audio path is throughconnected */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + call->audio_path = 1; + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); +} + +/* all calls from multipoint interface have released */ +void mt_free(call_t *call) +{ + osmo_cc_msg_t *msg; + + PDEBUG(DDSS1, DEBUG_DEBUG, "Got MT_FREE (release from stack) (old pid 0x%x)\n", call->l3_pid); + + /* create osmo-cc message */ + switch (call->state) { + case ISDN_STATE_OUT_SETUP: + msg = osmo_cc_new_msg(OSMO_CC_MSG_REJ_IND); + break; + default: + msg = osmo_cc_new_msg(OSMO_CC_MSG_REL_IND); + } + + /* cause */ + osmo_cc_add_ie_cause(msg, call->collect_location, call->collect_cause, 0, 0); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + + /* terminate call */ + new_state(call, ISDN_STATE_IDLE); + call_destroy(call); +} + +/* take DSS1 message type and call sub routines (above) to handle each message */ +void dss1_message(isdn_t *isdn_ep, call_t *call, uint32_t cmd, uint32_t pid, struct l3_msg *l3m) +{ + int timer = 0; + + switch (cmd) { + case MT_TIMEOUT: + if (!l3m->cause) { + PDEBUG(DDSS1, DEBUG_ERROR, "timeout without cause.\n"); + break; + } + if (l3m->cause[0] != 5) { + PDEBUG(DDSS1, DEBUG_ERROR, "expecting timeout with timer diagnostic. (got len=%d)\n", l3m->cause[0]); + break; + } + timer = (l3m->cause[3]-'0')*100; + timer += (l3m->cause[4]-'0')*10; + timer += (l3m->cause[5]-'0'); + PDEBUG(DDSS1, DEBUG_DEBUG, "isdn timer T%d timeout\n", timer); + if (timer == 312) + t312_timeout_ind(); + break; + + case MT_SETUP: + if (call->state != ISDN_STATE_IDLE) + break; + setup_ind(call, pid, l3m); + break; + + case MT_SETUP_ACKNOWLEDGE: + setup_ack_ind(call, cmd, pid, l3m); + break; + + case MT_CALL_PROCEEDING: + proc_ind(call, cmd, pid, l3m); + break; + + case MT_ALERTING: + alert_ind(call, cmd, pid, l3m); + break; + + case MT_CONNECT: + setup_cnf(call, cmd, pid, l3m); + break; + + case MT_CONNECT_ACKNOWLEDGE: + setup_comp_ind(call, pid, l3m); + break; + + case MT_INFORMATION: + info_ind(call, pid, l3m); + break; + + case MT_DISCONNECT: + disconnect_ind(call, pid, l3m); + break; + + case MT_RELEASE: + release_ind(call, pid, l3m); + break; + + case MT_RELEASE_COMPLETE: + release_complete_ind(call, pid, l3m); + break; + + case MT_RESTART: + restart_ind(pid); + break; + + case MT_NOTIFY: + notify_ind(call, pid, l3m); + break; + + case MT_HOLD: + hold_ind(call, pid, l3m); + break; + + case MT_RETRIEVE: + retrieve_ind(call, pid, l3m); + break; + + case MT_SUSPEND: + suspend_ind(call, pid, l3m); + break; + + case MT_RESUME: + resume_ind(isdn_ep, pid, l3m); + break; + + case MT_FACILITY: + facility_ind(call, pid, l3m); + break; + + case MT_PROGRESS: + progress_ind(call, pid, l3m); + break; + + case MT_FREE: + mt_free(call); + break; + + default: + PDEBUG(DDSS1, DEBUG_ERROR, "unhandled message: cmd(0x%x) pid(0x%x)\n", cmd, pid); + } +} + +/* receive message from L3 stack and associate with a call instance by searching or creating it */ +int dss1_receive(isdn_t *isdn_ep, uint32_t cmd, uint32_t pid, struct l3_msg *l3m) +{ + call_t *call; + + PDEBUG(DDSS1, DEBUG_DEBUG, "message from L3 stack: cmd(0x%x) pid(0x%x)\n", cmd, pid); + + /* find call that is associated with the pid */ + call = isdn_ep->call_list; + while (call) { + if (call->l3_pid & MISDN_PID_CR_FLAG) { + /* local callref, so match value only */ + if ((call->l3_pid & MISDN_PID_CRVAL_MASK) == (pid & MISDN_PID_CRVAL_MASK)) { + break; + } + } else { + /* remote callref, ref + channel id */ + if (call->l3_pid == pid) { + break; + } + } + call = call->next; + } + + /* messages for a call */ + if (call) { + /* after answering the phone, the PID will be completed and CES will be assigned */ + if (cmd == MT_ASSIGN) { + PDEBUG(DDSS1, DEBUG_DEBUG, "Got assignment (old pid 0x%x, new pid 0x%x)\n", call->l3_pid, pid); + if ((call->l3_pid & MISDN_PID_CRTYPE_MASK) != MISDN_PID_MASTER) + PDEBUG(DDSS1, DEBUG_ERROR, "strange setup-procid 0x%x\n", call->l3_pid); + call->l3_pid = pid; + if (call->state == ISDN_STATE_OUT_CONNECTING || call->state == ISDN_STATE_CONNECT) + call->l3_ces = pid >> 16; + } + /* if process id is master process, but a child disconnects */ + if (call->isdn_ep->ntmode + && (pid & MISDN_PID_CRTYPE_MASK) != MISDN_PID_MASTER + && (call->l3_pid & MISDN_PID_CRTYPE_MASK) == MISDN_PID_MASTER) { + if (cmd == MT_DISCONNECT || cmd == MT_RELEASE) { + /* send special indication for child disconnect */ + disconnect_ind_i(call, pid, l3m); + return 0; + } + if (cmd == MT_RELEASE_COMPLETE) + return 0; + } + /* if we have child pid and got different child pid message, ignore */ + if (call->isdn_ep->ntmode + && (pid & MISDN_PID_CRTYPE_MASK) != MISDN_PID_MASTER + && (call->l3_pid & MISDN_PID_CRTYPE_MASK) != MISDN_PID_MASTER + && pid != call->l3_pid) + return 0; + + /* process message */ + dss1_message(isdn_ep, call, cmd, pid, l3m); + return 0; + } + + /* messages without call */ + switch(cmd) { + case MT_SETUP: + /* creating call instance, transparent until setup with hdlc */ + call = call_create(isdn_ep, 0, 0, B_MODE_TRANSPARENT); + if (!call) { + PDEBUG(DDSS1, DEBUG_ERROR, "Cannot create calll instance.\n"); + abort(); + } + dss1_message(isdn_ep, call, cmd, pid, l3m); + break; + + case MT_RESUME: + /* resume existing call instance */ + dss1_message(isdn_ep, NULL, cmd, pid, l3m); + break; + + case MT_FREE: + PDEBUG(DDSS1, DEBUG_DEBUG, "unused L3ID released (pid = 0x%x, call->cc_callref)\n", pid); + break; + + case MT_RELEASE_COMPLETE: + PDEBUG(DDSS1, DEBUG_ERROR, "MT_RELEASE_COMPLETE must be ignored by stack, not sent to app\n"); + break; + + case MT_FACILITY: + // facility als broadcast + break; + + case MT_L2IDLE: + // L2 became idle - we could sent a MT_L2RELEASE if we are the L2 master + PDEBUG(DDSS1, DEBUG_DEBUG, "Got L2 idle\n"); + break; + + default: + PDEBUG(DDSS1, DEBUG_ERROR, "unhandled message: cmd(0x%x) pid(0x%x)\n", cmd, pid); + return -EINVAL; + } + + return 0; +} + +/* + * handles all requests from osmo-cc + */ + +/* CC-SETUP REQUEST */ +void setup_req(call_t *call, osmo_cc_msg_t *msg) +{ + const char *sdp; + struct l3_msg *l3m; + uint8_t plan, type, screen, present, reason; + uint8_t capability, mode, rate, coding, user; +// uint8_t presentation, interpretation, hlc, exthlc; + int has_user; + char callerid[33]; + char dialing[33]; + char redir[33]; + char keypad[33]; + char display[128]; + int channel, exclusive; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "SETUP REQUEST\n"); + + /* sdp accept, force our preferred codec */ + sdp = osmo_cc_helper_audio_accept(call, (call->isdn_ep->law == 'a') ? codecs_alaw : codecs_ulaw, bchannel_send, msg, &call->cc_session, &call->codec, 1); + if (!sdp) { + release_and_destroy(call, 47, 415/* Unsupported Media*/, 0); + return; + } + call->sdp = strdup(sdp); + PDEBUG(DDSS1, DEBUG_INFO, "Codec %s selected for transmission.\n", call->codec->payload_name); + + /* get channel */ + rc = hunt_bchannel_out(call->isdn_ep, &call->b_channel, &call->b_exclusive); + if (rc < 0) { + PDEBUG(DDSS1, DEBUG_NOTICE, "There is no channel available on the interface.\n"); + release_and_destroy(call, -rc, 0, 0); + return; + } + channel = call->b_channel; + exclusive = call->b_exclusive; + + /* creating pid */ + call->l3_pid = request_new_pid(call->isdn_ep->ml3); + if (call->l3_pid == MISDN_PID_NONE) { + PDEBUG(DDSS1, DEBUG_NOTICE, "There is no free L3ID on the mISDN stack, please restart!\n"); + release_and_destroy(call, OSMO_CC_ISDN_CAUSE_RESOURCE_UNAVAIL, 0, 0); + return; + } + PDEBUG(DDSS1, DEBUG_DEBUG, "new L3ID assigned (pid = 0x%x)\n", call->l3_pid); + + /* creating setup */ + l3m = create_l3msg(); + + /* bearer capability FIXME: clearmode */ + rc = osmo_cc_get_ie_bearer(msg, 0, &coding, &capability, &mode); + if (rc < 0) { + coding = OSMO_CC_CODING_ITU_T; + capability = OSMO_CC_CAPABILITY_AUDIO; + mode = OSMO_CC_MODE_CIRCUIT; + } + rate = (mode == OSMO_CC_MODE_PACKET) ? 0x00 : 0x10; + if (mode == OSMO_CC_MODE_CIRCUIT && call->isdn_ep->law == 'u') { + has_user = 1; + user = 2; + } + if (mode == OSMO_CC_MODE_CIRCUIT && call->isdn_ep->law == 'a') { + has_user = 1; + user = 3; + } + + /* set bchannel mode */ + if (capability==OSMO_CC_CAPABILITY_DATA + || capability==OSMO_CC_CAPABILITY_DATA_RESTRICTED + || capability==OSMO_CC_CAPABILITY_VIDEO) + call->b_mode = B_MODE_HDLC; + enc_ie_bearer(l3m, coding, capability, 1, mode, rate, 0, 0, has_user, user); + + /* channel information */ + if (call->isdn_ep->ntmode || channel != CHANNEL_ANY) /* only omit channel id in te-mode/any channel */ + enc_ie_channel_id(l3m, call->isdn_ep->pri, exclusive, channel); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + + /* keypad */ + rc = osmo_cc_get_ie_keypad(msg, 0, keypad, sizeof(keypad)); + if (rc >= 0) + enc_ie_keypad(l3m, keypad); + + /* caller information */ + rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, callerid, sizeof(callerid)); + if (rc >= 0) { + enc_ie_calling_pn(l3m, type, plan, 1, present, screen, callerid); + /* secondary caller info */ + rc = osmo_cc_get_ie_calling(msg, 1, &type, &plan, &present, &screen, callerid, sizeof(callerid)); + if (rc >= 0) + enc_ie_calling_pn(l3m, type, 1, plan, present, screen, callerid); + } + + /* dialing information */ + rc = osmo_cc_get_ie_called(msg, 0, &type, &plan, dialing, sizeof(dialing)); + if (rc >= 0) { + /* check MSN */ + struct msn_list *m = call->isdn_ep->msn_list; + if (m) { + /* we have an MSN list */ + while (m) { + if (!strcmp(dialing, m->msn)) + break; + } + /* not found, so we use first MSN */ + if (!m) { + strncpy(dialing, call->isdn_ep->msn_list->msn, sizeof(dialing) - 1); + dialing[sizeof(dialing) - 1] = '\0'; + } + } + enc_ie_called_pn(l3m, type, plan, dialing); + } + + /* redirecting number */ + rc = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, redir, sizeof(redir)); + if (rc >= 0) { + /* sending redirecting number only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_redirecting(l3m, type, plan, 1, present, screen, 1, reason, redir); + } + + rc = osmo_cc_get_ie_complete(msg, 0); + if (rc >= 0) { + call->sending_complete = 1; + enc_ie_complete(l3m, 1); + } + + new_state(call, ISDN_STATE_OUT_SETUP); + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_SETUP, call->l3_pid, l3m); +} + +void proc_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg, int with_ies); + +/* process progress message and handle tones */ +int process_progress(call_t *call, osmo_cc_msg_t *msg, struct l3_msg *l3m, int call_state, uint8_t cause) +{ + uint8_t coding, location, progress; + int tone; + int rc; + + /* read progress message from upper layer */ + rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); + if (rc < 0) { + coding = OSMO_CC_CODING_ITU_T; + progress = 0; + } + + /* the audio path is throughconnected */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) + call->audio_path = 1; + + /* if local tones are not enabled, don't process them here */ + if (!call->isdn_ep->local_tones) + goto finish; + + /* if we have remote tones, we don't process local tones */ + if (call->send_remote_tones) + goto finish; + + /* if we are getting remote tones... */ + if (coding == OSMO_CC_CODING_ITU_T && (progress == 1 || progress == 8)) { + /* if we are sending local tones, we stop them */ + if (call->send_local_tones) { + PDEBUG(DDSS1, DEBUG_DEBUG, "Stop sending locally generated tones.\n"); + call->send_local_tones = 0; + bchannel_tone(call, 0); + } + PDEBUG(DDSS1, DEBUG_DEBUG, "Using remote tones.\n"); + call->send_remote_tones = 1; + goto finish; + } + + /* if we have local tones the first time, send progress indicator */ + if (!call->send_local_tones) { + PDEBUG(DDSS1, DEBUG_DEBUG, "Start sending locally generated tones.\n"); + call->send_local_tones = 1; + coding = OSMO_CC_CODING_ITU_T; + location = call->isdn_ep->serving_location; + progress = OSMO_CC_PROGRESS_INBAND_INFO_AVAILABLE; + } + + /* select local tones from call state */ + switch (call_state) { + case ISDN_STATE_IN_OVERLAP: + if (call->any_dialing) + bchannel_tone(call, 0); + else { + switch (call->isdn_ep->local_tones) { + case TONES_TYPE_GERMAN: + tone = TONE_GERMAN_DIALTONE; + break; + case TONES_TYPE_OLDGERMAN: + tone = TONE_GERMAN_OLDDIALTONE; + break; + default: + tone = TONE_AMERICAN_DIALTONE; + } + bchannel_tone(call, tone); + } + break; + case ISDN_STATE_IN_PROCEEDING: + bchannel_tone(call, 0); + break; + case ISDN_STATE_IN_ALERTING: + switch (call->isdn_ep->local_tones) { + case TONES_TYPE_GERMAN: + tone = TONE_GERMAN_RINGING; + break; + case TONES_TYPE_OLDGERMAN: + tone = TONE_GERMAN_OLDRINGING; + break; + default: + tone = TONE_AMERICAN_RINGING; + } + bchannel_tone(call, tone); + break; + case ISDN_STATE_OUT_DISCONNECT: + switch (cause) { + case 17: + case 18: + switch (call->isdn_ep->local_tones) { + case TONES_TYPE_GERMAN: + tone = TONE_GERMAN_BUSY; + break; + case TONES_TYPE_OLDGERMAN: + tone = TONE_GERMAN_OLDBUSY; + break; + default: + tone = TONE_AMERICAN_BUSY; + } + break; + case 16: + case 19: + switch (call->isdn_ep->local_tones) { + case TONES_TYPE_GERMAN: + tone = TONE_GERMAN_HANGUP; + break; + case TONES_TYPE_OLDGERMAN: + tone = TONE_GERMAN_OLDHANGUP; + break; + default: + tone = TONE_AMERICAN_BUSY; + } + break; + case 34: + switch (call->isdn_ep->local_tones) { + case TONES_TYPE_GERMAN: + tone = TONE_GERMAN_GASSENBESETZT; + break; + case TONES_TYPE_OLDGERMAN: + tone = TONE_GERMAN_OLDHANGUP; + break; + default: + tone = TONE_SPECIAL_INFO; + } + break; + default: + switch (call->isdn_ep->local_tones) { + case TONES_TYPE_GERMAN: + tone = TONE_SPECIAL_INFO; + break; + case TONES_TYPE_OLDGERMAN: + tone = TONE_SPECIAL_INFO; + break; + default: + tone = TONE_SPECIAL_INFO; + } + } + bchannel_tone(call, tone); + break; + } + +finish: + /* append progress message. return TRUE, if message was added */ + if (progress) { + enc_ie_progress(l3m, coding, location, progress); + return 1; + } + return 0; +} + +/* CC-SETUP-ACKNOWLEDGE REQUEST */ +void setup_ack_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + char display[128]; + int rc; + + /* in case of sending complete, we proceed */ + if (call->sending_complete) { + new_state(call, ISDN_STATE_IN_PROCEEDING); + proc_req(call, pid, msg, 1); + return; + } + + /* in case of te-mode and multipoint, we proceed, because overlap dialing is not supported in that mode */ + if (!call->isdn_ep->ntmode && !call->isdn_ep->ptp) { + new_state(call, ISDN_STATE_IN_PROCEEDING); + proc_req(call, pid, msg, 1); + return; + } + + PDEBUG(DDSS1, DEBUG_INFO, "SETUP-ACKNOWLEDGE REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* creating setup acknowledge */ + l3m = create_l3msg(); + + /* channel information */ + if (!call->channel_negotiated) { + call->channel_negotiated = 1; + enc_ie_channel_id(l3m, call->isdn_ep->pri, 1, call->b_channel); + } + + // NOTE: codec negotiation is performed at cc_message() + + /* progress information */ + process_progress(call, msg, l3m, ISDN_STATE_IN_OVERLAP, 0); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + + new_state(call, ISDN_STATE_IN_OVERLAP); + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_SETUP_ACKNOWLEDGE, call->l3_pid, l3m); +} + +/* CC-PROCEEDING REQUEST */ +void proc_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg, int with_ies) +{ + struct l3_msg *l3m; + uint8_t plan, type, screen, present, reason; + uint8_t notify; + char redir[33]; + char display[128]; + int rc; + + if (call->proceeding_sent) + return; + call->proceeding_sent = 1; + + PDEBUG(DDSS1, DEBUG_INFO, "PROCEEDING REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* creating proceeding */ + l3m = create_l3msg(); + + /* channel information */ + if (!call->channel_negotiated) { + call->channel_negotiated = 1; + enc_ie_channel_id(l3m, call->isdn_ep->pri, 1, call->b_channel); + } + + // NOTE: codec negotiation is performed at cc_message() + + if (!with_ies) + goto skip_ies; + + /* progress information */ + process_progress(call, msg, l3m, ISDN_STATE_IN_PROCEEDING, 0); + + /* notify */ + rc = osmo_cc_get_ie_notify(msg, 0, ¬ify); + if (rc >= 0) + enc_ie_notify(l3m, notify); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + + /* redirection number */ + rc = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, redir, sizeof(redir)); + /* sending redirection number only in ntmode */ + if (rc >= 0 && call->isdn_ep->ntmode) + enc_ie_redirection(l3m, type, plan, 1, present, redir); + +skip_ies: + new_state(call, ISDN_STATE_IN_PROCEEDING); + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_CALL_PROCEEDING, call->l3_pid, l3m); +} + +/* CC-ALERTING REQUEST */ +void alert_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + uint8_t plan, type, screen, present, reason; + uint8_t notify; + char redir[33]; + char display[128]; + int rc; + + /* NT-MODE in setup state we must send PROCEEDING first */ + if (!call->proceeding_sent && call->isdn_ep->ntmode) { + proc_req(call, pid, msg, 0); + } + + PDEBUG(DDSS1, DEBUG_INFO, "ALERTING REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* creating alerting */ + l3m = create_l3msg(); + + /* channel information */ + if (!call->channel_negotiated) { + call->channel_negotiated = 1; + enc_ie_channel_id(l3m, call->isdn_ep->pri, 1, call->b_channel); + } + + // NOTE: codec negotiation is performed at cc_message() + + /* progress information */ + process_progress(call, msg, l3m, ISDN_STATE_IN_ALERTING, 0); + + /* notify */ + rc = osmo_cc_get_ie_notify(msg, 0, ¬ify); + if (rc >= 0) + enc_ie_notify(l3m, notify); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + + /* redirection number */ + rc = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, redir, sizeof(redir)); + /* sending redirection number only in ntmode */ + if (rc >= 0 && call->isdn_ep->ntmode) + enc_ie_redirection(l3m, type, plan, 1, present, redir); + + new_state(call, ISDN_STATE_IN_ALERTING); + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_ALERTING, call->l3_pid, l3m); +} + +/* CC-CONNECT REQUEST */ +void setup_rsp(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + uint8_t type, plan, present, screen; + uint8_t coding, location, progress; + time_t current_time; + char connected[33]; + char display[128]; + int rc; + + /* NT-MODE in setup state we must send PROCEEDING first */ + if (!call->proceeding_sent && call->isdn_ep->ntmode) { + proc_req(call, pid, msg, 0); + } + + PDEBUG(DDSS1, DEBUG_INFO, "CONNECT REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* stop local tones, if sending until now */ + if (call->send_local_tones) { + PDEBUG(DDSS1, DEBUG_INFO, "Stop sending locally generated tones.\n"); + call->send_local_tones = 0; + bchannel_tone(call, 0); + } + + /* creating connect */ + l3m = create_l3msg(); + + /* channel information */ + if (!call->channel_negotiated) { + call->channel_negotiated = 1; + enc_ie_channel_id(l3m, call->isdn_ep->pri, 1, call->b_channel); + } + + // NOTE: codec negotiation is performed at cc_message() + + /* progress information */ + rc = osmo_cc_get_ie_progress(msg, 0, &coding, &location, &progress); + if (rc >= 0) + enc_ie_progress(l3m, coding, location, progress); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + + /* date & time, in NT mode only */ + if (call->isdn_ep->ntmode) { + time(¤t_time); + enc_ie_date(l3m, current_time, 0); + } + + /* connected number */ + rc = osmo_cc_get_ie_calling(msg, 0, &type, &plan, &present, &screen, connected, sizeof(connected)); + if (rc >= 0) { + enc_ie_connected_pn(l3m, type, plan, 1, present, screen, connected); + rc = osmo_cc_get_ie_calling(msg, 1, &type, &plan, &present, &screen, connected, sizeof(connected)); + /* secondary connected info */ + if (rc >= 0) + enc_ie_connected_pn(l3m, type, plan, 1, present, screen, connected); + } + + new_state(call, ISDN_STATE_IN_CONNECTING); + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_CONNECT, call->l3_pid, l3m); + + /* in NT mode we might not receive CONNECT ACKNOWLEDGE */ + if (call->isdn_ep->ntmode) { + new_state(call, ISDN_STATE_CONNECT); + + /* create osmo-cc message */ + msg = osmo_cc_new_msg(OSMO_CC_MSG_SETUP_COMP_IND); + + /* send message to osmo-cc */ + osmo_cc_ll_msg(&call->isdn_ep->cc_ep, call->cc_callref, msg); + } + + /* the audio path is throughconnected */ + call->audio_path = 1; +} + +/* CC-CONNECT ACKNOWLEDGE REQUEST */ +void setup_comp_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "CONNECT ACKNOWLEDGE REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + new_state(call, ISDN_STATE_CONNECT); + + /* only send in NT mode */ + if (!call->isdn_ep->ntmode) + return; + + /* creating connect acknowledge */ + l3m = create_l3msg(); + + /* if we had no bchannel before, we send it now (answer call with NO_CHANNEL) */ + if (call->setup_comp_req_channel_assignment && call->b_channel) + enc_ie_channel_id(l3m, call->isdn_ep->pri, 1, call->b_channel); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_CONNECT_ACKNOWLEDGE, call->l3_pid, l3m); +} + +/* CC-INFORMATION REQUEST */ +void info_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + uint8_t type, plan; + char keypad[33]; + char dialing[33]; + int rc_called, rc_kp; + + PDEBUG(DDSS1, DEBUG_INFO, "INFORMATION REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* keypad */ + rc_kp = osmo_cc_get_ie_keypad(msg, 0, keypad, sizeof(keypad)); + + /* dialing information */ + rc_called = osmo_cc_get_ie_called(msg, 0, &type, &plan, dialing, sizeof(dialing)); + + if (rc_called >= 0 || rc_kp >= 0) { + /* creating information */ + l3m = create_l3msg(); + if (rc_kp >= 0) + enc_ie_keypad(l3m, keypad); + if (rc_called >= 0) + enc_ie_called_pn(l3m, type, plan, dialing); + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_INFORMATION, call->l3_pid, l3m); + } + + /* reset overlap timeout */ + new_state(call, call->state); +} + +/* CC-PROGRESS REQUEST */ +void progress_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "PROGRESS REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* creating progress */ + l3m = create_l3msg(); + + /* progress information */ + rc = process_progress(call, msg, l3m, call->state, 0); + + /* send message to ISDN */ + if (rc) + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_PROGRESS, call->l3_pid, l3m); + else + free_l3msg(l3m); +} + +/* CC-NOTIFY REQUEST */ +void notify_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + uint8_t notify; + uint8_t plan, type, screen, present, reason; + char redir[32]; + char display[128]; + int rc_notify, rc_redir, rc_display; + + PDEBUG(DDSS1, DEBUG_INFO, "NOTIFY REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* notify */ + rc_notify = osmo_cc_get_ie_notify(msg, 0, ¬ify); + + /* redirection number */ + rc_redir = osmo_cc_get_ie_redir(msg, 0, &type, &plan, &present, &screen, &reason, redir, sizeof(redir)); + + /* display */ + rc_display = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + + if (rc_notify >= 0) { + /* creating notify */ + l3m = create_l3msg(); + enc_ie_notify(l3m, notify); + /* sending display only in ntmode */ + if (rc_display >= 0 && call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + /* sending redirection number only in ntmode */ + if (rc_redir >= 0 && call->isdn_ep->ntmode) + enc_ie_redirection(l3m, type, plan, 1, present, redir); + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_NOTIFY, call->l3_pid, l3m); + } +} + +/* CC-REJECT REQUEST */ +void rej_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + uint8_t location, isdn_cause, socket_cause; + uint16_t sip_cause; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "REJECT REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* creating release complete */ + l3m = create_l3msg(); + + /* cause */ + rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); + if (rc < 0) { + location = OSMO_CC_LOCATION_BEYOND_INTERWORKING; + isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; + } + enc_ie_cause(l3m, location, isdn_cause); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_RELEASE_COMPLETE, call->l3_pid, l3m); + + /* call terminated */ + new_state(call, ISDN_STATE_IDLE); + call_destroy(call); +} + +/* CC-DISCONNECT REQUEST */ +void disc_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + uint8_t location; + uint8_t isdn_cause, socket_cause; + uint16_t sip_cause; + char display[128]; + int rc; + + /* send a proceeding and open channel if we are still in setup state */ + if (call->state == ISDN_STATE_IN_SETUP) { + proc_req(call, pid, msg, 0); + } + + PDEBUG(DDSS1, DEBUG_INFO, "DISCONNECT REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* creating disconnect */ + l3m = create_l3msg(); + + /* cause */ + rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); + if (rc < 0) { + location = OSMO_CC_LOCATION_BEYOND_INTERWORKING; + isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; + } + enc_ie_cause(l3m, location, isdn_cause); + + /* reset states and handle progress information */ + if (call->send_local_tones) { + PDEBUG(DDSS1, DEBUG_DEBUG, "Stop sending locally generated tones.\n"); + call->send_local_tones = 0; + bchannel_tone(call, 0); + } + call->send_remote_tones = 0; + call->audio_path = 0; + process_progress(call, msg, l3m, ISDN_STATE_OUT_DISCONNECT, isdn_cause); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + + new_state(call, ISDN_STATE_OUT_DISCONNECT); + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_DISCONNECT, call->l3_pid, l3m); +} + +/* CC-RELEASE REQUEST */ +void rel_req(call_t *call, uint32_t pid, osmo_cc_msg_t *msg) +{ + struct l3_msg *l3m; + uint8_t location, isdn_cause, socket_cause; + uint16_t sip_cause; + char display[128]; + int rc; + + PDEBUG(DDSS1, DEBUG_INFO, "RELEASE REQUEST (pid = 0x%x callref = %d)\n", pid, call->cc_callref); + + /* creating release */ + l3m = create_l3msg(); + + /* cause */ + rc = osmo_cc_get_ie_cause(msg, 0, &location, &isdn_cause, &sip_cause, &socket_cause); + if (rc < 0) { + location = OSMO_CC_LOCATION_BEYOND_INTERWORKING; + isdn_cause = OSMO_CC_ISDN_CAUSE_NORM_CALL_CLEAR; + } + enc_ie_cause(l3m, location, isdn_cause); + + /* display */ + rc = osmo_cc_get_ie_display(msg, 0, display, sizeof(display)); + if (rc >= 0) { + /* sending display text only in ntmode */ + if (call->isdn_ep->ntmode) + enc_ie_display(l3m, display); + } + + new_state(call, ISDN_STATE_OUT_RELEASE); + + /* send message to ISDN */ + call->isdn_ep->ml3->to_layer3(call->isdn_ep->ml3, MT_RELEASE, call->l3_pid, l3m); +} + +void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg) +{ + isdn_t *isdn_ep = ep->priv; + call_t *call; + + /* hunt for callref */ + call = isdn_ep->call_list; + while (call) { + if (call->cc_callref == callref) + break; + call = call->next; + } + + /* process SETUP */ + if (!call) { + if (msg->type != OSMO_CC_MSG_SETUP_REQ) { + PDEBUG(DDSS1, DEBUG_ERROR, "received message without call instance, please fix!\n"); + osmo_cc_free_msg(msg); + return; + } + /* creating call instance, transparent until setup with hdlc */ + call = call_create(isdn_ep, 0, 0, B_MODE_TRANSPARENT); + if (!call) { + PDEBUG(DDSS1, DEBUG_ERROR, "Cannot create call instance.\n"); + abort(); + } + /* link with cc */ + call->cc_callref = callref; + } + + switch (msg->type) { + case OSMO_CC_MSG_SETUP_REQ: /* dial-out command received from epoint */ + setup_req(call, msg); + break; + case OSMO_CC_MSG_SETUP_ACK_REQ: /* more information is needed */ + osmo_cc_helper_audio_negotiate(msg, &call->cc_session, &call->codec); + setup_ack_req(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_PROC_REQ: /* call of endpoint is proceeding */ + osmo_cc_helper_audio_negotiate(msg, &call->cc_session, &call->codec); + proc_req(call, call->l3_pid, msg, 1); + break; + case OSMO_CC_MSG_ALERT_REQ: /* call of endpoint is ringing */ + osmo_cc_helper_audio_negotiate(msg, &call->cc_session, &call->codec); + alert_req(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_SETUP_RSP: /* call of endpoint is connected */ + osmo_cc_helper_audio_negotiate(msg, &call->cc_session, &call->codec); + setup_rsp(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_SETUP_COMP_REQ: /* call of endpoint is connected */ + setup_comp_req(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_INFO_REQ: /* overlap dialing */ + if (isdn_ep->ntmode + && call->state != ISDN_STATE_OUT_OVERLAP + && call->state != ISDN_STATE_OUT_CONNECTING + && call->state != ISDN_STATE_IN_OVERLAP + && call->state != ISDN_STATE_IN_PROCEEDING + && call->state != ISDN_STATE_IN_ALERTING + && call->state != ISDN_STATE_IN_CONNECTING + && call->state != ISDN_STATE_CONNECT + && call->state != ISDN_STATE_OUT_DISCONNECT + && call->state != ISDN_STATE_IN_DISCONNECT) + break; + if (!isdn_ep->ntmode + && call->state != ISDN_STATE_OUT_OVERLAP + && call->state != ISDN_STATE_OUT_PROCEEDING + && call->state != ISDN_STATE_OUT_ALERTING + && call->state != ISDN_STATE_OUT_CONNECTING + && call->state != ISDN_STATE_IN_OVERLAP + && call->state != ISDN_STATE_IN_PROCEEDING + && call->state != ISDN_STATE_IN_ALERTING + && call->state != ISDN_STATE_IN_CONNECTING + && call->state != ISDN_STATE_CONNECT + && call->state != ISDN_STATE_OUT_DISCONNECT + && call->state != ISDN_STATE_IN_DISCONNECT) + break; + info_req(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_PROGRESS_REQ: /* progress */ + osmo_cc_helper_audio_negotiate(msg, &call->cc_session, &call->codec); + if (isdn_ep->ntmode + && call->state != ISDN_STATE_OUT_OVERLAP + && call->state != ISDN_STATE_OUT_PROCEEDING + && call->state != ISDN_STATE_IN_OVERLAP + && call->state != ISDN_STATE_IN_PROCEEDING + && call->state != ISDN_STATE_IN_ALERTING) + break; + if (!isdn_ep->ntmode + && call->state != ISDN_STATE_OUT_OVERLAP + && call->state != ISDN_STATE_OUT_PROCEEDING + && call->state != ISDN_STATE_OUT_ALERTING + && call->state != ISDN_STATE_IN_OVERLAP + && call->state != ISDN_STATE_IN_PROCEEDING) + break; + progress_req(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_NOTIFY_REQ: /* display and notifications */ + if (call->state != ISDN_STATE_IN_PROCEEDING + && call->state != ISDN_STATE_IN_ALERTING + && call->state != ISDN_STATE_IN_CONNECTING + && call->state != ISDN_STATE_CONNECT) + break; + notify_req(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_REJ_REQ: /* call has been rejected */ + rej_req(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_DISC_REQ: /* call has been disconnected */ + osmo_cc_helper_audio_negotiate(msg, &call->cc_session, &call->codec); + if (call->state != ISDN_STATE_IN_SETUP + && call->state != ISDN_STATE_IN_OVERLAP + && call->state != ISDN_STATE_IN_PROCEEDING + && call->state != ISDN_STATE_IN_ALERTING + && call->state != ISDN_STATE_IN_CONNECTING + && call->state != ISDN_STATE_OUT_OVERLAP + && call->state != ISDN_STATE_OUT_PROCEEDING + && call->state != ISDN_STATE_OUT_ALERTING + && call->state != ISDN_STATE_OUT_CONNECTING + && call->state != ISDN_STATE_CONNECT + && call->state != ISDN_STATE_IN_DISCONNECT) + break; + disc_req(call, call->l3_pid, msg); + break; + case OSMO_CC_MSG_REL_REQ: /* release isdn port */ + rel_req(call, call->l3_pid, msg); + break; + default: + PDEBUG(DDSS1, DEBUG_ERROR, "received an unsupported CC message: %d\n", msg->type); + } + + osmo_cc_free_msg(msg); +} + diff --git a/src/isdn/dss1.h b/src/isdn/dss1.h new file mode 100644 index 0000000..dbc739c --- /dev/null +++ b/src/isdn/dss1.h @@ -0,0 +1,4 @@ + +int dss1_receive(isdn_t *isdn_ep, uint32_t cmd, uint32_t pid, struct l3_msg *l3m); +void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg); + diff --git a/src/isdn/ie.c b/src/isdn/ie.c new file mode 100644 index 0000000..cba3236 --- /dev/null +++ b/src/isdn/ie.c @@ -0,0 +1,1310 @@ + +/*****************************************************************************\ +** ** +** PBX4Linux ** +** ** +**---------------------------------------------------------------------------** +** Copyright: Andreas Eversberg ** +** ** +** information elements encode and decode ** +** ** +\*****************************************************************************/ + +#include +#include +#include +#include "../libdebug/debug.h" +#ifndef u_char +#define u_char unsigned char +#endif +#include +#include +#include "ie.h" + +/* + the pointer of enc_ie_* always points to the IE itself + if qi is not NULL (TE-mode), offset is set +*/ + +/* support stuff */ +static void strnncpy(char *dst, uint8_t *src, int len, int dst_len) +{ + if (len > dst_len - 1) + len = dst_len - 1; + memcpy(dst, src, len); + dst[len] = '\0'; +} + + +/* IE_COMPLETE */ +void enc_ie_complete(struct l3_msg *l3m, int complete) +{ + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE COMPLETE\n"); + + if (complete<0 || complete>1) { + PDEBUG(DDSS1, DEBUG_ERROR, "complete(%d) is out of range.\n", complete); + return; + } + + if (complete) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> complete\n"); + l3m->sending_complete++; + } +} + +void dec_ie_complete(struct l3_msg *l3m, int *complete) +{ + *complete = 0; + // special case: p is not a pointer, it's a value + uint8_t p = l3m->sending_complete; + if (p) { + *complete = 1; + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE COMPLETE\n"); + } + + + if (*complete) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> complete\n"); +} + + +/* IE_BEARER */ +void enc_ie_bearer(struct l3_msg *l3m, uint8_t coding, uint8_t capability, int has_mode, uint8_t mode, uint8_t rate, int has_multi, uint8_t multi, int has_user, uint8_t user) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE BEARER\n"); + + if (coding > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "coding(%d) is out of range.\n", coding); + return; + } + if (capability > 31) { + PDEBUG(DDSS1, DEBUG_ERROR, "capability(%d) is out of range.\n", capability); + return; + } + if (mode > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "mode(%d) is out of range.\n", mode); + return; + } + if (rate > 31) { + PDEBUG(DDSS1, DEBUG_ERROR, "rate(%d) is out of range.\n", rate); + return; + } + if (multi > 127) { + PDEBUG(DDSS1, DEBUG_ERROR, "multi(%d) is out of range.\n", multi); + return; + } + if (user > 31) { + PDEBUG(DDSS1, DEBUG_ERROR, "user L1(%d) is out of range.\n", user); + return; + } + if (rate != 24 && has_multi) { + PDEBUG(DDSS1, DEBUG_ERROR, "multi(%d) is only possible if rate(%d) would be 24.\n", multi, rate); + has_multi = 0; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> coding = %d\n", coding); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> capability = %d\n", capability); + if (has_mode) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> mode = %d\n", mode); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> rate = %d\n", rate); + } + if (has_multi) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> multi = %d\n", multi); + if (has_user) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> user = %d\n", user); + + l = 1 + (!!has_mode) + (!!has_multi) + (!!has_user); + p[0] = IE_BEARER; + p[1] = l; + p[2] = 0x80 + (coding << 5) + capability; + if (has_mode) { + p[3] = 0x80 + (mode << 5) + rate; + if (has_multi) + p[4] = 0x80 + multi; + if (has_user) + p[4 + (!!has_multi)] = 0xa0 + user; + } + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_bearer(struct l3_msg *l3m, uint8_t *coding, uint8_t *capability, int *has_mode, uint8_t *mode, uint8_t *rate, int *has_multi, uint8_t *multi, int *has_user, uint8_t *user) +{ + uint8_t *p = l3m->bearer_capability; + + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE BEARER\n"); + + if (p[0] < 2) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *has_mode = 0; + *has_multi = 0; + *has_user = 0; + + *coding = (p[1]&0x60) >> 5; + *capability = p[1] & 0x1f; + if (p[0]>=2) { + *has_mode = 1; + *mode = (p[2]&0x60) >> 5; + *rate = p[2] & 0x1f; + } + if (p[0]>=3 && *rate==0x18) { + *has_multi = 1; + *multi = p[3] & 0x7f; + if (p[0]>=4) { + *has_user = 1; + *user = p[4] & 0x1f; + } + } else { + if (p[0]>=3) { + *has_user = 1; + *user = p[3] & 0x1f; + } + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> coding = %d\n", *coding); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> capability = %d\n", *capability); + if (*has_mode) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> mode = %d\n", *mode); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> rate = %d\n", *rate); + } + if (*has_multi) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> multi = %d\n", *multi); + if (*has_user) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> user = %d\n", *user); + + return 0; +} + + +/* IE_HLC */ +void enc_ie_hlc(struct l3_msg *l3m, uint8_t coding, uint8_t interpretation, uint8_t presentation, uint8_t hlc, int has_exthlc, uint8_t exthlc) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE HLC\n"); + + if (coding > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "coding(%d) is out of range.\n", coding); + return; + } + if (interpretation > 7) { + PDEBUG(DDSS1, DEBUG_ERROR, "interpretation(%d) is out of range.\n", interpretation); + return; + } + if (presentation > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "presentation(%d) is out of range.\n", presentation); + return; + } + if (hlc > 127) { + PDEBUG(DDSS1, DEBUG_ERROR, "hlc(%d) is out of range.\n", hlc); + return; + } + if (exthlc > 127) { + PDEBUG(DDSS1, DEBUG_ERROR, "hlc(%d) is out of range.\n", exthlc); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> coding = %d\n", coding); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> interpretation = %d\n", interpretation); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> presentation = %d\n", presentation); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> hlc = %d\n", hlc); + if (has_exthlc) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> exthlc = %d\n", exthlc); + + l = 2 + (!!has_exthlc); + p[0] = IE_HLC; + p[1] = l; + p[2] = 0x80 + (coding << 5) + (interpretation << 2) + presentation; + if (has_exthlc) { + p[3] = hlc; + p[4] = 0x80 + exthlc; + } else + p[3] = 0x80 + hlc; + add_layer3_ie(l3m, p[0], p[1], p+2); +} + +int dec_ie_hlc(struct l3_msg *l3m, uint8_t *coding, uint8_t *interpretation, uint8_t *presentation, uint8_t *hlc, int *has_exthlc, uint8_t *exthlc) +{ + *has_exthlc = 0; + + uint8_t *p = l3m->hlc; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE HLC\n"); + + if (p[0] < 2) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *coding = (p[1]&0x60) >> 5; + *interpretation = (p[1]&0x1c) >> 2; + *presentation = p[1] & 0x03; + *hlc = p[2] & 0x7f; + if (p[0]>=3) { + *has_exthlc = 1; + *exthlc = p[3] & 0x7f; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> coding = %d\n", *coding); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> interpretation = %d\n", *interpretation); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> presentation = %d\n", *presentation); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> hlc = %d\n", *hlc); + if (*has_exthlc) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> exthlc = %d\n", *exthlc); + + return 0; +} + + +/* IE_CALL_ID */ +void enc_ie_call_id(struct l3_msg *l3m, uint8_t *callid, int callid_len) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE CALL ID\n"); + + if (callid_len == 0) { + return; + } + if (callid_len > 8) { + PDEBUG(DDSS1, DEBUG_ERROR, "callid_len(%d) is out of range.\n", callid_len); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> value = %s\n", debug_hex(callid, callid_len)); + + l = callid_len; + p[0] = IE_CALL_ID; + p[1] = l; + memcpy(p+2, callid, callid_len); + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_call_id(struct l3_msg *l3m, uint8_t *callid, int *callid_len) +{ + uint8_t *p = l3m->call_id; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE CALL ID\n"); + + if (p[0] > 8) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too long (len=%d)\n", p[0]); + return -EINVAL; + } + + *callid_len = p[0]; + memcpy(callid, p+1, *callid_len); + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> value = %s\n", debug_hex(callid, *callid_len)); + + return 0; +} + + +/* IE_CALLED_PN */ +void enc_ie_called_pn(struct l3_msg *l3m, uint8_t type, uint8_t plan, char *number) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE CALLED PN\n"); + + if (type > 7) { + PDEBUG(DDSS1, DEBUG_ERROR, "type(%d) is out of range.\n", type); + return; + } + if (plan > 15) { + PDEBUG(DDSS1, DEBUG_ERROR, "plan(%d) is out of range.\n", plan); + return; + } + if (!number[0]) { + PDEBUG(DDSS1, DEBUG_ERROR, "number is not given.\n"); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", plan); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + l = 1; + if (number[0]) + l += strlen(number); + p[0] = IE_CALLED_PN; + p[1] = l; + p[2] = 0x80 + (type << 4) + plan; + memcpy(p + 3, number, strlen(number)); + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_called_pn(struct l3_msg *l3m, uint8_t *type, uint8_t *plan, char *number, int number_len) +{ + uint8_t *p = l3m->called_nr; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE CALLED PN\n"); + + if (p[0] < 2) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *type = (p[1]&0x70) >> 4; + *plan = p[1] & 0xf; + strnncpy(number, p + 2, p[0] - 1, number_len); + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", *type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", *plan); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + return 0; +} + + +/* IE_CALLING_PN */ +void enc_ie_calling_pn(struct l3_msg *l3m, uint8_t type, uint8_t plan, int has_present, uint8_t present, uint8_t screen, char *number) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE CALLING PN\n"); + + if (type > 7) { + PDEBUG(DDSS1, DEBUG_ERROR, "type(%d) is out of range.\n", type); + return; + } + if (plan > 15) { + PDEBUG(DDSS1, DEBUG_ERROR, "plan(%d) is out of range.\n", plan); + return; + } + if (present > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "present(%d) is out of range.\n", present); + return; + } + if (screen > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "screen(%d) is out of range.\n", screen); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", plan); + if (has_present) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> present = %d\n", present); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> screen = %d\n", screen); + } + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + l = 1; + if (number[0]) + l += strlen(number); + if (has_present) + l += 1; + p[0] = IE_CALLING_PN; + p[1] = l; + if (has_present) { + p[2] = 0x00 + (type << 4) + plan; + p[3] = 0x80 + (present << 5) + screen; + memcpy(p + 4, number, strlen(number)); + } else { + p[2] = 0x80 + (type << 4) + plan; + memcpy(p + 3, number, strlen(number)); + } + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_calling_pn(struct l3_msg *l3m, int secondary_ie, uint8_t *type, uint8_t *plan, int *has_present, uint8_t *present, uint8_t *screen, char *number, int number_len) +{ + uint8_t *p; + + *has_present = 0; + + if (secondary_ie) { + unsigned int numextra = sizeof(l3m->extra) / sizeof(struct m_extie); + unsigned int i; + /* second calling party number */ + p = NULL; + i = 0; + while(i < numextra) { + if (!l3m->extra[i].val) + break; + if (l3m->extra[i].ie == IE_CALLING_PN) { + p = l3m->extra[i].val; + break; + } + i++; + } + } else { + p = l3m->calling_nr; + } + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE CALLING PN\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *type = (p[1] & 0x70) >> 4; + *plan = p[1] & 0xf; + if (!(p[1] & 0x80)) { + if (p[0] < 2) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + *has_present = 1; + *present = (p[2]&0x60) >> 5; + *screen = p[2] & 0x3; + strnncpy(number, p + 3, p[0] - 2, number_len); + } else { + strnncpy(number, p + 2, p[0] - 1, number_len); + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", *type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", *plan); + if (*has_present) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> present = %d\n", *present); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> screen = %d\n", *screen); + } + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + return 0; +} + + +/* IE_CONNECTED_PN */ +void enc_ie_connected_pn(struct l3_msg *l3m, uint8_t type, uint8_t plan, int has_present, uint8_t present, uint8_t screen, char *number) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE CONNECTED PN\n"); + + if (type > 7) { + PDEBUG(DDSS1, DEBUG_ERROR, "type(%d) is out of range.\n", type); + return; + } + if (plan > 15) { + PDEBUG(DDSS1, DEBUG_ERROR, "plan(%d) is out of range.\n", plan); + return; + } + if (present > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "present(%d) is out of range.\n", present); + return; + } + if (screen > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "screen(%d) is out of range.\n", screen); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", plan); + if (has_present) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> present = %d\n", present); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> screen = %d\n", screen); + } + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + l = 1; + l += strlen(number); + if (has_present) + l += 1; + p[0] = IE_CONNECT_PN; + p[1] = l; + if (has_present) { + p[2] = 0x00 + (type << 4) + plan; + p[3] = 0x80 + (present << 5) + screen; + memcpy(p + 4, number, strlen(number)); + } else { + p[2] = 0x80 + (type << 4) + plan; + memcpy(p + 3, number, strlen(number)); + } + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_connected_pn(struct l3_msg *l3m, uint8_t *type, uint8_t *plan, int *has_present, uint8_t *present, uint8_t *screen, char *number, int number_len) +{ + *has_present = 0; + + uint8_t *p = l3m->connected_nr; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE CONNECTED PN\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *type = (p[1] & 0x70) >> 4; + *plan = p[1] & 0xf; + if (!(p[1] & 0x80)) { + if (p[0] < 2) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + *has_present = 1; + *present = (p[2]&0x60) >> 5; + *screen = p[2] & 0x3; + strnncpy(number, p+3, p[0]-2, number_len); + } else { + strnncpy(number, p+2, p[0]-1, number_len); + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", *type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", *plan); + if (*has_present) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> present = %d\n", *present); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> screen = %d\n", *screen); + } + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + return 0; +} + + +/* IE_CAUSE */ +void enc_ie_cause(struct l3_msg *l3m, uint8_t location, uint8_t cause) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE CAUSE\n"); + + if (location > 10) { + PDEBUG(DDSS1, DEBUG_ERROR, "location(%d) is out of range.\n", location); + return; + } + if (cause > 127) { + PDEBUG(DDSS1, DEBUG_ERROR, "cause(%d) is out of range.\n", cause); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> location = %d\n", location); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> value = %d\n", cause); + + l = 2; + p[0] = IE_CAUSE; + p[1] = l; + p[2] = 0x80 + location; + p[3] = 0x80 + cause; + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_cause(struct l3_msg *l3m, uint8_t *location, uint8_t *cause) +{ + uint8_t *p = l3m->cause; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE CAUSE\n"); + + if (p[0] < 2) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *location = p[1] & 0x0f; + *cause = p[2] & 0x7f; + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> location = %d\n", *location); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> value = %d\n", *cause); + + return 0; +} + + +/* IE_CHANNEL_ID */ +void enc_ie_channel_id(struct l3_msg *l3m, int pri, int exclusive, int channel) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE CHANNEL ID\n"); + + if (exclusive < 0 || exclusive > 1) { + PDEBUG(DDSS1, DEBUG_ERROR, "exclusive(%d) is out of range.\n", exclusive); + return; + } + if ((channel<=0 && channel != CHANNEL_NO && channel != CHANNEL_ANY) + || (!pri && channel > 2) + || (pri && channel > 127) + || (pri && channel == 16)) { + PDEBUG(DDSS1, DEBUG_ERROR, "channel(%d) is out of range.\n", channel); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> exclusive = %d\n", exclusive); + switch(channel) { + case CHANNEL_ANY: + PDEBUG(DDSS1, DEBUG_DEBUG, " -> channel = any channel\n"); + break; + case CHANNEL_NO: + PDEBUG(DDSS1, DEBUG_DEBUG, " -> channel = no channel\n"); + break; + default: + PDEBUG(DDSS1, DEBUG_DEBUG, " -> channel = %d\n", channel); + } + + if (!pri) { + /* BRI */ + l = 1; + p[0] = IE_CHANNEL_ID; + p[1] = l; + if (channel == CHANNEL_NO) + channel = 0; + else if (channel == CHANNEL_ANY) + channel = 3; + p[2] = 0x80 + (exclusive << 3) + channel; + add_layer3_ie(l3m, p[0], p[1], p + 2); + } else { + /* PRI */ + if (channel == CHANNEL_NO || channel == CHANNEL_ANY) { + if (channel == CHANNEL_NO) + channel = 0; + else + channel = 3; + l = 1; + p[0] = IE_CHANNEL_ID; + p[1] = l; + p[2] = 0x80 + 0x20 + channel; + add_layer3_ie(l3m, p[0], p[1], p + 2); + return; /* end */ + } + l = 3; + p[0] = IE_CHANNEL_ID; + p[1] = l; + p[2] = 0x80 + 0x20 + (exclusive << 3) + 0x01; + p[3] = 0x80 + 3; /* CCITT, Number, B-type */ + p[4] = 0x80 + channel; + add_layer3_ie(l3m, p[0], p[1], p + 2); + } +} + +int dec_ie_channel_id(struct l3_msg *l3m, int pri, int *exclusive, int *channel) +{ + *exclusive = -1; + *channel = -1; + + uint8_t *p = l3m->channel_id; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE CHANNEL ID\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + if (p[1] & 0x40) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error refering to channels of other interfaces is not supported\n"); + return -EINVAL; + } + if (p[1] & 0x04) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error using d-channel is not supported\n"); + return -EINVAL; + } + + *exclusive = (p[1] & 0x08) >> 3; + if (!pri) { + /* BRI */ + if (p[1] & 0x20) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error extended channel ID with non PRI interface\n"); + return -EINVAL; + } + *channel = p[1] & 0x03; + if (*channel == 3) + *channel = CHANNEL_ANY; + else if (*channel == 0) + *channel = CHANNEL_NO; + } else { + /* PRI */ + if (p[0] < 1) { + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short for PRI (len=%d)\n", p[0]); + return -EINVAL; + } + if (!(p[1] & 0x20)) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error basic channel ID with PRI interface\n"); + return -EINVAL; + } + if ((p[1]&0x03) == 0x00) { + /* no channel */ + *channel = CHANNEL_NO; + return -EINVAL; + } + if ((p[1]&0x03) == 0x03) { + /* any channel */ + *channel = CHANNEL_ANY; + return -EINVAL; + } + if (p[0] < 3) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short for PRI with channel (len=%d)\n", p[0]); + return -EINVAL; + } + if (p[2] & 0x10) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error channel map not supported\n"); + return -EINVAL; + } + *channel = p[3] & 0x7f; + if ((*channel<1) || (*channel==16)) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error PRI interface channel out of range (%d)\n", *channel); + return -EINVAL; + } + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> exclusive = %d\n", *exclusive); + switch(*channel) { + case CHANNEL_ANY: + PDEBUG(DDSS1, DEBUG_DEBUG, " -> channel = any channel\n"); + break; + case CHANNEL_NO: + PDEBUG(DDSS1, DEBUG_DEBUG, " -> channel = no channel\n"); + break; + default: + PDEBUG(DDSS1, DEBUG_DEBUG, " -> channel = %d\n", *channel); + } + + return 0; +} + + +/* IE_DATE */ +void enc_ie_date(struct l3_msg *l3m, time_t ti, int no_seconds) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE DATE\n"); + + struct tm *tm; + + tm = localtime(&ti); + if (!tm) { + PDEBUG(DDSS1, DEBUG_ERROR, "localtime() returned NULL.\n"); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> day = %d.%d.%d\n", tm->tm_mday, tm->tm_mon+1, tm->tm_year%100); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> time = %d:%d:%d\n", tm->tm_hour, tm->tm_min, tm->tm_sec); + + l = 5 + (!no_seconds); + p[0] = IE_DATE; + p[1] = l; + p[2] = tm->tm_year % 100; + p[3] = tm->tm_mon + 1; + p[4] = tm->tm_mday; + p[5] = tm->tm_hour; + p[6] = tm->tm_min; + if (!no_seconds) + p[7] = tm->tm_sec; + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + + +/* IE_DISPLAY */ +void enc_ie_display(struct l3_msg *l3m, char *display) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE DISPLAY\n"); + + if (!display[0]) { + PDEBUG(DDSS1, DEBUG_ERROR, "display text not given.\n"); + return; + } + + if (strlen(display) > 80) { + PDEBUG(DDSS1, DEBUG_ERROR, "display text too long (max 80 chars), cutting.\n"); + display[80] = '\0'; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> text = %s\n", display); + + l = strlen(display); + p[0] = IE_DISPLAY; + p[1] = l; + memcpy(p + 2, display, strlen(display)); + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_display(struct l3_msg *l3m, char *display, int display_len) +{ + uint8_t *p = l3m->display; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE DISPLAY\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + strnncpy(display, p+1, p[0], display_len); + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> text = %s\n", display); + + return 0; +} + + +/* IE_KEYPAD */ +void enc_ie_keypad(struct l3_msg *l3m, char *keypad) +{ + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE KEYPAD\n"); + + uint8_t p[256]; + int l; + + if (!keypad[0]) { + PDEBUG(DDSS1, DEBUG_ERROR, "keypad info not given.\n"); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> keypad = %s\n", keypad); + + l = strlen((char *)keypad); + p[0] = IE_KEYPAD; + p[1] = l; + memcpy(p + 2, keypad, strlen(keypad)); + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_keypad(struct l3_msg *l3m, char *keypad, int keypad_len) +{ + uint8_t *p = l3m->keypad; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE KEYPAD\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + strnncpy(keypad, p + 1, p[0], keypad_len); + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> keypad = %s\n", keypad); + + return 0; +} + + +/* IE_NOTIFY */ +void enc_ie_notify(struct l3_msg *l3m, uint8_t notify) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE NOTIFY\n"); + + if (notify > 0x7f) { + PDEBUG(DDSS1, DEBUG_ERROR, "notify(%d) is out of range.\n", notify); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> notify = %d\n", notify); + + l = 1; + p[0] = IE_NOTIFY; + p[1] = l; + p[2] = 0x80 + notify; + add_layer3_ie(l3m, p[0], p[1], p+2); +} + +int dec_ie_notify(struct l3_msg *l3m, uint8_t *notify) +{ + *notify = -1; + + uint8_t *p = l3m->notify; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE NOTIFY\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *notify = p[1] & 0x7f; + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> notify = %d\n", *notify); + + return 0; +} + + +/* IE_PROGRESS */ +void enc_ie_progress(struct l3_msg *l3m, uint8_t coding, uint8_t location, uint8_t progress) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE PROGRESS\n"); + + if (coding > 0x03) { + PDEBUG(DDSS1, DEBUG_ERROR, "coding(%d) is out of range.\n", coding); + return; + } + if (location > 0x0f) { + PDEBUG(DDSS1, DEBUG_ERROR, "location(%d) is out of range.\n", location); + return; + } + if (progress > 0x7f) { + PDEBUG(DDSS1, DEBUG_ERROR, "progress(%d) is out of range.\n", progress); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> coding = %d\n", coding); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> location = %d\n", location); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> indicator = %d\n", progress); + + l = 2; + p[0] = IE_PROGRESS; + p[1] = l; + p[2] = 0x80 + (coding<<5) + location; + p[3] = 0x80 + progress; + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_progress(struct l3_msg *l3m, uint8_t *coding, uint8_t *location, uint8_t *progress) +{ + uint8_t *p = l3m->progress; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE PROGRESS\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *coding = (p[1] & 0x60) >> 5; + *location = p[1] & 0x0f; + *progress = p[2] & 0x7f; + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> coding = %d\n", *coding); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> location = %d\n", *location); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> indicator = %d\n", *progress); + + return 0; +} + + +/* IE_REDIRECTING_NR (redirecting = during MT_SETUP) */ +void enc_ie_redirecting(struct l3_msg *l3m, uint8_t type, uint8_t plan, int has_present, uint8_t present, uint8_t screen, int has_reason, uint8_t reason, char *number) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE REDIRECTING NR\n"); + + if (type > 7) { + PDEBUG(DDSS1, DEBUG_ERROR, "type(%d) is out of range.\n", type); + return; + } + if (plan > 15) { + PDEBUG(DDSS1, DEBUG_ERROR, "plan(%d) is out of range.\n", plan); + return; + } + if (present > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "present(%d) is out of range.\n", present); + return; + } + if (screen > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "screen(%d) is out of range.\n", screen); + return; + } + if (reason > 0x0f) { + PDEBUG(DDSS1, DEBUG_ERROR, "reason(%d) is out of range.\n", reason); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", plan); + if (has_present) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> present = %d\n", present); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> screen = %d\n", screen); + if (has_reason) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> reason = %d\n", reason); + } + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + l = 1; + l += strlen(number); + if (has_present) { + l += 1; + if (has_reason) + l += 1; + } + p[0] = IE_REDIRECTING_NR; + p[1] = l; + if (has_present) { + if (has_reason) { + p[2] = 0x00 + (type << 4) + plan; + p[3] = 0x00 + (present << 5) + screen; + p[4] = 0x80 + reason; + memcpy(p + 5, number, strlen(number)); + } else { + p[2] = 0x00 + (type << 4) + plan; + p[3] = 0x80 + (present << 5) + screen; + memcpy(p + 4, number, strlen(number)); + } + } else { + p[2] = 0x80 + (type << 4) + plan; + memcpy(p + 3, number, strlen(number)); + } + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_redirecting(struct l3_msg *l3m, uint8_t *type, uint8_t *plan, int *has_present, uint8_t *present, uint8_t *screen, int *has_reason, uint8_t *reason, char *number, int number_len) +{ + *has_present = 0; + *has_reason = 0; + + uint8_t *p = l3m->redirecting_nr; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE REDIRECTING NR\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *type = (p[1] & 0x70) >> 4; + *plan = p[1] & 0xf; + if (!(p[1] & 0x80)) { + *has_present = 1; + *present = (p[2] & 0x60) >> 5; + *screen = p[2] & 0x3; + if (!(p[2] & 0x80)) { + *has_reason = 1; + *reason = p[3] & 0x0f; + strnncpy(number, p + 4, p[0] - 3, number_len); + } else { + strnncpy(number, p + 3, p[0] - 2, number_len); + } + } else { + strnncpy(number, p+2, p[0]-1, number_len); + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", *type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", *plan); + if (*has_present) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> present = %d\n", *present); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> screen = %d\n", *screen); + if (*has_reason) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> reason = %d\n", *reason); + } + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + return 0; +} + + +/* IE_REDIRECTION (redirection = during MT_NOTIFY) */ +void enc_ie_redirection(struct l3_msg *l3m, uint8_t type, uint8_t plan, int has_present, uint8_t present, char *number) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE REDIRECTION NR\n"); + + if (type > 7) { + PDEBUG(DDSS1, DEBUG_ERROR, "type(%d) is out of range.\n", type); + return; + } + if (plan > 15) { + PDEBUG(DDSS1, DEBUG_ERROR, "plan(%d) is out of range.\n", plan); + return; + } + if (present > 3) { + PDEBUG(DDSS1, DEBUG_ERROR, "present(%d) is out of range.\n", present); + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", plan); + if (has_present) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> present = %d\n", present); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + l = 1; + l += strlen(number); + if (has_present) + l += 1; + p[0] = IE_REDIRECTION_NR; + p[1] = l; + if (has_present) { + p[2] = 0x00 + (type << 4) + plan; + p[3] = 0x80 + (present << 5); + memcpy(p + 4, number, strlen(number)); + } else { + p[2] = 0x80 + (type << 4) + plan; + memcpy(p + 3, number, strlen(number)); + } + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_redirection(struct l3_msg *l3m, uint8_t *type, uint8_t *plan, int *has_present, uint8_t *present, char *number, int number_len) +{ + *has_present = 0; + + uint8_t *p = l3m->redirection_nr; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE REDIRECTION NR\n"); + + if (p[0] < 1) { + PDEBUG(DDSS1, DEBUG_DEBUG, " -> error IE too short (len=%d)\n", p[0]); + return -EINVAL; + } + + *type = (p[1] & 0x70) >> 4; + *plan = p[1] & 0xf; + if (!(p[1] & 0x80)) { + *has_present = 1; + *present = (p[2]&0x60) >> 5; + strnncpy(number, p + 3, p[0] - 2, number_len); + } else { + strnncpy(number, p + 2, p[0] - 1, number_len); + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> type = %d\n", *type); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> plan = %d\n", *plan); + if (*has_present) + PDEBUG(DDSS1, DEBUG_DEBUG, " -> present = %d\n", *present); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> number = %s\n", number); + + return 0; +} + + +/* IE_FACILITY */ +void enc_ie_facility(struct l3_msg *l3m, uint8_t *facility, int facility_len) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE FACILITY\n"); + + if (!facility || facility_len <= 0) + return; + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> value = %s\n", debug_hex(facility, facility_len)); + + l = facility_len; + p[0] = IE_FACILITY; + p[1] = l; + memcpy(p + 2, facility, facility_len); + add_layer3_ie(l3m, p[0], p[1], p+2); +} + +int dec_ie_facility(struct l3_msg *l3m, uint8_t *facility, int *facility_len) +{ + *facility_len = 0; + + uint8_t *p = l3m->facility; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE FACILITY\n"); + + *facility_len = p[0]; + memcpy(facility, p + 1, *facility_len); + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> value = %s\n", debug_hex(facility, *facility_len)); + + return 0; +} + + +/* IE_USERUSER */ +void enc_ie_useruser(struct l3_msg *l3m, uint8_t protocol, uint8_t *user, int user_len) +{ + uint8_t p[256]; + int l; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE USER-USER\n"); + + if (protocol > 127) { + PDEBUG(DDSS1, DEBUG_ERROR, "protocol(%d) is out of range.\n", protocol); + return; + } + if (!user || user_len <= 0) { + return; + } + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> protocol = %d\n", protocol); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> value = %s\n", debug_hex(user, user_len)); + + l = user_len; + p[0] = IE_USER_USER; + p[1] = l; + p[2] = 0x80 + protocol; + memcpy(p + 3, user, user_len); + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + +int dec_ie_useruser(struct l3_msg *l3m, uint8_t *protocol, uint8_t *user, int *user_len) +{ + *user_len = 0; + + uint8_t *p = l3m->useruser; + if (!p) + return -EINVAL; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Decode IE USER-USER\n"); + + *user_len = p[0] - 1; + if (p[0] < 1) + return -EINVAL; + *protocol = p[1]; + memcpy(user, p + 2, (*user_len <= 128) ? *user_len : 128); /* clip to 128 maximum */ + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> protocol = %d\n", *protocol); + PDEBUG(DDSS1, DEBUG_DEBUG, " -> value = %s\n", debug_hex(user, *user_len)); + + return 0; +} + +/* IE_SIGNAL */ +void enc_ie_signal(struct l3_msg *l3m, uint8_t signal) +{ + uint8_t p[256]; + + PDEBUG(DDSS1, DEBUG_DEBUG, " Encode IE SIGNAL\n"); + + PDEBUG(DDSS1, DEBUG_DEBUG, " -> signal = %d\n", signal); + + p[0] = IE_SIGNAL; + p[1] = 1; + p[2] = signal; + add_layer3_ie(l3m, p[0], p[1], p + 2); +} + diff --git a/src/isdn/ie.h b/src/isdn/ie.h new file mode 100644 index 0000000..a1852e1 --- /dev/null +++ b/src/isdn/ie.h @@ -0,0 +1,42 @@ + +#define CHANNEL_NO -2 /* incoming call on hold */ +#define CHANNEL_ANY -3 /* give me any channel */ +#define CHANNEL_FREE -100 /* select channel that is free */ + +void enc_ie_complete(struct l3_msg *l3m, int complete); +void dec_ie_complete(struct l3_msg *l3m, int *complete); +void enc_ie_bearer(struct l3_msg *l3m, uint8_t coding, uint8_t capability, int has_mode, uint8_t mode, uint8_t rate, int has_multi, uint8_t multi, int has_user, uint8_t user); +int dec_ie_bearer(struct l3_msg *l3m, uint8_t *coding, uint8_t *capability, int *has_mode, uint8_t *mode, uint8_t *rate, int *has_multi, uint8_t *multi, int *has_user, uint8_t *user); +void enc_ie_hlc(struct l3_msg *l3m, uint8_t coding, uint8_t interpretation, uint8_t presentation, uint8_t hlc, int has_exthlc, uint8_t exthlc); +int dec_ie_hlc(struct l3_msg *l3m, uint8_t *coding, uint8_t *interpretation, uint8_t *presentation, uint8_t *hlc, int *has_exthlc, uint8_t *exthlc); +void enc_ie_call_id(struct l3_msg *l3m, uint8_t *callid, int callid_len); +int dec_ie_call_id(struct l3_msg *l3m, uint8_t *callid, int *callid_len); +void enc_ie_called_pn(struct l3_msg *l3m, uint8_t type, uint8_t plan, char *number); +int dec_ie_called_pn(struct l3_msg *l3m, uint8_t *type, uint8_t *plan, char *number, int number_len); +void enc_ie_calling_pn(struct l3_msg *l3m, uint8_t type, uint8_t plan, int has_presen, uint8_t present, uint8_t screen, char *number); +int dec_ie_calling_pn(struct l3_msg *l3m, int secondary_ie, uint8_t *type, uint8_t *plan, int *has_present, uint8_t *present, uint8_t *screen, char *number, int number_len); +void enc_ie_connected_pn(struct l3_msg *l3m, uint8_t type, uint8_t plan, int has_present, uint8_t present, uint8_t screen, char *number); +int dec_ie_connected_pn(struct l3_msg *l3m, uint8_t *type, uint8_t *plan, int *has_present, uint8_t *present, uint8_t *screen, char *number, int number_len); +void enc_ie_cause(struct l3_msg *l3m, uint8_t location, uint8_t cause); +int dec_ie_cause(struct l3_msg *l3m, uint8_t *location, uint8_t *cause); +void enc_ie_channel_id(struct l3_msg *l3m, int pri, int exclusive, int channel); +int dec_ie_channel_id(struct l3_msg *l3m, int pri, int *exclusive, int *channel); +void enc_ie_date(struct l3_msg *l3m, time_t ti, int no_seconds); +void enc_ie_display(struct l3_msg *l3m, char *display); +int dec_ie_display(struct l3_msg *l3m, char *display, int display_len); +void enc_ie_keypad(struct l3_msg *l3m, char *keypad); +int dec_ie_keypad(struct l3_msg *l3m, char *keypad, int keypad_len); +void enc_ie_notify(struct l3_msg *l3m, uint8_t notify); +int dec_ie_notify(struct l3_msg *l3m, uint8_t *notify); +void enc_ie_progress(struct l3_msg *l3m, uint8_t coding, uint8_t location, uint8_t progress); +int dec_ie_progress(struct l3_msg *l3m, uint8_t *coding, uint8_t *location, uint8_t *progress); +void enc_ie_redirecting(struct l3_msg *l3m, uint8_t type, uint8_t plan, int has_present, uint8_t present, uint8_t screen, int has_reason, uint8_t reason, char *number); +int dec_ie_redirecting(struct l3_msg *l3m, uint8_t *type, uint8_t *plan, int *has_present, uint8_t *present, uint8_t *screen, int *has_reason, uint8_t *reason, char *number, int number_len); +void enc_ie_redirection(struct l3_msg *l3m, uint8_t type, uint8_t plan, int has_present, uint8_t present, char *number); +int dec_ie_redirection(struct l3_msg *l3m, uint8_t *type, uint8_t *plan, int *has_present, uint8_t *present, char *number, int number_len); +void enc_ie_facility(struct l3_msg *l3m, uint8_t *facility, int facility_len); +int dec_ie_facility(struct l3_msg *l3m, uint8_t *facility, int *facility_len); +void enc_ie_useruser(struct l3_msg *l3m, uint8_t protocol, uint8_t *user, int user_len); +int dec_ie_useruser(struct l3_msg *l3m, uint8_t *protocol, uint8_t *user, int *user_len); +void enc_ie_signal(struct l3_msg *l3m, uint8_t signal); + diff --git a/src/isdn/isdn.c b/src/isdn/isdn.c new file mode 100644 index 0000000..e583a0e --- /dev/null +++ b/src/isdn/isdn.c @@ -0,0 +1,2072 @@ +/* layer 1/2 handling + * + * (C) 2020 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../libg711/g711.h" +#include "isdn.h" +#include "dss1.h" +#include "ie.h" +#ifndef u_char +#define u_char unsigned char +#endif +#include +#include + +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + +#define B_TIMER_ACTIVATING 1 // seconds +#define B_TIMER_DEACTIVATING 1 // seconds + +int check_mISDN_dsp(void) +{ + char buffer[256]; + int found = 0; + FILE *fp = fopen("/proc/modules", "r"); + + if (!fp) { + PDEBUG(DISDN, DEBUG_ERROR, "Failed to read /proc/modules.\n"); + return -errno; + } + + while((fgets(buffer, sizeof(buffer), fp))) { + buffer[sizeof(buffer) - 1] = '\0'; + if (strstr(buffer, "mISDN_dsp")) + found = 1; + } + + fclose(fp); + + if (found) + return 0; + + PDEBUG(DISDN, DEBUG_ERROR, "Required module 'mISDN_dsp' is not loaded. Run 'modprobe mISDN_dsp' to load the module!\n"); + return -EINVAL; +} + +/* + * Channel selection + */ + +/* set default out_channel */ +static void default_out_channel(isdn_t *isdn_ep) +{ + struct select_channel *selchannel; + + selchannel = calloc(1, sizeof(struct select_channel)); + if (isdn_ep->ntmode) + selchannel->channel = CHANNEL_FREE; + else + selchannel->channel = CHANNEL_ANY; + isdn_ep->out_channel = selchannel; + + /* additional channel selection for multipoint NT ports */ + if (!isdn_ep->ptp && isdn_ep->ntmode) { + selchannel->next = calloc(1, sizeof(struct select_channel)); + selchannel = selchannel->next; + selchannel->channel = CHANNEL_NO; // call waiting + } +} + +/* set default in_channel */ +static void default_in_channel(isdn_t *isdn_ep) +{ + struct select_channel *selchannel; + + selchannel = calloc(1, sizeof(struct select_channel)); + selchannel->channel = CHANNEL_FREE; + + isdn_ep->in_channel = selchannel; +} + +/* parse string for a positive number */ +static int get_number(char *value) +{ + int val = 0; + char text[10]; + + val = atoi(value); + + sprintf(text, "%d", val); + + if (!strcmp(value, text)) + return val; + + return -1; +} + +/* remove element from buffer + * and return pointer to next element in buffer */ +static char *get_seperated(char *buffer) +{ + while(*buffer) { + if (*buffer==',' || *buffer<=32) { /* seperate */ + *buffer++ = '\0'; + while((*buffer>'\0' && *buffer<=32) || *buffer==',') + buffer++; + return(buffer); + } + buffer++; + } + return(buffer); +} + + +/* parse outgoing channel list */ +static int parse_out_channel(isdn_t *isdn_ep, const char *_list) +{ + char list[strlen(_list) + 1]; + struct select_channel *selchannel, **selchannel_p = &isdn_ep->out_channel; + int val; + char *p, *el; + + strcpy(list, _list); + p = list; + while(*p) { + el = p; + p = get_seperated(p); + if (!strcasecmp(el, "force")) { + isdn_ep->out_channel_exclusive = 1; + if (isdn_ep->out_channel) { + PDEBUG(DISDN, DEBUG_ERROR, "Error outgoing channel string: value 'force' may only appear as first element in list.\n"); + return(-1); + } + } else + if (!strcasecmp(el, "any")) { + val = CHANNEL_ANY; + goto selchannel; + } else + if (!strcasecmp(el, "free")) { + val = CHANNEL_FREE; + goto selchannel; + } else + if (!strcasecmp(el, "no")) { + val = CHANNEL_NO; + goto selchannel; + } else { + val = get_number(el); + if (val == -1) { + PDEBUG(DISDN, DEBUG_ERROR, "Error outgoing channel string: expecting a comma seperated list of 'force', 'any', 'free', 'no' and any channel number.\n"); + return(-1); + } + + if (val<1 || val==16 || val>126) { + PDEBUG(DISDN, DEBUG_ERROR, "Error outgoing channel string: channel '%d' out of range.\n", val); + return(-1); + } + selchannel: + /* add to select-channel list */ + selchannel = calloc(1, sizeof(struct select_channel)); + /* set value */ + selchannel->channel = val; + /* tail port */ + *selchannel_p = selchannel; + selchannel_p = &((*selchannel_p)->next); + } + } + return(0); +} + +/* parse incoming channel list */ +static int parse_in_channel(isdn_t *isdn_ep, const char *_list) +{ + char list[strlen(_list) + 1]; + struct select_channel *selchannel, **selchannel_p = &isdn_ep->in_channel; + int val; + char *p, *el; + + strcpy(list, _list); + p = list; + while(*p) { + el = p; + p = get_seperated(p); + if (isdn_ep->in_channel) if (isdn_ep->in_channel->channel == CHANNEL_FREE) { + PDEBUG(DISDN, DEBUG_ERROR, "Error incoming channel list: values behind 'free' keyword have no effect.\n"); + return(-1); + } + if (!strcasecmp(el, "free")) { + val = CHANNEL_FREE; + goto selchannel; + } else { + val = get_number(el); + if (val == -1) { + PDEBUG(DISDN, DEBUG_ERROR, "Error incoming channel list: expectng a comma seperated list of channel numbers and 'free'.\n"); + return(-1); + } + + if (val<1 || val==16 || val>126) { + PDEBUG(DISDN, DEBUG_ERROR, "Error incoming channel list: channel '%d' out of range.\n", val); + return(-1); + } + selchannel: + /* add to select-channel list */ + selchannel = calloc(1, sizeof(struct select_channel)); + /* set value */ + selchannel->channel = val; + /* tail port */ + *selchannel_p = selchannel; + selchannel_p = &((*selchannel_p)->next); + } + } + return(0); +} + +/* hunt bchannel for incoming setup */ +int hunt_bchannel_in(isdn_t *isdn_ep, int channel, int exclusive) +{ + struct select_channel *selchannel; + int i; + + PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (incoming call)\n"); + + if (exclusive<0) + exclusive = 0; + if (channel == CHANNEL_NO) + PDEBUG(DISDN, DEBUG_DEBUG, " -> request = no-channel\n"); + else if (channel > 0) + PDEBUG(DISDN, DEBUG_DEBUG, " -> request = channel %d%s\n", channel, exclusive?" (forced)":""); + else + PDEBUG(DISDN, DEBUG_DEBUG, " -> request = any channel\n"); + if (channel==CHANNEL_NO && !isdn_ep->ntmode) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = incoming call-waiting not supported for TE-mode\n"); + return(-6); // channel unacceptable + } + if (channel <= 0) /* not given, no channel, whatever.. */ + channel = CHANNEL_ANY; /* any channel */ + if (isdn_ep->b_reserved >= isdn_ep->b_num) { // of out chan.. + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = all channels are reserved\n"); + return(-34); // no channel + } + if (channel == CHANNEL_ANY) + goto get_from_list; + if (channel > 0) { + /* check for given channel in selection list */ + selchannel = isdn_ep->in_channel; + while(selchannel) { + if (selchannel->channel == channel || selchannel->channel == CHANNEL_FREE) + break; + selchannel = selchannel->next; + } + if (!selchannel) + channel = 0; + + /* exclusive channel requests must be in the list */ + if (exclusive) { + /* no exclusive channel */ + if (!channel) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = exclusively requested channel not in list\n"); + return(-6); // channel unacceptable + } + /* get index for channel */ + i = channel-1-(channel>=17); + if (i < 0 || i >= isdn_ep->b_num || channel == 16) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = exclusively requested channel outside interface range\n"); + return(-6); // channel unacceptable + } + /* check if busy */ + if (isdn_ep->b_call[i] == NULL) + goto use_channel; + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = exclusively requested channel is busy\n"); + return(-6); // channel unacceptable + } + + /* requested channels in list will be used */ + if (channel) { + /* get index for channel */ + i = channel-1-(channel>=17); + if (i < 0 || i >= isdn_ep->b_num || channel == 16) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> quested channel %d outside interface range\n", channel); + } else /* if inside range (else) check if available */ + if (isdn_ep->b_call[i] == NULL) + goto use_channel; + } + + /* if channel is not available or not in list, it must be searched */ + get_from_list: + /* check for first free channel in list */ + channel = 0; + selchannel = isdn_ep->in_channel; + while(selchannel) { + switch(selchannel->channel) { + case CHANNEL_FREE: /* free channel */ + PDEBUG(DISDN, DEBUG_DEBUG, " -> hunting free channel\n"); + if (isdn_ep->b_reserved >= isdn_ep->b_num) + break; /* all channel in use or reserverd */ + /* find channel */ + i = 0; + while(i < isdn_ep->b_num) { + if (isdn_ep->b_call[i] == NULL) { + channel = i+1+(i>=15); + break; + } + i++; + } + break; + + default: + PDEBUG(DISDN, DEBUG_DEBUG, " -> hunting channel %d\n", selchannel->channel); + if (selchannel->channel<1 || selchannel->channel==16) + break; /* invalid channels */ + i = selchannel->channel-1-(selchannel->channel>=17); + if (i >= isdn_ep->b_num) + break; /* channel not in port */ + if (isdn_ep->b_call[i] == NULL) { + channel = selchannel->channel; + break; + } + break; + } + if (channel) + break; /* found channel */ + selchannel = selchannel->next; + } + if (!channel) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel available\n"); + return(-6); // channel unacceptable + } + } +use_channel: + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = channel available\n"); + PDEBUG(DISDN, DEBUG_DEBUG, " -> connect to channel %d\n", channel); + return(channel); +} + +/* hunt bchannel for outgoing setup */ +int hunt_bchannel_out(isdn_t *isdn_ep, int *channel, int *exclusive) +{ + struct select_channel *selchannel; + int i; + + PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (outgoing call)\n"); + + /* see if link is up on PTP*/ + if (isdn_ep->l2hold && isdn_ep->l2link<1) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = layer 2 is down\n"); + return -27; + } + + /* check for channel form selection list */ + *exclusive = isdn_ep->out_channel_exclusive; + *channel = 0; + selchannel = isdn_ep->out_channel; + while(selchannel) { + switch(selchannel->channel) { + case CHANNEL_FREE: /* free channel */ + if (isdn_ep->b_reserved >= isdn_ep->b_num) + break; /* all channel in use or reserverd */ + /* find channel */ + i = 0; + while(i < isdn_ep->b_num) { + if (isdn_ep->b_call[i] == NULL) { + *channel = i+1+(i>=15); + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = free channel %d\n", *channel); + break; + } + i++; + } + if (*channel) + break; + PDEBUG(DISDN, DEBUG_DEBUG, " -> no free channel found\n"); + break; + + case CHANNEL_ANY: /* don't ask for channel */ + if (isdn_ep->b_reserved >= isdn_ep->b_num) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> cannot ask for any channel, because all channel are reserved\n"); + break; /* all channel in use or reserverd */ + } + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = any channel\n"); + *channel = CHANNEL_ANY; + break; + + case CHANNEL_NO: /* call waiting */ + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel\n"); + *channel = CHANNEL_NO; + break; + + default: + if (selchannel->channel<1 || selchannel->channel==16) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> channel %d is out of range\n", selchannel->channel); + break; /* invalid channels */ + } + i = selchannel->channel-1-(selchannel->channel>=17); + if (i >= isdn_ep->b_num) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> channel %d is out of range\n", selchannel->channel); + break; /* channel not in port */ + } + if (isdn_ep->b_call[i] == NULL) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = channel %d\n", selchannel->channel); + *channel = selchannel->channel; + break; + } + break; + } + if (*channel) + break; /* found channel */ + selchannel = selchannel->next; + } + + if (*channel) + return 0; + + /* if channel was found, return channel */ + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel found\n"); + + return -34; +} + +/* open channel of incoming setup */ +int open_bchannel_in(call_t *call, int channel, int exclusive) +{ + int rc; + + rc = seize_bchannel(call, channel, exclusive); + if (rc < 0) + return rc; + + bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE); + + return 0; +} + +/* open channel of outgoing setup */ +int open_bchannel_out(call_t *call, unsigned int cmd, int channel, int exclusive) +{ + int rc; + + /* correct exclusive to 0, if no explicit channel was given */ + if (exclusive < 0 || channel <= 0) + exclusive = 0; + + /* select scenario */ + if (call->b_channel && call->b_exclusive) { + /*** we gave an exclusive channel (or if we are done) ***/ + + /* if not first reply, we are done */ + if (call->state != ISDN_STATE_OUT_SETUP) + return(0); + + PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (reply from outgoing call)\n"); + + PDEBUG(DISDN, DEBUG_DEBUG, " -> request = %d (forced)\n", call->b_channel); + PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply = %d\n":" -> reply = (none)\n", channel); + + /* if give channel not accepted or not equal */ + if (channel != -1 && call->b_channel != channel) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = forced channel not accepted\n"); + return -44; + } + + rc = seize_bchannel(call, channel, 1); // exclusively + if (rc < 0) { + PDEBUG(DISDN, DEBUG_DEBUG, "result = requested channel not available anymore\n"); + return -47; + } + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = channel was accepted\n"); + + /* activate our exclusive channel */ + bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE); + } else + if (call->b_channel) { + /*** we gave a non-exclusive channel ***/ + + /* if not first reply, we are done */ + if (call->state != ISDN_STATE_OUT_SETUP) + return(0); + + PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (reply from outgoing call)\n"); + + PDEBUG(DISDN, DEBUG_DEBUG, " -> request = %d (suggest)\n", call->b_channel); + PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply = %d\n":" -> reply = (none)\n", channel); + + /* if channel was accepted as given */ + if (channel==-1 || call->b_channel==channel) { + call->b_exclusive = 1; // we are done + + /* if channel was accepted, try to get it */ + rc = seize_bchannel(call, channel, 1); // exclusively + if (rc < 0) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel not available\n"); + return -47; + } + + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = channel was accepted as given\n"); + + /* activate channel accepted by remote */ + bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE); + return(0); + } + + /* if channel value is faulty */ + if (channel <= 0) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = illegal reply\n"); + return -111; // protocol error + } + + /* if channel was not accepted, try to get it */ + rc = seize_bchannel(call, channel, 1); // exclusively + if (rc < 0) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel not available\n"); + return -47; + } + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel accepted\n"); + + /* activate channel given by remote */ + bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE); + } else + if (call->b_reserve) { + /*** we sent 'any channel acceptable' ***/ + + /* if not first reply, we are done */ + if (call->state != ISDN_STATE_OUT_SETUP) + return(0); + + PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (reply from outgoing call)\n"); + + PDEBUG(DISDN, DEBUG_DEBUG, " -> request = any\n"); + PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply = %d\n":" -> reply = (none)\n", channel); + /* if no channel was replied */ + if (channel <= 0) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel, protocol error\n"); + return -111; // protocol error + } + + /* we will see, if our received channel is available */ + rc = seize_bchannel(call, channel, 1); // exclusively + if (rc < 0) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel not available\n"); + return -47; + } + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel accepted\n"); + + /* activate channel given by remote */ + bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE); + } else { + /*** we sent 'no channel available' ***/ + + /* if not the first reply, but a connect, we are forced */ + if (cmd == MT_CONNECT && call->state != ISDN_STATE_OUT_SETUP) { + PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (connect reply from outgoing call)\n"); + + PDEBUG(DISDN, DEBUG_DEBUG, " -> request = no channel\n"); + PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply %d%s\n":" -> reply (none)\n", channel, exclusive?" (forced)":""); + if (channel > 0) { + goto use_from_connect; + } + rc = seize_bchannel(call, CHANNEL_ANY, 0); // any channel + if (rc < 0) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel available during call-waiting\n"); + return -34; + } + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = using channel %d\n", call->b_channel); + call->b_exclusive = 1; // we are done + + /* activate channel given by remote */ + bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE); + return(0); + } + + /* if not first reply, we are done */ + if (call->state != ISDN_STATE_OUT_SETUP) + return(0); + + PDEBUG(DISDN, DEBUG_DEBUG, "Channel Selection (reply from outgoing call)\n"); + + PDEBUG(DISDN, DEBUG_DEBUG, " -> request = no channel\n"); + PDEBUG(DISDN, DEBUG_DEBUG, (channel>=0)?" -> reply = %d\n":" -> reply = (none)\n", channel); + /* if first reply has no channel, we are done */ + if (channel <= 0) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = no channel until connect\n"); + return(0); + } + + /* we will see, if our received channel is available */ + use_from_connect: + rc = seize_bchannel(call, channel, exclusive); + if (rc < 0) { + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel not available\n"); + return -47; + } + PDEBUG(DISDN, DEBUG_DEBUG, " -> result = replied channel accepted\n"); + call->b_exclusive = 1; // we are done + + /* activate channel given by remote */ + bchannel_event(call->isdn_ep, call->b_index, B_EVENT_USE); + } + return(0); + +} + +/* + * ISDN instance + */ + +/* create isdn interface instance */ +isdn_t *isdn_create(void) +{ + isdn_t *isdn_ep; + + isdn_ep = calloc(1, sizeof(*isdn_ep)); + if (!isdn_ep) { + PDEBUG(DISDN, DEBUG_ERROR, "No memory!\n"); + abort(); + } + + if (pthread_mutex_init(&isdn_ep->upqueue_lock, NULL)) { + PDEBUG(DISDN, DEBUG_ERROR, "Cannot init mutex!\n"); + abort(); + } + + PDEBUG(DISDN, DEBUG_DEBUG, "ISDN instance created\n"); + + return isdn_ep; +} + +/* destroy isdn interface instance and free all resource */ +void isdn_destroy(isdn_t *isdn_ep) +{ + struct msn_list *msn; + + /* remove stack instance */ + isdn_close(isdn_ep); + + /* close mISDN socket */ + if (isdn_ep->socket > 0) + close(isdn_ep->socket); + + /* destroy all calls */ + while (isdn_ep->call_list) + call_destroy(isdn_ep->call_list); + + /* free msn list */ + while (isdn_ep->msn_list) { + msn = isdn_ep->msn_list; + isdn_ep->msn_list = msn->next; + free(msn); + } + + pthread_mutex_destroy(&isdn_ep->upqueue_lock); + + timer_exit(&isdn_ep->l2establish_timer); + + free(isdn_ep); + + PDEBUG(DISDN, DEBUG_DEBUG, "ISDN instance destroyed\n"); +} + +/* initialization and configuration to isdn interface instance */ +int isdn_initialize(isdn_t *isdn_ep, char law, const char *portname, int ntmode, int ptp, int layer1hold, int layer2hold, const char *channel_out, const char *channel_in, const char *timeouts, int tx_delay, int tx_gain, int rx_gain, const char *pipeline, int dtmf, int local_tones, int serving_location) +{ + int rc; + + /* open mISDN socket */ + rc = socket(PF_ISDN, SOCK_RAW, ISDN_P_BASE); + if (rc < 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Cannot open mISDN due to errno=%d:%s. (Does your Kernel support socket based mISDN? Protocol family is %d.)\n", errno, strerror(errno), PF_ISDN); + return -errno; + } + isdn_ep->socket = rc; + + /* store settings */ + isdn_ep->law = law; + isdn_ep->portname = strdup(portname); + isdn_ep->ntmode = ntmode; + isdn_ep->ptp = ptp; + isdn_ep->l1hold = layer1hold; + isdn_ep->l2hold = layer2hold; + isdn_ep->timeouts = timeouts; + isdn_ep->tx_delay = tx_delay; + isdn_ep->tx_gain = tx_gain; + isdn_ep->rx_gain = rx_gain; + isdn_ep->pipeline = pipeline; + isdn_ep->dtmf = dtmf; + isdn_ep->local_tones = local_tones; + isdn_ep->serving_location = serving_location; + + /* channel selection list */ + if (channel_out) { + rc = parse_out_channel(isdn_ep, channel_out); + if (rc < 0) + return rc; + } else + default_out_channel(isdn_ep); + if (channel_in) { + rc = parse_in_channel(isdn_ep, channel_in); + if (rc < 0) + return rc; + } else + default_in_channel(isdn_ep); + + PDEBUG(DISDN, DEBUG_DEBUG, "ISDN instance initialized\n"); + + return 0; +} + +/* add MSN number */ +void isdn_add_msn(isdn_t *isdn_ep, const char *msn) +{ + struct msn_list *m, **m_p; + + m = calloc(1, sizeof(*m) + strlen(msn) + 1); + if (!m) { + PDEBUG(DISDN, DEBUG_ERROR, "No memory!\n"); + abort(); + } + + m_p = &isdn_ep->msn_list; + while (*m_p) + m_p = &((*m_p)->next); + *m_p = m; + + strcpy(m->msn, msn); + + PDEBUG(DISDN, DEBUG_DEBUG, "added MSDN number '%s'\n", msn); +} + +/* + * call instance + */ + +call_t *call_create(isdn_t *isdn_ep, int channel, int exclusive, int mode) +{ + call_t *call, **call_p; + int rc; + + call = calloc(1, sizeof(*call)); + if (!call) { + PDEBUG(DISDN, DEBUG_ERROR, "No memory!\n"); + abort(); + } + + call_p = &isdn_ep->call_list; + while (*call_p) + call_p = &((*call_p)->next); + *call_p = call; + + call->isdn_ep = isdn_ep; + call->b_index = -1; + call->b_channel = 0; + call->b_exclusive = 0; + call->b_reserve = 0; + call->b_mode = mode; + call->hold = 0; + call->tx_gain = isdn_ep->tx_gain; + call->rx_gain = isdn_ep->rx_gain; + call->conf = 0; + call->mute = 0; + call->txdata = 0; + call->tx_delay = isdn_ep->tx_delay; + call->echo = 0; + call->tone = 0; + call->rxoff = 0; + call->dtmf = isdn_ep->dtmf; + call->dtmf_threshold = 0; + call->pipeline = isdn_ep->pipeline; + + /* if any channel requested by constructor */ + if (channel == CHANNEL_ANY) { + /* reserve channel */ + call->b_reserve = 1; + isdn_ep->b_reserved++; + } + + /* reserve channel */ + if (channel > 0) // only if constructor was called with a channel resevation + seize_bchannel(call, channel, exclusive); + + /* allocate jitter buffer */ + rc = jitter_create(&call->dejitter, 8000 / 10); // FIXME: size + if (rc < 0) + abort(); + + PDEBUG(DISDN, DEBUG_DEBUG, "Created new call on port #%d:%s\n", isdn_ep->portnum, isdn_ep->portname); + + return call; +} + +void call_destroy(call_t *call) +{ + call_t **call_p; + + /* free jitter buffer */ + jitter_destroy(&call->dejitter); + + /* remove B-channel relation */ + drop_bchannel(call); + + /* free session description */ + if (call->cc_session) + osmo_cc_free_session(call->cc_session); + free((char *)call->sdp); + + /* detach */ + call_p = &call->isdn_ep->call_list; + while (*call_p) { + if (*call_p == call) + break; + call_p = &((*call_p)->next); + } + *call_p = call->next; + + free(call); + + PDEBUG(DISDN, DEBUG_DEBUG, "destroyed call instance\n"); +} + +/* + * bchannel handling + */ + +/* send control information to the channel (dsp-module) */ +static void ph_control(int sock, uint32_t c1, uint32_t c2) +{ + uint8_t buffer[MISDN_HEADER_LEN + 4 + 4]; + struct mISDNhead *ctrl = (struct mISDNhead *)buffer; + uint32_t *d = (uint32_t *)(buffer + MISDN_HEADER_LEN); + int len = 8; + int rc; + + if (sock < 0) + return; + + if (c1 == DTMF_TONE_START && c2 == 0) + len = 4; + + PDEBUG(DISDN, DEBUG_DEBUG, "sending PH_CONTROL_REQ %d, %d\n", c1, c2); + + ctrl->prim = PH_CONTROL_REQ; + ctrl->id = 0; + *d++ = c1; + *d++ = c2; + rc = sendto(sock, buffer, MISDN_HEADER_LEN + len, 0, NULL, 0); + if (rc <= 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Failed to send to socket %d (errno=%d:%s)\n", sock, errno, strerror(errno)); + abort(); + } +} + +/* send control inforamtion, but with different length */ +static void ph_control_block(int sock, uint32_t c1, const void *c2, int c2_len) +{ + uint8_t *buffer[MISDN_HEADER_LEN + 4 + c2_len]; + struct mISDNhead *ctrl = (struct mISDNhead *)buffer; + uint32_t *d = (uint32_t *)(buffer + MISDN_HEADER_LEN); + int rc; + + if (sock < 0) + return; + + PDEBUG(DISDN, DEBUG_DEBUG, "sending PH_CONTROL_REQ %d\n", c1); + + ctrl->prim = PH_CONTROL_REQ; + ctrl->id = 0; + *d++ = c1; + memcpy(d, c2, c2_len); + rc = sendto(sock, buffer, sizeof(buffer), 0, NULL, 0); + if (rc <= 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Failed to send to socket %d (errno=%d:%s)\n", sock, errno, strerror(errno)); + abort(); + } +} + +/* create B-channel stack */ +static int bchannel_create(isdn_t *isdn_ep, int index) +{ + int channel = index + 1 + (index >= 15); + struct sockaddr_mISDN addr; + int flags; + int rc; + + memset(&addr, 0, sizeof(addr)); + + if (isdn_ep->b_sock[index] > 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Socket already created for index %d\n", index); + return -EIO; + } + + /* open socket */ + if (isdn_ep->b_mode[index] == B_MODE_HDLC) { + PDEBUG(DISDN, DEBUG_NOTICE, "Use B-Channel with HDLC l!!!\n"); + } + rc = socket(PF_ISDN, SOCK_DGRAM, (isdn_ep->b_mode[index] == B_MODE_HDLC) ? ISDN_P_B_L2DSPHDLC : ISDN_P_B_L2DSP); + if (rc < 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Failed to open bchannel-socket for index %d with mISDN-DSP layer. Did you load mISDN_dsp.ko?\n", index); + return(0); + } + isdn_ep->b_sock[index] = rc; + + /* set nonblocking io */ + flags = fcntl(isdn_ep->b_sock[index], F_GETFL); + flags |= O_NONBLOCK; + fcntl(isdn_ep->b_sock[index], F_SETFL, flags); + + /* bind socket to bchannel */ + addr.family = AF_ISDN; + addr.dev = isdn_ep->portnum; + addr.channel = channel; + rc = bind(isdn_ep->b_sock[index], (struct sockaddr *)&addr, sizeof(addr)); + if (rc < 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Failed to bind bchannel-socket for index %d with mISDN-DSP layer (errno=%d). Did you load mISDN_dsp.ko?\n", index, errno); + close(isdn_ep->b_sock[index]); + return -errno; + } + + PDEBUG(DISDN, DEBUG_DEBUG, "created socket #%d for B-channel %d\n", isdn_ep->b_sock[index], channel); + + return 0; +} + +/* activate or deactivate B-channel */ +static void bchannel_activate(isdn_t *isdn_ep, int index, int activate, int timeout) +{ + struct mISDNhead act; + int channel = index + 1 + (index >= 15); + int rc; + + + if (isdn_ep->b_sock[index] <= 0) + return; + + act.prim = (activate) ? PH_ACTIVATE_REQ : PH_DEACTIVATE_REQ; + act.id = 0; + rc = sendto(isdn_ep->b_sock[index], &act, MISDN_HEADER_LEN, 0, NULL, 0); + if (rc < 0) + PDEBUG(DISDN, DEBUG_ERROR, "Failed to send to socket #%d, of B-channel %d\n", isdn_ep->b_sock[index], channel); + + PDEBUG(DISDN, DEBUG_DEBUG, "%s B-channel %d%s\n", (activate) ? "activating" : "deactivating", channel, (timeout) ? " after timeout recovery" : ""); + + /* reset rx buffer */ + isdn_ep->b_buffer_pos[index] = 0; +} + +/* configure B-channel */ +static void bchannel_configure(isdn_t *isdn_ep, int index) +{ + call_t *call; + int handle, mode; + + if (isdn_ep->b_sock[index] <= 0) + return; + + handle = isdn_ep->b_sock[index]; + call = isdn_ep->b_call[index]; + mode = isdn_ep->b_mode[index]; + + /* set dsp features */ + if (call->txdata) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TXDATA*\n"); + ph_control(handle, (call->txdata) ? DSP_TXDATA_ON : DSP_TXDATA_OFF, 0); + } + if (call->tx_delay && mode == B_MODE_TRANSPARENT) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_DELAY\n"); + ph_control(handle, DSP_DELAY, call->tx_delay); + } + if (!call->tx_delay && mode == B_MODE_TRANSPARENT) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TX_DEJITTER\n"); + ph_control(handle, DSP_TX_DEJITTER, 1); + } + if (call->tx_gain && mode == B_MODE_TRANSPARENT) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_VOL_CHANGE_TX\n"); + ph_control(handle, DSP_VOL_CHANGE_TX, call->tx_gain); + } + if (call->rx_gain && mode == B_MODE_TRANSPARENT) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_VOL_CHANGE_RX\n"); + ph_control(handle, DSP_VOL_CHANGE_RX, call->rx_gain); + } + if (call->pipeline && call->pipeline[0] && mode == B_MODE_TRANSPARENT) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_PIPELINE_CFG\n"); + ph_control_block(handle, DSP_PIPELINE_CFG, call->pipeline, strlen(call->pipeline) + 1); + } + if (call->conf && !call->mute) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_CONF_JOIN\n"); + ph_control(handle, DSP_CONF_JOIN, call->conf); + } + if (call->echo) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_ECHO_ON\n"); + ph_control(handle, DSP_ECHO_ON, 0); + } + if (call->tone && mode == B_MODE_TRANSPARENT) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TONE_PATT_ON (tone=%d)\n", call->tone); + ph_control(handle, DSP_TONE_PATT_ON, call->tone); + } + if (call->rxoff) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_RECEIVE_OFF\n"); + ph_control(handle, DSP_RECEIVE_OFF, 0); + } + if (call->dtmf && mode == B_MODE_TRANSPARENT) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DTMF_TONE_START\n"); + ph_control(handle, DTMF_TONE_START, call->dtmf_threshold); + } +} + +void bchannel_tone(call_t *call, int tone) +{ + call->tone = tone; + + if (call->b_index < 0) + return; + + int handle = call->isdn_ep->b_sock[call->b_index]; + int mode = call->isdn_ep->b_mode[call->b_index]; + + if (mode != B_MODE_TRANSPARENT) + return; + + if (call->tone) { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TONE_PATT_ON (tone=%d)\n", call->tone); + ph_control(handle, DSP_TONE_PATT_ON, call->tone); + } else { + PDEBUG(DISDN, DEBUG_DEBUG, "PH_CONTROL: set DSP_TONE_PATT_OFF\n"); + ph_control(handle, DSP_TONE_PATT_OFF, 0); + } +} + +#if 0 +/* set bridge ID for B-channel */ +static void bchannel_conference(call_t *call, int oldconf, int newconf) +{ + int *sock_p = &call->isdn_ep->b_sock[call->b_index]; + int index, channel; + + index = call->b_index; + channel = index + 1 + (index >= 15); + + if (*sock_p <= 0) + return; + + if (oldconf != newconf) { + PDEBUG(DISDN, DEBUG_DEBUG, "change conference from conf=%d to conf=%d for channel %d\n", oldconf, newconf, channel); + if (index > -1 && call->isdn_ep->b_state[index] == B_STATE_ACTIVE) + ph_control(*sock_p, (newconf) ? DSP_CONF_JOIN : DSP_CONF_SPLIT, newconf); + } else + PDEBUG(DISDN, DEBUG_DEBUG, "we already have conf=%d at channel %d\n", newconf, channel); + +} +#endif + +/* destroy B-channel stack */ +static void bchannel_destroy(isdn_t *isdn_ep, int index) +{ + int channel = index + 1 + (index >= 15); + + if (isdn_ep->b_sock[index] <= 0) + return; + + PDEBUG(DISDN, DEBUG_DEBUG, "destroyed socket #%d for B-channel %d\n", isdn_ep->b_sock[index], channel); + + close(isdn_ep->b_sock[index]); + isdn_ep->b_sock[index] = 0; +} + +/* + * bchannel procedure + * ------------------ + * + * A bchannel goes through the following states in this order: + * + * - B_STATE_IDLE + * No one is using the bchannel. + * It is available and not linked to call instance, nor reserved. + * + * - B_STATE_ACTIVATING + * The bchannel stack is created and an activation request is sent. + * It MAY be linked to a call, but already unlinked due to call release. + * + * - B_STATE_ACTIVE + * The bchannel is active and cofigured for the need of the call. + * Also it is linked to a call, otherwhise it would be deactivated. + * + * - B_STATE_DEACTIVATING + * The bchannel is in deactivating state, due to deactivation request. + * It may be linked to a call, that likes to reactivate it. + * + * - B_STATE_IDLE + * See above. + * After deactivating bchannel, and if not used, the bchannel becomes idle again. + * + * A bchannel can have the following events: + * + * - B_EVENT_USE + * A bchannel is required by a call instance. + * + * - B_EVENT_ACTIVATED + * The bchannel beomes active. + * + * - B_EVENT_DROP + * The bchannel is not required by a call anymore + * + * - B_EVENT_DEACTIVATED + * The bchannel becomes inactive. + * + * All actions taken on these events depend on the current bchannel's state and + * wether it is linked to a call instance. + * + */ + +/* process bchannel events */ +void bchannel_event(isdn_t *isdn_ep, int index, int event) +{ + call_t *call; + int state; + int channel; + int timer = -1; // no change + + channel = index + 1 + (index >= 15); + call = isdn_ep->b_call[index]; + state = isdn_ep->b_state[index]; + + PDEBUG(DISDN, DEBUG_DEBUG, "handling event %d at state %d for B-channel %d\n", event, state, channel); + + switch(event) { + case B_EVENT_USE: + /* call must be linked in order to allow activation */ + if (!call) { + PDEBUG(DISDN, DEBUG_ERROR, "bchannel must be linked to a call instance\n"); + abort(); + } + switch(state) { + case B_STATE_IDLE: + /* create stack and send activation request */ + if (bchannel_create(isdn_ep, index) == 0) { + bchannel_activate(isdn_ep, index, 1, 0); + state = B_STATE_ACTIVATING; + timer = B_TIMER_ACTIVATING; + } + break; + + case B_STATE_ACTIVATING: + /* do nothing, because it is already activating */ + break; + + default: + /* problems that might ocurr: + * B_EVENT_USE is received when channel already in use. + */ + PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state); + } + break; + + + case B_EVENT_ACTIVATED: + timer = 0; + switch(state) { + case B_STATE_ACTIVATING: + if (call) { + /* bchannel is active and used by a call instance, so we configure bchannel */ + bchannel_configure(isdn_ep, index); + state = B_STATE_ACTIVE; + //FIXME: init buffer state for delay + } else { + /* bchannel is active, but not used anymore (or has wrong stack config), so we deactivate */ + bchannel_activate(isdn_ep, index, 0, 0); + state = B_STATE_DEACTIVATING; + timer = B_TIMER_DEACTIVATING; + } + break; + + default: + PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state); + } + break; + + case B_EVENT_DROP: + if (!call) { + PDEBUG(DISDN, DEBUG_ERROR, "bchannel must be linked to a call instance\n"); + abort(); + } + switch(state) { + case B_STATE_IDLE: + /* bchannel is idle due to an error, so we do nothing */ + break; + + case B_STATE_ACTIVATING: + /* do nothing because we must wait until bchanenl is active before deactivating */ + break; + + case B_STATE_ACTIVE: + /* bchannel is active, so we deactivate */ + bchannel_activate(isdn_ep, index, 0, 0); + state = B_STATE_DEACTIVATING; + timer = B_TIMER_DEACTIVATING; + break; + + case B_STATE_DEACTIVATING: + /* we may have taken an already deactivating bchannel, but do not require it anymore, so we do nothing */ + break; + + default: + PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state); + } + break; + + case B_EVENT_DEACTIVATED: + timer = 0; + switch(state) { + case B_STATE_IDLE: + /* ignore due to deactivation confirm after unloading */ + break; + + case B_STATE_DEACTIVATING: + bchannel_destroy(isdn_ep, index); + state = B_STATE_IDLE; + if (call) { + /* bchannel is now deactivate, but is requied by call instance, so we reactivate */ + if (bchannel_create(isdn_ep, index) == 0) { + bchannel_activate(isdn_ep, index, 1, 0); + state = B_STATE_ACTIVATING; + timer = B_TIMER_ACTIVATING; + } + } + break; + + default: + PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state); + } + break; + + case B_EVENT_TIMEOUT: + timer = 0; + switch(state) { + case B_STATE_IDLE: + /* ignore due to deactivation confirm after unloading */ + break; + + case B_STATE_ACTIVATING: + bchannel_activate(isdn_ep, index, 1, 1); + timer = B_TIMER_ACTIVATING; + break; + + case B_STATE_DEACTIVATING: + bchannel_activate(isdn_ep, index, 0, 1); + timer = B_TIMER_DEACTIVATING; + break; + + default: + PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d at state %d, please correct.\n", event, state); + } + break; + + default: + PDEBUG(DISDN, DEBUG_ERROR, "Illegal event %d, please correct.\n", event); + } + + isdn_ep->b_state[index] = state; + if (timer == 0) + timer_stop(&isdn_ep->b_timer[index]); + else if (timer > 0) + timer_start(&isdn_ep->b_timer[index], timer); +} + +static void bchannel_receive(isdn_t *isdn_ep, int index, struct mISDNhead *hh, unsigned char *data, int len); + +/* handle frames from B-channel */ +static int bchannel_work(isdn_t *isdn_ep, int index) +{ + int channel = index + 1 + (index >= 15); + unsigned char buffer[2048+MISDN_HEADER_LEN]; + struct mISDNhead *hh = (struct mISDNhead *)buffer; + int rc; + + rc = recv(isdn_ep->b_sock[index], buffer, sizeof(buffer), 0); + if (rc < 0) { + if (errno == EAGAIN) + return 0; + PDEBUG(DISDN, DEBUG_ERROR, "read error B-channel data (socket #%d errno=%d:%s)\n", isdn_ep->b_sock[index], errno, strerror(errno)); + return 0; + } + if (rc < (int)MISDN_HEADER_LEN) { + PDEBUG(DISDN, DEBUG_ERROR, "read short frame, got %d, expected %d\n", rc, (int)MISDN_HEADER_LEN); + return 0; + } + switch (hh->prim) { + /* we don't care about confirms, we use rx data to sync tx */ + case PH_DATA_CNF: + break; + + /* we receive audio data, we respond to it AND we send tones */ + case PH_DATA_IND: + case DL_DATA_IND: + case PH_DATA_REQ: + case DL_DATA_REQ: + case PH_CONTROL_IND: + if (isdn_ep->b_call[index]) + bchannel_receive(isdn_ep, index, hh, buffer + MISDN_HEADER_LEN, rc - MISDN_HEADER_LEN); + else + PDEBUG(DISDN, DEBUG_DEBUG, "b-channel is not associated to an ISDNPort (channel %d), ignoring.\n", channel); + break; + + case PH_ACTIVATE_IND: + case DL_ESTABLISH_IND: + case PH_ACTIVATE_CNF: + case DL_ESTABLISH_CNF: + PDEBUG(DISDN, DEBUG_DEBUG, "DL_ESTABLISH confirm: bchannel is now activated (channel %d).\n", channel); + bchannel_event(isdn_ep, index, B_EVENT_ACTIVATED); + break; + + case PH_DEACTIVATE_IND: + case DL_RELEASE_IND: + case PH_DEACTIVATE_CNF: + case DL_RELEASE_CNF: + PDEBUG(DISDN, DEBUG_DEBUG, "DL_RELEASE confirm: bchannel is now de-activated (channel %d).\n", channel); + bchannel_event(isdn_ep, index, B_EVENT_DEACTIVATED); + break; + + default: + PDEBUG(DISDN, DEBUG_ERROR, "child message not handled: prim(0x%x) channel(%d) msg->len(%d)\n", hh->prim, channel, rc - (int)MISDN_HEADER_LEN); + } + + return 1; +} + +static void b_timer_timeout(struct timer *timer) +{ + struct b_timer_inst *ti = timer->priv; + + bchannel_event(ti->isdn_ep, ti->index, B_EVENT_TIMEOUT); +} + +/* + * check for available channel and reserve+set it. + * give channel number or SEL_CHANNEL_ANY or SEL_CHANNEL_NO + * give exclusiv flag + * returns -(cause value) or x = channel x or 0 = no channel + * NOTE: no activation is done here + */ +int seize_bchannel(call_t *call, int channel, int exclusive) +{ + isdn_t *isdn_ep; + int index; + + isdn_ep = call->isdn_ep; + + /* the channel is what we already have */ + if (call->b_channel == channel) + return channel; + + /* if channel already in use, release it */ + if (call->b_channel) + drop_bchannel(call); + + /* if CHANNEL_NO */ + if (channel == CHANNEL_NO || channel == 0) + return 0; + + /* is channel out of range ? */ + if (channel == 16 + || (channel > isdn_ep->b_num && channel < 16) + || ((channel - 1) > isdn_ep->b_num && channel > 16)) /* channel-1 because channel 16 is not counted */ + return -6; /* channel unacceptable */ + + /* request exclusive channel */ + if (exclusive && channel > 0) { + index = channel - 1 - (channel > 16); + if (isdn_ep->b_call[index]) + return -44; /* requested channel not available */ + goto seize; + } + + /* ask for channel */ + if (channel > 0) { + index = channel - 1 - (channel > 16); + if (!isdn_ep->b_call[index]) + goto seize; + } + + /* search for channel */ + for (index = 0; index < isdn_ep->b_num; index++) { + if (!isdn_ep->b_call[index]) { + channel = index + 1 + (index >= 15); + goto seize; + } + } + return -34; /* no free channel */ + +seize: + PDEBUG(DISDN, DEBUG_DEBUG, "seizing bchannel %d (index %d)\n", channel, index); + + /* link Port, set parameters */ + isdn_ep->b_call[index] = call; + isdn_ep->b_mode[index] = call->b_mode; + call->b_index = index; + call->b_channel = channel; + call->b_exclusive = exclusive; + + /* reserve channel */ + if (!call->b_reserve) { + call->b_reserve = 1; + isdn_ep->b_reserved++; + } + + return channel; +} + +/* + * drop reserved channel and unset it. + * deactivation is also done + */ +void drop_bchannel(call_t *call) +{ + isdn_t *isdn_ep; + + isdn_ep = call->isdn_ep; + + /* unreserve channel */ + if (call->b_reserve) { + isdn_ep->b_reserved--; + call->b_reserve = 0; + } + + /* if not in use */ + if (call->b_index < 0) + return; + if (!call->b_channel) + return; + + PDEBUG(DISDN, DEBUG_DEBUG, "dropping bchannel %d (index %d)\n", call->b_channel, call->b_index); + + if (isdn_ep->b_state[call->b_index] != B_STATE_IDLE) + bchannel_event(isdn_ep, call->b_index, B_EVENT_DROP); + isdn_ep->b_call[call->b_index] = NULL; + isdn_ep->b_mode[call->b_index] = 0; + call->b_index = -1; + call->b_channel = 0; + call->b_exclusive = 0; +} + +static void send_to_rtp(call_t *call, unsigned char *data, int len) +{ + call_t *other; + + if (!call || !call->audio_path) + return; + + if (call->conference_3pty) { + int16_t *audio; + int audio_len; + sample_t samples_local[len], samples_remote_active[len], samples_remote_hold[len], mix[len]; + int i; + + /* there should be no call on hold with audio coming from */ + if (call->hold) + return; + + /* convert local audio from interface to samples */ + if (call->isdn_ep->law == 'a') + g711_decode_alaw_flipped(data, len, (uint8_t **)&audio, &audio_len); + else + g711_decode_ulaw_flipped(data, len, (uint8_t **)&audio, &audio_len); + int16_to_samples(samples_local, audio, len); + // don't free audio, because we need that later when encoding + + /* convert remote RTP to samples */ + jitter_load(&call->dejitter, samples_remote_active, len); + + /* search other party on hold */ + other = call->isdn_ep->call_list; + while (other) { + if (other != call + && other->l3_ces == call->l3_ces + && other->hold && other->conference_3pty) + break; + other = other->next; + } + + /* convert remote RTP to samples */ + if (other) + jitter_load(&other->dejitter, samples_remote_hold, len); + else + memset(samples_remote_hold, 0, sizeof(sample_t) * len); + + /* mix audio for local interface and forward */ + for (i = 0; i < len; i++) + mix[i] = samples_remote_active[i] + samples_remote_hold[i]; /* both remote parties */ + samples_to_int16(audio, mix, len); + if (call->isdn_ep->law == 'a') + g711_encode_alaw_flipped((uint8_t *)audio, audio_len, &data, &len); + else + g711_encode_ulaw_flipped((uint8_t *)audio, audio_len, &data, &len); + if (call->b_index >= 0) { + unsigned char buf[MISDN_HEADER_LEN + len]; + struct mISDNhead *frm = (struct mISDNhead *)buf; + int rc; + memcpy(buf + MISDN_HEADER_LEN, data, len); + frm->prim = PH_DATA_REQ; + frm->id = 0; + rc = send(call->isdn_ep->b_sock[call->b_index], buf, MISDN_HEADER_LEN + len, 0); + if (rc < 0) + PDEBUG(DISDN, DEBUG_ERROR, "write error B-channel data (socket #%d errno=%d:%s)\n", call->isdn_ep->b_sock[call->b_index], errno, strerror(errno)); + } + free(data); + + /* mix audio for (active) remote interface and forward */ + for (i = 0; i < len; i++) + mix[i] = samples_local[i] + samples_remote_hold[i]; /* local + remote (hold) party */ + samples_to_int16(audio, mix, len); + if (call->isdn_ep->law == 'a') + g711_encode_alaw_flipped((uint8_t *)audio, audio_len, &data, &len); + else + g711_encode_ulaw_flipped((uint8_t *)audio, audio_len, &data, &len); + osmo_cc_rtp_send(call->codec, data, len, 1, len); + free(data); + + /* mix audio for (hold) remote interface and forward */ + if (other) { + for (i = 0; i < len; i++) + mix[i] = samples_local[i] + samples_remote_active[i]; /* local + remote (active) party */ + samples_to_int16(audio, mix, len); + if (call->isdn_ep->law == 'a') + g711_encode_alaw_flipped((uint8_t *)audio, audio_len, &data, &len); + else + g711_encode_ulaw_flipped((uint8_t *)audio, audio_len, &data, &len); + osmo_cc_rtp_send(other->codec, data, len, 1, len); + free(data); + } + + free(audio); + return; + } + + osmo_cc_rtp_send(call->codec, data, len, 1, len); +} + +/* receive audio and control from B-channel */ +static void bchannel_receive(isdn_t *isdn_ep, int index, struct mISDNhead *hh, unsigned char *data, int len) +{ + uint8_t *buffer = isdn_ep->b_buffer[index]; + int *buffer_pos = &(isdn_ep->b_buffer_pos[index]); + unsigned int cont = *((unsigned int *)data); + + if (hh->prim == PH_CONTROL_IND) { + if (len < 4) { + PDEBUG(DISDN, DEBUG_ERROR, "SHORT READ OF PH_CONTROL INDICATION\n"); + return; + } + if ((cont & (~DTMF_TONE_MASK)) == DTMF_TONE_VAL) { +// send_cc_dtmf(call, cont & DTMF_TONE_MASK); +// FIXME: DTMF via telephony events?? + return; + } + return; + } + + if (hh->prim != PH_DATA_IND && hh->prim != DL_DATA_IND) + return; + + /* add to buffer */ + while (len) { + buffer[(*buffer_pos)++] = *data++; + len--; + if (*buffer_pos == 160) { + *buffer_pos = 0; + send_to_rtp(isdn_ep->b_call[index], buffer, 160); + } + } +} + +void isdn_rtp_work(isdn_t *isdn_ep) +{ + call_t *call; + + call = isdn_ep->call_list; + while (call) { + if (call->cc_session) + osmo_cc_session_handle(call->cc_session); + call = call->next; + } +} + +/* send audio to B-channel */ +void bchannel_send(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len) +{ + call_t *call = codec->media->session->priv; + + /* conference parties store their audio in jitter buffer */ + if (call->conference_3pty) { + int16_t *audio; + int audio_len; + sample_t samples[len]; + /* alaw/ulaw to linear */ + if (call->isdn_ep->law == 'a') + g711_decode_alaw_flipped(data, len, (uint8_t **)&audio, &audio_len); + else + g711_decode_ulaw_flipped(data, len, (uint8_t **)&audio, &audio_len); + int16_to_samples(samples, audio, len); + free(audio); + /* enqueue data to jitter buffer */ + jitter_save(&call->dejitter, samples, len); + return; + } + + /* no conference, just forward to ISDN interface */ + if (call->b_index >= 0) { + unsigned char buf[MISDN_HEADER_LEN + len]; + struct mISDNhead *frm = (struct mISDNhead *)buf; + int rc; + memcpy(buf + MISDN_HEADER_LEN, data, len); + frm->prim = PH_DATA_REQ; + frm->id = 0; + rc = send(call->isdn_ep->b_sock[call->b_index], buf, MISDN_HEADER_LEN + len, 0); + if (rc < 0) + PDEBUG(DISDN, DEBUG_ERROR, "write error B-channel data (socket #%d errno=%d:%s)\n", call->isdn_ep->b_sock[call->b_index], errno, strerror(errno)); + } +} + +/* + * isdn stack handling + */ + +int do_layer3(struct mlayer3 *ml3, unsigned int cmd, unsigned int pid, struct l3_msg *l3m) +{ + isdn_t *isdn_ep = (isdn_t *)ml3->priv; + struct mbuffer *mb; + + /* queue message, create, if required */ + if (!l3m) { + l3m = alloc_l3_msg(); + if (!l3m) { + PDEBUG(DISDN, DEBUG_ERROR, "No memory for layer 3 message\n"); + abort(); + } + } + mb = container_of(l3m, struct mbuffer, l3); + l3m->type = cmd; + l3m->pid = pid; + + pthread_mutex_lock(&isdn_ep->upqueue_lock); + mqueue_tail(&isdn_ep->upqueue, mb); + pthread_mutex_unlock(&isdn_ep->upqueue_lock); + + return 0; +} + +int mISDN_getportbyname(int sock, int cnt, const char *portname) +{ + struct mISDN_devinfo devinfo; + int port = 0, rc; + + memset(&devinfo, 0, sizeof(devinfo)); + + /* resolve name */ + while (port < cnt) { + devinfo.id = port; + rc = ioctl(sock, IMGETDEVINFO, &devinfo); + if (rc < 0) + return rc; + if (!strcasecmp(devinfo.name, portname)) + break; + port++; + } + if (port == cnt) + return -EIO; + + return port; +} + +static void l2establish_timeout(struct timer *timer); + +/* open mISDN port */ +int isdn_open(isdn_t *isdn_ep) +{ + const char *portname = isdn_ep->portname; + int portnum; + int ptp = isdn_ep->ptp; + int force_nt = isdn_ep->ntmode; + int l1hold = isdn_ep->l1hold; + int l2hold = isdn_ep->l2hold; + int i, cnt; + int pri, bri; + int nt, te; + struct mISDN_devinfo devinfo; + unsigned int protocol, prop; + int rc; + + /* queue must be initializes, because l3-thread may send messages during open_layer3() */ + mqueue_init(&isdn_ep->upqueue); + isdn_ep->upqueue_initialized = 1; + + /* port number given ? */ + for (i = 0; i < (int)strlen(portname); i++){ + if (portname[i] < '0' || portname[i] > '9') + break; + } + if (i == (int)strlen(portname)) + portnum = atoi(portname); + else + portnum = -1; + + memset(&devinfo, 0, sizeof(devinfo)); + + /* check port counts */ + rc = ioctl(isdn_ep->socket, IMGETCOUNT, &cnt); + if (rc < 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Cannot get number of mISDN devices. (ioctl IMGETCOUNT failed errno = %d)\n", errno); + goto error; + } + + if (cnt <= 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Found no card. Please be sure to load card drivers.\n"); + goto error; + } + if (portnum < 0) { + portnum = mISDN_getportbyname(isdn_ep->socket, cnt, portname); + if (portnum < 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Port name '%s' not found, use 'misdn_info' tool to list all existing ports.\n", portname); + goto error; + } + // note: 'portnum' has now the port number + } + if (portnum > cnt || portnum < 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Port (%d) given at 'ports' (options.conf) is out of existing port range (%d-%d)\n", portnum, 0, cnt); + goto error; + } + + /* get port attributes */ + pri = bri = nt = te = 0; + devinfo.id = portnum; + rc = ioctl(isdn_ep->socket, IMGETDEVINFO, &devinfo); + if (rc < 0) { + PDEBUG(DISDN, DEBUG_ERROR, "Cannot get device information for port %d. (ioctl IMGETDEVINFO failed errno=%d)\n", portnum, errno); + goto error; + } + if (devinfo.Dprotocols & (1 << ISDN_P_TE_S0)) { + bri = 1; + te = 1; + } + if (devinfo.Dprotocols & (1 << ISDN_P_NT_S0)) { + bri = 1; + nt = 1; + } + if (devinfo.Dprotocols & (1 << ISDN_P_TE_E1)) { + pri = 1; + te = 1; + } + if (devinfo.Dprotocols & (1 << ISDN_P_NT_E1)) { + pri = 1; + nt = 1; + } + if (force_nt && !nt) { + PDEBUG(DISDN, DEBUG_ERROR, "Port %d does not support NT-mode\n", portnum); + goto error; + } + if (bri && pri) { + PDEBUG(DISDN, DEBUG_ERROR, "Port %d supports BRI and PRI?? What kind of controller is that?. (Can't use this!)\n", portnum); + goto error; + } + if (!bri && !pri) { + PDEBUG(DISDN, DEBUG_ERROR, "Port %d does not support BRI nor PRI\n", portnum); + goto error; + } + if (!nt && !te) { + PDEBUG(DISDN, DEBUG_ERROR, "Port %d does not support NT-mode nor TE-mode!\n", portnum); + goto error; + } + /* set NT by turning off TE */ + if (force_nt && nt) + te = 0; + /* if TE an NT is supported (and not forced to NT), turn off NT */ + if (te && nt) + nt = 0; + + + /* check for continous channelmap with no bchannel on slot 16 */ + if (test_channelmap(0, devinfo.channelmap)) { + PDEBUG(DISDN, DEBUG_ERROR, "Port %d provides channel 0, but we cannot access it!\n", portnum); + goto error; + } + i = 1; + while(i < (int)devinfo.nrbchan + 1) { + if (i == 16) { + if (test_channelmap(i, devinfo.channelmap)) { + PDEBUG(DISDN, DEBUG_ERROR, "Port %d provides bchannel 16. Pleas upgrade mISDN, if this port is mISDN loopback interface.\n", portnum); + goto error; + } + } else { + if (!test_channelmap(i, devinfo.channelmap)) { + PDEBUG(DISDN, DEBUG_ERROR, "Port %d has no channel on slot %d!\n", portnum, i); + goto error; + } + } + i++; + } + + timer_init(&isdn_ep->l2establish_timer, l2establish_timeout, isdn_ep); + + isdn_ep->l1link = -1; + isdn_ep->l2link = -1; + + /* if pri, must set PTP */ + if (pri) + ptp = 1; + + /* set l2hold */ + switch (l2hold) { + case -1: // off + l2hold = 0; + break; + case 1: // on + l2hold = 1; + break; + default: + if (ptp) + l2hold = 1; + else + l2hold = 0; + break; + } + + /* allocate ressources of port */ + protocol = (nt) ? L3_PROTOCOL_DSS1_NET : L3_PROTOCOL_DSS1_USER; + prop = (1 << MISDN_FLG_L2_CLEAN); + if (ptp) // ptp forced + prop |= (1 << MISDN_FLG_PTP); + if (nt) // supports hold/retrieve on nt-mode + prop |= (1 << MISDN_FLG_NET_HOLD); + if (l1hold) // supports layer 1 hold + prop |= (1 << MISDN_FLG_L1_HOLD); + if (l2hold) // supports layer 2 hold + prop |= (1 << MISDN_FLG_L2_HOLD); + /* open layer 3 */ + isdn_ep->ml3 = open_layer3(portnum, protocol, prop, do_layer3, isdn_ep); + if (!isdn_ep->ml3) { + PDEBUG(DISDN, DEBUG_ERROR, "open_layer3() failed for port %d\n", portnum); + goto error; + } + + isdn_ep->b_num = devinfo.nrbchan; + isdn_ep->portnum = portnum; + if (isdn_ep->portname) + free(isdn_ep->portname); + isdn_ep->portname = strdup(devinfo.name); + isdn_ep->ntmode = nt; + isdn_ep->pri = pri; + isdn_ep->ptp = ptp; + isdn_ep->l1hold = l1hold; + isdn_ep->l2hold = l2hold; + PDEBUG(DISDN, DEBUG_DEBUG, "Port has %d b-channels.\n", isdn_ep->b_num); + for (i = 0; i < isdn_ep->b_num; i++) { + isdn_ep->b_state[i] = B_STATE_IDLE; + timer_init(&isdn_ep->b_timer[i], b_timer_timeout, &isdn_ep->b_timer_inst); + isdn_ep->b_timer_inst[i].isdn_ep = isdn_ep; + isdn_ep->b_timer_inst[i].index = i; + } + + /* if ptp, pull up the link */ + if (isdn_ep->l2hold && (isdn_ep->ptp || !isdn_ep->ntmode)) { + PDEBUG(DISDN, DEBUG_DEBUG, "reqzest layer 2 to be establised for tei 0\n"); + timer_start(&isdn_ep->l2establish_timer, 5.0); /* 5 seconds */ + } + + /* for nt-mode ptmp the link is always up */ + if (isdn_ep->ntmode && !isdn_ep->ptp) + isdn_ep->l2link = 1; + + PDEBUG(DISDN, DEBUG_DEBUG, "using 'mISDN_dsp.o' module\n"); + + return 0; + +error: + isdn_close(isdn_ep); + return -EINVAL; +} + +/* close mISDN port */ +void isdn_close(isdn_t *isdn_ep) +{ + int i; + struct select_channel *selchannel; + + /* free bchannels */ + for (i = 0; i < isdn_ep->b_num; i++) { + if (isdn_ep->b_sock[i] > 0) { + bchannel_destroy(isdn_ep, i); + PDEBUG(DISDN, DEBUG_DEBUG, "freeing %s port %s bchannel (index %d).\n", (isdn_ep->ntmode) ? "NT" : "TE", isdn_ep->portname, i); + } + timer_exit(&isdn_ep->b_timer[i]); + } + timer_exit(&isdn_ep->l2establish_timer); + + /* close layer 3, if open */ + if (isdn_ep->ml3) { + close_layer3(isdn_ep->ml3); + } + + /* purge upqueue */ + if (isdn_ep->upqueue_initialized) + mqueue_purge(&isdn_ep->upqueue); + + if (isdn_ep->portname) { + free(isdn_ep->portname); + isdn_ep->portname = NULL; + } + + while (isdn_ep->in_channel) { + selchannel = isdn_ep->in_channel; + isdn_ep->in_channel = selchannel->next; + free(selchannel); + } + while (isdn_ep->out_channel) { + selchannel = isdn_ep->out_channel; + isdn_ep->out_channel = selchannel->next; + free(selchannel); + } +} + +/* receive message from ISDN protocol stack (out of queue) */ +int isdn_dchannel_work(isdn_t *isdn_ep) +{ + struct mbuffer *mb; + struct l3_msg *l3m; + int w = 0; + + /* handle queued up-messages (d-channel) */ + pthread_mutex_lock(&isdn_ep->upqueue_lock); + mb = mdequeue(&isdn_ep->upqueue); + pthread_mutex_unlock(&isdn_ep->upqueue_lock); + if (mb) { + w |= 1; + l3m = &mb->l3; + switch(l3m->type) { + case MPH_ACTIVATE_IND: + if (isdn_ep->l1link != 1) { + PDEBUG(DISDN, DEBUG_INFO, "layer 1 becomes active\n"); + isdn_ep->l1link = 1; + } + break; + + case MPH_DEACTIVATE_IND: + if (isdn_ep->l1link != 0) { + PDEBUG(DISDN, DEBUG_INFO, "layer 1 becomes inactive\n"); + isdn_ep->l1link = 0; + } + break; + + case MPH_INFORMATION_IND: + switch (l3m->pid) { + case L1_SIGNAL_LOS_ON: + PDEBUG(DISDN, DEBUG_DEBUG, "received LOS\n"); + isdn_ep->los = 1; + break; + case L1_SIGNAL_LOS_OFF: + PDEBUG(DISDN, DEBUG_DEBUG, "LOS is gone\n"); + isdn_ep->los = 0; + break; + case L1_SIGNAL_AIS_ON: + PDEBUG(DISDN, DEBUG_DEBUG, "received AIS\n"); + isdn_ep->ais = 1; + break; + case L1_SIGNAL_AIS_OFF: + PDEBUG(DISDN, DEBUG_DEBUG, "AIS is gone\n"); + isdn_ep->ais = 0; + break; + case L1_SIGNAL_RDI_ON: + PDEBUG(DISDN, DEBUG_DEBUG, "received RDI\n"); + isdn_ep->rdi = 1; + break; + case L1_SIGNAL_RDI_OFF: + PDEBUG(DISDN, DEBUG_DEBUG, "RDI is gone\n"); + isdn_ep->rdi = 0; + break; + case L1_SIGNAL_SLIP_TX: + isdn_ep->slip_tx++; + PDEBUG(DISDN, DEBUG_DEBUG, "recevied TX slip #%d\n", isdn_ep->slip_tx); + break; + case L1_SIGNAL_SLIP_RX: + isdn_ep->slip_rx++; + PDEBUG(DISDN, DEBUG_DEBUG, "recevied RX slip #%d\n", isdn_ep->slip_rx); + break; + } + break; + + case MT_L2ESTABLISH: + PDEBUG(DISDN, DEBUG_INFO, "layer 2 becomes active (tei = %d)\n", l3m->pid); + isdn_ep->l2link = 1; + if (l3m->pid < 128) + isdn_ep->l2mask[l3m->pid >> 3] |= (1 << (l3m->pid & 7)); + if ((!isdn_ep->ntmode || isdn_ep->ptp) && l3m->pid < 127) { + if (timer_running(&isdn_ep->l2establish_timer)) + timer_stop(&isdn_ep->l2establish_timer); + } + break; + + case MT_L2RELEASE: + if (l3m->pid < 128) + isdn_ep->l2mask[l3m->pid >> 3] &= ~(1 << (l3m->pid & 7)); + if (!timer_running(&isdn_ep->l2establish_timer)) { + PDEBUG(DISDN, DEBUG_INFO, "layer 2 becomes inactive (tei = %d)\n", l3m->pid); + /* down if not nt-ptmp */ + if (!isdn_ep->ntmode || isdn_ep->ptp) + isdn_ep->l2link = 0; + } + if ((!isdn_ep->ntmode || isdn_ep->ptp) && l3m->pid < 127) { + if (!timer_running(&isdn_ep->l2establish_timer) && isdn_ep->l2hold) { + timer_start(&isdn_ep->l2establish_timer, 5.0); /* 5 seconds */ + isdn_ep->ml3->to_layer3(isdn_ep->ml3, MT_L2ESTABLISH, 0, NULL); + } + } + break; + + default: + /* l3-data is sent to DSS1 handling */ + dss1_receive(isdn_ep, l3m->type, l3m->pid, l3m); + } + /* free message */ + free_l3_msg(l3m); + } + + return w; +} + + +/* l2 establish timer fires */ +static void l2establish_timeout(struct timer *timer) +{ + isdn_t *isdn_ep = timer->priv; + + if (isdn_ep->l2hold && (isdn_ep->ptp || !isdn_ep->ntmode)) { + PDEBUG(DISDN, DEBUG_DEBUG, "the L2 establish timer expired, we try to establish the link portnum=%d.\n", isdn_ep->portnum); + isdn_ep->ml3->to_layer3(isdn_ep->ml3, MT_L2ESTABLISH, 0, NULL); + timer_start(&isdn_ep->l2establish_timer, 5.0); /* 5 seconds */ + } +} + +void isdn_bchannel_work(isdn_t *isdn_ep) +{ + int w, i; + + for (i = 0; i < isdn_ep->b_num; i++) { + do { + if (isdn_ep->b_sock[i] > 0) + w = bchannel_work(isdn_ep, i); + else + w = 0; + } while (w); + } +} diff --git a/src/isdn/isdn.h b/src/isdn/isdn.h new file mode 100644 index 0000000..ab6a0e2 --- /dev/null +++ b/src/isdn/isdn.h @@ -0,0 +1,201 @@ + +#include +#include "../libtimer/timer.h" +#include "../libosmocc/endpoint.h" +#include "../libosmocc/helper.h" +#include "../libsample/sample.h" +#include "../libjitter/jitter.h" + +#define B_MODE_TRANSPARENT 0 +#define B_MODE_HDLC 1 + +#define B_STATE_IDLE 0 /* not open */ +#define B_STATE_ACTIVATING 1 /* DL_ESTABLISH sent */ +#define B_STATE_ACTIVE 2 /* channel active */ +#define B_STATE_DEACTIVATING 3 /* DL_RELEASE sent */ + +#define B_EVENT_USE 0 /* activate bchannel */ +#define B_EVENT_ACTIVATED 1 /* DL_ESTABLISH received */ +#define B_EVENT_DROP 2 /* deactivate bchannel */ +#define B_EVENT_DEACTIVATED 3 /* DL_RELEASE received */ +#define B_EVENT_TIMEOUT 4 /* timeout happed during (de)activation */ + +#define TONES_TYPE_AMERICAN 1 +#define TONES_TYPE_GERMAN 2 +#define TONES_TYPE_OLDGERMAN 3 + +enum isdn_state { + ISDN_STATE_IDLE = 0, /* no call */ + ISDN_STATE_IN_SETUP, /* incoming connection */ + ISDN_STATE_OUT_SETUP, /* outgoing connection */ + ISDN_STATE_IN_OVERLAP, /* more informatiopn needed */ + ISDN_STATE_OUT_OVERLAP, /* more informatiopn needed */ + ISDN_STATE_IN_PROCEEDING,/* call is proceeding */ + ISDN_STATE_OUT_PROCEEDING,/* call is proceeding */ + ISDN_STATE_IN_ALERTING, /* call is ringing */ + ISDN_STATE_OUT_ALERTING,/* call is ringing */ + ISDN_STATE_IN_CONNECTING,/* wait for ack after answer */ + ISDN_STATE_OUT_CONNECTING,/* wait for ack after answer */ + ISDN_STATE_CONNECT, /* call is connected and transmission is enabled */ + ISDN_STATE_IN_DISCONNECT,/* incoming disconnected */ + ISDN_STATE_OUT_DISCONNECT,/* outgoing disconnected */ + ISDN_STATE_OUT_RELEASE, /* call released */ + ISDN_STATE_SUSPENDED, /* call suspended */ +}; + +struct isdn; + +struct b_timer_inst { + struct isdn *isdn_ep; + int index; +}; + +struct msn_list { + struct msn_list *next; + char msn[0]; +}; + +struct select_channel { + struct select_channel *next; + int channel; +}; + +struct call_list; + +typedef struct isdn { + struct call_list *call_list; + struct msn_list *msn_list; + + /* settings */ + char law; + struct select_channel *out_channel; /* list of outgoing channels to select */ + struct select_channel *in_channel; /* the same for incoming channels */ + int out_channel_exclusive; /* force channel */ + int serving_location; /* who we serve when sending causes towards interface */ + const char *timeouts; + int tx_delay; + int tx_gain; + int rx_gain; + const char *pipeline; + int dtmf; + int local_tones; + + /* osmo-cc */ + struct osmo_cc_endpoint cc_ep; + + /* mISDN */ + char *portname; + int portnum; + int ntmode; + int ptp; + int pri; + int l1hold; + int l2hold; + int socket; + pthread_mutex_t upqueue_lock; + struct mqueue upqueue; + int upqueue_initialized; + struct mlayer3 *ml3; + int los, ais, rdi, slip_rx, slip_tx; + int l1link; /* current state */ + int l2link; /* current state */ + struct timer l2establish_timer; + int b_num; + int b_reserved; + int b_mode[128]; + int b_state[128]; + int b_sock[128]; + struct call_list *b_call[128]; + struct timer b_timer[128]; + struct b_timer_inst b_timer_inst[128]; + uint8_t b_buffer[128][160]; + int b_buffer_pos[128]; + unsigned char l2mask[16]; /* 128 bits for each tei */ +} isdn_t; + +typedef struct call_list { + struct call_list *next; + isdn_t *isdn_ep; + + /* mISDN states */ + uint32_t l3_pid; + uint16_t l3_ces; + int tx_gain; + int rx_gain; + int mute; + int txdata; + int tx_delay; + int echo; + int conf; + int tone; + int rxoff; + int dtmf; + int dtmf_threshold; + const char *pipeline; + + /* osmo-cc states */ + uint32_t cc_callref; + osmo_cc_session_t *cc_session; + osmo_cc_session_codec_t *codec; + const char *sdp; + char codec_law; + int codec_negotiated; + int audio_path; + + /* bchannel states */ + int b_index; + int b_channel; + int b_exclusive; + int b_reserve; + int b_mode; + + /* call states */ + enum isdn_state state; + int any_dialing; /* if any digit was dialed, we track this for dial tone */ + int channel_negotiated; + int send_local_tones; /* using locally generated tones, because upper layer does not privide them */ + int send_remote_tones; /* using remote tones, as we received a progress indicator 1 or 8 from upper layer */ + int proceeding_sent; + int sending_complete; /* send proceeding when osmo-cc requests overlap */ + int setup_comp_req_channel_assignment; /* must assign channel on setup comp req */ + uint8_t collect_cause, collect_location; + int hold; /* if call is on hold */ + int conference_3pty; /* if call is the active call in a 3pty conference */ + int park_len; + uint8_t park_callid[8]; + + /* jitter buffer for 3pty call */ + jitter_t dejitter; + +} call_t; + +int check_mISDN_dsp(void); + +/* channel selection */ +int hunt_bchannel_in(isdn_t *isdn_ep, int channel, int exclusive); +int hunt_bchannel_out(isdn_t *isdn_ep, int *channel, int *exclusive); +int open_bchannel_in(call_t *call, int channel, int exclusive); +int open_bchannel_out(call_t *call, unsigned int cmd, int channel, int exclusive); + +/* isdn instance */ +isdn_t *isdn_create(void); +void isdn_destroy(isdn_t *isdn_ep); +int isdn_initialize(isdn_t *isdn_ep, char law, const char *portname, int ntmode, int ptp, int layer1hold, int layer2hold, const char *channel_out, const char *channel_in, const char *timeouts, int tx_delay, int tx_gain, int rx_gain, const char *pipeline, int dtmf, int local_tones, int serving_location); +int isdn_open(isdn_t *isdn_ep); +void isdn_close(isdn_t *isdn_ep); +void isdn_add_msn(isdn_t *isdn_ep, const char *msn); +int isdn_dchannel_work(isdn_t *isdn_ep); +void isdn_bchannel_work(isdn_t *isdn_ep); +void isdn_rtp_work(isdn_t *isdn_ep); + +/* call instance */ +call_t *call_create(isdn_t *isdn_ep, int channel, int exclusive, int mode); +void call_destroy(call_t *call); + +/* channel allocation and handling */ +void bchannel_tone(call_t *call, int tone); +void bchannel_event(isdn_t *isdn_ep, int index, int event); +int seize_bchannel(call_t *call, int channel, int exclusive); +void drop_bchannel(call_t *call); +void bchannel_send(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len); + diff --git a/src/isdn/main.c b/src/isdn/main.c new file mode 100644 index 0000000..f67e3d1 --- /dev/null +++ b/src/isdn/main.c @@ -0,0 +1,378 @@ +/* osmo-cc-misdn-endpoint main + * + * (C) 2020 by Andreas Eversberg + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../libdebug/debug.h" +#include "../liboptions/options.h" +#include "../libg711/g711.h" +#include +#include "isdn.h" +#include "dss1.h" + +isdn_t *isdn_ep = NULL; +int num_kanal = 1; + +static char law = 'a'; +static const char *portname = "0"; +static int ntmode = 0; +static int ptp = 0; +static int layer1hold = 0; +static int layer2hold = 0; +static const char *channel_out = NULL; +static const char *channel_in = NULL; +static const char *timeouts = NULL; +static int tx_delay = 0; +static int tx_gain = 0; +static int rx_gain = 0; +static const char *pipeline = NULL; +static int dtmf = 1; +static int local_tones = 0; +static int debug_mISDN = 0; +static int serving_location = 1; /* private network serving local user */ +#define MAX_CC_ARGS 1024 +static int cc_argc = 0; +static const char *cc_argv[MAX_CC_ARGS]; + +static void print_usage(const char *app) +{ + printf("Usage: %s [--port ] [--nt] []\n", app); +} + +static void print_help() +{ + /* - - */ + printf(" -h --help\n"); + printf(" This help\n"); + printf(" -v --verbose | ,[,[,...]] | list\n"); + printf(" Use 'list' to get a list of all levels and categories\n"); + printf(" Verbose level: digit of debug level (default = '%d')\n", debuglevel); + printf(" Verbose level+category: level digit followed by one or more categories\n"); + printf(" -> If no category is specified, all categories are selected\n"); + printf(" --ulaw\n"); + printf(" Use U-LAW for b-channel coding instead of alaw.\n"); + printf(" -p --port | \n"); + printf(" Number or name of misdn port (see misdn_info). (Default = %s)\n", portname); + printf(" -n --nt\n"); + printf(" The given port is configured as NT-mode, instead of TE-mode.\n"); + printf(" -0 --ptp\n"); + printf(" The given port is configured as point-to-point. PRI-Ports are always\n"); + printf(" configured as point-to-point.\n"); + printf(" -M --msn [--msn ...]\n"); + printf(" Give one or multiple MSN numbers. If MSN numbers are defined, only\n"); + printf(" MSN numbers that are defined are allowed. If a different MSN number\n"); + printf(" is received from a phone, it is replaced by the first MSN number.\n"); + printf(" This is only useful for NT-mode with point-to-mulipoint cofiguration.\n"); + printf(" (Any MSN is accepted and forwarded by default.).\n"); + printf(" -1 --layer-1-hold 0 | 1\n"); + printf(" Keep layer 1 always active. (Default for point-to-point)\n"); + printf(" -2 --layer-2-hold 0 | 1\n"); + printf(" Keep layer 2 always active. (Default for point-to-point)\n"); + printf(" --channel-out [force,][][,...][,free][,any][,no]\n"); + printf(" Channel selection list for all outgoing calls to the interface.\n"); + printf(" A free channels is searched in order of appearance.\n"); + printf(" force - Forces the selected port with no acceptable alternative.\n"); + printf(" -> this will be automatically set for multipoint NT-mode ports\n"); + printf(" [,...] - List of channels to search.\n"); + printf(" free - Select any free channel\n"); + printf(" any - On outgoing calls, signal 'any channel acceptable'. (see DSS1)\n"); + printf(" no - Signal 'no channel available' aka 'call waiting'. (see DSS1)\n"); + printf(" --channel-in [][,...][,free]\n"); + printf(" Give list of channels to select for calls from ISDN\n"); + printf(" Channel selection list for all incoming calls from the interface.\n"); + printf(" A free channels is accepted if in the list.\n"); + printf(" If any channel was requested, the first free channel found is selected.\n"); + printf(" [,...] - List of channels to accept.\n"); + printf(" free - Accept any free channel\n"); + + printf(" --timeouts ,,,,\n"); + printf(" Alter ISDN protocol times.\n"); + printf(" The default is 120 seconds for all states. Use 0 to disable.\n"); + printf(" --tx-delay \n"); + printf(" Give a delay in milliseconds. This is required for modem/fax. Audio\n"); + printf(" toward ISDN interface is buffered with the given delay.\n"); + printf(" This feature turns off the dejittering.\n"); + printf(" --tx-gain \n"); + printf(" Changes gain of audio towards ISDN interface. Give Gain in steps of\n"); + printf(" 6 dB. (-48 .. 48)\n"); + printf(" --rx-gain \n"); + printf(" Changes gain of audio coming from ISDN interface. Give Gain in steps\n"); + printf(" of 6 dB. (-48 .. 48)\n"); + printf(" --pipeline \n"); + printf(" mISDN allows to use echo cancelation modules. See mISDN documentation.\n"); + printf(" --dtmf 1 | 0\n"); + printf(" Turns DTMF detection on or off (default is %d).\n", dtmf); + printf(" -T --local-tones german | oldgerman | american\n"); + printf(" Send locally generated tones, if not provided by remote interface.\n"); + printf(" -D --debug-misdn\n"); + printf(" Enables mISDN stack debugging.\n"); + printf(" --serving-location (see Q.931)\n"); + printf(" 0 = user, 1 = private network serving local user (default=%d)\n", serving_location); + printf(" -C --cc \"\" [--cc ...]\n"); + printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n"); +} + +#define OPT_ULAW 256 +#define OPT_CHANNEL_OUT 257 +#define OPT_CHANNEL_IN 258 +#define OPT_TIMEOUTS 259 +#define OPT_TX_DELAY 260 +#define OPT_TX_GAIN 261 +#define OPT_RX_GAIN 262 +#define OPT_PIPELINE 263 +#define OPT_DTMF 264 +#define OPT_SERVING 265 + +static void add_options(void) +{ + option_add('h', "help", 0); + option_add('v', "verbose", 1); + option_add(OPT_ULAW, "ulaw", 0); + option_add('p', "port", 1); + option_add('n', "nt", 0); + option_add('0', "ptp", 0); + option_add('M', "msn", 1); + option_add('1', "layer-1-hold", 1); + option_add('2', "layer-2-hold", 1); + option_add(OPT_CHANNEL_OUT, "channel-out", 1); + option_add(OPT_CHANNEL_IN, "channel-in", 1); + option_add(OPT_TIMEOUTS, "timeouts", 1); + option_add(OPT_TX_DELAY, "tx-delay", 1); + option_add(OPT_TX_GAIN, "tx-gain", 1); + option_add(OPT_RX_GAIN, "rx-gain", 1); + option_add(OPT_PIPELINE, "pipeline", 1); + option_add(OPT_DTMF, "dtmf", 1); + option_add('T', "local-tones", 1); + option_add('D', "debug-misdn", 0); + option_add(OPT_SERVING, "serving-location", 0); + option_add('C', "cc", 1); +} + +static int handle_options(int short_option, int argi, char **argv) +{ + int rc; + + switch (short_option) { + case 'h': + print_usage(argv[0]); + print_help(); + return 0; + case 'v': + if (!strcasecmp(argv[argi], "list")) { + debug_list_cat(); + return 0; + } + rc = parse_debug_opt(argv[argi]); + if (rc < 0) { + fprintf(stderr, "Failed to parse debug option, please use -h for help.\n"); + return rc; + } + break; + case OPT_ULAW: + law = 'u'; + break; + case 'p': + portname = strdup(argv[argi]); + break; + case 'n': + ntmode = 1; + break; + case '0': + ptp = 1; + break; + case 'm': + isdn_add_msn(isdn_ep, argv[argi]); + break; + case '1': + layer1hold = atoi(argv[argi]); + break; + case '2': + layer2hold = atoi(argv[argi]); + break; + case OPT_CHANNEL_OUT: + channel_out = strdup(argv[argi]); + break; + case OPT_CHANNEL_IN: + channel_in = strdup(argv[argi]); + break; + case OPT_TIMEOUTS: + timeouts = strdup(argv[argi]); + break; + case OPT_TX_DELAY: + tx_delay = atoi(argv[argi]); + break; + case OPT_TX_GAIN: + tx_gain = atoi(argv[argi]) / 6; + break; + case OPT_RX_GAIN: + rx_gain = atoi(argv[argi]) / 6; + break; + case OPT_PIPELINE: + pipeline = strdup(argv[argi]); + break; + case OPT_DTMF: + dtmf = atoi(argv[argi]); + break; + case 'T': + if (!strcasecmp(argv[argi], "american")) + local_tones = TONES_TYPE_AMERICAN; + else if (!strcasecmp(argv[argi], "german")) + local_tones = TONES_TYPE_GERMAN; + else if (!strcasecmp(argv[argi], "oldgerman")) + local_tones = TONES_TYPE_OLDGERMAN; + else { + fprintf(stderr, "Invalid tones type given!\n"); + return -EINVAL; + } + break; + case 'D': + debug_mISDN = 1; + break; + case OPT_SERVING: + serving_location = atoi(argv[argi]); + break; + case 'C': + if (!strcasecmp(argv[argi], "help")) { + osmo_cc_help(); + return 0; + } + if (cc_argc == MAX_CC_ARGS) { + fprintf(stderr, "Too many osmo-cc args!\n"); + break; + } + cc_argv[cc_argc++] = strdup(argv[argi]); + break; + default: + return -EINVAL; + } + return 1; +} + +static int quit = 0; +void sighandler(int sigset) +{ + if (sigset == SIGHUP || sigset == SIGPIPE) + return; + + fprintf(stderr, "\nSignal %d received.\n", sigset); + + quit = 1; +} + +static struct mi_ext_fn_s mi_fn; + +static int mISDNlib_debug(const char *file, int line, const char *func, int __attribute__((unused)) level, const char *fmt, va_list va) +{ + char text[256]; + vsnprintf(text, sizeof(text) - 1, fmt, va); + text[sizeof(text) - 1] = '\0'; +// PDEBUG(DDSS1, DEBUG_NOTICE, "libmisdn (file %s, line %d, func %s(), level %d): %s\n", file, line, func, level, text); + _printdebug(file, func, line, DMISDN, DEBUG_NOTICE, NULL, "%s", text); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int argi, rc; + int layer3_initialized = 0; + + g711_init(); + + isdn_ep = isdn_create(); + if (!isdn_ep) + goto error; + + cc_argv[cc_argc++] = strdup("remote auto"); + + /* handle options / config file */ + add_options(); + rc = options_config_file("~/.osmocom/isdn/isdn.conf", handle_options); + if (rc < 0) + return 0; + argi = options_command_line(argc, argv, handle_options); + if (argi <= 0) + return argi; + + /* misdn init and debug */ + rc = check_mISDN_dsp(); + if (rc) + goto error; + mi_fn.prt_debug = mISDNlib_debug; + init_layer3(4, &mi_fn); + layer3_initialized = 1; + mISDN_set_debug_level((debug_mISDN) ? 0xfffffeff : 0); + + rc = isdn_initialize(isdn_ep, law, portname, ntmode, ptp, layer1hold, layer2hold, channel_out, channel_in, timeouts, tx_delay, tx_gain, rx_gain, pipeline, dtmf, local_tones, serving_location); + if (rc) { + PDEBUG(DISDN, DEBUG_ERROR, "mISDN initializing failed!\n"); + goto error; + } + + rc = isdn_open(isdn_ep); + if (rc) { + PDEBUG(DISDN, DEBUG_ERROR, "mISDN open failed!\n"); + goto error; + } + + rc = osmo_cc_new(&isdn_ep->cc_ep, OSMO_CC_VERSION, isdn_ep->portname, serving_location, cc_message, NULL, isdn_ep, cc_argc, cc_argv); + if (rc < 0) + goto error; + + signal(SIGINT, sighandler); + signal(SIGHUP, sighandler); + signal(SIGTERM, sighandler); + signal(SIGPIPE, sighandler); + + while (!quit) { + int w; + process_timer(); + isdn_bchannel_work(isdn_ep); + isdn_rtp_work(isdn_ep); + do { + w = 0; + w |= osmo_cc_handle(); + w |= isdn_dchannel_work(isdn_ep); + } while (w); + usleep(1000); + } + + signal(SIGINT, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + +error: + if (isdn_ep) { + osmo_cc_delete(&isdn_ep->cc_ep); + isdn_destroy(isdn_ep); + } + + if (layer3_initialized) + cleanup_layer3(); + + return 0; +} +