diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | include/Makefile.am | 2 | ||||
-rw-r--r-- | include/dtmf_scheduler.h | 25 | ||||
-rw-r--r-- | include/mgcp/mgcp_internal.h | 2 | ||||
-rw-r--r-- | include/mgcp_ss7.h | 1 | ||||
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/dtmf_scheduler.c | 59 | ||||
-rw-r--r-- | src/mgcp_ss7.c | 72 | ||||
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/dtmf/Makefile.am | 8 | ||||
-rw-r--r-- | tests/dtmf/dtmf_test.c | 103 | ||||
-rw-r--r-- | tests/dtmf/dtmf_test.ok | 1 | ||||
-rw-r--r-- | tests/testsuite.at | 6 |
14 files changed, 286 insertions, 3 deletions
@@ -20,6 +20,9 @@ stamp-h1 *.o *.sw? *.orig +*.gcda +*.gcno +*.info # binaries cellmgr_ng @@ -34,6 +37,7 @@ aclocal.m4 autom4te.cache/ configure.lineno tests/atconfig +tests/dtmf/dtmf_test tests/isup/isup_parse_test tests/mgcp/mgcp_patch_test tests/package.m4 diff --git a/configure.ac b/configure.ac index b37d8d8..f0a50e6 100644 --- a/configure.ac +++ b/configure.ac @@ -52,4 +52,5 @@ AC_OUTPUT( tests/patching/Makefile tests/isup/Makefile tests/mgcp/Makefile + tests/dtmf/Makefile Makefile) diff --git a/include/Makefile.am b/include/Makefile.am index 01c7b9c..c923515 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -2,6 +2,6 @@ noinst_HEADERS = mtp_level3.h mtp_data.h ipaccess.h thread.h mtp_pcap.h \ mgcp_ss7.h bss_patch.h bssap_sccp.h bsc_data.h udp_input.h \ snmp_mtp.h cellmgr_debug.h bsc_sccp.h bsc_ussd.h sctp_m2ua.h \ isup_types.h counter.h msc_connection.h ss7_application.h \ - mgcp_patch.h ss7_vty.h + mgcp_patch.h ss7_vty.h dtmf_scheduler.h SUBDIRS = mgcp diff --git a/include/dtmf_scheduler.h b/include/dtmf_scheduler.h new file mode 100644 index 0000000..22a4e55 --- /dev/null +++ b/include/dtmf_scheduler.h @@ -0,0 +1,25 @@ +#ifndef DTMF_SCHEDULER_H +#define DTMF_SCHEDULER_H + +/** + * The state/queue for DTMF signalling. + */ +struct dtmf_state { + int size; /* <! The last tone to play */ + char tones[24]; /* <! Pending tones */ + int playing; /* <! Playing a tone right now? */ +}; + +/* initialize */ +void dtmf_state_init(struct dtmf_state *state); + +/* add a tone to the list */ +int dtmf_state_add(struct dtmf_state *state, char tone); + +/* tones that should be played, playing will be set to 1 */ +void dtmf_state_get_pending(struct dtmf_state *state, char *tones); + +/* call when the playout is done */ +void dtmf_state_played(struct dtmf_state *state); + +#endif diff --git a/include/mgcp/mgcp_internal.h b/include/mgcp/mgcp_internal.h index 1918cb0..6b2989d 100644 --- a/include/mgcp/mgcp_internal.h +++ b/include/mgcp/mgcp_internal.h @@ -24,6 +24,7 @@ #define OPENBSC_MGCP_DATA_H #include <osmocom/core/select.h> +#include <dtmf_scheduler.h> #define CI_UNUSED 0 @@ -124,6 +125,7 @@ struct mgcp_endpoint { struct mgcp_rtp_tap taps[MGCP_TAP_COUNT]; /* Special MGW handling */ + struct dtmf_state dtmf_state; int blocked; unsigned int hw_dsp_port; /** This is index 1 based */ unsigned int audio_port; diff --git a/include/mgcp_ss7.h b/include/mgcp_ss7.h index 4847989..babfde6 100644 --- a/include/mgcp_ss7.h +++ b/include/mgcp_ss7.h @@ -46,6 +46,7 @@ enum { MGCP_SS7_MUTE_STATUS, MGCP_SS7_ALLOCATE, MGCP_SS7_DELETE, + MGCP_SS7_DTMF, }; struct mgcp_ss7_cmd { diff --git a/src/Makefile.am b/src/Makefile.am index 4ef3edf..e681537 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,7 +6,8 @@ AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(LIBOSMOVTY_CFLAGS) sbin_PROGRAMS = cellmgr_ng osmo_stp mgcp_mgw mgcp_mgw_SOURCES = mgcp_ss7.c mgcp_ss7_vty.c mgcp_hw.c thread.c debug.c \ - mgcp/mgcp_protocol.c mgcp/mgcp_network.c mgcp/mgcp_vty.c + mgcp/mgcp_protocol.c mgcp/mgcp_network.c mgcp/mgcp_vty.c \ + dtmf_scheduler.c mgcp_mgw_LDADD = $(LAFORGE_LIBS) $(NEXUSWARE_C7_LIBS) $(NEXUSWARE_UNIPORTE_LIBS) \ $(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) -lpthread -lcrypto diff --git a/src/dtmf_scheduler.c b/src/dtmf_scheduler.c new file mode 100644 index 0000000..26dc090 --- /dev/null +++ b/src/dtmf_scheduler.c @@ -0,0 +1,59 @@ +/* + * (C) 2012 by Holger Hans Peter Freyther + * (C) 2012 by On-Waves + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "dtmf_scheduler.h" +#include <string.h> +#include <stdio.h> + +void dtmf_state_init(struct dtmf_state *state) +{ + memset(state, 0, sizeof(*state)); +} + +int dtmf_state_add(struct dtmf_state *state, char tone) +{ + /* we would override the head */ + if (state->size == sizeof(state->tones)) + return -1; + + state->tones[state->size++] = tone; + return 0; +} + +void dtmf_state_get_pending(struct dtmf_state *state, char *tones) +{ + int pos; + + for (pos = 0; pos < state->size; ++pos) + tones[pos] = state->tones[pos]; + + /* consume everything up to the tail */ + state->size = 0; + + /* remember that we play things */ + if (pos > 0) + state->playing = 1; + tones[pos] = '\0'; +} + +void dtmf_state_played(struct dtmf_state *state) +{ + state->playing = 0; +} diff --git a/src/mgcp_ss7.c b/src/mgcp_ss7.c index 47c162b..c9e9b4f 100644 --- a/src/mgcp_ss7.c +++ b/src/mgcp_ss7.c @@ -70,6 +70,44 @@ static void mgcp_ss7_do_exec(struct mgcp_ss7 *mgcp, uint8_t type, struct mgcp_en /* Contains a mapping from UniPorte to the MGCP side of things */ static struct mgcp_endpoint *s_endpoints[240]; +static void play_pending_tones(struct mgcp_endpoint *endp) +{ + ToneGenerationT toneGeneration; + char tones[25]; + + /* Check if we need to play anything? */ + dtmf_state_get_pending(&endp->dtmf_state, tones); + + /* nothing to play? */ + if (strlen(tones) == 0) + return; + + /* fill out the data now */ + osmo_static_assert(sizeof(tones) <= sizeof(toneGeneration.list), Enough_space_for_tones); + memset(&toneGeneration, 0, sizeof(toneGeneration)); + toneGeneration.count = strlen(tones); + strcpy(toneGeneration.list, tones); + MtnSaSetMOB(endp->audio_port, ChannelType_PORT, + PredefMob_C_TONE_GENERATION, (char *) &toneGeneration, + sizeof(toneGeneration), 1); +} + +static void send_dtmf(struct mgcp_endpoint *mgw_endp, int ascii_tone) +{ + int rc; + rc = dtmf_state_add(&mgw_endp->dtmf_state, ascii_tone); + if (rc != 0) { + fprintf(stderr, "DTMF queue too long on 0x%x\n", + ENDPOINT_NUMBER(mgw_endp)); + syslog(LOG_ERR, "DTMF queue too long on 0x%x\n", + ENDPOINT_NUMBER(mgw_endp)); + return; + } + + if (!mgw_endp->dtmf_state.playing) + play_pending_tones(mgw_endp); +} + static int select_voice_port(struct mgcp_endpoint *endp) { int mgw_port; @@ -200,6 +238,24 @@ static int uniporte_events(unsigned long port, EventTypeT event, fprintf(stderr, "State change on a non blocked port. ERROR.\n"); } endp->block_processing = 0; + } else if (info->trapId == Trap_TONE_GENERATION_COMPLETE) { + sprintf(text, "DTMF complete on #%ld", port); + puts(text); + + /* update the mgcp state */ + if (port >= ARRAY_SIZE(s_endpoints)) { + syslog(LOG_ERR, "The port is bigger than we can manage.\n"); + fprintf(stderr, "The port is bigger than we can manage.\n"); + return 0; + } + + endp = s_endpoints[port]; + if (!endp) { + syslog(LOG_ERR, "Unexpected event on port %d\n", port); + fprintf(stderr, "Unexpected event on port %d\n", port); + return 0; + } + play_pending_tones(endp); } } else if ( event == Event_MANAGED_OBJECT_SET_COMPLETE ) { @@ -356,6 +412,9 @@ static void allocate_endp(struct mgcp_ss7 *ss7, struct mgcp_endpoint *endp) int mgw_port; unsigned long mgw_address, loc_address; + /* reset the DTMF state */ + dtmf_state_init(&endp->dtmf_state); + /* now find the voice processor we want to use */ mgw_port = select_voice_port(endp); if (mgw_port < 0) @@ -471,6 +530,10 @@ static void mgcp_ss7_do_exec(struct mgcp_ss7 *mgcp, uint8_t type, if (mgw_endp->audio_port != UINT_MAX) update_mute_status(mgw_endp->audio_port, param); break; + case MGCP_SS7_DTMF: + if (mgw_endp->audio_port != UINT_MAX) + send_dtmf(mgw_endp, param); + break; case MGCP_SS7_DELETE: if (mgw_endp->audio_port != UINT_MAX) { rc = MtnSaDisconnect(mgw_endp->audio_port); @@ -487,6 +550,7 @@ static void mgcp_ss7_do_exec(struct mgcp_ss7 *mgcp, uint8_t type, mgw_endp->audio_port = UINT_MAX; mgw_endp->block_processing = 1; } + dtmf_state_init(&mgw_endp->dtmf_state); hw_maybe_loop_endp(mgw_endp); break; case MGCP_SS7_ALLOCATE: @@ -579,6 +643,12 @@ static int mgcp_ss7_policy(struct mgcp_trunk_config *tcfg, int endp_no, int stat return rc; } +static int mgcp_dtmf_cb(struct mgcp_endpoint *endp, char tone, const char *data) +{ + mgcp_ss7_exec(endp, MGCP_SS7_DTMF, tone); + return 0; +} + static void enqueue_msg(struct osmo_wqueue *queue, struct sockaddr_in *addr, struct msgb *msg) { struct sockaddr_in *data; @@ -905,6 +975,8 @@ int main(int argc, char **argv) return -1; } + g_cfg->rqnt_cb = mgcp_dtmf_cb; + if (mgcp_parse_config(config_file, g_cfg) != 0) { LOGP(DMGCP, LOGL_ERROR, "Failed to parse the config file: '%s'\n", config_file); diff --git a/tests/Makefile.am b/tests/Makefile.am index 6efc649..5c9cdd5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = mtp patching isup mgcp +SUBDIRS = mtp patching isup mgcp dtmf # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac diff --git a/tests/dtmf/Makefile.am b/tests/dtmf/Makefile.am new file mode 100644 index 0000000..4241120 --- /dev/null +++ b/tests/dtmf/Makefile.am @@ -0,0 +1,8 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) +noinst_PROGRAMS = dtmf_test + +EXTRA_DIST = dtmf_test.ok + +dtmf_test_SOURCES = dtmf_test.c $(top_srcdir)/src/dtmf_scheduler.c +dtmf_test_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/tests/dtmf/dtmf_test.c b/tests/dtmf/dtmf_test.c new file mode 100644 index 0000000..5ffb121 --- /dev/null +++ b/tests/dtmf/dtmf_test.c @@ -0,0 +1,103 @@ +/* + * (C) 2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2012 by On-Waves + * All Rights Reserved + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <dtmf_scheduler.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#define ASSERT(got,want) \ + if (got != want) { \ + fprintf(stderr, "Values should be the same 0x%x 0x%x at %s:%d\n", \ + got, want, __FILE__, __LINE__); \ + abort(); \ + } + +static void test_queue_while_play(void) +{ + struct dtmf_state state; + char tone[sizeof(state.tones) + 1]; + + dtmf_state_init(&state); + + ASSERT(dtmf_state_add(&state, 'a'), 0); + ASSERT(dtmf_state_add(&state, 'b'), 0); + ASSERT(dtmf_state_add(&state, 'c'), 0); + + dtmf_state_get_pending(&state, tone); + ASSERT(strlen(tone), 3); + ASSERT(state.playing, 1); + ASSERT(strcmp(tone, "abc"), 0); + + ASSERT(dtmf_state_add(&state, 'd'), 0); + dtmf_state_played(&state); + ASSERT(state.playing, 0); + + dtmf_state_get_pending(&state, tone); + ASSERT(strlen(tone), 1); + ASSERT(state.playing, 1); + ASSERT(strcmp(tone, "d"), 0); + + ASSERT(state.playing, 1); + dtmf_state_played(&state); + ASSERT(state.playing, 0); + + /* and check that nothing is played */ + dtmf_state_get_pending(&state, tone); + ASSERT(strlen(tone), 0); + ASSERT(state.playing, 0); +} + +static void test_queue_over_flow(void) +{ + struct dtmf_state state; + const size_t max_items = sizeof(state.tones); + char tone[sizeof(state.tones) + 1]; + int i; + + dtmf_state_init(&state); + + /* add everything that should fit.. */ + for (i = 0; i < max_items; ++i) { + ASSERT(dtmf_state_add(&state, 'a' + i), 0); + } + + /* this should fail */ + ASSERT(dtmf_state_add(&state, 'Z'), -1); + + /* read all of it */ + dtmf_state_get_pending(&state, tone); + ASSERT(strlen(tone), max_items); + for (i = 0; i < strlen(tone); ++i) + ASSERT(tone[i], 'a' + i); + ASSERT(state.playing, 1); + dtmf_state_played(&state); + ASSERT(state.playing, 0); +} + + +int main(int argc, char **argv) +{ + test_queue_while_play(); + test_queue_over_flow(); + printf("All tests passed.\n"); + return 0; +} diff --git a/tests/dtmf/dtmf_test.ok b/tests/dtmf/dtmf_test.ok new file mode 100644 index 0000000..828a010 --- /dev/null +++ b/tests/dtmf/dtmf_test.ok @@ -0,0 +1 @@ +All tests passed. diff --git a/tests/testsuite.at b/tests/testsuite.at index 069a502..de06c92 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -24,3 +24,9 @@ AT_KEYWORDS([patching]) cat $abs_srcdir/patching/patching_test.ok > expout AT_CHECK([$abs_top_builddir/tests/patching/patching_test], [], [expout], [ignore]) AT_CLEANUP + +AT_SETUP([dtmf]) +AT_KEYWORDS([dtmf]) +cat $abs_srcdir/dtmf/dtmf_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/dtmf/dtmf_test], [], [expout], [ignore]) +AT_CLEANUP |