From f7cd2544398d60afc6999aa41f5f51600a62e301 Mon Sep 17 00:00:00 2001 From: Moises Silva Date: Wed, 19 Aug 2009 21:47:56 +0000 Subject: [PATCH] added initial OpenR2 support git-svn-id: http://svn.openzap.org/svn/openzap/trunk@808 a93c3328-9c30-0410-af19-c9cd2b2d52af --- libs/freetdm/Makefile.am | 17 +- libs/freetdm/configure.ac | 3 + libs/freetdm/mod_openzap/mod_openzap.c | 329 +++- libs/freetdm/src/include/openzap.h | 13 +- libs/freetdm/src/include/zap_types.h | 6 +- libs/freetdm/src/ozmod/ozmod_r2/ozmod_r2.c | 1368 +++++++++++++++++ .../src/ozmod/ozmod_wanpipe/ozmod_wanpipe.c | 69 +- libs/freetdm/src/ozmod/ozmod_zt/ozmod_zt.c | 40 +- libs/freetdm/src/testr2.c | 86 ++ libs/freetdm/src/zap_config.c | 4 +- libs/freetdm/src/zap_io.c | 9 +- 11 files changed, 1899 insertions(+), 45 deletions(-) create mode 100644 libs/freetdm/src/ozmod/ozmod_r2/ozmod_r2.c create mode 100644 libs/freetdm/src/testr2.c diff --git a/libs/freetdm/Makefile.am b/libs/freetdm/Makefile.am index bce0cf2bd5..c8d334c864 100644 --- a/libs/freetdm/Makefile.am +++ b/libs/freetdm/Makefile.am @@ -101,7 +101,7 @@ core-install: install-libLTLIBRARIES # # tools & test programs # -noinst_PROGRAMS = testtones detect_tones detect_dtmf testisdn testpri testboost testanalog testapp testcid +noinst_PROGRAMS = testtones detect_tones detect_dtmf testisdn testpri testr2 testboost testanalog testapp testcid testapp_SOURCES = $(SRC)/testapp.c testapp_LDADD = libopenzap.la @@ -131,6 +131,10 @@ testpri_SOURCES = $(SRC)/testpri.c testpri_LDADD = libopenzap.la testpri_CFLAGS = $(AM_CFLAGS) $(MY_CFLAGS) +testr2_SOURCES = $(SRC)/testr2.c +testr2_LDADD = libopenzap.la +testr2_CFLAGS = $(AM_CFLAGS) $(MY_CFLAGS) + testboost_SOURCES = $(SRC)/testboost.c testboost_LDADD = libopenzap.la testboost_CFLAGS = $(AM_CFLAGS) $(MY_CFLAGS) @@ -152,6 +156,10 @@ if LIBPRI mod_LTLIBRARIES += ozmod_libpri.la endif +if OPENR2 +mod_LTLIBRARIES += ozmod_r2.la +endif + ozmod_zt_la_SOURCES = $(SRC)/ozmod/ozmod_zt/ozmod_zt.c ozmod_zt_la_CFLAGS = $(AM_CFLAGS) $(MY_CFLAGS) ozmod_zt_la_LDFLAGS = -module -avoid-version @@ -218,6 +226,13 @@ ozmod_libpri_la_LDFLAGS = -module -avoid-version -lpri ozmod_libpri_la_LIBADD = $(MYLIB) endif +if OPENR2 +ozmod_r2_la_SOURCES = $(SRC)/ozmod/ozmod_r2/ozmod_r2.c +ozmod_r2_la_CFLAGS = $(AM_CFLAGS) $(MY_CFLAGS) +ozmod_r2_la_LDFLAGS = -module -avoid-version -lopenr2 +ozmod_r2_la_LIBADD = $(MYLIB) +endif + dox doxygen: cd docs && doxygen $(OZ_SRCDIR)/docs/Doxygen.conf diff --git a/libs/freetdm/configure.ac b/libs/freetdm/configure.ac index e89564af7a..64f7a46438 100644 --- a/libs/freetdm/configure.ac +++ b/libs/freetdm/configure.ac @@ -168,6 +168,9 @@ AM_CONDITIONAL([LIBSANGOMA],[test "${have_libsangoma}" = "yes"]) AM_CONDITIONAL([LIBPRI],[test "${enable_libpri}" = "yes"]) +AC_CHECK_LIB([openr2], [openr2_context_set_io_type], [have_openr2="yes"]) +AM_CONDITIONAL([OPENR2],[test "${have_openr2}" = "yes"]) + COMP_VENDOR_CFLAGS="$COMP_VENDOR_CFLAGS" AC_SUBST(COMP_VENDOR_CFLAGS) AC_CONFIG_FILES([Makefile diff --git a/libs/freetdm/mod_openzap/mod_openzap.c b/libs/freetdm/mod_openzap/mod_openzap.c index e2a7db22c0..905627920f 100644 --- a/libs/freetdm/mod_openzap/mod_openzap.c +++ b/libs/freetdm/mod_openzap/mod_openzap.c @@ -24,6 +24,7 @@ * Contributor(s): * * Anthony Minessale II + * Moises Silva * * * mod_openzap.c -- OPENZAP Endpoint Module @@ -446,6 +447,7 @@ static switch_status_t channel_on_hangup(switch_core_session_t *session) switch (tech_pvt->zchan->type) { case ZAP_CHAN_TYPE_FXO: case ZAP_CHAN_TYPE_EM: + case ZAP_CHAN_TYPE_CAS: { zap_set_state_locked(tech_pvt->zchan, ZAP_CHANNEL_STATE_HANGUP); @@ -723,6 +725,65 @@ static switch_status_t channel_write_frame(switch_core_session_t *session, switc } +static switch_status_t channel_receive_message_cas(switch_core_session_t *session, switch_core_session_message_t *msg) +{ + switch_channel_t *channel; + private_t *tech_pvt; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + tech_pvt = (private_t *) switch_core_session_get_private(session); + assert(tech_pvt != NULL); + + zap_log(ZAP_LOG_DEBUG, "Got Freeswitch message in R2 channel %d [%d]\n", tech_pvt->zchan->physical_chan_id, + msg->message_id); + + switch (msg->message_id) { + case SWITCH_MESSAGE_INDICATE_RINGING: + { + if (switch_channel_test_flag(channel, CF_OUTBOUND)) { + zap_set_flag_locked(tech_pvt->zchan, ZAP_CHANNEL_PROGRESS); + } else { + zap_set_state_locked_wait(tech_pvt->zchan, ZAP_CHANNEL_STATE_PROGRESS); + } + } + break; + case SWITCH_MESSAGE_INDICATE_PROGRESS: + { + if (switch_channel_test_flag(channel, CF_OUTBOUND)) { + zap_set_flag_locked(tech_pvt->zchan, ZAP_CHANNEL_PROGRESS); + zap_set_flag_locked(tech_pvt->zchan, ZAP_CHANNEL_MEDIA); + } else { + zap_set_state_locked_wait(tech_pvt->zchan, ZAP_CHANNEL_STATE_PROGRESS); + zap_set_state_locked_wait(tech_pvt->zchan, ZAP_CHANNEL_STATE_PROGRESS_MEDIA); + } + } + break; + case SWITCH_MESSAGE_INDICATE_ANSWER: + { + if (switch_channel_test_flag(channel, CF_OUTBOUND)) { + zap_set_flag_locked(tech_pvt->zchan, ZAP_CHANNEL_ANSWERED); + } else { + /* lets make the ozmod_r2 module life easier by moving thru each + * state waiting for completion, clumsy, but does the job + */ + if (tech_pvt->zchan->state < ZAP_CHANNEL_STATE_PROGRESS) { + zap_set_state_locked_wait(tech_pvt->zchan, ZAP_CHANNEL_STATE_PROGRESS); + } + if (tech_pvt->zchan->state < ZAP_CHANNEL_STATE_PROGRESS_MEDIA) { + zap_set_state_locked_wait(tech_pvt->zchan, ZAP_CHANNEL_STATE_PROGRESS_MEDIA); + } + zap_set_state_locked_wait(tech_pvt->zchan, ZAP_CHANNEL_STATE_UP); + } + } + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} static switch_status_t channel_receive_message_b(switch_core_session_t *session, switch_core_session_message_t *msg) { @@ -876,7 +937,10 @@ static switch_status_t channel_receive_message(switch_core_session_t *session, s break; case ZAP_CHAN_TYPE_B: status = channel_receive_message_b(session, msg); - break; + break; + case ZAP_CHAN_TYPE_CAS: + status = channel_receive_message_cas(session, msg); + break; default: status = SWITCH_STATUS_FALSE; break; @@ -1473,6 +1537,106 @@ static ZIO_SIGNAL_CB_FUNCTION(on_fxs_signal) return status; } +static ZIO_SIGNAL_CB_FUNCTION(on_r2_signal) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + zap_status_t status = ZAP_SUCCESS; + + zap_log(ZAP_LOG_DEBUG, "Got R2 channel sig [%s] in channel %d\n", zap_signal_event2str(sigmsg->event_id), sigmsg->channel->physical_chan_id); + + switch(sigmsg->event_id) { + /* on_call_disconnect from the R2 side */ + case ZAP_SIGEVENT_STOP: + { + while((session = zap_channel_get_session(sigmsg->channel, 0))) { + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, sigmsg->channel->caller_data.hangup_cause); + zap_channel_clear_token(sigmsg->channel, switch_core_session_get_uuid(session)); + switch_core_session_rwunlock(session); + } + } + break; + + /* on_call_offered from the R2 side */ + case ZAP_SIGEVENT_START: + { + status = zap_channel_from_event(sigmsg, &session); + } + break; + + /* on DNIS received from the R2 forward side, return status == ZAP_BREAK to stop requesting DNIS */ + case ZAP_SIGEVENT_COLLECTED_DIGIT: + { + char *regex = SPAN_CONFIG[sigmsg->channel->span->span_id].dial_regex; + char *fail_regex = SPAN_CONFIG[sigmsg->channel->span->span_id].fail_dial_regex; + + if (switch_strlen_zero(regex)) { + regex = NULL; + } + + if (switch_strlen_zero(fail_regex)) { + fail_regex = NULL; + } + + zap_log(ZAP_LOG_DEBUG, "R2 DNIS so far [%s]\n", sigmsg->channel->caller_data.dnis.digits); + + if ((regex || fail_regex) && !switch_strlen_zero(sigmsg->channel->caller_data.dnis.digits)) { + switch_regex_t *re = NULL; + int ovector[30]; + int match = 0; + + if (fail_regex) { + match = switch_regex_perform(sigmsg->channel->caller_data.dnis.digits, fail_regex, &re, ovector, sizeof(ovector) / sizeof(ovector[0])); + status = match ? ZAP_SUCCESS : ZAP_BREAK; + switch_regex_safe_free(re); + } + + if (status == ZAP_SUCCESS && regex) { + match = switch_regex_perform(sigmsg->channel->caller_data.dnis.digits, regex, &re, ovector, sizeof(ovector) / sizeof(ovector[0])); + status = match ? ZAP_BREAK : ZAP_SUCCESS; + } + + switch_regex_safe_free(re); + } + } + break; + + case ZAP_SIGEVENT_PROGRESS: + { + if ((session = zap_channel_get_session(sigmsg->channel, 0))) { + channel = switch_core_session_get_channel(session); + switch_channel_mark_ring_ready(channel); + switch_core_session_rwunlock(session); + } + } + break; + + case ZAP_SIGEVENT_UP: + { + if ((session = zap_channel_get_session(sigmsg->channel, 0))) { + zap_tone_type_t tt = ZAP_TONE_DTMF; + channel = switch_core_session_get_channel(session); + switch_channel_mark_answered(channel); + if (zap_channel_command(sigmsg->channel, ZAP_COMMAND_ENABLE_DTMF_DETECT, &tt) != ZAP_SUCCESS) { + zap_log(ZAP_LOG_ERROR, "Failed to enable DTMF detection in R2 channel %d:%d\n", sigmsg->channel->span_id, sigmsg->channel->chan_id); + } + switch_core_session_rwunlock(session); + } + } + break; + + default: + { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unhandled event %d from R2 for channel %d:%d\n", + sigmsg->event_id, sigmsg->channel->span_id, sigmsg->channel->chan_id); + } + break; + } + + return status; +} + static ZIO_SIGNAL_CB_FUNCTION(on_clear_channel_signal) { switch_core_session_t *session = NULL; @@ -2154,7 +2318,170 @@ static switch_status_t load_config(void) } } + if ((spans = switch_xml_child(cfg, "r2_spans"))) { + for (myspan = switch_xml_child(spans, "span"); myspan; myspan = myspan->next) { + char *id = (char *) switch_xml_attr(myspan, "id"); + char *name = (char *) switch_xml_attr(myspan, "name"); + zap_status_t zstatus = ZAP_FAIL; + /* strings */ + const char *variant = "itu"; + const char *category = "national_subscriber"; + const char *logdir = "/usr/local/freeswitch/log/"; /* FIXME: get PREFIX variable */ + const char *logging = "notice,warning,error"; + const char *advanced_protocol_file = ""; + + /* booleans */ + int call_files = 0; + int get_ani_first = -1; + int immediate_accept = -1; + int double_answer = -1; + int skip_category = -1; + int forced_release = -1; + int charge_calls = -1; + + /* integers */ + int mfback_timeout = -1; + int metering_pulse_timeout = -1; + int allow_collect_calls = -1; + int max_ani = 10; + int max_dnis = 4; + + /* common non r2 stuff */ + const char *context = "default"; + const char *dialplan = "XML"; + char *dial_regex = NULL; + char *fail_dial_regex = NULL; + uint32_t span_id = 0; + zap_span_t *span = NULL; + + + for (param = switch_xml_child(myspan, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + + /* string parameters */ + if (!strcasecmp(var, "variant")) { + variant = val; + } else if (!strcasecmp(var, "category")) { + category = val; + } else if (!strcasecmp(var, "logdir")) { + logdir = val; + } else if (!strcasecmp(var, "logging")) { + logging = val; + } else if (!strcasecmp(var, "advanced_protocol_file")) { + advanced_protocol_file = val; + + /* booleans */ + } else if (!strcasecmp(var, "allow_collect_calls")) { + allow_collect_calls = switch_true(val); + } else if (!strcasecmp(var, "immediate_accept")) { + immediate_accept = switch_true(val); + } else if (!strcasecmp(var, "double_answer")) { + double_answer = switch_true(val); + } else if (!strcasecmp(var, "skip_category")) { + skip_category = switch_true(var); + } else if (!strcasecmp(var, "forced_release")) { + forced_release = switch_true(val); + } else if (!strcasecmp(var, "charge_calls")) { + charge_calls = switch_true(val); + } else if (!strcasecmp(var, "get_ani_first")) { + get_ani_first = switch_true(val); + } else if (!strcasecmp(var, "call_files")) { + call_files = switch_true(val); + + /* integers */ + } else if (!strcasecmp(var, "mfback_timeout")) { + mfback_timeout = atoi(val); + } else if (!strcasecmp(var, "metering_pulse_timeout")) { + metering_pulse_timeout = atoi(val); + } else if (!strcasecmp(var, "max_ani")) { + max_ani = atoi(val); + } else if (!strcasecmp(var, "max_dnis")) { + max_dnis = atoi(val); + + /* common non r2 stuff */ + } else if (!strcasecmp(var, "context")) { + context = val; + } else if (!strcasecmp(var, "dialplan")) { + dialplan = val; + } else if (!strcasecmp(var, "dial-regex")) { + dial_regex = val; + } else if (!strcasecmp(var, "fail-dial-regex")) { + fail_dial_regex = val; + } + } + + if (!id && !name) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "span missing required param 'id'\n"); + continue; + } + + if (name) { + zstatus = zap_span_find_by_name(name, &span); + } else { + if (switch_is_number(id)) { + span_id = atoi(id); + zstatus = zap_span_find(span_id, &span); + } + + if (zstatus != ZAP_SUCCESS) { + zstatus = zap_span_find_by_name(id, &span); + } + } + + if (zstatus != ZAP_SUCCESS) { + zap_log(ZAP_LOG_ERROR, "Error finding OpenZAP span id:%s name:%s\n", switch_str_nil(id), switch_str_nil(name)); + continue; + } + + if (!span_id) { + span_id = span->span_id; + } + + if (zap_configure_span("r2", span, on_r2_signal, + "variant", variant, + "max_ani", max_ani, + "max_dnis", max_dnis, + "category", category, + "logdir", logdir, + "logging", logging, + "advanced_protocol_file", advanced_protocol_file, + "allow_collect_calls", allow_collect_calls, + "immediate_accept", immediate_accept, + "double_answer", double_answer, + "skip_category", skip_category, + "forced_release", forced_release, + "charge_calls", charge_calls, + "get_ani_first", get_ani_first, + "call_files", call_files, + "mfback_timeout", mfback_timeout, + "metering_pulse_timeout", metering_pulse_timeout, + TAG_END) != ZAP_SUCCESS) { + zap_log(ZAP_LOG_ERROR, "Error configuring R2 OpenZAP span %d, error: %s\n", + span_id, span->last_error); + continue; + } + + if (dial_regex) { + switch_set_string(SPAN_CONFIG[span->span_id].dial_regex, dial_regex); + } + + if (fail_dial_regex) { + switch_set_string(SPAN_CONFIG[span->span_id].fail_dial_regex, fail_dial_regex); + } + + SPAN_CONFIG[span->span_id].span = span; + switch_copy_string(SPAN_CONFIG[span->span_id].context, context, sizeof(SPAN_CONFIG[span->span_id].context)); + switch_copy_string(SPAN_CONFIG[span->span_id].dialplan, dialplan, sizeof(SPAN_CONFIG[span->span_id].dialplan)); + switch_copy_string(SPAN_CONFIG[span->span_id].type, "r2", sizeof(SPAN_CONFIG[span->span_id].type)); + + if (zap_span_start(span) == ZAP_FAIL) { + zap_log(ZAP_LOG_ERROR, "Error starting R2 OpenZAP span %d, error: %s\n", span_id, span->last_error); + continue; + } + } + } switch_xml_free(xml); diff --git a/libs/freetdm/src/include/openzap.h b/libs/freetdm/src/include/openzap.h index 5600219d96..6b0c703f23 100644 --- a/libs/freetdm/src/include/openzap.h +++ b/libs/freetdm/src/include/openzap.h @@ -280,6 +280,17 @@ } \ } while(0); +#define zap_locked_wait_for_flag_cleared(obj, flag, time) \ + do { \ + int __safety = time; \ + while(__safety-- && zap_test_flag(obj, flag)) { \ + zap_sleep(10); \ + } \ + if(!__safety) { \ + zap_log(ZAP_LOG_CRIT, "flag %d was never cleared\n", flag); \ + } \ + } while(0); + typedef enum { ZAP_STATE_CHANGE_FAIL, @@ -517,7 +528,7 @@ struct zap_channel { struct zap_span *span; struct zap_io_interface *zio; zap_hash_t *variable_hash; - unsigned char cas_bits; + unsigned char rx_cas_bits; }; diff --git a/libs/freetdm/src/include/zap_types.h b/libs/freetdm/src/include/zap_types.h index 13e8c2a7a3..713e67e3e8 100644 --- a/libs/freetdm/src/include/zap_types.h +++ b/libs/freetdm/src/include/zap_types.h @@ -175,7 +175,8 @@ typedef enum { ZAP_SIGTYPE_RBS, ZAP_SIGTYPE_ANALOG, ZAP_SIGTYPE_SS7BOOST, - ZAP_SIGTYPE_M3UA + ZAP_SIGTYPE_M3UA, + ZAP_SIGTYPE_R2 } zap_signal_type_t; typedef enum { @@ -279,6 +280,9 @@ typedef enum { ZAP_COMMAND_GET_RX_GAIN, ZAP_COMMAND_SET_TX_GAIN, ZAP_COMMAND_GET_TX_GAIN, + ZAP_COMMAND_FLUSH_TX_BUFFERS, + ZAP_COMMAND_FLUSH_RX_BUFFERS, + ZAP_COMMAND_FLUSH_BUFFERS, ZAP_COMMAND_COUNT } zap_command_t; diff --git a/libs/freetdm/src/ozmod/ozmod_r2/ozmod_r2.c b/libs/freetdm/src/ozmod/ozmod_r2/ozmod_r2.c new file mode 100644 index 0000000000..0ffb61d0b0 --- /dev/null +++ b/libs/freetdm/src/ozmod/ozmod_r2/ozmod_r2.c @@ -0,0 +1,1368 @@ +/* + * Copyright (c) 2009, Moises Silva + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of the original author; nor the names of any contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include "openzap.h" + +/* debug thread count for r2 legs */ +static zap_mutex_t* g_thread_count_mutex; +static int32_t g_thread_count = 0; + +/* when the users kills a span we clear this flag to kill the signaling thread */ +/* FIXME: what about the calls that are already up-and-running? */ +typedef enum { + ZAP_R2_RUNNING = (1 << 0), +} zap_r2_flag_t; + +/* private call information stored in zchan->call_data void* ptr */ +#define R2CALL(zchan) ((zap_r2_call_t*)((zchan)->call_data)) +typedef struct zap_r2_call_t { + openr2_chan_t *r2chan; + int accepted:1; + int answer_pending:1; + int state_ack_pending:1; + int disconnect_rcvd:1; + int zap_started:1; + zap_channel_state_t chanstate; + zap_size_t dnis_index; + zap_size_t ani_index; + char name[10]; +} zap_r2_call_t; + +/* this is just used as place holder in the stack when configuring the span to avoid using bunch of locals */ +typedef struct oz_r2_conf_s { + /* openr2 types */ + openr2_variant_t variant; + openr2_calling_party_category_t category; + openr2_log_level_t loglevel; + + /* strings */ + char *logdir; + char *advanced_protocol_file; + + /* ints */ + int32_t max_ani; + int32_t max_dnis; + int32_t mfback_timeout; + int32_t metering_pulse_timeout; + + /* booleans */ + int immediate_accept; + int skip_category; + int get_ani_first; + int call_files; + int double_answer; + int charge_calls; + int forced_release; + int allow_collect_calls; +} oz_r2_conf_t; + +/* r2 configuration stored in span->signal_data */ +typedef struct zap_r2_data_s { + /* signaling callback */ + zio_signal_cb_t sig_cb; + /* span flags */ + zap_r2_flag_t flags; + /* openr2 handle for the R2 variant context */ + openr2_context_t *r2context; + /* category to use when making calls */ + openr2_calling_party_category_t category; + /* whether to use OR2_CALL_WITH_CHARGE or OR2_CALL_NO_CHARGE when accepting a call */ + int charge_calls:1; + /* allow or reject collect calls */ + int allow_collect_calls:1; + /* whether to use forced release when hanging up */ + int forced_release:1; + /* whether accept the call when offered, or wait until the user decides to accept */ + int accept_on_offer:1; +} zap_r2_data_t; + +/* one element per span will be stored in g_mod_data_hash global var to keep track of them + and destroy them on module unload */ +typedef struct zap_r2_span_pvt_s { + openr2_context_t *r2context; /* r2 context allocated for this span */ + zap_hash_t *r2calls; /* hash table of allocated call data per channel for this span */ +} zap_r2_span_pvt_t; + +/* span monitor thread */ +static void *zap_r2_run(zap_thread_t *me, void *obj); + +/* channel monitor thread */ +static void *zap_r2_channel_run(zap_thread_t *me, void *obj); + +/* hash of all the private span allocations + we need to keep track of them to destroy them when unloading the module + since openzap does not notify signaling modules when destroying a span + span -> zap_r2_mod_allocs_t */ +static zap_hash_t *g_mod_data_hash; + +/* IO interface for the command API */ +static zap_io_interface_t g_zap_r2_interface; + +static void oz_r2_clean_call(zap_r2_call_t *call) +{ + openr2_chan_t *r2chan = call->r2chan; + memset(call, 0, sizeof(*call)); + call->r2chan = r2chan; +} + +static void oz_r2_accept_call(zap_channel_t *zchan) +{ + openr2_chan_t *r2chan = R2CALL(zchan)->r2chan; + // FIXME: not always accept as no charge, let the user decide that + // also we should check the return code from openr2_chan_accept_call and handle error condition + // hanging up the call with protocol error as the reason, this openr2 API will fail only when there something + // wrong at the I/O layer or the library itself + openr2_chan_accept_call(r2chan, OR2_CALL_NO_CHARGE); + R2CALL(zchan)->accepted = 1; +} + +static void oz_r2_answer_call(zap_channel_t *zchan) +{ + openr2_chan_t *r2chan = R2CALL(zchan)->r2chan; + // FIXME + // 1. check openr2_chan_answer_call return code + // 2. The openr2_chan_answer_call_with_mode should be used depending on user settings + // openr2_chan_answer_call_with_mode(r2chan, OR2_ANSWER_SIMPLE); + openr2_chan_answer_call(r2chan); + R2CALL(zchan)->answer_pending = 0; +} + +static ZIO_CHANNEL_OUTGOING_CALL_FUNCTION(r2_outgoing_call) +{ + zap_status_t status; + zap_mutex_lock(zchan->mutex); + + /* the channel may be down but the thread not quite done */ + zap_locked_wait_for_flag_cleared(zchan, ZAP_CHANNEL_INTHREAD, 200); + + if (zap_test_flag(zchan, ZAP_CHANNEL_INTHREAD)) { + zap_log(ZAP_LOG_ERROR, "%d:%d Yay! R2 outgoing call in channel that is already in thread.\n", + zchan->span_id, zchan->chan_id); + zap_mutex_unlock(zchan->mutex); + return ZAP_FAIL; + } + + oz_r2_clean_call(zchan->call_data); + R2CALL(zchan)->chanstate = ZAP_CHANNEL_STATE_DOWN; + zap_channel_set_state(zchan, ZAP_CHANNEL_STATE_DIALING, 0); + zap_set_flag(zchan, ZAP_CHANNEL_OUTBOUND); + R2CALL(zchan)->zap_started = 1; + zap_mutex_unlock(zchan->mutex); + + status = zap_thread_create_detached(zap_r2_channel_run, zchan); + if (status == ZAP_FAIL) { + zap_log(ZAP_LOG_ERROR, "%d:%d Cannot handle request to start call in channel, failed to create thread!\n", + zchan->span_id, zchan->chan_id); + zap_channel_done(zchan); + return ZAP_FAIL; + } + + return ZAP_SUCCESS; +} + +static zap_status_t zap_r2_start(zap_span_t *span) +{ + zap_r2_data_t *r2_data = span->signal_data; + zap_set_flag(r2_data, ZAP_R2_RUNNING); + return zap_thread_create_detached(zap_r2_run, span); +} + +/* always called from the monitor thread */ +static void zap_r2_on_call_init(openr2_chan_t *r2chan) +{ + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + zap_status_t status; + zap_log(ZAP_LOG_NOTICE, "Received request to start call on chan %d\n", openr2_chan_get_number(r2chan)); + + zap_mutex_lock(zchan->mutex); + + if (zchan->state != ZAP_CHANNEL_STATE_DOWN) { + zap_log(ZAP_LOG_ERROR, "Cannot handle request to start call in channel %d, invalid state (%d)\n", + openr2_chan_get_number(r2chan), zchan->state); + zap_mutex_unlock(zchan->mutex); + return; + } + + /* the channel may be down but the thread not quite done */ + zap_locked_wait_for_flag_cleared(zchan, ZAP_CHANNEL_INTHREAD, 200); + + if (zap_test_flag(zchan, ZAP_CHANNEL_INTHREAD)) { + zap_log(ZAP_LOG_ERROR, "Cannot handle request to start call in channel %d, already in thread!\n", + openr2_chan_get_number(r2chan)); + zap_mutex_unlock(zchan->mutex); + return; + } + oz_r2_clean_call(zchan->call_data); + R2CALL(zchan)->chanstate = ZAP_CHANNEL_STATE_DOWN; + zap_channel_set_state(zchan, ZAP_CHANNEL_STATE_COLLECT, 0); + zap_mutex_unlock(zchan->mutex); + + status = zap_thread_create_detached(zap_r2_channel_run, zchan); + if (status == ZAP_FAIL) { + zap_log(ZAP_LOG_ERROR, "Cannot handle request to start call in channel %d, failed to create thread!\n", + openr2_chan_get_number(r2chan)); + } +} + +/* only called for incoming calls when the ANI, DNIS etc is complete and the user has to decide either to accept or reject the call */ +static void zap_r2_on_call_offered(openr2_chan_t *r2chan, const char *ani, const char *dnis, openr2_calling_party_category_t category) +{ + zap_sigmsg_t sigev; + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + zap_r2_data_t *r2data = zchan->span->signal_data; + + zap_log(ZAP_LOG_NOTICE, "Call offered on chan %d, ANI = %s, DNIS = %s, Category = %s\n", openr2_chan_get_number(r2chan), + ani, dnis, openr2_proto_get_category_string(category)); + + /* notify the user about the new call */ + memset(&sigev, 0, sizeof(sigev)); + sigev.chan_id = zchan->chan_id; + sigev.span_id = zchan->span_id; + sigev.channel = zchan; + sigev.event_id = ZAP_SIGEVENT_START; + + if (r2data->sig_cb(&sigev) != ZAP_SUCCESS) { + zap_log(ZAP_LOG_NOTICE, "Failed to handle call offered on chan %d\n", openr2_chan_get_number(r2chan)); + openr2_chan_disconnect_call(r2chan, OR2_CAUSE_OUT_OF_ORDER); + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_CANCEL); + return; + } + zap_channel_use(zchan); + R2CALL(zchan)->zap_started = 1; +} + +static void zap_r2_on_call_accepted(openr2_chan_t *r2chan, openr2_call_mode_t mode) +{ + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + zap_log(ZAP_LOG_NOTICE, "Call accepted on chan %d\n", openr2_chan_get_number(r2chan)); + /* at this point the MF signaling has ended and there is no point on keep reading */ + openr2_chan_disable_read(r2chan); + if (OR2_DIR_BACKWARD == openr2_chan_get_direction(r2chan)) { + R2CALL(zchan)->state_ack_pending = 1; + if (R2CALL(zchan)->answer_pending) { + zap_log(ZAP_LOG_DEBUG, "Answer was pending on chan %d, answering now.\n", openr2_chan_get_number(r2chan)); + oz_r2_answer_call(zchan); + return; + } + } else { + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_PROGRESS); + } +} + +static void zap_r2_on_call_answered(openr2_chan_t *r2chan) +{ + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + zap_log(ZAP_LOG_NOTICE, "Call answered on chan %d\n", openr2_chan_get_number(r2chan)); + /* notify the upper layer of progress in the outbound call */ + if (OR2_DIR_FORWARD == openr2_chan_get_direction(r2chan)) { + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_UP); + } +} + +/* may be called in the signaling or media thread depending on whether the hangup is product of MF or CAS signaling */ +static void zap_r2_on_call_disconnect(openr2_chan_t *r2chan, openr2_call_disconnect_cause_t cause) +{ + zap_sigmsg_t sigev; + zap_r2_data_t *r2data; + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + zap_log(ZAP_LOG_NOTICE, "Call disconnected on chan %d\n", openr2_chan_get_number(r2chan)); + + zap_log(ZAP_LOG_DEBUG, "Got openr2 disconnection, clearing call on channel %d\n", zchan->physical_chan_id); + + R2CALL(zchan)->disconnect_rcvd = 1; + + /* acknowledge the hangup, cause will be ignored. From here to -> HANGUP once the openzap side hangs up as well */ + openr2_chan_disconnect_call(r2chan, OR2_CAUSE_NORMAL_CLEARING); + + /* if the call has not been started yet we must go to HANGUP right here */ + if (!R2CALL(zchan)->zap_started) { + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_HANGUP); + return; + } + + /* FIXME: use the cause received from openr2 and map it to zap cause */ + zchan->caller_data.hangup_cause = ZAP_CAUSE_NORMAL_CLEARING; + + /* notify the user of the call terminating */ + memset(&sigev, 0, sizeof(sigev)); + sigev.chan_id = zchan->chan_id; + sigev.span_id = zchan->span_id; + sigev.channel = zchan; + sigev.event_id = ZAP_SIGEVENT_STOP; + r2data = zchan->span->signal_data; + + r2data->sig_cb(&sigev); +} + +static void zap_r2_on_call_end(openr2_chan_t *r2chan) +{ + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + zap_log(ZAP_LOG_NOTICE, "Call finished on chan %d\n", openr2_chan_get_number(r2chan)); + /* this means the openzap side disconnected the call, therefore we must move to DOWN here */ + if (!R2CALL(zchan)->disconnect_rcvd) { + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_DOWN); + return; + } +} + +static void zap_r2_on_call_read(openr2_chan_t *r2chan, const unsigned char *buf, int buflen) +{ + zap_log(ZAP_LOG_NOTICE, "Call read data on chan %d\n", openr2_chan_get_number(r2chan)); +} + +static void zap_r2_on_hardware_alarm(openr2_chan_t *r2chan, int alarm) +{ + zap_log(ZAP_LOG_NOTICE, "Alarm on chan %d (%d)\n", openr2_chan_get_number(r2chan), alarm); +} + +static void zap_r2_on_os_error(openr2_chan_t *r2chan, int errorcode) +{ + zap_log(ZAP_LOG_ERROR, "OS error on chan %d: %s\n", openr2_chan_get_number(r2chan), strerror(errorcode)); +} + +static void zap_r2_on_protocol_error(openr2_chan_t *r2chan, openr2_protocol_error_t reason) +{ + zap_sigmsg_t sigev; + zap_r2_data_t *r2data; + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + + zap_log(ZAP_LOG_ERROR, "Protocol error on chan %d\n", openr2_chan_get_number(r2chan)); + + R2CALL(zchan)->disconnect_rcvd = 1; + + if (!R2CALL(zchan)->zap_started) { + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_HANGUP); + return; + } + + zchan->caller_data.hangup_cause = ZAP_CAUSE_PROTOCOL_ERROR; + + /* notify the user of the call terminating */ + memset(&sigev, 0, sizeof(sigev)); + sigev.chan_id = zchan->chan_id; + sigev.span_id = zchan->span_id; + sigev.channel = zchan; + sigev.event_id = ZAP_SIGEVENT_STOP; + r2data = zchan->span->signal_data; + + r2data->sig_cb(&sigev); +} + +static void zap_r2_on_line_blocked(openr2_chan_t *r2chan) +{ + zap_log(ZAP_LOG_NOTICE, "Far end blocked on chan %d\n", openr2_chan_get_number(r2chan)); +} + +static void zap_r2_on_line_idle(openr2_chan_t *r2chan) +{ + zap_log(ZAP_LOG_NOTICE, "Far end unblocked on chan %d\n", openr2_chan_get_number(r2chan)); +} + +static void zap_r2_write_log(openr2_log_level_t level, const char *message) +{ + switch (level) { + case OR2_LOG_NOTICE: + zap_log(ZAP_LOG_NOTICE, "%s", message); + break; + case OR2_LOG_WARNING: + zap_log(ZAP_LOG_WARNING, "%s", message); + break; + case OR2_LOG_ERROR: + zap_log(ZAP_LOG_ERROR, "%s", message); + break; + case OR2_LOG_STACK_TRACE: + case OR2_LOG_MF_TRACE: + case OR2_LOG_CAS_TRACE: + case OR2_LOG_DEBUG: + case OR2_LOG_EX_DEBUG: + zap_log(ZAP_LOG_DEBUG, "%s", message); + break; + default: + zap_log(ZAP_LOG_WARNING, "We should handle logging level %d here.\n", level); + zap_log(ZAP_LOG_DEBUG, "%s", message); + break; + } +} + +static void zap_r2_on_context_log(openr2_context_t *r2context, openr2_log_level_t level, const char *fmt, va_list ap) +{ +#define CONTEXT_TAG "Context -" + char logmsg[256]; + char completemsg[sizeof(logmsg) + sizeof(CONTEXT_TAG) - 1]; + vsnprintf(logmsg, sizeof(logmsg), fmt, ap); + snprintf(completemsg, sizeof(completemsg), CONTEXT_TAG "%s", logmsg); + zap_r2_write_log(level, completemsg); +#undef CONTEXT_TAG +} + +static void zap_r2_on_chan_log(openr2_chan_t *r2chan, openr2_log_level_t level, const char *fmt, va_list ap) +{ +#define CHAN_TAG "Chan " + char logmsg[256]; + char completemsg[sizeof(logmsg) + sizeof(CHAN_TAG) - 1]; + vsnprintf(logmsg, sizeof(logmsg), fmt, ap); + snprintf(completemsg, sizeof(completemsg), CHAN_TAG "%d: %s", openr2_chan_get_number(r2chan), logmsg); + zap_r2_write_log(level, completemsg); +#undef CHAN_TAG +} + +static int zap_r2_on_dnis_digit_received(openr2_chan_t *r2chan, char digit) +{ + zap_sigmsg_t sigev; + zap_r2_data_t *r2data; + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + zap_size_t collected_len = R2CALL(zchan)->dnis_index; + + zap_log(ZAP_LOG_DEBUG, "DNIS digit %d received chan %d\n", digit, openr2_chan_get_number(r2chan)); + + /* save the digit we just received */ + zchan->caller_data.dnis.digits[collected_len] = digit; + collected_len++; + zchan->caller_data.dnis.digits[collected_len] = '\0'; + R2CALL(zchan)->dnis_index = collected_len; + + /* notify the user about the new digit and check if we should stop requesting more DNIS */ + memset(&sigev, 0, sizeof(sigev)); + sigev.chan_id = zchan->chan_id; + sigev.span_id = zchan->span_id; + sigev.channel = zchan; + sigev.event_id = ZAP_SIGEVENT_COLLECTED_DIGIT; + r2data = zchan->span->signal_data; + if (r2data->sig_cb(&sigev) == ZAP_BREAK) { + zap_log(ZAP_LOG_NOTICE, "Requested to stop getting DNIS. Current DNIS = %s on chan %d\n", zchan->caller_data.dnis.digits, openr2_chan_get_number(r2chan)); + return OR2_STOP_DNIS_REQUEST; + } + + /* the only other reason to stop requesting DNIS is that there is no more room to save it */ + if (collected_len == (sizeof(zchan->caller_data.dnis.digits) - 1)) { + zap_log(ZAP_LOG_NOTICE, "No more room for DNIS. Current DNIS = %s on chan %d\n", zchan->caller_data.dnis.digits, openr2_chan_get_number(r2chan)); + return OR2_STOP_DNIS_REQUEST; + } + + return OR2_CONTINUE_DNIS_REQUEST; +} + +static void zap_r2_on_ani_digit_received(openr2_chan_t *r2chan, char digit) +{ + zap_channel_t *zchan = openr2_chan_get_client_data(r2chan); + zap_size_t collected_len = R2CALL(zchan)->ani_index; + + /* check if we should drop ANI */ + if (collected_len == (sizeof(zchan->caller_data.ani.digits) - 1)) { + zap_log(ZAP_LOG_NOTICE, "No more room for ANI %c on chan %d, digit dropped.\n", digit, openr2_chan_get_number(r2chan)); + return; + } + zap_log(ZAP_LOG_DEBUG, "ANI digit %c received chan %d\n", digit, openr2_chan_get_number(r2chan)); + + /* save the digit we just received */ + zchan->caller_data.ani.digits[collected_len++] = digit; + zchan->caller_data.ani.digits[collected_len] = '\0'; +} + +static openr2_event_interface_t zap_r2_event_iface = { + .on_call_init = zap_r2_on_call_init, + .on_call_offered = zap_r2_on_call_offered, + .on_call_accepted = zap_r2_on_call_accepted, + .on_call_answered = zap_r2_on_call_answered, + .on_call_disconnect = zap_r2_on_call_disconnect, + .on_call_end = zap_r2_on_call_end, + .on_call_read = zap_r2_on_call_read, + .on_hardware_alarm = zap_r2_on_hardware_alarm, + .on_os_error = zap_r2_on_os_error, + .on_protocol_error = zap_r2_on_protocol_error, + .on_line_blocked = zap_r2_on_line_blocked, + .on_line_idle = zap_r2_on_line_idle, + /* cast seems to be needed to get rid of the annoying warning regarding format attribute */ + .on_context_log = (openr2_handle_context_logging_func)zap_r2_on_context_log, + .on_dnis_digit_received = zap_r2_on_dnis_digit_received, + .on_ani_digit_received = zap_r2_on_ani_digit_received, + /* so far we do nothing with billing pulses */ + .on_billing_pulse_received = NULL +}; + +static int zap_r2_io_set_cas(openr2_chan_t *r2chan, int cas) +{ + zap_channel_t *zap_chan = openr2_chan_get_fd(r2chan); + zap_status_t status = zap_channel_command(zap_chan, ZAP_COMMAND_SET_CAS_BITS, &cas); + if (ZAP_FAIL == status) { + return -1; + } + return 0; +} + +static int zap_r2_io_get_cas(openr2_chan_t *r2chan, int *cas) +{ + zap_channel_t *zap_chan = openr2_chan_get_fd(r2chan); + zap_status_t status = zap_channel_command(zap_chan, ZAP_COMMAND_GET_CAS_BITS, cas); + if (ZAP_FAIL == status) { + return -1; + } + return 0; +} + +static int zap_r2_io_flush_write_buffers(openr2_chan_t *r2chan) +{ + zap_channel_t *zap_chan = openr2_chan_get_fd(r2chan); + zap_status_t status = zap_channel_command(zap_chan, ZAP_COMMAND_FLUSH_TX_BUFFERS, NULL); + if (ZAP_FAIL == status) { + return -1; + } + return 0; +} + +static int zap_r2_io_write(openr2_chan_t *r2chan, const void *buf, int size) +{ + zap_channel_t *zap_chan = openr2_chan_get_fd(r2chan); + zap_size_t outsize = size; + zap_status_t status = zap_channel_write(zap_chan, (void *)buf, size, &outsize); + if (ZAP_FAIL == status) { + return -1; + } + return outsize; +} + +static int zap_r2_io_read(openr2_chan_t *r2chan, const void *buf, int size) +{ + zap_channel_t *zap_chan = openr2_chan_get_fd(r2chan); + zap_size_t outsize = size; + zap_status_t status = zap_channel_read(zap_chan, (void *)buf, &outsize); + if (ZAP_FAIL == status) { + return -1; + } + return outsize; +} + +static int zap_r2_io_wait(openr2_chan_t *r2chan, int *flags, int block) +{ + zap_status_t status; + zap_wait_flag_t zapflags = 0; + + zap_channel_t *zap_chan = openr2_chan_get_fd(r2chan); + int32_t timeout = block ? -1 : 0; + + if (*flags & OR2_IO_READ) { + zapflags |= ZAP_READ; + } + if (*flags & OR2_IO_WRITE) { + zapflags |= ZAP_WRITE; + } + if (*flags & OR2_IO_OOB_EVENT) { + zapflags |= ZAP_EVENTS; + } + + status = zap_channel_wait(zap_chan, &zapflags, timeout); + + if (ZAP_SUCCESS != status) { + return -1; + } + + *flags = 0; + if (zapflags & ZAP_READ) { + *flags |= OR2_IO_READ; + } + if (zapflags & ZAP_WRITE) { + *flags |= OR2_IO_WRITE; + } + if (zapflags & ZAP_EVENTS) { + *flags |= OR2_IO_OOB_EVENT; + } + + return 0; +} + +/* The following openr2 hooks never get called, read on for reasoning ... */ +/* since openzap takes care of opening the file descriptor and using openr2_chan_new_from_fd, openr2 should never call this hook */ +static openr2_io_fd_t zap_r2_io_open(openr2_context_t *r2context, int channo) +{ + zap_log(ZAP_LOG_ERROR, "I should not be called (I/O open)!!\n"); + return NULL; +} + +/* since openzap takes care of closing the file descriptor and uses openr2_chan_new_from_fd, openr2 should never call this hook */ +static int zap_r2_io_close(openr2_chan_t *r2chan) +{ + zap_log(ZAP_LOG_ERROR, "I should not be called (I/O close)!!\n"); + return 0; +} + +/* since openzap takes care of opening the file descriptor and using openr2_chan_new_from_fd, openr2 should never call this hook */ +static int zap_r2_io_setup(openr2_chan_t *r2chan) +{ + zap_log(ZAP_LOG_ERROR, "I should not be called (I/O Setup)!!\n"); + return 0; +} + +/* since the signaling thread calls openr2_chan_process_cas_signaling directly, openr2 should never call this hook */ +static int zap_r2_io_get_oob_event(openr2_chan_t *r2chan, openr2_oob_event_t *event) +{ + *event = 0; + zap_log(ZAP_LOG_ERROR, "I should not be called (I/O get oob event)!!\n"); + return 0; +} + +static openr2_io_interface_t zap_r2_io_iface = { + .open = zap_r2_io_open, /* never called */ + .close = zap_r2_io_close, /* never called */ + .set_cas = zap_r2_io_set_cas, + .get_cas = zap_r2_io_get_cas, + .flush_write_buffers = zap_r2_io_flush_write_buffers, + .write = zap_r2_io_write, + .read = zap_r2_io_read, + .setup = zap_r2_io_setup, /* never called */ + .wait = zap_r2_io_wait, + .get_oob_event = zap_r2_io_get_oob_event /* never called */ +}; + +static ZIO_SIG_CONFIGURE_FUNCTION(zap_r2_configure_span) + //zap_status_t (zap_span_t *span, zio_signal_cb_t sig_cb, va_list ap) +{ + int i = 0; + int conf_failure = 0; + char *var = NULL; + char *val = NULL; + zap_r2_data_t *r2data = NULL; + zap_r2_span_pvt_t *spanpvt = NULL; + zap_r2_call_t *r2call = NULL; + openr2_chan_t *r2chan = NULL; + + assert(sig_cb != NULL); + + oz_r2_conf_t r2conf = + { + .variant = OR2_VAR_ITU, + .category = OR2_CALLING_PARTY_CATEGORY_NATIONAL_SUBSCRIBER, + .loglevel = OR2_LOG_ERROR | OR2_LOG_WARNING, + .max_ani = 10, + .max_dnis = 4, + .mfback_timeout = -1, + .metering_pulse_timeout = -1, + .allow_collect_calls = -1, + .immediate_accept = -1, + .skip_category = -1, + .forced_release = -1, + .charge_calls = -1, + .get_ani_first = -1, + .call_files = -1, + .logdir = NULL, + .advanced_protocol_file = NULL + }; + + + if (span->signal_type) { + snprintf(span->last_error, sizeof(span->last_error), "Span is already configured for signalling."); + return ZAP_FAIL; + } + + while ((var = va_arg(ap, char *))) { + zap_log(ZAP_LOG_DEBUG, "Reading R2 parameter %s for span %d\n", var, span->span_id); + if (!strcasecmp(var, "variant")) { + if (!(val = va_arg(ap, char *))) { + break; + } + if (zap_strlen_zero_buf(val)) { + zap_log(ZAP_LOG_NOTICE, "Ignoring empty R2 variant parameter\n"); + continue; + } + r2conf.variant = openr2_proto_get_variant(val); + if (r2conf.variant == OR2_VAR_UNKNOWN) { + zap_log(ZAP_LOG_ERROR, "Unknown R2 variant %s\n", val); + conf_failure = 1; + break; + } + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d for variant %s\n", span->span_id, val); + } else if (!strcasecmp(var, "category")) { + if (!(val = va_arg(ap, char *))) { + break; + } + if (zap_strlen_zero_buf(val)) { + zap_log(ZAP_LOG_NOTICE, "Ignoring empty R2 category parameter\n"); + continue; + } + r2conf.category = openr2_proto_get_category(val); + if (r2conf.category == OR2_CALLING_PARTY_CATEGORY_UNKNOWN) { + zap_log(ZAP_LOG_ERROR, "Unknown R2 caller category %s\n", val); + conf_failure = 1; + break; + } + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with default category %s\n", span->span_id, val); + } else if (!strcasecmp(var, "logdir")) { + if (!(val = va_arg(ap, char *))) { + break; + } + if (zap_strlen_zero_buf(val)) { + zap_log(ZAP_LOG_NOTICE, "Ignoring empty R2 logdir parameter\n"); + continue; + } + r2conf.logdir = val; + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with logdir %s\n", span->span_id, val); + } else if (!strcasecmp(var, "logging")) { + if (!(val = va_arg(ap, char *))) { + break; + } + if (zap_strlen_zero_buf(val)) { + zap_log(ZAP_LOG_NOTICE, "Ignoring empty R2 logging parameter\n"); + continue; + } + openr2_log_level_t tmplevel; + char *clevel; + char *logval = malloc(strlen(val)+1); /* alloca man page scared me, so better to use good ol' malloc */ + if (!logval) { + zap_log(ZAP_LOG_WARNING, "Ignoring R2 logging parameter: '%s', failed to alloc memory\n", val); + continue; + } + strcpy(logval, val); + while (logval) { + clevel = strsep(&logval, ","); + if (-1 == (tmplevel = openr2_log_get_level(clevel))) { + zap_log(ZAP_LOG_WARNING, "Ignoring invalid R2 logging level: '%s'\n", clevel); + continue; + } + r2conf.loglevel |= tmplevel; + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with loglevel %s\n", span->span_id, clevel); + } + free(logval); + } else if (!strcasecmp(var, "advanced_protocol_file")) { + if (!(val = va_arg(ap, char *))) { + break; + } + if (zap_strlen_zero_buf(val)) { + zap_log(ZAP_LOG_NOTICE, "Ignoring empty R2 advanced_protocol_file parameter\n"); + continue; + } + r2conf.advanced_protocol_file = val; + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with advanced protocol file %s\n", span->span_id, val); + } else if (!strcasecmp(var, "allow_collect_calls")) { + r2conf.allow_collect_calls = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with allow collect calls max ani = %d\n", span->span_id, r2conf.allow_collect_calls); + } else if (!strcasecmp(var, "double_answer")) { + r2conf.double_answer = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with double answer = %d\n", span->span_id, r2conf.double_answer); + } else if (!strcasecmp(var, "immediate_accept")) { + r2conf.immediate_accept = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with immediate accept = %d\n", span->span_id, r2conf.immediate_accept); + } else if (!strcasecmp(var, "skip_category")) { + r2conf.skip_category = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with skip category = %d\n", span->span_id, r2conf.skip_category); + } else if (!strcasecmp(var, "forced_release")) { + r2conf.forced_release = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with forced release = %d\n", span->span_id, r2conf.forced_release); + } else if (!strcasecmp(var, "charge_calls")) { + r2conf.charge_calls = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with charge calls = %d\n", span->span_id, r2conf.charge_calls); + } else if (!strcasecmp(var, "get_ani_first")) { + r2conf.get_ani_first = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with get ani first = %d\n", span->span_id, r2conf.get_ani_first); + } else if (!strcasecmp(var, "call_files")) { + r2conf.call_files = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with call files = %d\n", span->span_id, r2conf.call_files); + } else if (!strcasecmp(var, "mfback_timeout")) { + r2conf.mfback_timeout = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with MF backward timeout = %dms\n", span->span_id, r2conf.mfback_timeout); + } else if (!strcasecmp(var, "metering_pulse_timeout")) { + r2conf.metering_pulse_timeout = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with metering pulse timeout = %dms\n", span->span_id, r2conf.metering_pulse_timeout); + } else if (!strcasecmp(var, "max_ani")) { + r2conf.max_ani = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with max ani = %d\n", span->span_id, r2conf.max_ani); + } else if (!strcasecmp(var, "max_dnis")) { + r2conf.max_dnis = va_arg(ap, int); + zap_log(ZAP_LOG_DEBUG, "Configuring R2 span %d with max dnis = %d\n", span->span_id, r2conf.max_dnis); + } else { + snprintf(span->last_error, sizeof(span->last_error), "Unknown R2 parameter [%s]", var); + return ZAP_FAIL; + } + } + + if (conf_failure) { + snprintf(span->last_error, sizeof(span->last_error), "R2 configuration error"); + return ZAP_FAIL; + } + + r2data = malloc(sizeof(*r2data)); + if (!r2data) { + snprintf(span->last_error, sizeof(span->last_error), "Failed to allocate R2 data."); + return ZAP_FAIL; + } + memset(r2data, 0, sizeof(*r2data)); + + spanpvt = malloc(sizeof(*spanpvt)); + if (!spanpvt) { + snprintf(span->last_error, sizeof(span->last_error), "Failed to allocate private span data container."); + goto fail; + } + memset(spanpvt, 0, sizeof(*spanpvt)); + + r2data->r2context = openr2_context_new(r2conf.variant, &zap_r2_event_iface, r2conf.max_ani, r2conf.max_dnis); + if (!r2data->r2context) { + snprintf(span->last_error, sizeof(span->last_error), "Cannot create openr2 context for span."); + goto fail; + } + openr2_context_set_io_type(r2data->r2context, OR2_IO_CUSTOM, &zap_r2_io_iface); + openr2_context_set_log_level(r2data->r2context, r2conf.loglevel); + openr2_context_set_ani_first(r2data->r2context, r2conf.get_ani_first); + openr2_context_set_skip_category_request(r2data->r2context, r2conf.skip_category); + openr2_context_set_mf_back_timeout(r2data->r2context, r2conf.mfback_timeout); + openr2_context_set_metering_pulse_timeout(r2data->r2context, r2conf.metering_pulse_timeout); + openr2_context_set_double_answer(r2data->r2context, r2conf.double_answer); + openr2_context_set_immediate_accept(r2data->r2context, r2conf.immediate_accept); + if (r2conf.logdir) { + openr2_context_set_log_directory(r2data->r2context, r2conf.logdir); + } + if (r2conf.advanced_protocol_file) { + openr2_context_configure_from_advanced_file(r2data->r2context, r2conf.advanced_protocol_file); + } + + spanpvt->r2calls = create_hashtable(ZAP_MAX_CHANNELS_SPAN, zap_hash_hashfromstring, zap_hash_equalkeys); + if (!spanpvt->r2calls) { + snprintf(span->last_error, sizeof(span->last_error), "Cannot create channel calls hash for span."); + goto fail; + } + + for (i = 1; (i <= span->chan_count) && (i <= ZAP_MAX_CHANNELS_SPAN); i++) { + r2chan = openr2_chan_new_from_fd(r2data->r2context, span->channels[i], span->channels[i]->physical_chan_id); + if (!r2chan) { + snprintf(span->last_error, sizeof(span->last_error), "Cannot create all openr2 channels for span."); + goto fail; + } + if (r2conf.call_files) { + openr2_chan_enable_call_files(r2chan); + openr2_chan_set_log_level(r2chan, r2conf.loglevel); + } + + r2call = malloc(sizeof(*r2call)); + if (!r2call) { + snprintf(span->last_error, sizeof(span->last_error), "Cannot create all R2 call data structures for the span."); + zap_safe_free(r2chan); + goto fail; + } + memset(r2call, 0, sizeof(*r2call)); + openr2_chan_set_logging_func(r2chan, zap_r2_on_chan_log); + openr2_chan_set_client_data(r2chan, span->channels[i]); + r2call->r2chan = r2chan; + span->channels[i]->call_data = r2call; + /* value and key are the same so just free one of them */ + snprintf(r2call->name, sizeof(r2call->name), "chancall%d", i); + hashtable_insert(spanpvt->r2calls, (void *)r2call->name, r2call, HASHTABLE_FLAG_FREE_VALUE); + + } + spanpvt->r2context = r2data->r2context; + + /* just the value must be freed by the hash */ + hashtable_insert(g_mod_data_hash, (void *)span->name, spanpvt, HASHTABLE_FLAG_FREE_VALUE); + + span->start = zap_r2_start; + r2data->flags = 0; + r2data->sig_cb = sig_cb; + span->signal_type = ZAP_SIGTYPE_R2; + span->signal_data = r2data; + span->outgoing_call = r2_outgoing_call; + + return ZAP_SUCCESS; + +fail: + + if (r2data && r2data->r2context) { + openr2_context_delete(r2data->r2context); + } + if (spanpvt && spanpvt->r2calls) { + hashtable_destroy(spanpvt->r2calls); + } + zap_safe_free(r2data); + zap_safe_free(spanpvt); + return ZAP_FAIL; + +} + +static void *zap_r2_channel_run(zap_thread_t *me, void *obj) +{ + zap_channel_t *closed_chan; + uint32_t interval = 0; + zap_sigmsg_t sigev; + zap_channel_t *zchan = (zap_channel_t *)obj; + openr2_chan_t *r2chan = R2CALL(zchan)->r2chan; + zap_r2_data_t *r2data = zchan->span->signal_data; + + zap_set_flag_locked(zchan, ZAP_CHANNEL_INTHREAD); + + zap_mutex_lock(g_thread_count_mutex); + g_thread_count++; + zap_mutex_unlock(g_thread_count_mutex); + + zap_log(ZAP_LOG_DEBUG, "R2 CHANNEL thread starting on %d in state %s.\n", + zchan->physical_chan_id, + zap_channel_state2str(zchan->state)); + + if (zap_channel_open_chan(zchan) != ZAP_SUCCESS) { + zap_log(ZAP_LOG_ERROR, "OPEN ERROR [%s]\n", zchan->last_error); + goto endthread; + } + + zap_channel_command(zchan, ZAP_COMMAND_GET_INTERVAL, &interval); + + assert(interval != 0); + zap_log(ZAP_LOG_DEBUG, "Got %d interval for chan %d\n", interval, zchan->physical_chan_id); + + if (!zap_test_flag(zchan, ZAP_CHANNEL_OUTBOUND)) { + /* FIXME: is this needed? */ + memset(zchan->caller_data.dnis.digits, 0, sizeof(zchan->caller_data.collected)); + memset(zchan->caller_data.ani.digits, 0, sizeof(zchan->caller_data.collected)); + } + + memset(&sigev, 0, sizeof(sigev)); + sigev.chan_id = zchan->chan_id; + sigev.span_id = zchan->span_id; + sigev.channel = zchan; + + while (zap_running()) { + int32_t read_enabled = openr2_chan_get_read_enabled(r2chan); + zap_wait_flag_t flags = read_enabled ? ( ZAP_READ | ZAP_WRITE ) : 0; + + if (zap_test_flag(zchan, ZAP_CHANNEL_STATE_CHANGE) && (R2CALL(zchan)->chanstate != zchan->state)) { + + zap_log(ZAP_LOG_DEBUG, "Executing state handler on %d:%d for %s\n", zchan->span_id, zchan->chan_id, zap_channel_state2str(zchan->state)); + R2CALL(zchan)->chanstate = zchan->state; + + if (!zap_test_flag(zchan, ZAP_CHANNEL_OUTBOUND) && !R2CALL(zchan)->accepted && + (zchan->state == ZAP_CHANNEL_STATE_PROGRESS || + zchan->state == ZAP_CHANNEL_STATE_PROGRESS_MEDIA || + zchan->state == ZAP_CHANNEL_STATE_UP) ) { + /* if an accept ack will be required we should not acknowledge the state change just yet, + it will be done below after processing the MF signals, otherwise we have a race condition between openzap calling + openr2_chan_answer_call and openr2 accepting the call first, if openzap calls openr2_chan_answer_call before the accept cycle + completes, openr2 will fail to answer the call */ + zap_log(ZAP_LOG_DEBUG, "State ack in chan %d:%d for state %s will have to wait a bit\n", zchan->span_id, zchan->chan_id, zap_channel_state2str(zchan->state)); + } else if (zchan->state != ZAP_CHANNEL_STATE_DOWN){ + /* the down state will be completed in zap_channel_done below */ + zap_clear_flag_locked(zchan, ZAP_CHANNEL_STATE_CHANGE); + zap_channel_complete_state(zchan); + } + + switch (zchan->state) { + + /* starting an incoming call */ + case ZAP_CHANNEL_STATE_COLLECT: + { + zap_log(ZAP_LOG_DEBUG, "COLLECT: Starting processing of incoming call in channel %d with interval %d\n", zchan->physical_chan_id, interval); + } + break; + + /* starting an outgoing call */ + case ZAP_CHANNEL_STATE_DIALING: + { + // FIXME: use user defined calling party + zap_channel_use(zchan); + zap_log(ZAP_LOG_DEBUG, "DIALING: Starting processing of outgoing call in channel %d with interval %d\n", zchan->physical_chan_id, interval); + if (openr2_chan_make_call(r2chan, zchan->caller_data.cid_num.digits, zchan->caller_data.ani.digits, OR2_CALLING_PARTY_CATEGORY_NATIONAL_SUBSCRIBER)) { + zap_log(ZAP_LOG_ERROR, "%d:%d Failed to make call in R2 channel, openr2_chan_make_call failed\n", zchan->span_id, zchan->chan_id); + zchan->caller_data.hangup_cause = ZAP_CAUSE_DESTINATION_OUT_OF_ORDER; + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_HANGUP); + } + } + break; + + /* the call is ringing */ + case ZAP_CHANNEL_STATE_PROGRESS: + case ZAP_CHANNEL_STATE_PROGRESS_MEDIA: + { + if (!zap_test_flag(zchan, ZAP_CHANNEL_OUTBOUND)) { + if (!R2CALL(zchan)->accepted) { + zap_log(ZAP_LOG_DEBUG, "PROGRESS: Accepting call on channel %d\n", zchan->physical_chan_id); + oz_r2_accept_call(zchan); + } + } else { + zap_log(ZAP_LOG_DEBUG, "PROGRESS: Notifying progress in channel %d\n", zchan->physical_chan_id); + sigev.event_id = ZAP_SIGEVENT_PROGRESS; + if (r2data->sig_cb(&sigev) != ZAP_SUCCESS) { + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_HANGUP); + } + } + } + break; + + /* the call was answered */ + case ZAP_CHANNEL_STATE_UP: + { + zap_log(ZAP_LOG_DEBUG, "UP: Call was answered on channel %d\n", zchan->physical_chan_id); + if (!zap_test_flag(zchan, ZAP_CHANNEL_OUTBOUND)) { + if (!R2CALL(zchan)->accepted) { + zap_log(ZAP_LOG_DEBUG, "UP: Call has not been accepted, need to accept first\n"); + // the answering will be done in the on_call_accepted handler + oz_r2_accept_call(zchan); + R2CALL(zchan)->answer_pending = 1; + } else { + oz_r2_answer_call(zchan); + } + } else { + zap_log(ZAP_LOG_DEBUG, "UP: Notifying of call answered in channel %d\n", zchan->physical_chan_id); + sigev.event_id = ZAP_SIGEVENT_UP; + if (r2data->sig_cb(&sigev) != ZAP_SUCCESS) { + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_HANGUP); + } + } + } + break; + + /* just got hangup */ + case ZAP_CHANNEL_STATE_HANGUP: + { + /* FIXME: the cause should be retrieved from zchan->caller_data.hangup_cause and translated from Q931 to R2 cause */ + zap_log(ZAP_LOG_DEBUG, "HANGUP: Clearing call on channel %d\n", zchan->physical_chan_id); + if (!R2CALL(zchan)->disconnect_rcvd) { + /* this will disconnect the call, but need to wait for the call end before moving to DOWN */ + openr2_chan_disconnect_call(r2chan, OR2_CAUSE_NORMAL_CLEARING); + } else { + /* at this point on_call_end possibly was already called, + * but we needed to wait for the openzap confirmation before moving to DOWN */ + zap_set_state_locked(zchan, ZAP_CHANNEL_STATE_DOWN); + } + } + break; + + /* just got hangup from the openzap side due to abnormal failure */ + case ZAP_CHANNEL_STATE_CANCEL: + { + zap_log(ZAP_LOG_DEBUG, "CANCEL: Unable to receive call on channel %d\n", zchan->physical_chan_id); + openr2_chan_disconnect_call(r2chan, OR2_CAUSE_OUT_OF_ORDER); + } + break; + + /* finished call for good */ + case ZAP_CHANNEL_STATE_DOWN: + { + zap_log(ZAP_LOG_DEBUG, "DOWN: Placing channel %d back to the pool of available channels\n", zchan->physical_chan_id); + zap_channel_done(zchan); + goto endthread; + } + break; + + default: + { + zap_log(ZAP_LOG_ERROR, "%s: Unhandled channel state change in channel %d\n", zap_channel_state2str(zchan->state), zchan->physical_chan_id); + } + break; + + } + } + + if (flags) { + if (zap_channel_wait(zchan, &flags, interval * 2) != ZAP_SUCCESS) { + zap_log(ZAP_LOG_DEBUG, "zap_channel_wait did not return ZAP_SUCCESS\n"); + continue; + } + + /* handle timeout events first if any */ + openr2_chan_run_schedule(r2chan); + + /* openr2 will now try to detect MF tones, make sense out of them, reply if necessary with another tone and trigger + * telephony events via the call event interface we provided when creating the R2 context. + * openr2 will also call our I/O callbacks to retrieve audio from the channel and call our wait poll I/O registered callback + * and will not return from this function until the I/O poll callback returns no pending events + * */ + openr2_chan_process_mf_signaling(r2chan); + if (R2CALL(zchan)->state_ack_pending) { + zap_clear_flag_locked(zchan, ZAP_CHANNEL_STATE_CHANGE); + zap_channel_complete_state(zchan); + R2CALL(zchan)->state_ack_pending = 0; + } + } else { + /* once the MF signaling has end we just loop here waiting for state changes */ + zap_sleep(interval); + } + + } + +endthread: + + closed_chan = zchan; + zap_channel_close(&closed_chan); + zap_clear_flag(zchan, ZAP_CHANNEL_INTHREAD); + zap_log(ZAP_LOG_DEBUG, "R2 channel %d thread ended.\n", zchan->physical_chan_id); + + zap_mutex_lock(g_thread_count_mutex); + g_thread_count--; + zap_mutex_unlock(g_thread_count_mutex); + + return NULL; +} + +static void *zap_r2_run(zap_thread_t *me, void *obj) +{ + openr2_chan_t *r2chan; + zap_status_t status; + zap_span_t *span = (zap_span_t *) obj; + zap_r2_data_t *r2data = span->signal_data; + int waitms = 1000; + int i; + + zap_log(ZAP_LOG_DEBUG, "OpenR2 monitor thread started.\n"); + for (i = 1; i <= span->chan_count; i++) { + r2chan = R2CALL(span->channels[i])->r2chan; + openr2_chan_set_idle(r2chan); + openr2_chan_process_cas_signaling(r2chan); + } + + while (zap_running() && zap_test_flag(r2data, ZAP_R2_RUNNING)) { + status = zap_span_poll_event(span, waitms); + if (ZAP_FAIL == status) { + zap_log(ZAP_LOG_ERROR, "Failure Polling event! [%s]\n", span->last_error); + continue; + } + if (ZAP_SUCCESS == status) { + zap_event_t *event; + zap_log(ZAP_LOG_ERROR, "success getting poll event\n"); + while (zap_span_next_event(span, &event) == ZAP_SUCCESS) { + if (event->enum_id == ZAP_OOB_CAS_BITS_CHANGE) { + r2chan = R2CALL(event->channel)->r2chan; + zap_log(ZAP_LOG_DEBUG, "Handling CAS on channel %d.\n", openr2_chan_get_number(r2chan)); + // we only expect CAS and other OOB events on this thread/loop, once a call is started + // the MF events (in-band signaling) are handled in the call thread + openr2_chan_process_cas_signaling(r2chan); + } else { + zap_log(ZAP_LOG_DEBUG, "Ignoring event %d on channel %d.\n", event->enum_id, openr2_chan_get_number(r2chan)); + // XXX TODO: handle alarms here XXX + } + } + } else if (status != ZAP_TIMEOUT) { + zap_log(ZAP_LOG_ERROR, "zap_span_poll_event returned %d.\n", status); + } else { + //zap_log(ZAP_LOG_DEBUG, "timed out waiting for event on span %d\n", span->span_id); + } + } + + /* + FIXME: we should set BLOCKED but at this point I/O routines of openzap caused segfault + for (i = 1; i <= span->chan_count; i++) { + r2chan = R2CALL(span->channels[i])->r2chan; + openr2_chan_set_blocked(r2chan); + } + */ + + zap_clear_flag(r2data, ZAP_R2_RUNNING); + zap_log(ZAP_LOG_DEBUG, "R2 thread ending.\n"); + + return NULL; + +} + +static ZIO_API_FUNCTION(zap_r2_api) +{ + char *mycmd = NULL, *argv[10] = { 0 }; + int argc = 0; + + if (data) { + mycmd = strdup(data); + argc = zap_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc == 2) { + if (!strcasecmp(argv[0], "kill")) { + int span_id = atoi(argv[1]); + zap_span_t *span = NULL; + + if (zap_span_find_by_name(argv[1], &span) == ZAP_SUCCESS || zap_span_find(span_id, &span) == ZAP_SUCCESS) { + zap_r2_data_t *r2data = span->signal_data; + + if (span->start != zap_r2_start) { + stream->write_function(stream, "-ERR invalid span.\n"); + goto done; + } + + zap_clear_flag(r2data, ZAP_R2_RUNNING); + stream->write_function(stream, "+OK killed.\n"); + goto done; + } else { + stream->write_function(stream, "-ERR invalid span.\n"); + goto done; + } + } + + if (!strcasecmp(argv[0], "status")) { + int span_id = atoi(argv[1]); + zap_r2_data_t *r2data = NULL; + zap_span_t *span = NULL; + openr2_chan_t *r2chan = NULL; + openr2_context_t *r2context = NULL; + int i = 0; + + if (zap_span_find_by_name(argv[1], &span) == ZAP_SUCCESS || zap_span_find(span_id, &span) == ZAP_SUCCESS) { + if (span->start != zap_r2_start) { + stream->write_function(stream, "-ERR not an R2 span.\n"); + goto done; + } + if (!(r2data = span->signal_data)) { + stream->write_function(stream, "-ERR invalid span. No R2 singal data in span.\n"); + goto done; + } + r2context = r2data->r2context; + openr2_variant_t r2variant = openr2_context_get_variant(r2context); + stream->write_function(stream, + "Variant: %s\n" + "Max ANI: %d\n" + "Max DNIS: %d\n" + "ANI First: %s\n" + "Immediate Accept: %s\n", + openr2_proto_get_variant_string(r2variant), + openr2_context_get_max_ani(r2context), + openr2_context_get_max_dnis(r2context), + openr2_context_get_ani_first(r2context) ? "Yes" : "No", + openr2_context_get_immediate_accept(r2context) ? "Yes" : "No"); + stream->write_function(stream, "\n"); + stream->write_function(stream, "%4s %-12.12s %-12.12s\n", "Channel", "Tx CAS", "Rx CAS"); + for (i = 1; i <= span->chan_count; i++) { + if (i == 16) continue; + r2chan = R2CALL(span->channels[i])->r2chan; + stream->write_function(stream, "%4d %-12.12s %-12.12s\n", + span->channels[i]->physical_chan_id, + openr2_chan_get_tx_cas_string(r2chan), + openr2_chan_get_rx_cas_string(r2chan)); + } + stream->write_function(stream, "\n"); + stream->write_function(stream, "+OK.\n"); + goto done; + } else { + stream->write_function(stream, "-ERR invalid span.\n"); + goto done; + } + } + + } + + if (argc == 1) { + if (!strcasecmp(argv[0], "threads")) { + zap_mutex_lock(g_thread_count_mutex); + stream->write_function(stream, "%d R2 channel threads up\n", g_thread_count); + zap_mutex_unlock(g_thread_count_mutex); + stream->write_function(stream, "+OK.\n"); + goto done; + } + + if (!strcasecmp(argv[0], "version")) { + stream->write_function(stream, "OpenR2 version: %s, revision: %s\n", openr2_get_version(), openr2_get_revision()); + stream->write_function(stream, "+OK.\n"); + goto done; + } + + if (!strcasecmp(argv[0], "variants")) { + int32_t numvariants = 0; + const openr2_variant_entry_t *variants = openr2_proto_get_variant_list(&numvariants); + if (!variants) { + stream->write_function(stream, "-ERR failed to retrieve openr2 variant list.\n"); + goto done; + } +#define VARIANT_FORMAT "%4s %40s\n" + stream->write_function(stream, VARIANT_FORMAT, "Variant Code", "Country"); + numvariants--; + for (; numvariants; numvariants--) { + stream->write_function(stream, VARIANT_FORMAT, variants[numvariants].name, variants[numvariants].country); + } + stream->write_function(stream, "+OK.\n"); +#undef VARIANT_FORMAT + goto done; + } + } + + stream->write_function(stream, "-ERR invalid command.\n"); + +done: + + zap_safe_free(mycmd); + + return ZAP_SUCCESS; + +} + +static ZIO_IO_LOAD_FUNCTION(zap_r2_io_init) +{ + assert(zio != NULL); + memset(&g_zap_r2_interface, 0, sizeof(g_zap_r2_interface)); + + g_zap_r2_interface.name = "r2"; + g_zap_r2_interface.api = zap_r2_api; + + *zio = &g_zap_r2_interface; + + return ZAP_SUCCESS; +} + +static ZIO_SIG_LOAD_FUNCTION(zap_r2_init) +{ + g_mod_data_hash = create_hashtable(10, zap_hash_hashfromstring, zap_hash_equalkeys); + if (!g_mod_data_hash) { + return ZAP_FAIL; + } + zap_mutex_create(&g_thread_count_mutex); + return ZAP_SUCCESS; +} + +static ZIO_SIG_UNLOAD_FUNCTION(zap_r2_destroy) +{ + zap_hash_iterator_t *i = NULL; + zap_r2_span_pvt_t *spanpvt = NULL; + const void *key = NULL; + void *val = NULL; + for (i = hashtable_first(g_mod_data_hash); i; i = hashtable_next(i)) { + hashtable_this(i, &key, NULL, &val); + if (key && val) { + spanpvt = val; + openr2_context_delete(spanpvt->r2context); + hashtable_destroy(spanpvt->r2calls); + } + } + hashtable_destroy(g_mod_data_hash); + zap_mutex_destroy(&g_thread_count_mutex); + return ZAP_SUCCESS; +} + +zap_module_t zap_module = { + "r2", + zap_r2_io_init, + NULL, + zap_r2_init, + zap_r2_configure_span, + zap_r2_destroy +}; + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 + */ diff --git a/libs/freetdm/src/ozmod/ozmod_wanpipe/ozmod_wanpipe.c b/libs/freetdm/src/ozmod/ozmod_wanpipe/ozmod_wanpipe.c index c45e5f295b..241d180a37 100644 --- a/libs/freetdm/src/ozmod/ozmod_wanpipe/ozmod_wanpipe.c +++ b/libs/freetdm/src/ozmod/ozmod_wanpipe/ozmod_wanpipe.c @@ -202,7 +202,7 @@ static unsigned wp_open_range(zap_span_t *span, unsigned spanno, unsigned start, unsigned configured = 0, x; if (type == ZAP_CHAN_TYPE_CAS) { - zap_log(ZAP_LOG_DEBUG, "Configuring CAS channels with abcd == 0x%X\n", cas_bits); + zap_log(ZAP_LOG_DEBUG, "Configuring Wanpipe CAS channels with abcd == 0x%X\n", cas_bits); } for(x = start; x < end; x++) { zap_channel_t *chan; @@ -210,22 +210,25 @@ static unsigned wp_open_range(zap_span_t *span, unsigned spanno, unsigned start, const char *dtmf = "none"; sockfd = tdmv_api_open_span_chan(spanno, x); + if (sockfd == WP_INVALID_SOCKET) { + zap_log(ZAP_LOG_ERROR, "Failed to open wanpipe device span %d channel %d\n", spanno, x); + continue; + } - if (sockfd != WP_INVALID_SOCKET && zap_span_add_channel(span, sockfd, type, &chan) == ZAP_SUCCESS) { + if (zap_span_add_channel(span, sockfd, type, &chan) == ZAP_SUCCESS) { wanpipe_tdm_api_t tdm_api; + memset(&tdm_api, 0, sizeof(tdm_api)); #ifdef LIBSANGOMA_VERSION - sangoma_status_t sangstatus; + sangoma_status_t sangstatus; sangoma_wait_obj_t *sangoma_wait_obj; - sangstatus = sangoma_wait_obj_create(&sangoma_wait_obj, sockfd, SANGOMA_DEVICE_WAIT_OBJ); - if (sangstatus != SANG_STATUS_SUCCESS) { - zap_log(ZAP_LOG_ERROR, "failure create waitable object for s%dc%d\n", spanno, x); - continue; - } + sangstatus = sangoma_wait_obj_create(&sangoma_wait_obj, sockfd, SANGOMA_DEVICE_WAIT_OBJ); + if (sangstatus != SANG_STATUS_SUCCESS) { + zap_log(ZAP_LOG_ERROR, "failure create waitable object for s%dc%d\n", spanno, x); + continue; + } chan->mod_data = sangoma_wait_obj; #endif - - memset(&tdm_api,0,sizeof(tdm_api)); chan->physical_span_id = spanno; chan->physical_chan_id = x; @@ -237,7 +240,7 @@ static unsigned wp_open_range(zap_span_t *span, unsigned spanno, unsigned start, dtmf = "software"; /* FIXME: Handle Error Condition Check for return code */ - err= sangoma_tdm_get_hw_coding(chan->sockfd, &tdm_api); + err = sangoma_tdm_get_hw_coding(chan->sockfd, &tdm_api); if (tdm_api.wp_tdm_cmd.hw_tdm_coding) { chan->native_codec = chan->effective_codec = ZAP_CODEC_ALAW; @@ -270,15 +273,26 @@ static unsigned wp_open_range(zap_span_t *span, unsigned spanno, unsigned start, if (type == ZAP_CHAN_TYPE_CAS || ((span->trunk_type == ZAP_TRUNK_T1 || span->trunk_type == ZAP_TRUNK_E1) && type != ZAP_CHAN_TYPE_B)) { #ifdef LIBSANGOMA_VERSION - sangoma_tdm_write_rbs(chan->sockfd,&tdm_api,chan->physical_chan_id,wanpipe_swap_bits(cas_bits)); + sangoma_tdm_write_rbs(chan->sockfd,&tdm_api,chan->physical_chan_id, wanpipe_swap_bits(cas_bits)); + + /* this should probably be done for old libsangoma but I am not sure if the API is available and I'm lazy to check, + The poll rate is hard coded to 100 per second (done in the driver, is the max rate of polling allowed by wanpipe) + */ + if (sangoma_tdm_enable_rbs_events(chan->sockfd, &tdm_api, 100)) { + zap_log(ZAP_LOG_ERROR, "Failed to enable RBS/CAS events in device %d:%d fd:%d\n", chan->span_id, chan->chan_id, sockfd); + continue; + } + /* probably done by the driver but lets write defensive code this time */ + sangoma_flush_bufs(chan->sockfd, &tdm_api); #else - sangoma_tdm_write_rbs(chan->sockfd,&tdm_api,wanpipe_swap_bits(cas_bits)); + sangoma_tdm_write_rbs(chan->sockfd,&tdm_api, wanpipe_swap_bits(cas_bits)); #endif } if (!zap_strlen_zero(name)) { zap_copy_string(chan->chan_name, name, sizeof(chan->chan_name)); } + if (!zap_strlen_zero(number)) { zap_copy_string(chan->chan_number, number, sizeof(chan->chan_number)); } @@ -288,7 +302,7 @@ static unsigned wp_open_range(zap_span_t *span, unsigned spanno, unsigned start, spanno, x, chan->span_id, chan->chan_id, sockfd, dtmf); } else { - zap_log(ZAP_LOG_ERROR, "failure configuring device s%dc%d\n", spanno, x); + zap_log(ZAP_LOG_ERROR, "zap_span_add_channel failed for wanpipe span %d channel %d\n", spanno, x); } } @@ -371,15 +385,13 @@ static ZIO_CONFIGURE_SPAN_FUNCTION(wanpipe_configure_span) } if (!(sp && ch)) { - zap_log(ZAP_LOG_ERROR, "Invalid input\n"); + zap_log(ZAP_LOG_ERROR, "No valid wanpipe span and channel was specified\n"); continue; } - channo = atoi(ch); spanno = atoi(sp); - if (channo < 0) { zap_log(ZAP_LOG_ERROR, "Invalid channel number %d\n", channo); continue; @@ -528,16 +540,24 @@ static ZIO_COMMAND_FUNCTION(wanpipe_command) case ZAP_COMMAND_SET_CAS_BITS: { #ifdef LIBSANGOMA_VERSION - err=sangoma_tdm_write_rbs(zchan->sockfd,&tdm_api,zchan->physical_chan_id,wanpipe_swap_bits(ZAP_COMMAND_OBJ_INT)); + err = sangoma_tdm_write_rbs(zchan->sockfd,&tdm_api, zchan->physical_chan_id, wanpipe_swap_bits(ZAP_COMMAND_OBJ_INT)); #else - err=sangoma_tdm_write_rbs(zchan->sockfd,&tdm_api,wanpipe_swap_bits(ZAP_COMMAND_OBJ_INT)); + err = sangoma_tdm_write_rbs(zchan->sockfd, &tdm_api, wanpipe_swap_bits(ZAP_COMMAND_OBJ_INT)); #endif } break; case ZAP_COMMAND_GET_CAS_BITS: { - /* wanpipe does not has a command to get the CAS bits so we emulate it */ - ZAP_COMMAND_OBJ_INT = zchan->cas_bits; +#ifdef LIBSANGOMA_VERSION + unsigned char rbsbits; + err = sangoma_tdm_read_rbs(zchan->sockfd, &tdm_api, zchan->physical_chan_id, &rbsbits); + if (!err) { + ZAP_COMMAND_OBJ_INT = wanpipe_swap_bits(rbsbits); + } +#else + // does sangoma_tdm_read_rbs is available here? + ZAP_COMMAND_OBJ_INT = zchan->rx_cas_bits; +#endif } break; default: @@ -817,7 +837,6 @@ ZIO_SPAN_NEXT_EVENT_FUNCTION(wanpipe_next_event) { uint32_t i,err; zap_oob_event_t event_id; - for(i = 1; i <= span->chan_count; i++) { if (span->channels[i]->last_event_time && !zap_test_flag(span->channels[i], ZAP_CHANNEL_EVENT)) { uint32_t diff = (uint32_t)(zap_current_time_in_ms() - span->channels[i]->last_event_time); @@ -856,7 +875,7 @@ ZIO_SPAN_NEXT_EVENT_FUNCTION(wanpipe_next_event) memset(&tdm_api, 0, sizeof(tdm_api)); zap_clear_flag(span->channels[i], ZAP_CHANNEL_EVENT); - err=sangoma_tdm_read_event(zchan->sockfd,&tdm_api); + err = sangoma_tdm_read_event(zchan->sockfd, &tdm_api); if (err != ZAP_SUCCESS) { snprintf(span->last_error, sizeof(span->last_error), "%s", strerror(errno)); return ZAP_FAIL; @@ -926,9 +945,7 @@ ZIO_SPAN_NEXT_EVENT_FUNCTION(wanpipe_next_event) case WP_TDMAPI_EVENT_RBS: { event_id = ZAP_OOB_CAS_BITS_CHANGE; - /* save the CAS bits, user should retrieve it with ZAP_COMMAND_GET_CAS_BITS - is there a best play to store this? instead of adding cas_bits member to zap_chan? */ - span->channels[i]->cas_bits = wanpipe_swap_bits(tdm_api.wp_tdm_cmd.event.wp_tdm_api_event_rbs_bits); + span->channels[i]->rx_cas_bits = wanpipe_swap_bits(tdm_api.wp_tdm_cmd.event.wp_tdm_api_event_rbs_bits); } break; case WP_TDMAPI_EVENT_DTMF: diff --git a/libs/freetdm/src/ozmod/ozmod_zt/ozmod_zt.c b/libs/freetdm/src/ozmod/ozmod_zt/ozmod_zt.c index a8ce338a63..9e4369334b 100644 --- a/libs/freetdm/src/ozmod/ozmod_zt/ozmod_zt.c +++ b/libs/freetdm/src/ozmod/ozmod_zt/ozmod_zt.c @@ -442,7 +442,7 @@ static ZIO_CONFIGURE_SPAN_FUNCTION(zt_configure_span) { int items, i; - char *mydata, *item_list[10]; + char *mydata, *item_list[10]; char *ch, *mx; unsigned char cas_bits = 0; int channo; @@ -777,10 +777,9 @@ static ZIO_COMMAND_FUNCTION(zt_command) zchan->packet_len = len; zchan->effective_interval = zchan->native_interval = zchan->packet_len / 8; - if (zchan->effective_codec == ZAP_CODEC_SLIN) { - zchan->packet_len *= 2; - } - + if (zchan->effective_codec == ZAP_CODEC_SLIN) { + zchan->packet_len *= 2; + } } } break; @@ -792,21 +791,42 @@ static ZIO_COMMAND_FUNCTION(zt_command) break; case ZAP_COMMAND_GET_CAS_BITS: { - /* probably we should call ZT_GETRXBITS instead? */ - ZAP_COMMAND_OBJ_INT = zchan->cas_bits; + err = ioctl(zchan->sockfd, codes.GETRXBITS, &zchan->rx_cas_bits); + if (!err) { + ZAP_COMMAND_OBJ_INT = zchan->rx_cas_bits; + } + } + break; + case ZAP_COMMAND_FLUSH_TX_BUFFERS: + { + int flushmode = ZT_FLUSH_WRITE; + err = ioctl(zchan->sockfd, codes.FLUSH, &flushmode); + } + break; + case ZAP_COMMAND_FLUSH_RX_BUFFERS: + { + int flushmode = ZT_FLUSH_READ; + err = ioctl(zchan->sockfd, codes.FLUSH, &flushmode); + } + break; + case ZAP_COMMAND_FLUSH_BUFFERS: + { + int flushmode = ZT_FLUSH_BOTH; + err = ioctl(zchan->sockfd, codes.FLUSH, &flushmode); } break; default: + err = ZAP_NOTIMPL; break; }; - if (err) { + if (err && err != ZAP_NOTIMPL) { snprintf(zchan->last_error, sizeof(zchan->last_error), "%s", strerror(errno)); return ZAP_FAIL; } - return ZAP_SUCCESS; + return err == 0 ? ZAP_SUCCESS : err; } /** @@ -1019,7 +1039,7 @@ ZIO_SPAN_NEXT_EVENT_FUNCTION(zt_next_event) if (err) { return ZAP_FAIL; } - span->channels[i]->cas_bits = bits; + span->channels[i]->rx_cas_bits = bits; } break; default: diff --git a/libs/freetdm/src/testr2.c b/libs/freetdm/src/testr2.c new file mode 100644 index 0000000000..f5f093cb4f --- /dev/null +++ b/libs/freetdm/src/testr2.c @@ -0,0 +1,86 @@ +#include "openzap.h" +#include + +static int R = 0; +static zap_mutex_t *mutex = NULL; + +static ZIO_SIGNAL_CB_FUNCTION(on_r2_signal) +{ + zap_log(ZAP_LOG_DEBUG, "Got R2 channel sig [%s] in channel\n", zap_signal_event2str(sigmsg->event_id), sigmsg->channel->physical_chan_id); + return ZAP_SUCCESS; +} + +static void handle_SIGINT(int sig) +{ + zap_mutex_lock(mutex); + R = 0; + zap_mutex_unlock(mutex); + return; +} + +int main(int argc, char *argv[]) +{ + zap_span_t *span; + zap_mutex_create(&mutex); + + zap_global_set_default_logger(ZAP_LOG_LEVEL_DEBUG); + + if (argc < 2) { + printf("umm no\n"); + exit(-1); + } + + if (zap_global_init() != ZAP_SUCCESS) { + fprintf(stderr, "Error loading OpenZAP\n"); + exit(-1); + } + + printf("OpenZAP loaded\n"); + + if (zap_span_find(atoi(argv[1]), &span) != ZAP_SUCCESS) { + fprintf(stderr, "Error finding OpenZAP span\n"); + goto done; + } + + + + if (zap_configure_span("r2", span, on_r2_signal, + "variant", "mx", + "max_ani", 10, + "max_dnis", 4, + "logging", "all", + TAG_END) == ZAP_SUCCESS) { + + + zap_span_start(span); + } else { + fprintf(stderr, "Error starting R2 span\n"); + goto done; + } + + signal(SIGINT, handle_SIGINT); + zap_mutex_lock(mutex); + R = 1; + zap_mutex_unlock(mutex); + while(R) { + zap_sleep(1 * 1000); + } + + done: + + zap_global_destroy(); + + return 1; + +} + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab: + */ diff --git a/libs/freetdm/src/zap_config.c b/libs/freetdm/src/zap_config.c index 32567041eb..05d5db3c61 100644 --- a/libs/freetdm/src/zap_config.c +++ b/libs/freetdm/src/zap_config.c @@ -216,14 +216,14 @@ OZ_DECLARE (int) zap_config_get_cas_bits(char *strvalue, unsigned char *outbits) int x = 0; char *double_colon = strchr(strvalue, ':'); if (!double_colon) { - zap_log(ZAP_LOG_ERROR, "No CAS bits specified: %s, :xxxx definition expected, where x is 1 or 0\n", double_colon); + zap_log(ZAP_LOG_ERROR, "No CAS bits specified: %s, :xxxx definition expected, where x is 1 or 0\n", strvalue); return -1; } double_colon++; *outbits = 0; cas_bits[4] = 0; if (sscanf(double_colon, "%c%c%c%c", &cas_bits[0], &cas_bits[1], &cas_bits[2], &cas_bits[3]) != 4) { - zap_log(ZAP_LOG_ERROR, "Invalid CAS bits specified: %s, :xxxx definition expected, where x is 1 or 0\n", double_colon); + zap_log(ZAP_LOG_ERROR, "Invalid CAS bits specified: '%s', :xxxx definition expected, where x is 1 or 0\n", double_colon); return -1; } zap_log(ZAP_LOG_DEBUG, "CAS bits specification found: %s\n", cas_bits); diff --git a/libs/freetdm/src/zap_io.c b/libs/freetdm/src/zap_io.c index 4b62dd3153..a20ea0ccf9 100644 --- a/libs/freetdm/src/zap_io.c +++ b/libs/freetdm/src/zap_io.c @@ -1522,14 +1522,17 @@ OZ_DECLARE(zap_status_t) zap_channel_command(zap_channel_t *zchan, zap_command_t if (!zchan->zio->command) { snprintf(zchan->last_error, sizeof(zchan->last_error), "method not implemented"); - zap_log(ZAP_LOG_ERROR, "no commnand functon!\n"); + zap_log(ZAP_LOG_ERROR, "no command function defined by the I/O openzap module!\n"); GOTO_STATUS(done, ZAP_FAIL); } status = zchan->zio->command(zchan, command, obj); - - done: + if (status == ZAP_NOTIMPL) { + snprintf(zchan->last_error, sizeof(zchan->last_error), "I/O command %d not implemented in backend", command); + zap_log(ZAP_LOG_ERROR, "I/O backend does not support command %d!\n", command); + } +done: zap_mutex_unlock(zchan->mutex); return status;