[mod_rayo, mod_ssml, iksemel] Remove from tree

This commit is contained in:
Andrey Volk
2024-12-31 13:43:48 +03:00
parent 3687972db7
commit 0e412ac0ee
211 changed files with 2 additions and 56590 deletions
-2
View File
@@ -80,8 +80,6 @@ directories/mod_ldap/Win32/
languages/mod_managed/Win32/
/applications/mod_av/test/Makefile
/applications/mod_av/test/Makefile.in
/event_handlers/mod_rayo/test/Makefile
/event_handlers/mod_rayo/test/Makefile.in
/applications/mod_av/test/test_avformat
/applications/mod_av/test/test_mod_av
/languages/mod_lua/test/Makefile
-2
View File
@@ -9,8 +9,6 @@ check: $(OUR_CHECK_MODULES)
mod_skypopen-all: mod_gsmopen-all
mod_gsmopen-all: mod_spandsp-all
mod_rayo-all: mod_ssml-all
mod_ssml-all: mod_rayo-all
$(OUR_MODULES) $(OUR_CLEAN_MODULES) $(OUR_INSTALL_MODULES) $(OUR_UNINSTALL_MODULES) $(OUR_DISABLED_MODULES) $(OUR_DISABLED_CLEAN_MODULES) $(OUR_DISABLED_INSTALL_MODULES) $(OUR_DISABLED_UNINSTALL_MODULES) $(OUR_TEST_MODULES) $(OUR_CHECK_MODULES):
@set fnord $$MAKEFLAGS; amf=$$2; \
@@ -7,7 +7,6 @@
<load module="mod_sndfile"/>
<load module="mod_loopback"/>
<load module="mod_dptools"/>
<load module="mod_ssml"/>
<load module="mod_tone_stream"/>
<load module="mod_conference"/>
<load module="mod_commands"/>
@@ -1,44 +0,0 @@
include $(top_srcdir)/build/modmake.rulesam
MODNAME=mod_rayo
IKS_DIR=$(switch_srcdir)/libs/iksemel
IKS_BUILDDIR=$(switch_builddir)/libs/iksemel
IKS_LA=$(IKS_BUILDDIR)/src/libiksemel.la
noinst_LTLIBRARIES = librayomod.la
librayomod_la_SOURCES = mod_rayo.c iks_helpers.c nlsml.c rayo_components.c rayo_cpa_component.c rayo_cpa_detector.c rayo_elements.c rayo_fax_components.c
librayomod_la_SOURCES += rayo_input_component.c rayo_output_component.c rayo_prompt_component.c rayo_record_component.c sasl.c srgs.c xmpp_streams.c rayo_exec_component.c
librayomod_la_CFLAGS = $(AM_CFLAGS) -I$(switch_builddir)/libs/iksemel/include $(PCRE_CFLAGS)
mod_LTLIBRARIES = mod_rayo.la
mod_rayo_la_SOURCES =
mod_rayo_la_CFLAGS = $(AM_CFLAGS) -I$(IKS_DIR)/include $(PCRE_CFLAGS)
mod_rayo_la_LIBADD = $(switch_builddir)/libfreeswitch.la $(IKS_LA) $(PCRE_LIBS) librayomod.la
mod_rayo_la_LDFLAGS = -avoid-version -module -no-undefined -shared
BUILT_SOURCES=$(IKS_LA)
$(IKS_LA): $(IKS_BUILDDIR) $(IKS_DIR) $(IKS_DIR)/.update
@cd $(IKS_BUILDDIR) && $(MAKE)
@$(TOUCH_TARGET)
noinst_PROGRAMS = test/test_iks test/test_nlsml test/test_srgs
test_test_iks_SOURCES = test/test_iks.c
test_test_iks_CFLAGS = $(AM_CFLAGS) -I. -I$(switch_builddir)/libs/iksemel/include $(PCRE_CFLAGS) -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
test_test_iks_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
test_test_iks_LDADD = librayomod.la $(IKS_LA) $(PCRE_LIBS) $(switch_builddir)/libfreeswitch.la
test_test_nlsml_SOURCES = test/test_nlsml.c
test_test_nlsml_CFLAGS = $(AM_CFLAGS) -I. -I$(switch_builddir)/libs/iksemel/include $(PCRE_CFLAGS) -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
test_test_nlsml_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
test_test_nlsml_LDADD = librayomod.la $(IKS_LA) $(PCRE_LIBS) $(switch_builddir)/libfreeswitch.la
test_test_srgs_SOURCES = test/test_srgs.c
test_test_srgs_CFLAGS = $(AM_CFLAGS) -I. -I$(switch_builddir)/libs/iksemel/include $(PCRE_CFLAGS) -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\"
test_test_srgs_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS)
test_test_srgs_LDADD = librayomod.la $(IKS_LA) $(PCRE_LIBS) $(switch_builddir)/libfreeswitch.la
TESTS = $(noinst_PROGRAMS)
@@ -1,315 +0,0 @@
<configuration name="rayo.conf" description="Rayo server config">
<!-- rayo params -->
<settings>
<!-- ends idle calls : unbridged calls that have not been controlled by client in some time -->
<param name="max-idle-sec" value="300"/>
<!-- conference profile to use for mixers- sla = shared line appearance / conference /w no audio -->
<param name="mixer-conf-profile" value="sla"/>
<!-- if true, to attribute in offer uses URI instead of name/number -->
<param name="offer-uri" value="true"/>
<!-- how offers are distributed to clients (all, first, random). -->
<param name="offer-algorithm" value="all"/>
<!-- If offer is not answered after timeout, next client is offered (based on algorithm picked).
If no other clients are available, the call is rejected. Set to 0 to disable -->
<param name="offer-timeout-ms" value="5000"/>
<!-- if true, channel variables are added to rayo client offer -->
<param name="add-variables-to-offer" value="false"/>
<!-- if true, channel variables are added to offer, ringing, answered, and end events sent to rayo clients -->
<param name="add-variables-to-events" value="false"/>
</settings>
<!-- record component params -->
<record>
<param name="record-file-prefix" value="$${recordings_dir}/"/>
</record>
<!-- input component params -->
<input>
<param name="default-recognizer" value="pocketsphinx"/>
</input>
<!-- send/receivefax component params -->
<fax>
<!-- where to store incoming faxes -->
<param name="receivefax-file-prefix" value="/tmp/"/>
</fax>
<!-- Call progress analysis configuration -->
<!-- Define CPA detectors. These are sources for Rayo CPA signal events -->
<!-- Detectors must fire an event that can be translated by mod_rayo into a CPA signal event -->
<cpa>
<!-- map DTMF events to CPA -->
<detector name="core_dtmf_event">
<event class="DTMF" value-header="DTMF-Digit" duration-header="DTMF-Duration">
<signal-type value="dtmf"/>
</event>
</detector>
<!-- map mod_spandsp fax detector to the Rayo CPA events. Fires DETECTED_FAX_* event once and quits. -->
<detector name="mod_spandsp_fax_ced">
<start application="spandsp_start_fax_detect" data="event 'Event-Name=CUSTOM,Event-Subclass=DETECTED_FAX_CED' 500 ced"/>
<stop application="spandsp_stop_fax_detect" data=""/>
<event class="CUSTOM" subclass="DETECTED_FAX_CED">
<signal-type value="fax-ced"/>
</event>
</detector>
<detector name="mod_spandsp_fax_cng">
<start application="spandsp_start_fax_detect" data="event 'Event-Name=CUSTOM,Event-Subclass=DETECTED_FAX_CNG' 500"/>
<stop application="spandsp_stop_fax_detect" data=""/>
<event class="CUSTOM" subclass="DETECTED_FAX_CNG">
<signal-type value="fax-cng"/>
</event>
</detector>
<!-- map mod_spandsp call progress tone detector to Rayo CPA signal events. Fires DETECTED_TONE events until stopped. -->
<detector name="mod_spandsp_tone">
<start application="spandsp_start_tone_detect" data="1"/>
<stop application="spandsp_stop_tone_detect" data=""/>
<!-- map tone events to Rayo CPA signal type -->
<event class="DETECTED_TONE" type-header="Detected-Tone">
<signal-type header-value="SIT" value="sit"/>
<signal-type header-value="BUSY_TONE" value="busy"/>
<signal-type header-value="REORDER_TONE" value="congestion"/>
<signal-type header-value="RING_TONE" value="ring"/>
</event>
</detector>
<!-- map mod_avmd detector to Rayo CPA beep event. Fires avmd::beep event once. -->
<detector name="mod_avmd">
<start application="avmd" data=""/>
<stop application="avmd" data="stop"/>
<event class="CUSTOM" subclass="avmd::beep">
<signal-type value="beep"/>
</event>
</detector>
<!-- Alternative beep detector using mod_vmd. Fires vmd::beep events until stopped. -->
<!--detector name="mod_vmd">
<start application="vmd" data=""/>
<stop application="vmd" data="stop"/>
<event class="CUSTOM" subclass="vmd::beep">
<signal-type value="beep"/>
</event>
</detector-->
</cpa>
<!-- XMPP server domain -->
<domain name="$${rayo_domain_name}" shared-secret="ClueCon">
<!-- use this instead if you want secure XMPP client to server connections. Put .crt and .key file in freeswitch/certs -->
<!--domain name="$${rayo_domain_name}" shared-secret="ClueCon" cert="$${base_dir}/certs/$${rayo_domain_name}.crt" key="$${base_dir}/certs/$${rayo_domain_name}.key"-->
<!-- Listeners for new Rayo client connections -->
<!--listen type="c2s" port="5222" address="$${local_ip_v4}" acl="rayo-clients"/-->
<listen type="c2s" port="5222" address="$${rayo_ip}" acl=""/>
<!-- Listeners for new server connections -->
<!--listen type="s2s" port="5269" address="$${local_ip_v4}" acl="rayo-servers"/-->
<!-- servers to connect to -->
<!--connect port="5269" address="node.example.com" domain="example.com"/-->
<!-- Authorized users -->
<users>
<user name="usera" password="1"/>
</users>
</domain>
<!-- URI mapping to endpoint / gateway -->
<dial-gateways>
<dial-gateway uriprefix="default" dialprefix="sofia/gateway/outbound/" strip="0"/>
<dial-gateway uriprefix="sip:" dialprefix="sofia/external/" strip="0"/>
<dial-gateway uriprefix="tel:" dialprefix="sofia/gateway/outbound/" strip="4"/>
<!-- pass through user and sofia unaltered -->
<dial-gateway uriprefix="user/" dialprefix="" strip=""/>
<dial-gateway uriprefix="sofia/" dialprefix="" strip=""/>
<dial-gateway uriprefix="loopback/" dialprefix="" strip=""/>
</dial-gateways>
<!-- IQ request aliases. Used mainly for testing purposes or for controlling a rayo call via the console -->
<aliases>
<alias name="detect" target="call" args="1"><![CDATA[<input xmlns="urn:xmpp:rayo:input:1" mode="cpa"><grammar url="urn:xmpp:rayo:cpa:$1:1"/></input>]]></alias>
<alias name="detect-once" target="call" args="1"><![CDATA[<input xmlns="urn:xmpp:rayo:input:1" mode="cpa"><grammar url="urn:xmpp:rayo:cpa:$1:1?terminate=true"/></input>]]></alias>
<alias name="detect-tones" target="call"><![CDATA[<input xmlns="urn:xmpp:rayo:input:1" mode="cpa"><grammar url="urn:xmpp:rayo:cpa:busy:1"/><grammar url="urn:xmpp:rayo:cpa:congestion:1"/><grammar url="urn:xmpp:rayo:cpa:sit:1"/></input>]]></alias>
<alias name="ping" target="external"><![CDATA[<iq type="get"><ping xmlns="urn:xmpp:ping"/></iq>]]></alias>
<alias name="dial" target="server" args="2"><![CDATA[<dial xmlns="urn:xmpp:rayo:1" from="$1" to="$2"/>]]></alias>
<alias name="answer" target="call"><![CDATA[<answer xmlns="urn:xmpp:rayo:1"/>]]></alias>
<alias name="hangup" target="call"><![CDATA[<hangup xmlns="urn:xmpp:rayo:1"/>]]></alias>
<alias name="join" target="call" args="1"><![CDATA[<join xmlns="urn:xmpp:rayo:1" call-uri="xmpp:$1"/>]]></alias>
<alias name="join_mixer_duplex" target="call" args="1"><![CDATA[<join xmlns="urn:xmpp:rayo:1" mixer-name="$1" direction="duplex"/>]]></alias>
<alias name="join_mixer_send" target="call" args="1"><![CDATA[<join xmlns="urn:xmpp:rayo:1" mixer-name="$1" direction="send"/>]]></alias>
<alias name="join_mixer_recv" target="call" args="1"><![CDATA[<join xmlns="urn:xmpp:rayo:1" mixer-name="$1" direction="recv"/>]]></alias>
<alias name="unjoin_mixer" target="call" args="1"><![CDATA[<unjoin xmlns="urn:xmpp:rayo:1" mixer-name="$1"/>]]></alias>
<alias name="unjoin" target="call"><![CDATA[<unjoin xmlns="urn:xmpp:rayo:1"/>]]></alias>
<alias name="unjoin_call" target="call" args="1"><![CDATA[<unjoin xmlns="urn:xmpp:rayo:1" call-uri="xmpp:$1"/>]]></alias>
<alias name="stop" target="component"><![CDATA[<stop xmlns="urn:xmpp:rayo:ext:1"/>]]></alias>
<alias name="output_bad" target="call"><![CDATA[<output xmlns="urn:xmpp:rayo:output:1" repeat-time="100"></output>]]></alias>
<alias name="pause" target="output"><![CDATA[<pause xmlns="urn:xmpp:rayo:output:1"/>]]></alias>
<alias name="resume" target="output"><![CDATA[<resume xmlns="urn:xmpp:rayo:output:1"/>]]></alias>
<alias name="speed-up" target="output"><![CDATA[<speed-up xmlns="urn:xmpp:rayo:output:1"/>]]></alias>
<alias name="speed-down" target="output"><![CDATA[<speed-down xmlns="urn:xmpp:rayo:output:1"/>]]></alias>
<alias name="volume-up" target="output"><![CDATA[<volume-up xmlns="urn:xmpp:rayo:output:1"/>]]></alias>
<alias name="volume-down" target="output"><![CDATA[<volume-down xmlns="urn:xmpp:rayo:output:1"/>]]></alias>
<alias name="receivefax" target="call"><![CDATA[<receivefax xmlns="urn:xmpp:rayo:fax:1"/>]]></alias>
<alias name="sendfax" target="call" args="1"><![CDATA[<sendfax xmlns="urn:xmpp:rayo:fax:1"><document xmlns="urn:xmpp:rayo:fax:1" url="$1"/></sendfax>]]></alias>
<alias name="record" target="call"><![CDATA[<record xmlns="urn:xmpp:rayo:record:1"/>]]></alias>
<alias name="record_pause" target="record"><![CDATA[<pause xmlns="urn:xmpp:rayo:record:1"/>]]></alias>
<alias name="record_resume" target="record"><![CDATA[<resume xmlns="urn:xmpp:rayo:record:1"/>]]></alias>
<alias name="prompt_barge" target="call">
<![CDATA[
<prompt xmlns="urn:xmpp:rayo:prompt:1" barge-in="true">
<output xmlns="urn:xmpp:rayo:output:1" repeat-times="5">
<document content-type="application/ssml+xml">
<![CDATA[<speak><p>Please press a digit.</p></speak>]]]]><![CDATA[>
</document>
</output>
<input xmlns="urn:xmpp:rayo:input:1" mode="dtmf" initial-timeout="5000" inter-digit-timeout="3000">
<grammar content-type="application/srgs+xml">
<![CDATA[<grammar mode="dtmf"><rule id="digit" scope="public"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></rule></grammar>]]]]><![CDATA[>
</grammar>
</input>
</prompt>
]]>
</alias>
<alias name="prompt_no_barge" target="call">
<![CDATA[
<prompt xmlns="urn:xmpp:rayo:prompt:1" barge-in="false">
<output xmlns="urn:xmpp:rayo:output:1" repeat-times="5">
<document content-type="application/ssml+xml">
<![CDATA[<speak><p>Please press a digit.</p></speak>]]]]><![CDATA[>
</document>
</output>
<input xmlns="urn:xmpp:rayo:input:1" mode="dtmf" initial-timeout="5000" inter-digit-timeout="3000">
<grammar content-type="application/srgs+xml">
<![CDATA[<grammar mode="dtmf"><rule id="digit" scope="public"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></rule></grammar>]]]]><![CDATA[>
</grammar>
</input>
</prompt>
]]>
</alias>
<alias name="prompt_long" target="call">
<![CDATA[
<prompt xmlns="urn:xmpp:rayo:prompt:1" barge-in="true">
<output xmlns="urn:xmpp:rayo:output:1" repeat-times="100">
<document content-type="application/ssml+xml">
<![CDATA[<speak><p>Please press a digit.</p></speak>]]]]><![CDATA[>
</document>
</output>
<input xmlns="urn:xmpp:rayo:input:1" mode="dtmf" initial-timeout="5000" inter-digit-timeout="3000">
<grammar content-type="application/srgs+xml">
<![CDATA[<grammar mode="dtmf"><rule id="digit" scope="public"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></rule></grammar>]]]]><![CDATA[>
</grammar>
</input>
</prompt>
]]>
</alias>
<alias name="prompt_multi_digit" target="call">
<![CDATA[
<prompt xmlns="urn:xmpp:rayo:prompt:1" barge-in="true">
<output xmlns="urn:xmpp:rayo:output:1" repeat-times="100">
<document content-type="application/ssml+xml">
<![CDATA[<speak><p>Please press a digit.</p></speak>]]]]><![CDATA[>
</document>
</output>
<input xmlns="urn:xmpp:rayo:input:1" mode="dtmf" initial-timeout="5000" inter-digit-timeout="3000">
<grammar content-type="application/srgs+xml">
<![CDATA[<grammar mode="dtmf"><rule id="digit" scope="public"><item repeat="1-4"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></item></rule></grammar>]]]]><![CDATA[>
</grammar>
</input>
</prompt>
]]>
</alias>
<alias name="prompt_terminator" target="call">
<![CDATA[
<prompt xmlns="urn:xmpp:rayo:prompt:1" barge-in="true">
<output xmlns="urn:xmpp:rayo:output:1" repeat-times="100">
<document content-type="application/ssml+xml">
<![CDATA[<speak><p>Please press a digit.</p></speak>]]]]><![CDATA[>
</document>
</output>
<input xmlns="urn:xmpp:rayo:input:1" mode="dtmf" initial-timeout="5000" inter-digit-timeout="3000" terminator="#">
<grammar content-type="application/srgs+xml">
<![CDATA[<grammar mode="dtmf"><rule id="digit" scope="public"><item repeat="1-4"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></item></rule></grammar>]]]]><![CDATA[>
</grammar>
</input>
</prompt>
]]>
</alias>
<alias name="prompt_input_bad" target="call">
<![CDATA[
<prompt xmlns="urn:xmpp:rayo:prompt:1" barge-in="true">
<output xmlns="urn:xmpp:rayo:output:1" repeat-times="100">
<document content-type="application/ssml+xml">
<![CDATA[<speak><p>Please press a digit.</p></speak>]]]]><![CDATA[>
</document>
</output>
<input xmlns="urn:xmpp:rayo:input:1" mode="dtf" initial-timeout="5000" inter-digit-timeout="3000" terminator="#">
<grammar content-type="application/srgs+xml">
<![CDATA[<grammar mode="dtmf"><rule id="digit" scope="public"><item repeat="1-4"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></item></rule></grammar>]]]]><![CDATA[>
</grammar>
</input>
</prompt>
]]>
</alias>
<alias name="prompt_output_bad" target="call">
<![CDATA[
<prompt xmlns="urn:xmpp:rayo:prompt:1" barge-in="true">
<output xmlns="urn:xmpp:rayo:output:1" repeat-time="100">
<document content-type="application/ssml+xml">
<![CDATA[<speak><p>Please press a digit.</p></speak>]]]]><![CDATA[>
</document>
</output>
<input xmlns="urn:xmpp:rayo:input:1" mode="dtmf" initial-timeout="5000" inter-digit-timeout="3000" terminator="#">
<grammar content-type="application/srgs+xml">
<![CDATA[<grammar mode="dtmf"><rule id="digit" scope="public"><item repeat="1-4"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></item></rule></grammar>]]]]><![CDATA[>
</grammar>
</input>
</prompt>
]]>
</alias>
<alias name="input" target="call">
<![CDATA[
<input xmlns="urn:xmpp:rayo:input:1" mode="dtmf" initial-timeout="5000" inter-digit-timeout="3000">
<grammar content-type="application/srgs+xml">
<![CDATA[<grammar mode="dtmf"><rule id="digit" scope="public"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></rule></grammar>]]]]><![CDATA[>
</grammar>
</input>
]]>
</alias>
<alias name="input_voice_yesno_pocketsphinx" target="call">
<![CDATA[
<input xmlns="urn:xmpp:rayo:input:1" mode="voice" recognizer="pocketsphinx" max-silence="5000" initial-timeout="5000">
<grammar content-type="application/srgs+xml>
<![CDATA[
<grammar xmlns="http://www.w3.org/2001/06/grammar"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd"
xml:lang="en-US" version="1.0">
<rule id="yesno"><one-of><item>yes</item><item>no</item></one-of></rule></grammar>
]]]]><![CDATA[>
</grammar>
</input>
]]>
</alias>
<alias name="input_voice_yesno_default" target="call">
<![CDATA[
<input xmlns="urn:xmpp:rayo:input:1" mode="voice" max-silence="5000" initial-timeout="5000">
<grammar content-type="application/srgs+xml>
<![CDATA[
<grammar xmlns="http://www.w3.org/2001/06/grammar"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd"
xml:lang="en-US" version="1.0">
<rule id="yesno"><one-of><item>yes</item><item>no</item></one-of></rule></grammar>
]]]]><![CDATA[>
</grammar>
</input>
]]>
</alias>
</aliases>
</configuration>
@@ -1,24 +0,0 @@
# Still a work in progress...
# Compile on CentOS 6.4
erlc -I /usr/lib64/ejabberd/include mod_rayo_gateway.erl
# Install in ejabberd
cp mod_rayo_gateway.beam /usr/lib64/ejabberd/ebin/
vi /etc/ejabberd/ejabberd.cfg
%%
%% Modules enabled in all ejabberd virtual hosts
%%
{modules,
[
...
{mod_rayo_gateway,[]},
...
]}.
ejabberdctl debug
(ejabberd@jabber)1> l(mod_rayo_gateway).
{module,mod_rayo_gateway}
(ejabberd@jabber)2>
@@ -1,759 +0,0 @@
%%
%% Copyright (c) 2013 Grasshopper
%%
%% Permission is hereby granted, free of charge, to any person obtaining a copy
%% of this software and associated documentation files (the "Software"), to deal
%% in the Software without restriction, including without limitation the rights
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%% copies of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.
%%
%% Contributors:
%% Chris Rienzo <chris.rienzo@grasshopper.com>
%%
%% Maintainer: Chris Rienzo <chris.rienzo@grasshopper.com>
%%
%% mod_rayo_gateway.erl -- ejabberd Rayo gateway module
%%
-module(mod_rayo_gateway).
-include("ejabberd.hrl").
-include("jlib.hrl").
-behavior(gen_server).
-behavior(gen_mod).
%% JID mappings
%%
%% Entity Internal JID Mapped JID
%% ====== =============== ===============
%% Client user@domain/resource gateway@internal_domain/gw-resource
%% Node node_domain external_domain
%% Call uuid@node_domain node_domain|uuid@external_domain
%% Call Resource uuid@node_domain/resource node_domain|uuid@external_domain/resource
%% Mixer name@node_domain node_domain|name@external_domain
%% Mixer Resource name@node_domain/resource node_domain|name@external_domain/resource
%% TODO don't allow nodes to act as clients
%% TODO don't allow clients to act as nodes
-export([
start_link/2,
start/2,
stop/1,
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3,
route_internal/3,
route_external/3
]).
-define(PROCNAME, ejabberd_mod_rayo_gateway).
-define(NS_RAYO, "urn:xmpp:rayo:1").
-define(NS_PING, "urn:xmpp:ping").
-record(rayo_config, {name, value}).
-record(rayo_clients, {jid, status}).
-record(rayo_nodes, {jid, status}).
-record(rayo_entities, {external_jid, internal_jid, dcp_jid, type}).
start_link(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
% Start the module process
start(Host, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
ChildSpec = {Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
1000,
worker,
[?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
% Shutdown the module
stop(Host) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
gen_server:call(Proc, stop),
supervisor:terminate_child(ejabberd_sup, Proc),
supervisor:delete_child(ejabberd_sup, Proc).
% Initialize the module
init([Host, Opts]) ->
?DEBUG("MOD_RAYO_GATEWAY: Starting", []),
mnesia:delete_table(rayo_clients),
mnesia:create_table(rayo_clients, [{attributes, record_info(fields, rayo_clients)}]),
mnesia:delete_table(rayo_nodes),
mnesia:create_table(rayo_nodes, [{attributes, record_info(fields, rayo_nodes)}]),
mnesia:delete_table(rayo_entities),
mnesia:create_table(rayo_entities, [{attributes, record_info(fields, rayo_entities)}, {index, [internal_jid]}]),
mnesia:delete_table(rayo_config),
mnesia:create_table(rayo_config, [{attributes, record_info(fields, rayo_config)}]),
{A1,A2,A3} = now(),
random:seed(A1, A2, A3),
% create virtual domains
InternalDomain = gen_mod:get_opt_host(Host, Opts, "rayo-int.@HOST@"),
ExternalDomain = gen_mod:get_opt_host(Host, Opts, "rayo.@HOST@"),
{ok, Hostname} = inet:gethostname(),
InternalClient = "gateway@" ++ InternalDomain ++ "/" ++ Hostname ++ "-" ++ integer_to_list(random:uniform(65535)),
?DEBUG("MOD_RAYO_GATEWAY: InternalDomain = ~p, ExternalDomain = ~p, InternalClient = ~p", [InternalDomain, ExternalDomain, InternalClient]),
mnesia:transaction(
fun() ->
mnesia:write(#rayo_config{name = "internal_domain", value = InternalDomain}),
mnesia:write(#rayo_config{name = "internal_client", value = InternalClient}),
mnesia:write(#rayo_config{name = "external_domain", value = ExternalDomain})
end
),
% set up routes to virtual domains
ejabberd_router:register_route(InternalDomain, {apply, ?MODULE, route_internal}),
ejabberd_router:register_route(ExternalDomain, {apply, ?MODULE, route_external}),
{ok, Host}.
handle_call(stop, _From, Host) ->
{stop, normal, ok, Host}.
handle_cast(_Msg, Host) ->
{noreply, Host}.
handle_info(_Msg, Host) ->
{noreply, Host}.
terminate(_Reason, Host) ->
ejabberd_router:unregister_route(Host),
ok.
code_change(_OldVsn, Host, _Extra) ->
{ok, Host}.
register_rayo_node(Jid) ->
Write = fun() ->
mnesia:write(#rayo_nodes{jid = Jid, status = "online" })
end,
Result = mnesia:transaction(Write),
?DEBUG("MOD_RAYO_GATEWAY: register node: ~p, result = ~p, ~p nodes total", [jlib:jid_to_string(Jid), Result, num_rayo_nodes()]),
case num_clients() >= 1 of
true ->
ejabberd_router:route(internal_client(), Jid, online_presence());
_ ->
ok
end,
ok.
% TODO call this when s2s connection is dropped
unregister_rayo_node(Jid) ->
Delete = fun() ->
mnesia:delete({rayo_nodes, Jid})
end,
Result = mnesia:transaction(Delete),
Size = mnesia:table_info(rayo_nodes, size),
?DEBUG("MOD_RAYO_GATEWAY: unregister node: ~p, result = ~p, ~p nodes total", [jlib:jid_to_string(Jid), Result, Size]),
ok.
% Add client
register_rayo_client(Jid) ->
Write = fun() ->
mnesia:write(#rayo_clients{jid = Jid, status = "online" })
end,
Result = mnesia:transaction(Write),
Size = num_clients(),
?DEBUG("MOD_RAYO_GATEWAY: register client: ~p, result = ~p, ~p clients total", [jlib:jid_to_string(Jid), Result, Size]),
case Size of
1 ->
route_to_list(internal_client(), all_rayo_nodes(), online_presence());
_ ->
ok
end,
ok.
% Remove client
% TODO call this when c2s connection is dropped
unregister_rayo_client(Jid) ->
Delete = fun() ->
mnesia:delete({rayo_clients, Jid})
end,
Result = mnesia:transaction(Delete),
Size = num_clients(),
?DEBUG("MOD_RAYO_GATEWAY: unregister client: ~p, result = ~p, ~p clients total", [jlib:jid_to_string(Jid), Result, Size]),
case Size of
0 ->
route_to_list(internal_client(), all_rayo_nodes(), offline_presence());
_ ->
ok
end,
ok.
% Add node entity
register_rayo_node_entity(ExtJid, IntJid, DcpJid, Type) ->
Write = fun() ->
mnesia:write(#rayo_entities{external_jid = ExtJid, internal_jid = IntJid, dcp_jid = DcpJid, type = Type})
end,
Result = mnesia:transaction(Write),
Size = mnesia:table_info(rayo_entities, size),
?DEBUG("MOD_RAYO_GATEWAY: register entity: ~p, result = ~p, ~p entities total", [jlib:jid_to_string(ExtJid), Result, Size]),
ok.
% Remove node entity
unregister_rayo_node_entity(ExtJid) ->
Delete = fun() ->
mnesia:delete({rayo_entities, ExtJid})
end,
Result = mnesia:transaction(Delete),
Size = mnesia:table_info(rayo_entities, size),
?DEBUG("MOD_RAYO_GATEWAY: unregister entity: ~p, result = ~p, ~p entities total", [jlib:jid_to_string(ExtJid), Result, Size]),
ok.
% find node entity given enitity's (or its component's) internal JID
find_rayo_node_entity_by_int_jid(IntJid) ->
% remove resource from JID to find component's parent call/mixer
case mnesia:dirty_index_read(rayo_entities, jlib:jid_remove_resource(IntJid), #rayo_entities.internal_jid) of
[Entity | _] ->
Entity;
_ ->
none
end.
% find node entity given enitity's (or its component's) external JID
find_rayo_node_entity_by_ext_jid(ExtJid) ->
% remove resource from JID to find component's parent call/mixer
case mnesia:dirty_read(rayo_entities, jlib:jid_remove_resource(ExtJid)) of
[Entity | _] ->
Entity;
_ ->
none
end.
% find entity Definitive Controlling Party JID given entity external JID
find_rayo_node_entity_dcp_by_ext_jid(ExtJid) ->
case find_rayo_node_entity_by_ext_jid(ExtJid) of
{rayo_entities, _, _, DcpJid, _} ->
DcpJid;
_ ->
none
end.
% find entity Definitive Controlling Party JID given entity internal JID
find_rayo_node_entity_dcp_by_int_jid(IntJid) ->
case find_rayo_node_entity_by_int_jid(IntJid) of
{rayo_entities, _, _, DcpJid, _} ->
DcpJid;
_ ->
none
end.
% create External JID from Internal JID
% intnode@intdomain/resource -> intdomain-intnode@extdomain/resource
create_external_jid({jid, Node, Domain, Resource, _, _, _}) ->
jlib:make_jid(Domain ++ "|" ++ Node, jlib:jid_to_string(external_domain()), Resource).
% create Internal JID from External JID
% intdomain-intnode@extdomain/resource -> intnode@intdomain/resource
create_internal_jid({jid, Node, _Domain, Resource, _, _, _}) ->
% TODO use rayo_entities to lookup node... it's safer
Idx = string:str(Node, "|"),
case Idx > 0 of
true ->
jlib:make_jid(string:substr(Node, Idx + 1), string:substr(Node, 1, Idx - 1), Resource);
false ->
none
end.
% Take control of entity
% Return {true, internal entity JID} if successful
set_entity_dcp(PcpJid, EntityJid) ->
SetDcp = fun() ->
case mnesia:wread(rayo_entities, EntityJid) of
[{rayo_entities, EntityJid, InternalJid, none, Type}] ->
% take control
case mnesia:write(#rayo_entities{external_jid = EntityJid, internal_jid = InternalJid, dcp_jid = PcpJid, type = Type}) of
ok ->
{true, InternalJid};
Else ->
{error, Else}
end;
_ ->
{false, []}
end
end,
{_, Result} = mnesia:transaction(SetDcp),
Result.
% Check if PCP has control of entity
% Return {true, internal entity JID} if true
is_entity_dcp(PcpJid, EntityJid) ->
% quick check first
case mnesia:dirty_read(rayo_entities, EntityJid) of
[{rayo_entities, EntityJid, _, none, _}] ->
% take control
set_entity_dcp(PcpJid, EntityJid);
[{rayo_entities, EntityJid, InternalJid, PcpJid, _}] ->
{true, InternalJid};
[{rayo_entities, EntityJid, InternalJid, _, _}] ->
{false, InternalJid};
[] ->
?DEBUG("MOD_RAYO_GATEWAY: no match for EntityJid ~p", [EntityJid]),
{false, none}
end.
% Handle presence to external domain
route_external(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "presence", _Attrs, _Els} = Presence) ->
?DEBUG("MOD_RAYO_GATEWAY: got client presence ~n~p", [Presence]),
route_client_presence(From, To, Presence),
ok;
% Handle presence to external domain resource
route_external(From, To, {xmlelement, "presence", _Attrs, _Els} = Presence) ->
?DEBUG("MOD_RAYO_GATEWAY: got client presence to mixer ~n~p", [Presence]),
% TODO check if actually being sent to mixer...
route_client_presence_to_mixer(From, To, Presence),
ok;
% Handle <message> to external domain
route_external(_From, _To, {xmlelement, "message", _Attrs, _Els} = Message) ->
% ignore
?DEBUG("MOD_RAYO_GATEWAY: got client message ~n~p", [Message]),
ok;
% Handle <iq> to external domain
route_external(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "iq", _Attrs, _Els} = IQ) ->
?DEBUG("MOD_RAYO_GATEWAY: got client iq to gateway ~n~p", [IQ]),
case get_attribute_as_list(IQ, "type", "") of
"get" ->
case get_element(IQ, ?NS_PING, "ping") of
undefined ->
route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST);
_ ->
route_result_reply(To, From, IQ)
end;
"set" ->
case get_element(IQ, ?NS_RAYO, "dial") of
undefined->
route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST);
_ ->
route_dial_call(To, From, IQ)
end;
"" ->
route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST)
end,
ok;
% Handle <iq> to external domain resource
route_external(From, To, {xmlelement, "iq", _Attrs, _Els} = IQ) ->
?DEBUG("MOD_RAYO_GATEWAY: got client iq ~n~p", [IQ]),
case is_entity_dcp(From, To) of
{true, _} ->
IntFrom = internal_client(),
IntTo = create_internal_jid(To),
route_iq_request(IntFrom, IntTo, IQ, fun(IQReply) -> route_iq_response(From, To, IQ, IQReply) end);
{false, _} ->
route_error_reply(To, From, IQ, ?ERR_CONFLICT);
_ ->
route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST)
end,
ok.
% Handle <presence> to internal domain
route_internal(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "presence", _Attrs, _Els} = Presence) ->
?DEBUG("MOD_RAYO_GATEWAY: got node presence to internal domain ~n~p", [Presence]),
route_server_presence(From, To, Presence),
ok;
% Handle <presence> to internal domain resource
route_internal(From, To, {xmlelement, "presence", _Attrs, _Els} = Presence) ->
?DEBUG("MOD_RAYO_GATEWAY: got node presence to internal domain ~n~p", [Presence]),
case To =:= internal_client() of
true ->
route_server_presence(From, To, Presence);
false ->
% TODO implement
ok
end,
ok;
% Handle <message> to internal domain
route_internal(_From, _To, {xmlelement, "message", _Attrs, _Els} = Message) ->
?DEBUG("MOD_RAYO_GATEWAY: got node message ~n~p", [Message]),
% ignore
ok;
% Handle <iq> to internal domain.
route_internal(From, {jid, [], _Domain, [], [], _LDomain, []} = To, {xmlelement, "iq", _Attrs, _Els} = IQ) ->
?DEBUG("MOD_RAYO_GATEWAY: got node iq ~n~p", [IQ]),
case get_attribute_as_list(IQ, "type", "") of
"get" ->
case get_element(IQ, ?NS_PING, "ping") of
undefined ->
route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST);
_ ->
route_result_reply(To, From, IQ)
end;
"result" ->
ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ));
"error" ->
ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ));
"" ->
% don't allow get/set from nodes
route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST)
end,
ok;
% Handle <iq> to internal domain resource.
route_internal(From, To, {xmlelement, "iq", _Attrs, _Els} = IQ) ->
?DEBUG("MOD_RAYO_GATEWAY: got node iq ~n~p", [IQ]),
case get_attribute_as_list(IQ, "type", "") of
"result" ->
ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ));
"error" ->
ejabberd_local:process_iq_reply(From, To, jlib:iq_query_or_response_info(IQ));
_ ->
% Don't allow get/set from nodes
route_error_reply(To, From, IQ, ?ERR_BAD_REQUEST)
end,
ok.
% Process presence message from rayo node
route_rayo_node_presence(From, _To, Presence) ->
case get_attribute_as_list(Presence, "type", "") of
"" ->
case get_element(Presence, "show") of
undefined ->
?DEBUG("MOD_RAYO_GATEWAY: ignoring empty presence", []);
Show ->
case get_cdata_as_list(Show) of
"chat" ->
register_rayo_node(From);
"dnd" ->
unregister_rayo_node(From);
"xa" ->
unregister_rayo_node(From);
"" ->
unregister_rayo_node(From)
end
end;
"unavailable" ->
%TODO broadcast end instead?
unregister_rayo_node(From)
end,
ok.
% Process presence from call
route_call_presence(From, _To, Presence) ->
%TODO join/unjoin mixer events
case get_attribute_as_list(Presence, "type", "") of
"" ->
case get_element(Presence, ?NS_RAYO, "offer") of
undefined ->
route_rayo_entity_stanza(From, Presence);
_ ->
route_offer_call(From, Presence)
end;
"unavailable" ->
case get_element(Presence, ?NS_RAYO, "end") of
undefined ->
route_rayo_entity_stanza(From, Presence);
_ ->
route_rayo_entity_stanza(From, Presence),
unregister_rayo_node_entity(create_external_jid(From))
end
end,
ok.
% presence from node
route_server_presence({jid, [], _Domain, [], [], _LDomain, []} = From, To, Presence) ->
route_rayo_node_presence(From, To, Presence),
ok;
% presence from call/mixer
route_server_presence(From, To, Presence) ->
% TODO mixer
route_call_presence(From, To, Presence),
ok.
% presence from Rayo Client
route_client_presence(From, _To, Presence) ->
case get_attribute_as_list(Presence, "type", "") of
"" ->
case get_element(Presence, "show") of
undefined ->
?DEBUG("MOD_RAYO_GATEWAY: ignoring empty presence", []);
Show ->
case get_cdata_as_list(Show) of
"chat" ->
register_rayo_client(From);
"dnd" ->
unregister_rayo_client(From);
_ ->
unregister_rayo_client(From)
end
end;
"unavailable" ->
unregister_rayo_client(From);
_ ->
ok
end,
ok.
% route client directed presence to mixer
route_client_presence_to_mixer(_From, _To, _Presence) ->
% TODO
ok.
% Handle offer to client
route_offer_call(From, Offer) ->
% Any clients available?
case pick_client() of
none ->
% TODO reject?
ok;
ClientDcp ->
% Remember call
ExtFrom = create_external_jid(From),
register_rayo_node_entity(ExtFrom, From, ClientDcp, call),
ejabberd_router:route(ExtFrom, ClientDcp, Offer)
end,
ok.
% convert URI to a JID
uri_to_jid(Uri) ->
JidString = case string:str(Uri, "xmpp:") of
1 ->
string:substr(Uri, 6);
_ ->
Uri
end,
jlib:string_to_jid(JidString).
% convert internal IQ reply to an external reply
create_external_iq_reply(OrigIQ, {xmlelement, _, _, Els} = IQReply) ->
IQId = get_attribute_as_list(OrigIQ, "id", ""),
IQType = get_attribute_as_list(IQReply, "type", ""),
{xmlelement, "iq", [{"id", IQId}, {"type", IQType}], Els}.
% Process dial response
route_dial_call_response(OrigFrom, OrigTo, OrigIQ, timeout) ->
% TODO retry on different node?
route_iq_response(OrigFrom, OrigTo, OrigIQ, timeout);
route_dial_call_response(OrigFrom, OrigTo, OrigIQ, IQReply) ->
?DEBUG("MOD_RAYO_GATEWAY: IQ response for ~p", [OrigIQ]),
IQReplyPacket = jlib:iq_to_xml(IQReply),
case get_element(IQReplyPacket, "error") of
undefined ->
case get_element(IQReplyPacket, "ref") of
undefined ->
ok;
Ref ->
IntJid = uri_to_jid(get_attribute_as_list(Ref, "uri", "")),
register_rayo_node_entity(create_external_jid(IntJid), IntJid, OrigFrom, call)
end;
_ ->
ok
end,
ejabberd_router:route(OrigTo, OrigFrom, create_external_iq_reply(OrigIQ, IQReplyPacket)),
ok.
% Forward dial to node
route_dial_call(From, To, Dial) ->
% any nodes available?
case num_rayo_nodes() > 0 of
true ->
IntFrom = internal_client(),
case pick_rayo_node() of
none ->
route_error_reply(To, From, Dial, ?ERR_SERVICE_UNAVAILABLE);
NodeJid ->
route_iq_request(IntFrom, NodeJid, Dial, fun(IQReply) -> route_dial_call_response(From, To, Dial, IQReply) end)
end;
_ ->
route_error_reply(To, From, Dial, ?ERR_RESOURCE_CONSTRAINT)
end.
% return configuration value given name
config_value(Name) ->
case catch mnesia:dirty_read(rayo_config, Name) of
[{rayo_config, Name, Value}] ->
Value;
_ ->
""
end.
% return internal client name
internal_client() ->
jlib:string_to_jid(config_value("internal_client")).
% return internal domain name
internal_domain() ->
jlib:string_to_jid(config_value("internal_domain")).
% return external domain name
external_domain() ->
jlib:string_to_jid(config_value("external_domain")).
% return number of registered clients
num_clients() ->
mnesia:table_info(rayo_clients, size).
% return all registered client JIDs
all_clients() ->
case mnesia:transaction(fun() -> mnesia:all_keys(rayo_clients) end) of
{atomic, Keys} ->
Keys;
_ ->
[]
end.
% pick a registered client
pick_client() ->
% pick at random for now...
case all_clients() of
[] ->
none;
AllClients ->
lists:nth(random:uniform(length(AllClients)), AllClients)
end.
% pick a registered node
pick_rayo_node() ->
% pick at random for now...
case all_rayo_nodes() of
[] ->
none;
AllNodes ->
lists:nth(random:uniform(length(AllNodes)), AllNodes)
end.
% return number of registered rayo nodes
num_rayo_nodes() ->
mnesia:table_info(rayo_nodes, size).
% return all rayo node JIDs
all_rayo_nodes() ->
case mnesia:transaction(fun() -> mnesia:all_keys(rayo_nodes) end) of
{atomic, Keys} ->
Keys;
_ ->
[]
end.
presence(Status) ->
{xmlelement, "presence", [], [
{xmlelement, "show", [], [
{xmlcdata, Status}
]}
]}.
online_presence() ->
presence(<<"chat">>).
offline_presence() ->
presence(<<"dnd">>).
route_to_list(From, ToList, Stanza) ->
lists:map(fun(To) -> ejabberd_router:route(From, To, Stanza) end, ToList),
ok.
% route stanza from entity
route_rayo_entity_stanza(From, Stanza) ->
case find_rayo_node_entity_dcp_by_int_jid(From) of
none ->
?DEBUG("MOD_RAYO_GATEWAY: Failed to find DCP for ~p", [From]),
ok;
DcpJid ->
ejabberd_router:route(create_external_jid(From), DcpJid, Stanza)
end,
ok.
% route IQ response from node to client
route_iq_response(OrigFrom, OrigTo, OrigIQ, timeout) ->
route_error_reply(OrigTo, OrigFrom, OrigIQ, ?ERR_REMOTE_SERVER_TIMEOUT),
ok;
route_iq_response(OrigFrom, OrigTo, OrigIQ, IQReply) ->
?DEBUG("MOD_RAYO_GATEWAY: IQ response for ~p", [OrigIQ]),
ejabberd_router:route(OrigTo, OrigFrom, create_external_iq_reply(OrigIQ, jlib:iq_to_xml(IQReply))),
ok.
% route IQ from client to node
route_iq_request(From, To, {xmlelement, "iq", _Atts, Els}, ResponseCallback) ->
ejabberd_local:route_iq(From, To, #iq{type = set, sub_el = Els}, ResponseCallback),
ok.
% route IQ error given request
route_error_reply(From, To, IQ, Reason) ->
ejabberd_router:route(From, To, jlib:make_error_reply(IQ, Reason)),
ok.
% route IQ result given request
route_result_reply(From, To, IQ) ->
ejabberd_router:route(From, To, jlib:make_result_iq_reply(IQ)),
ok.
% XML parsing helpers
get_element(Element, Name) ->
case xml:get_subtag(Element, Name) of
false ->
undefined;
Subtag ->
Subtag
end.
get_element(Element, NS, Name) ->
case get_element(Element, Name) of
undefined ->
undefined;
Subtag ->
case get_attribute_as_list(Subtag, "xmlns", "") of
"" ->
undefined;
NS ->
Subtag
end
end.
get_cdata_as_list(undefined) ->
"";
get_cdata_as_list(Element) ->
xml:get_tag_cdata(Element).
get_element_cdata_as_list(Element, Name) ->
get_cdata_as_list(get_element(Element, Name)).
get_element_cdata_as_list(Element, NS, Name) ->
get_cdata_as_list(get_element(Element, NS, Name)).
get_element_attribute_as_list(Element, Name, AttrName, Default) ->
get_attribute_as_list(get_element(Element, Name), AttrName, Default).
get_element_attribute_as_list(Element, NS, Name, AttrName, Default) ->
get_attribute_as_list(get_element(Element, NS, Name), AttrName, Default).
get_attribute_as_list(undefined, _Name, _Default) ->
undefined;
get_attribute_as_list(Element, Name, Default) ->
case xml:get_tag_attr_s(Name, Element) of
"" ->
Default;
Attr ->
Attr
end.
@@ -1,557 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2014, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* iks_helpers.c -- iksemel helpers
*
*/
#include "iks_helpers.h"
#include <switch.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#undef XMPP_ERROR
#define XMPP_ERROR(def_name, name, type) \
const struct xmpp_error def_name##_val = { name, type }; \
const struct xmpp_error *def_name = &def_name##_val;
#include "xmpp_errors.def"
/**
* Create a <presence> event
* @param name the event name
* @param namespace the event namespace
* @param from
* @param to
* @return the event XML node
*/
iks *iks_new_presence(const char *name, const char *namespace, const char *from, const char *to)
{
iks *event = iks_new("presence");
iks *x;
/* iks makes copies of attrib name and value */
iks_insert_attrib(event, "from", from);
iks_insert_attrib(event, "to", to);
x = iks_insert(event, name);
if (!zstr(namespace)) {
iks_insert_attrib(x, "xmlns", namespace);
}
return event;
}
/**
* Create error response from request
* @param req the request
* @param from
* @param to
* @param err the XMPP stanza error
* @return the error response
*/
iks *iks_new_error(iks *req, const struct xmpp_error *err)
{
iks *response = iks_copy(req);
iks *x;
/* <iq> */
iks_insert_attrib(response, "from", iks_find_attrib(req, "to"));
iks_insert_attrib(response, "to", iks_find_attrib(req, "from"));
iks_insert_attrib(response, "type", "error");
/* <error> */
x = iks_insert(response, "error");
iks_insert_attrib(x, "type", err->type);
/* e.g. <feature-not-implemented> */
x = iks_insert(x, err->name);
iks_insert_attrib(x, "xmlns", IKS_NS_XMPP_STANZAS);
return response;
}
/**
* Create error response from request
* @param req the request
* @param from
* @param to
* @param err the XMPP stanza error
* @param detail_text optional text to include in message
* @return the <iq> error response
*/
iks *iks_new_error_detailed(iks *req, const struct xmpp_error *err, const char *detail_text)
{
iks *reply = iks_new_error(req, err);
if (!zstr(detail_text)) {
iks *error = iks_find(reply, "error");
iks *text = iks_insert(error, "text");
iks_insert_attrib(text, "xml:lang", "en");
iks_insert_attrib(text, "xmlns", IKS_NS_XMPP_STANZAS);
iks_insert_cdata(text, detail_text, strlen(detail_text));
}
return reply;
}
/**
* Create error response from request
* @param req the request
* @param from
* @param to
* @param err the XMPP stanza error
* @param detail_text_format format string
* @param ...
* @return the error response
*/
iks *iks_new_error_detailed_printf(iks *req, const struct xmpp_error *err, const char *detail_text_format, ...)
{
iks *reply = NULL;
char *data;
va_list ap;
int ret;
va_start(ap, detail_text_format);
ret = switch_vasprintf(&data, detail_text_format, ap);
va_end(ap);
if (ret == -1) {
return NULL;
}
reply = iks_new_error_detailed(req, err, data);
free(data);
return reply;
}
/**
* Create <iq> result response from request
* @param iq the request
* @return the result response
*/
iks *iks_new_iq_result(iks *iq)
{
iks *response = iks_new("iq");
iks_insert_attrib(response, "from", iks_find_attrib(iq, "to"));
iks_insert_attrib(response, "to", iks_find_attrib(iq, "from"));
iks_insert_attrib(response, "type", "result");
iks_insert_attrib(response, "id", iks_find_attrib(iq, "id"));
return response;
}
/**
* Get attribute value of node, returning empty string if non-existent or not set.
* @param xml the XML node to search
* @param attrib the Attribute name
* @return the attribute value
*/
const char *iks_find_attrib_soft(iks *xml, const char *attrib)
{
char *value = iks_find_attrib(xml, attrib);
return zstr(value) ? "" : value;
}
/**
* Get attribute value of node, returning default value if missing. The default value
* is set in the node if missing.
* @param xml the XML node to search
* @param attrib the Attribute name
* @return the attribute value
*/
const char *iks_find_attrib_default(iks *xml, const char *attrib, const char *def)
{
char *value = iks_find_attrib(xml, attrib);
if (!value) {
iks_insert_attrib(xml, attrib, def);
return def;
}
return value;
}
/**
* Get attribute integer value of node
* @param xml the XML node to search
* @param attrib the Attribute name
* @return the attribute value
*/
int iks_find_int_attrib(iks *xml, const char *attrib)
{
return atoi(iks_find_attrib_soft(xml, attrib));
}
/**
* Get attribute boolean value of node
* @param xml the XML node to search
* @param attrib the Attribute name
* @return the attribute value
*/
int iks_find_bool_attrib(iks *xml, const char *attrib)
{
return switch_true(iks_find_attrib_soft(xml, attrib));
}
/**
* Get attribute double value of node
* @param xml the XML node to search
* @param attrib the Attribute name
* @return the attribute value
*/
double iks_find_decimal_attrib(iks *xml, const char *attrib)
{
return atof(iks_find_attrib_soft(xml, attrib));
}
/**
* Get attribute character value of node
* @param xml the XML node to search
* @param attrib the Attribute name
* @return the attribute value
*/
char iks_find_char_attrib(iks *xml, const char *attrib)
{
return iks_find_attrib_soft(xml, attrib)[0];
}
/**
* Convert iksemel XML node type to string
* @param type the XML node type
* @return the string value of type or "UNKNOWN"
*/
const char *iks_node_type_to_string(int type)
{
switch(type) {
case IKS_NODE_START: return "NODE_START";
case IKS_NODE_NORMAL: return "NODE_NORMAL";
case IKS_NODE_ERROR: return "NODE_ERROR";
case IKS_NODE_STOP: return "NODE_START";
default: return "NODE_UNKNOWN";
}
}
/**
* Convert iksemel error code to string
* @param err the iksemel error code
* @return the string value of error or "UNKNOWN"
*/
const char *iks_net_error_to_string(int err)
{
switch (err) {
case IKS_OK: return "OK";
case IKS_NOMEM: return "NOMEM";
case IKS_BADXML: return "BADXML";
case IKS_HOOK: return "HOOK";
case IKS_NET_NODNS: return "NET_NODNS";
case IKS_NET_NOSOCK: return "NET_NOSOCK";
case IKS_NET_NOCONN: return "NET_NOCONN";
case IKS_NET_RWERR: return "NET_RWERR";
case IKS_NET_NOTSUPP: return "NET_NOTSUPP";
case IKS_NET_TLSFAIL: return "NET_TLSFAIL";
case IKS_NET_DROPPED: return "NET_DROPPED";
case IKS_NET_UNKNOWN: return "NET_UNKNOWN";
default: return "UNKNOWN";
}
}
/**
* Insert attribute using format string
* @param xml node to insert attribute into
* @param name of attribute
* @param fmt format string
* @param ... format string args
*/
iks *iks_insert_attrib_printf(iks *xml, const char *name, const char *fmt, ...)
{
iks *node;
char *data;
va_list ap;
int ret;
va_start(ap, fmt);
ret = switch_vasprintf(&data, fmt, ap);
va_end(ap);
if (ret == -1) {
return NULL;
}
node = iks_insert_attrib(xml, name, data);
free(data);
return node;
}
/**
* @param value to match
* @param rule to check
* @return true if value is one of the comma-separated values in rule
*/
int value_matches(const char *value, const char *rule)
{
if (rule && *rule && value && *value && !strchr(value, ',')) {
const char *begin = strstr(rule, value);
const char *end = begin + strlen(value);
if (!begin) {
return 0;
}
if ((begin == rule || *(begin - 1) == ',') && (*end == ',' || *end == '\0')) {
return 1;
}
/* substring matched... try farther down the string */
return value_matches(value, end);
}
return 0;
}
/**
* Validate boolean
* @param value
* @return SWTICH_TRUE if boolean
*/
int iks_attrib_is_bool(const char *value)
{
if (value && *value && (!strcasecmp("true", value) || !strcasecmp("false", value))) {
return SWITCH_TRUE;
}
return SWITCH_FALSE;
}
/**
* Validate integer
* @param value
* @return SWTICH_TRUE if not negative
*/
int iks_attrib_is_not_negative(const char *value)
{
if (value && *value && switch_is_number(value)) {
int value_i = atoi(value);
if (value_i >= 0) {
return SWITCH_TRUE;
}
}
return SWITCH_FALSE;
}
/**
* Validate integer
* @param value
* @return SWTICH_TRUE if positive
*/
int iks_attrib_is_positive(const char *value)
{
if (value && *value && switch_is_number(value)) {
int value_i = atoi(value);
if (value_i > 0) {
return SWITCH_TRUE;
}
}
return SWITCH_FALSE;
}
/**
* Validate integer
* @param value
* @return SWTICH_TRUE if positive or -1
*/
int iks_attrib_is_positive_or_neg_one(const char *value)
{
if (value && *value && switch_is_number(value)) {
int value_i = atoi(value);
if (value_i == -1 || value_i > 0) {
return SWITCH_TRUE;
}
}
return SWITCH_FALSE;
}
/**
* Validate string
* @param value
* @return SWTICH_TRUE
*/
int iks_attrib_is_any(const char *value)
{
return SWITCH_TRUE;
}
/**
* Validate decimal
* @param value
* @return SWTICH_TRUE if 0.0 <= x <= 1.0
*/
int iks_attrib_is_decimal_between_zero_and_one(const char *value)
{
if (value && *value && switch_is_number(value)) {
double value_d = atof(value);
if (value_d >= 0.0 && value_d <= 1.0) {
return SWITCH_TRUE;
}
}
return SWITCH_FALSE;
}
/**
* Validate dtmf digit
* @param value
* @return SWITCH_TRUE if 0-9,a,b,c,d,A,B,C,D,*,#
*/
int iks_attrib_is_dtmf_digit(const char *value)
{
if (value && *value && strlen(value) == 1) {
switch (*value) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'A':
case 'a':
case 'B':
case 'b':
case 'C':
case 'c':
case 'D':
case 'd':
case '*':
case '#':
return SWITCH_TRUE;
}
}
return SWITCH_FALSE;
}
/**
* @param fn to evaluate attribute
* @param attrib to evaluate
* @return true if not set or is valid
*/
int validate_optional_attrib(iks_attrib_validation_function fn, const char *attrib)
{
if (!attrib || !*attrib) {
return SWITCH_TRUE;
}
return fn(attrib);
}
#define IKS_SHA256_HEX_DIGEST_LENGTH ((SHA256_DIGEST_LENGTH * 2) + 1)
/**
* Convert hash to a hex string.
* @param hash hash to convert
* @param str buffer to store hash - this buffer must be hashlen * 2 + 1 in size.
*/
static void iks_hash_to_hex_string(unsigned char *hash, int hashlen, unsigned char *str)
{
static const char *HEX = "0123456789abcdef";
int i;
/* convert to hex string with in-place algorithm */
for (i = hashlen - 1; i >= 0; i--) {
str[i * 2 + 1] = HEX[hash[i] & 0x0f];
str[i * 2] = HEX[(hash[i] >> 4) & 0x0f];
}
str[hashlen * 2] = '\0';
}
/**
* Generate SHA-256 hash of value as hex string
* @param data to hash
* @param datalen length of data to hash
* @return hash as a hex string
*/
static void iks_sha256_hex_string(const unsigned char *data, int datalen, unsigned char *hash)
{
/* hash data */
SHA256(data, datalen, hash);
iks_hash_to_hex_string(hash, SHA256_DIGEST_LENGTH, hash);
}
/**
* Generate HMAC SHA-256
* @param key the key
* @param keylen length of key
* @param message the message
* @param messagelen length of message
* @param hash buffer to store the hash - must be IKS_SHA256_HEX_DIGEST_LENGTH
*/
static void iks_hmac_sha256_hex_string(const unsigned char *key, int keylen, const unsigned char *message, int messagelen, unsigned char *hash)
{
unsigned int hash_len = SHA256_DIGEST_LENGTH;
HMAC(EVP_sha256(), key, keylen, message, messagelen, hash, &hash_len);
iks_hash_to_hex_string(hash, SHA256_DIGEST_LENGTH, hash);
}
/**
* Generate server dialback key. free() the returned value
* @param secret originating server shared secret
* @param receiving_server domain
* @param originating_server domain
* @param stream_id stream ID
* @return the dialback key
*/
char *iks_server_dialback_key(const char *secret, const char *receiving_server, const char *originating_server, const char *stream_id)
{
if (!zstr(secret) && !zstr(receiving_server) && !zstr(originating_server) && !zstr(stream_id)) {
unsigned char secret_hash[IKS_SHA256_HEX_DIGEST_LENGTH];
unsigned char *message = NULL;
unsigned char *dialback_key = malloc(sizeof(unsigned char) * IKS_SHA256_HEX_DIGEST_LENGTH);
iks_sha256_hex_string((unsigned char *)secret, strlen(secret), secret_hash);
message = (unsigned char *)switch_mprintf("%s %s %s", receiving_server, originating_server, stream_id);
iks_hmac_sha256_hex_string(secret_hash, strlen((char *)secret_hash), message, strlen((char *)message), dialback_key);
free(message);
return (char *)dialback_key;
}
return NULL;
}
/**
* Print base 64 encoded SHA-1 hash
* @param sha hash to print
* @param buf to store baes 64 encoded hash
*/
void iks_sha_print_base64(iksha *sha, char *buf)
{
int i;
char hex_digit[3] = { 0 };
char hex_buf[SHA_1_HASH_BUF_SIZE];
unsigned char bin_buf[SHA_1_HASH_BUF_SIZE / 2];
iks_sha_print(sha, hex_buf);
/* convert hex string to octets */
for (i = 0; i < SHA_1_HASH_BUF_SIZE; i += 2) {
hex_digit[0] = hex_buf[i];
hex_digit[1] = hex_buf[i + 1];
bin_buf[i / 2] = strtol(hex_digit, NULL, 16);
}
switch_b64_encode(bin_buf, SHA_1_HASH_BUF_SIZE / 2, (unsigned char *)buf, SHA_1_HASH_BUF_SIZE);
}
/* 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 noet
*/
@@ -1,109 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* iks_helpers.h -- iksemel constants and helpers
*
*/
#ifndef IKS_EXT_H
#define IKS_EXT_H
#include <iksemel.h>
#include <switch.h>
#define SHA_1_HASH_BUF_SIZE 40
#define IKS_JABBER_SERVER_PORT 5269
#define IKS_NS_XMPP_DISCO "http://jabber.org/protocol/disco#info"
#define IKS_NS_XMPP_PING "urn:xmpp:ping"
#define IKS_NS_XMPP_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas"
#define IKS_NS_XMPP_STREAMS "http://etherx.jabber.org/streams"
#define IKS_NS_XMPP_DIALBACK "jabber:server:dialback"
#define IKS_NS_XMPP_TLS "urn:ietf:params:xml:ns:xmpp-tls"
#define IKS_NS_XMPP_ENTITY_CAPABILITIES "http://jabber.org/protocol/caps"
struct xmpp_error {
const char *name;
const char *type;
};
#undef XMPP_ERROR
#define XMPP_ERROR(def_name, name, type) \
extern const struct xmpp_error *def_name;
#include "xmpp_errors.def"
/* See RFC-3920 XMPP core for error definitions */
SWITCH_DECLARE(iks *) iks_new_presence(const char *name, const char *namespace, const char *from, const char *to);
SWITCH_DECLARE(iks *) iks_new_error(iks *iq, const struct xmpp_error *err);
SWITCH_DECLARE(iks *) iks_new_error_detailed(iks *iq, const struct xmpp_error *err, const char *detail_text);
SWITCH_DECLARE(iks *) iks_new_error_detailed_printf(iks *iq, const struct xmpp_error *err, const char *detail_text_format, ...);
SWITCH_DECLARE(iks *) iks_new_iq_result(iks *iq);
SWITCH_DECLARE(const char *) iks_find_attrib_soft(iks *xml, const char *attrib);
SWITCH_DECLARE(const char *) iks_find_attrib_default(iks *xml, const char *attrib, const char *def);
SWITCH_DECLARE(int) iks_find_bool_attrib(iks *xml, const char *attrib);
SWITCH_DECLARE(int) iks_find_int_attrib(iks *xml, const char *attrib);
SWITCH_DECLARE(char) iks_find_char_attrib(iks *xml, const char *attrib);
SWITCH_DECLARE(double) iks_find_decimal_attrib(iks *xml, const char *attrib);
SWITCH_DECLARE(const char *) iks_node_type_to_string(int type);
SWITCH_DECLARE(const char *) iks_net_error_to_string(int err);
SWITCH_DECLARE(iks *) iks_insert_attrib_printf(iks *xml, const char *name, const char *fmt, ...);
SWITCH_DECLARE(char *) iks_server_dialback_key(const char *secret, const char *receiving_server, const char *originating_server, const char *stream_id);
SWITCH_DECLARE(void) iks_sha_print_base64(iksha *sha, char *buf);
/** A function to validate attribute value */
typedef int (*iks_attrib_validation_function)(const char *);
SWITCH_DECLARE(int) validate_optional_attrib(iks_attrib_validation_function fn, const char *attrib);
#define ELEMENT_DECL(name) SWITCH_DECLARE(int) VALIDATE_##name(iks *node);
#define ELEMENT(name) int VALIDATE_##name(iks *node) { int result = 1; if (!node) return 0;
#define ATTRIB(name, def, rule) result &= iks_attrib_is_##rule(iks_find_attrib_default(node, #name, #def));
#define OPTIONAL_ATTRIB(name, def, rule) result &= validate_optional_attrib(iks_attrib_is_##rule, iks_find_attrib_default(node, #name, #def));
#define STRING_ATTRIB(name, def, rule) result &= value_matches(iks_find_attrib_default(node, #name, #def), rule);
#define ELEMENT_END return result; }
SWITCH_DECLARE(int) value_matches(const char *value, const char *rule);
SWITCH_DECLARE(int) iks_attrib_is_bool(const char *value);
SWITCH_DECLARE(int) iks_attrib_is_not_negative(const char *value);
SWITCH_DECLARE(int) iks_attrib_is_positive(const char *value);
SWITCH_DECLARE(int) iks_attrib_is_positive_or_neg_one(const char *value);
SWITCH_DECLARE(int) iks_attrib_is_any(const char *value);
SWITCH_DECLARE(int) iks_attrib_is_decimal_between_zero_and_one(const char *value);
SWITCH_DECLARE(int) iks_attrib_is_dtmf_digit(const char *value);
#endif
/* 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 noet
*/
File diff suppressed because it is too large Load Diff
-187
View File
@@ -1,187 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* mod_rayo.h -- Rayo server / node implementation. Allows MxN clustering of FreeSWITCH and Rayo Clients (like Adhearsion)
*
*/
#ifndef MOD_RAYO_H
#define MOD_RAYO_H
#include <switch.h>
#include <iksemel.h>
#include "iks_helpers.h"
#define RAYO_VERSION "1"
#define RAYO_BASE "urn:xmpp:rayo:"
#define RAYO_NS RAYO_BASE RAYO_VERSION
#define RAYO_CLIENT_NS RAYO_BASE "client:" RAYO_VERSION
#define RAYO_CALL_NS RAYO_BASE "call:" RAYO_VERSION
#define RAYO_MIXER_NS RAYO_BASE "mixer:" RAYO_VERSION
#define RAYO_CPA_BASE RAYO_BASE "cpa:"
#define RAYO_CPA_NS RAYO_CPA_BASE RAYO_VERSION
#define RAT_CALL "CALL"
#define RAT_COMPONENT "COMPONENT"
#define RAT_CALL_COMPONENT RAT_COMPONENT"_CALL"
#define RAT_MIXER "MIXER"
#define RAT_MIXER_COMPONENT RAT_COMPONENT"_MIXER"
#define RAT_SERVER "SERVER"
#define RAT_PEER_SERVER "PEER_SERVER"
#define RAT_CLIENT "CLIENT"
struct rayo_actor;
struct rayo_call;
struct rayo_mixer;
struct rayo_component;
/**
* A message sent to an actor
*/
struct rayo_message {
iks *payload;
char *to_jid;
iksid *to;
char *from_jid;
iksid *from;
char *from_type;
char *from_subtype;
int is_reply;
char *file;
int line;
};
typedef void (* rayo_actor_cleanup_fn)(struct rayo_actor *);
typedef void (* rayo_actor_send_fn)(struct rayo_actor *, struct rayo_message *);
/**
* A rayo actor - this is an entity that can be controlled by a rayo client
*/
struct rayo_actor {
/** Type of actor */
char *type;
/** Sub-type of actor */
char *subtype;
/** domain part of JID */
char *domain;
/** Internal ID */
char *id;
/** actor JID */
char *jid;
/** Actor pool */
switch_memory_pool_t *pool;
/** synchronizes access to this actor */
switch_mutex_t *mutex;
/** an atomically incrementing sequence for this actor */
int seq;
/** number of users of this actor */
int ref_count;
/** destroy flag */
int destroy;
/** XMPP message handling function */
rayo_actor_send_fn send_fn;
/** optional cleanup */
rayo_actor_cleanup_fn cleanup_fn;
/** optional parent */
struct rayo_actor *parent;
};
/**
* A Rayo component
*/
struct rayo_component {
/** base actor class */
struct rayo_actor base;
/** owning client JID */
const char *client_jid;
/** external ref */
const char *ref;
/** true if component has completed */
int complete;
};
#define RAYO_ACTOR(x) ((struct rayo_actor *)x)
#define RAYO_COMPONENT(x) ((struct rayo_component *)x)
#define RAYO_CALL(x) ((struct rayo_call *)x)
#define RAYO_MIXER(x) ((struct rayo_mixer *)x)
SWITCH_DECLARE(void) rayo_message_send(struct rayo_actor *from, const char *to, iks *payload, int dup, int reply, const char *file, int line);
SWITCH_DECLARE(void) rayo_message_destroy(struct rayo_message *msg);
SWITCH_DECLARE(iks *) rayo_message_remove_payload(struct rayo_message *msg);
#define RAYO_SEND_MESSAGE(from, to, payload) rayo_message_send(RAYO_ACTOR(from), to, payload, 0, 0, __FILE__, __LINE__)
#define RAYO_SEND_MESSAGE_DUP(from, to, payload) rayo_message_send(RAYO_ACTOR(from), to, payload, 1, 0, __FILE__, __LINE__)
#define RAYO_SEND_REPLY(from, to, payload) rayo_message_send(RAYO_ACTOR(from), to, payload, 0, 1, __FILE__, __LINE__)
#define RAYO_SEND_REPLY_DUP(from, to, payload) rayo_message_send(RAYO_ACTOR(from), to, payload, 1, 1, __FILE__, __LINE__)
SWITCH_DECLARE(struct rayo_actor *) rayo_actor_locate(const char *jid, const char *file, int line);
SWITCH_DECLARE(struct rayo_actor *) rayo_actor_locate_by_id(const char *id, const char *file, int line);
SWITCH_DECLARE(int) rayo_actor_seq_next(struct rayo_actor *actor);
SWITCH_DECLARE(void) rayo_actor_retain(struct rayo_actor *actor, const char *file, int line);
SWITCH_DECLARE(void) rayo_actor_release(struct rayo_actor *actor, const char *file, int line);
SWITCH_DECLARE(void) rayo_actor_destroy(struct rayo_actor *actor, const char *file, int line);
#define RAYO_LOCATE(jid) rayo_actor_locate(jid, __FILE__, __LINE__)
#define RAYO_LOCATE_BY_ID(id) rayo_actor_locate_by_id(id, __FILE__, __LINE__)
#define RAYO_SET_EVENT_FN(actor, event) rayo_actor_set_event_fn(RAYO_ACTOR(actor), event)
#define RAYO_DOMAIN(x) RAYO_ACTOR(x)->domain
#define RAYO_JID(x) RAYO_ACTOR(x)->jid
#define RAYO_ID(x) RAYO_ACTOR(x)->id
#define RAYO_POOL(x) RAYO_ACTOR(x)->pool
#define RAYO_RETAIN(x) rayo_actor_retain(RAYO_ACTOR(x), __FILE__, __LINE__)
#define RAYO_RELEASE(x) rayo_actor_release(RAYO_ACTOR(x), __FILE__, __LINE__)
#define RAYO_DESTROY(x) rayo_actor_destroy(RAYO_ACTOR(x), __FILE__, __LINE__)
#define RAYO_SEQ_NEXT(x) rayo_actor_seq_next(RAYO_ACTOR(x))
SWITCH_DECLARE(int) rayo_call_is_joined(struct rayo_call *call);
SWITCH_DECLARE(int) rayo_call_is_faxing(struct rayo_call *call);
SWITCH_DECLARE(void) rayo_call_set_faxing(struct rayo_call *call, int faxing);
SWITCH_DECLARE(const char *) rayo_call_get_dcp_jid(struct rayo_call *call);
#define rayo_mixer_get_name(mixer) RAYO_ID(mixer)
#define rayo_component_init(component, pool, type, subtype, id, parent, client_jid) _rayo_component_init(component, pool, type, subtype, id, parent, client_jid, NULL, __FILE__, __LINE__)
#define rayo_component_init_cleanup(component, pool, type, subtype, id, parent, client_jid, cleanup) _rayo_component_init(component, pool, type, subtype, id, parent, client_jid, cleanup, __FILE__, __LINE__)
SWITCH_DECLARE(struct rayo_component *) _rayo_component_init(struct rayo_component *component, switch_memory_pool_t *pool, const char *type, const char *subtype, const char *id, struct rayo_actor *parent, const char *client_jid, rayo_actor_cleanup_fn cleanup, const char *file, int line);
SWITCH_DECLARE(switch_bool_t) is_component_actor(struct rayo_actor *);
typedef iks *(*rayo_actor_xmpp_handler)(struct rayo_actor *, struct rayo_message *, void *);
SWITCH_DECLARE(void) rayo_actor_command_handler_add(const char *type, const char *subtype, const char *name, rayo_actor_xmpp_handler fn);
SWITCH_DECLARE(void) rayo_actor_event_handler_add(const char *from_type, const char *from_subtype, const char *to_type, const char *to_subtype, const char *name, rayo_actor_xmpp_handler fn);
#endif
/* 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 noet
*/
-539
View File
@@ -1,539 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* nlsml.c -- Parses / creates NLSML results
*
*/
#include <switch.h>
#include <iksemel.h>
#include "nlsml.h"
#include "iks_helpers.h"
struct nlsml_parser;
/** function to handle tag attributes */
typedef int (* tag_attribs_fn)(struct nlsml_parser *, char **);
/** function to handle tag CDATA */
typedef int (* tag_cdata_fn)(struct nlsml_parser *, char *, size_t);
/**
* Tag definition
*/
struct tag_def {
tag_attribs_fn attribs_fn;
tag_cdata_fn cdata_fn;
switch_bool_t is_root;
switch_hash_t *children_tags;
};
/**
* library configuration
*/
typedef struct {
/** true if initialized */
switch_bool_t init;
/** Mapping of tag name to definition */
switch_hash_t *tag_defs;
/** library memory pool */
switch_memory_pool_t *pool;
} nlsml_globals;
static nlsml_globals globals = { 0 };
/**
* The node in the XML tree
*/
struct nlsml_node {
/** tag name */
const char *name;
/** tag definition */
struct tag_def *tag_def;
/** parent to this node */
struct nlsml_node *parent;
};
/**
* The SAX parser state
*/
struct nlsml_parser {
/** current node */
struct nlsml_node *cur;
/** optional UUID for logging */
const char *uuid;
/** true if a match exists */
int match;
/** true if noinput */
int noinput;
/** true if nomatch */
int nomatch;
};
/**
* Tag def destructor
*/
static void destroy_tag_def(void *ptr)
{
struct tag_def *tag = (struct tag_def *) ptr;
if (tag->children_tags) {
switch_core_hash_destroy(&tag->children_tags);
}
}
/**
* Add a definition for a tag
* @param tag the name
* @param attribs_fn the function to handle the tag attributes
* @param cdata_fn the function to handler the tag CDATA
* @param children_tags comma-separated list of valid child tag names
* @return the definition
*/
static struct tag_def *add_tag_def(const char *tag, tag_attribs_fn attribs_fn, tag_cdata_fn cdata_fn, const char *children_tags)
{
struct tag_def *def = switch_core_alloc(globals.pool, sizeof(*def));
switch_core_hash_init(&def->children_tags);
if (!zstr(children_tags)) {
char *children_tags_dup = switch_core_strdup(globals.pool, children_tags);
char *tags[32] = { 0 };
int tag_count = switch_separate_string(children_tags_dup, ',', tags, sizeof(tags) / sizeof(tags[0]));
if (tag_count) {
int i;
for (i = 0; i < tag_count; i++) {
switch_core_hash_insert(def->children_tags, tags[i], tags[i]);
}
}
}
def->attribs_fn = attribs_fn;
def->cdata_fn = cdata_fn;
def->is_root = SWITCH_FALSE;
switch_core_hash_insert_destructor(globals.tag_defs, tag, def, destroy_tag_def);
return def;
}
/**
* Add a definition for a root tag
* @param tag the name
* @param attribs_fn the function to handle the tag attributes
* @param cdata_fn the function to handler the tag CDATA
* @param children_tags comma-separated list of valid child tag names
* @return the definition
*/
static struct tag_def *add_root_tag_def(const char *tag, tag_attribs_fn attribs_fn, tag_cdata_fn cdata_fn, const char *children_tags)
{
struct tag_def *def = add_tag_def(tag, attribs_fn, cdata_fn, children_tags);
def->is_root = SWITCH_TRUE;
return def;
}
/**
* Handle tag attributes
* @param parser the parser
* @param name the tag name
* @param atts the attributes
* @return IKS_OK if OK IKS_BADXML on parse failure
*/
static int process_tag(struct nlsml_parser *parser, const char *name, char **atts)
{
struct nlsml_node *cur = parser->cur;
if (cur->tag_def->is_root && cur->parent == NULL) {
/* no parent for ROOT tags */
return cur->tag_def->attribs_fn(parser, atts);
} else if (!cur->tag_def->is_root && cur->parent) {
/* check if this child is allowed by parent node */
struct tag_def *parent_def = cur->parent->tag_def;
if (switch_core_hash_find(parent_def->children_tags, "ANY") ||
switch_core_hash_find(parent_def->children_tags, name)) {
return cur->tag_def->attribs_fn(parser, atts);
} else {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "<%s> cannot be a child of <%s>\n", name, cur->parent->name);
}
} else if (cur->tag_def->is_root && cur->parent != NULL) {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "<%s> must be the root element\n", name);
} else {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "<%s> cannot be a root element\n", name);
}
return IKS_BADXML;
}
/**
* Handle tag attributes that are ignored
* @param parser the parser
* @param atts the attributes
* @return IKS_OK
*/
static int process_attribs_ignore(struct nlsml_parser *parser, char **atts)
{
return IKS_OK;
}
/**
* Handle CDATA that is ignored
* @param parser the parser
* @param data the CDATA
* @param len the CDATA length
* @return IKS_OK
*/
static int process_cdata_ignore(struct nlsml_parser *parser, char *data, size_t len)
{
return IKS_OK;
}
/**
* Handle CDATA that is not allowed
* @param parser the parser
* @param data the CDATA
* @param len the CDATA length
* @return IKS_BADXML if any printable characters
*/
static int process_cdata_bad(struct nlsml_parser *parser, char *data, size_t len)
{
int i;
for (i = 0; i < len; i++) {
if (isgraph(data[i])) {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "Unexpected CDATA for <%s>\n", parser->cur->name);
return IKS_BADXML;
}
}
return IKS_OK;
}
/**
* Handle CDATA with match text
* @param parser the parser
* @param data the CDATA
* @param len the CDATA length
* @return IKS_OK
*/
static int process_cdata_match(struct nlsml_parser *parser, char *data, size_t len)
{
int i;
for (i = 0; i < len; i++) {
if (isgraph(data[i])) {
parser->match++;
return IKS_OK;
}
}
return IKS_OK;
}
/**
* Handle nomatch
* @param parser the parser
* @param atts the attributes
* @return IKS_OK
*/
static int process_nomatch(struct nlsml_parser *parser, char **atts)
{
parser->nomatch++;
return IKS_OK;
}
/**
* Handle noinput
* @param parser the parser
* @param atts the attributes
* @return IKS_OK
*/
static int process_noinput(struct nlsml_parser *parser, char **atts)
{
parser->noinput++;
return IKS_OK;
}
/**
* Process a tag
*/
static int tag_hook(void *user_data, char *name, char **atts, int type)
{
int result = IKS_OK;
struct nlsml_parser *parser = (struct nlsml_parser *)user_data;
if (type == IKS_OPEN || type == IKS_SINGLE) {
struct nlsml_node *child_node = malloc(sizeof(*child_node));
switch_assert(child_node);
child_node->name = name;
child_node->tag_def = switch_core_hash_find(globals.tag_defs, name);
if (!child_node->tag_def) {
child_node->tag_def = switch_core_hash_find(globals.tag_defs, "ANY");
}
child_node->parent = parser->cur;
parser->cur = child_node;
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_DEBUG1, "<%s>\n", name);
result = process_tag(parser, name, atts);
}
if (type == IKS_CLOSE || type == IKS_SINGLE) {
struct nlsml_node *node = parser->cur;
parser->cur = node->parent;
free(node);
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_DEBUG1, "</%s>\n", name);
}
return result;
}
/**
* Process cdata
* @param user_data the parser
* @param data the CDATA
* @param len the CDATA length
* @return IKS_OK
*/
static int cdata_hook(void *user_data, char *data, size_t len)
{
struct nlsml_parser *parser = (struct nlsml_parser *)user_data;
if (!parser) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing parser\n");
return IKS_BADXML;
}
if (parser->cur) {
struct tag_def *def = parser->cur->tag_def;
if (def) {
return def->cdata_fn(parser, data, len);
}
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser->uuid), SWITCH_LOG_INFO, "Missing definition for <%s>\n", parser->cur->name);
return IKS_BADXML;
}
return IKS_OK;
}
/**
* Parse the result, looking for noinput/nomatch/match
* @param nlsml_result the NLSML result to parse
* @param uuid optional UUID for logging
* @return true if successful
*/
enum nlsml_match_type nlsml_parse(const char *nlsml_result, const char *uuid)
{
struct nlsml_parser parser = { 0 };
int result = NMT_BAD_XML;
iksparser *p = NULL;
parser.uuid = uuid;
if (!zstr(nlsml_result)) {
p = iks_sax_new(&parser, tag_hook, cdata_hook);
if (iks_parse(p, nlsml_result, 0, 1) == IKS_OK) {
/* check result */
if (parser.match) {
result = NMT_MATCH;
goto end;
}
if (parser.nomatch) {
result = NMT_NOMATCH;
goto end;
}
if (parser.noinput) {
result = NMT_NOINPUT;
goto end;
}
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser.uuid), SWITCH_LOG_INFO, "NLSML result does not have match/noinput/nomatch!\n");
} else {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser.uuid), SWITCH_LOG_INFO, "Failed to parse NLSML!\n");
}
} else {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(parser.uuid), SWITCH_LOG_INFO, "Missing NLSML result\n");
}
end:
while (parser.cur) {
struct nlsml_node *node = parser.cur;
parser.cur = node->parent;
free(node);
}
if ( p ) {
iks_parser_delete(p);
}
return result;
}
#define NLSML_NS "http://www.ietf.org/xml/ns/mrcpv2"
/**
* Makes NLSML result to conform to mrcpv2
* @param result the potentially non-conforming result
* @return the conforming result
*/
iks *nlsml_normalize(const char *result)
{
iks *result_xml = NULL;
iksparser *p = iks_dom_new(&result_xml);
if (iks_parse(p, result, 0, 1) == IKS_OK && result_xml) {
/* for now, all that is needed is to set the proper namespace */
iks_insert_attrib(result_xml, "xmlns", NLSML_NS);
} else {
/* unexpected ... */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to normalize NLSML result: %s\n", result);
if (result_xml) {
iks_delete(result_xml);
}
}
iks_parser_delete(p);
return result_xml;
}
/**
* @return true if digit is a DTMF
*/
static int isdtmf(const char digit)
{
switch(digit) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '*':
case '#':
case 'a':
case 'A':
case 'b':
case 'B':
case 'c':
case 'C':
case 'd':
case 'D':
return 1;
}
return 0;
}
/**
* Construct an NLSML result for match
* @param match the matching digits or text
* @param interpretation the optional digit interpretation
* @param mode dtmf or speech
* @param confidence 0-100
* @return the NLSML <result>
*/
iks *nlsml_create_match(const char *match, const char *interpretation, const char *mode, int confidence)
{
iks *result = iks_new("result");
iks_insert_attrib(result, "xmlns", NLSML_NS);
iks_insert_attrib(result, "xmlns:xf", "http://www.w3.org/2000/xforms");
if (!zstr(match)) {
iks *interpretation_node = iks_insert(result, "interpretation");
iks *input_node = iks_insert(interpretation_node, "input");
iks *instance_node = iks_insert(interpretation_node, "instance");
iks_insert_attrib(input_node, "mode", mode);
iks_insert_attrib_printf(input_node, "confidence", "%d", confidence);
iks_insert_cdata(input_node, match, strlen(match));
if (zstr(interpretation)) {
iks_insert_cdata(instance_node, match, strlen(match));
} else {
iks_insert_cdata(instance_node, interpretation, strlen(interpretation));
}
}
return result;
}
/**
* Construct an NLSML result for match
* @param match the matching digits or text
* @param interpretation the optional digit interpretation
* @return the NLSML <result>
*/
iks *nlsml_create_dtmf_match(const char *digits, const char *interpretation)
{
iks *result = NULL;
int first = 1;
int i;
int num_digits = strlen(digits);
switch_stream_handle_t stream = { 0 };
SWITCH_STANDARD_STREAM(stream);
for (i = 0; i < num_digits; i++) {
if (isdtmf(digits[i])) {
if (first) {
stream.write_function(&stream, "%c", digits[i]);
first = 0;
} else {
stream.write_function(&stream, " %c", digits[i]);
}
}
}
result = nlsml_create_match((const char *)stream.data, interpretation, "dtmf", 100);
switch_safe_free(stream.data);
return result;
}
/**
* Initialize NLSML parser. This function is not thread safe.
*/
int nlsml_init(void)
{
if (globals.init) {
return 1;
}
globals.init = SWITCH_TRUE;
switch_core_new_memory_pool(&globals.pool);
switch_core_hash_init(&globals.tag_defs);
add_root_tag_def("result", process_attribs_ignore, process_cdata_ignore, "interpretation");
add_tag_def("interpretation", process_attribs_ignore, process_cdata_ignore, "input,model,xf:model,instance,xf:instance");
add_tag_def("input", process_attribs_ignore, process_cdata_match, "input,nomatch,noinput");
add_tag_def("noinput", process_noinput, process_cdata_bad, "");
add_tag_def("nomatch", process_nomatch, process_cdata_ignore, "");
add_tag_def("model", process_attribs_ignore, process_cdata_ignore, "ANY");
add_tag_def("xf:model", process_attribs_ignore, process_cdata_ignore, "ANY");
add_tag_def("instance", process_attribs_ignore, process_cdata_ignore, "ANY");
add_tag_def("xf:instance", process_attribs_ignore, process_cdata_ignore, "ANY");
add_tag_def("ANY", process_attribs_ignore, process_cdata_ignore, "ANY");
return 1;
}
/**
* Destruction of NLSML parser environment
*/
void nlsml_destroy(void)
{
if (globals.init) {
if (globals.tag_defs) {
switch_core_hash_destroy(&globals.tag_defs);
globals.tag_defs = NULL;
}
if (globals.pool) {
switch_core_destroy_memory_pool(&globals.pool);
globals.pool = NULL;
}
globals.init = SWITCH_FALSE;
}
}
/* 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 noet
*/
-60
View File
@@ -1,60 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* nlsml.h -- Parses / creates NLSML results
*
*/
#ifndef NLSML_H
#define NLSML_H
#include <iksemel.h>
#include <switch.h>
enum nlsml_match_type {
NMT_BAD_XML,
NMT_MATCH,
NMT_NOINPUT,
NMT_NOMATCH
};
SWITCH_DECLARE(int) nlsml_init(void);
SWITCH_DECLARE(void) nlsml_destroy(void);
SWITCH_DECLARE(enum nlsml_match_type) nlsml_parse(const char *result, const char *uuid);
SWITCH_DECLARE(iks *) nlsml_normalize(const char *result);
SWITCH_DECLARE(iks *) nlsml_create_dtmf_match(const char *digits, const char *interpretation);
SWITCH_DECLARE(iks *) nlsml_create_match(const char *digits, const char *interpretation, const char *mode, int confidence);
#endif
/* 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 noet
*/
@@ -1,262 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_components.c -- Rayo component interface
*
*/
#include "rayo_components.h"
#include <switch.h>
#include "mod_rayo.h"
#include "iks_helpers.h"
/**
* Get access to Rayo component data.
* @param id the component internal ID
* @return the component or NULL. Call rayo_component_unlock() when done with component pointer.
*/
struct rayo_component *rayo_component_locate(const char *id, const char *file, int line)
{
struct rayo_actor *actor = rayo_actor_locate_by_id(id, file, line);
if (actor && is_component_actor(actor)) {
return RAYO_COMPONENT(actor);
} else if (actor) {
RAYO_RELEASE(actor);
}
return NULL;
}
/**
* Send component start reply
* @param component the component
* @param iq the start request
*/
void rayo_component_send_start(struct rayo_component *component, iks *iq)
{
iks *response = iks_new_iq_result(iq);
iks *ref = iks_insert(response, "ref");
iks_insert_attrib(ref, "xmlns", RAYO_NS);
iks_insert_attrib_printf(ref, "uri", "xmpp:%s", RAYO_JID(component));
RAYO_SEND_REPLY(component, iks_find_attrib(response, "to"), response);
}
/**
* Create component complete event
* @param component the component
* @param reason_str the completion reason
* @param reason_namespace the completion reason namespace
* @param meta metadata to add as child
* @param child_of_complete if true metadata is child of complete instead of reason
* @return the event
*/
iks *rayo_component_create_complete_event_with_metadata(struct rayo_component *component, const char *reason_str, const char *reason_namespace, iks *meta, int child_of_complete)
{
iks *response = iks_new("presence");
iks *complete;
iks *reason;
iks_insert_attrib(response, "from", RAYO_JID(component));
iks_insert_attrib(response, "to", component->client_jid);
iks_insert_attrib(response, "type", "unavailable");
complete = iks_insert(response, "complete");
iks_insert_attrib(complete, "xmlns", RAYO_EXT_NS);
reason = iks_insert(complete, reason_str);
iks_insert_attrib(reason, "xmlns", reason_namespace);
if (meta) {
meta = iks_copy_within(meta, iks_stack(response));
if (child_of_complete) {
iks_insert_node(complete, meta);
} else {
iks_insert_node(reason, meta);
}
}
return response;
}
/**
* Create component complete event
* @param component the component
* @param reason the completion reason
* @param reason_namespace the completion reason namespace
* @return the event
*/
iks *rayo_component_create_complete_event(struct rayo_component *component, const char *reason, const char *reason_namespace)
{
return rayo_component_create_complete_event_with_metadata(component, reason, reason_namespace, NULL, 0);
}
/**
* Send rayo component complete event
*/
void rayo_component_send_complete_event(struct rayo_component *component, iks *response)
{
component->complete = 1;
RAYO_SEND_REPLY(component, iks_find_attrib(response, "to"), response);
RAYO_RELEASE(component);
RAYO_DESTROY(component);
}
/**
* Send rayo complete
*/
void rayo_component_send_complete(struct rayo_component *component, const char *reason, const char *reason_namespace)
{
rayo_component_send_complete_event(component, rayo_component_create_complete_event(component, reason, reason_namespace));
}
/**
* Send rayo complete
*/
void rayo_component_send_complete_with_metadata(struct rayo_component *component, const char *reason, const char *reason_namespace, iks *meta, int child_of_complete)
{
rayo_component_send_complete_event(component, rayo_component_create_complete_event_with_metadata(component, reason, reason_namespace, meta, child_of_complete));
}
/**
* Send rayo complete
*/
void rayo_component_send_complete_with_metadata_string(struct rayo_component *component, const char *reason, const char *reason_namespace, const char *meta, int child_of_complete)
{
iks *meta_xml = NULL;
iksparser *p = iks_dom_new(&meta_xml);
if (iks_parse(p, meta, 0, 1) != IKS_OK) {
/* unexpected ... */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "%s Failed to parse metadata for complete event: %s\n",
RAYO_JID(component), meta);
/* send without... */
rayo_component_send_complete(component, reason, reason_namespace);
} else {
rayo_component_send_complete_with_metadata(component, reason, reason_namespace, meta_xml, child_of_complete);
}
if (meta_xml) {
iks_delete(meta_xml);
}
iks_parser_delete(p);
}
/**
* Background API data
*/
struct component_bg_api_cmd {
const char *cmd;
const char *args;
switch_memory_pool_t *pool;
struct rayo_component *component;
};
/**
* Thread that outputs to component
* @param thread this thread
* @param obj the Rayo mixer context
* @return NULL
*/
static void *SWITCH_THREAD_FUNC component_bg_api_thread(switch_thread_t *thread, void *obj)
{
struct component_bg_api_cmd *cmd = (struct component_bg_api_cmd *)obj;
switch_stream_handle_t stream = { 0 };
switch_memory_pool_t *pool = cmd->pool;
SWITCH_STANDARD_STREAM(stream);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "BGAPI EXEC: %s %s\n", cmd->cmd, cmd->args);
if (switch_api_execute(cmd->cmd, cmd->args, NULL, &stream) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "BGAPI EXEC FAILURE\n");
rayo_component_send_complete(cmd->component, COMPONENT_COMPLETE_ERROR);
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "BGAPI EXEC RESULT: %s\n", (char *)stream.data);
}
switch_safe_free(stream.data);
switch_core_destroy_memory_pool(&pool);
return NULL;
}
/**
* Run a background API command
* @param cmd API command
* @param args API args
*/
void rayo_component_api_execute_async(struct rayo_component *component, const char *cmd, const char *args)
{
switch_thread_t *thread;
switch_threadattr_t *thd_attr = NULL;
struct component_bg_api_cmd *bg_cmd = NULL;
switch_memory_pool_t *pool;
/* set up command */
switch_core_new_memory_pool(&pool);
bg_cmd = switch_core_alloc(pool, sizeof(*bg_cmd));
bg_cmd->pool = pool;
bg_cmd->cmd = switch_core_strdup(pool, cmd);
bg_cmd->args = switch_core_strdup(pool, args);
bg_cmd->component = component;
/* create thread */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s BGAPI START\n", RAYO_JID(component));
switch_threadattr_create(&thd_attr, pool);
switch_threadattr_detach_set(thd_attr, 1);
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
switch_thread_create(&thread, thd_attr, component_bg_api_thread, bg_cmd, pool);
}
/**
* Handle configuration
*/
switch_status_t rayo_components_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
{
if (rayo_input_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS ||
rayo_output_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS ||
rayo_prompt_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS ||
rayo_record_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS ||
rayo_fax_components_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS ||
rayo_exec_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS) {
return SWITCH_STATUS_TERM;
}
return SWITCH_STATUS_SUCCESS;
}
/**
* Handle shutdown
*/
switch_status_t rayo_components_shutdown(void)
{
rayo_input_component_shutdown();
rayo_output_component_shutdown();
rayo_prompt_component_shutdown();
rayo_record_component_shutdown();
rayo_fax_components_shutdown();
rayo_exec_component_shutdown();
return SWITCH_STATUS_SUCCESS;
}
/* 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 noet
*/
@@ -1,107 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* components.c -- Rayo component implementations
*
*/
#ifndef RAYO_COMPONENTS_H
#define RAYO_COMPONENTS_H
#include <switch.h>
#include <iksemel.h>
#include "mod_rayo.h"
#define RAYO_EXT_NS RAYO_BASE "ext:" RAYO_VERSION
#define RAYO_EXT_COMPLETE_NS RAYO_BASE "ext:complete:" RAYO_VERSION
#define RAYO_OUTPUT_NS RAYO_BASE "output:" RAYO_VERSION
#define RAYO_OUTPUT_COMPLETE_NS RAYO_BASE "output:complete:" RAYO_VERSION
#define RAYO_INPUT_NS RAYO_BASE "input:" RAYO_VERSION
#define RAYO_INPUT_COMPLETE_NS RAYO_BASE "input:complete:" RAYO_VERSION
#define RAYO_RECORD_NS RAYO_BASE "record:" RAYO_VERSION
#define RAYO_RECORD_COMPLETE_NS RAYO_BASE "record:complete:" RAYO_VERSION
#define RAYO_PROMPT_NS RAYO_BASE "prompt:" RAYO_VERSION
#define RAYO_PROMPT_COMPLETE_NS RAYO_BASE "prompt:complete:" RAYO_VERSION
#define RAYO_FAX_NS RAYO_BASE "fax:" RAYO_VERSION
#define RAYO_FAX_COMPLETE_NS RAYO_BASE "fax:complete:" RAYO_VERSION
#define RAYO_EXEC_NS RAYO_BASE "exec:" RAYO_VERSION
#define RAYO_EXEC_COMPLETE_NS RAYO_BASE "exec:complete:" RAYO_VERSION
#define COMPONENT_COMPLETE_STOP "stop", RAYO_EXT_COMPLETE_NS
#define COMPONENT_COMPLETE_ERROR "error", RAYO_EXT_COMPLETE_NS
#define COMPONENT_COMPLETE_HANGUP "hangup", RAYO_EXT_COMPLETE_NS
#define COMPONENT_COMPLETE_DONE "done", RAYO_EXT_COMPLETE_NS
SWITCH_DECLARE(switch_status_t) rayo_components_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(switch_status_t) rayo_input_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(switch_status_t) rayo_output_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(switch_status_t) rayo_prompt_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(switch_status_t) rayo_record_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(switch_status_t) rayo_fax_components_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(switch_status_t) rayo_exec_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(switch_status_t) rayo_components_shutdown(void);
SWITCH_DECLARE(switch_status_t) rayo_input_component_shutdown(void);
SWITCH_DECLARE(switch_status_t) rayo_output_component_shutdown(void);
SWITCH_DECLARE(switch_status_t) rayo_prompt_component_shutdown(void);
SWITCH_DECLARE(switch_status_t) rayo_record_component_shutdown(void);
SWITCH_DECLARE(switch_status_t) rayo_fax_components_shutdown(void);
SWITCH_DECLARE(switch_status_t) rayo_exec_component_shutdown(void);
SWITCH_DECLARE(void) rayo_component_send_start(struct rayo_component *component, iks *iq);
SWITCH_DECLARE(void) rayo_component_send_iq_error(struct rayo_component *component, iks *iq, const char *error_name, const char *error_type);
SWITCH_DECLARE(void) rayo_component_send_iq_error_detailed(struct rayo_component *component, iks *iq, const char *error_name, const char *error_type, const char *detail);
SWITCH_DECLARE(void) rayo_component_send_complete(struct rayo_component *component, const char *reason, const char *reason_namespace);
SWITCH_DECLARE(void) rayo_component_send_complete_event(struct rayo_component *component, iks *response);
SWITCH_DECLARE(void) rayo_component_send_complete_with_metadata(struct rayo_component *component, const char *reason, const char *reason_namespace, iks *meta, int child_of_complete);
SWITCH_DECLARE(void) rayo_component_send_complete_with_metadata_string(struct rayo_component *component, const char *reason, const char *reason_namespace, const char *meta, int child_of_complete);
SWITCH_DECLARE(iks *) rayo_component_create_complete_event(struct rayo_component *component, const char *reason, const char *reason_namespace);
SWITCH_DECLARE(iks *) rayo_component_create_complete_event_with_metadata(struct rayo_component *component, const char *reason, const char *reason_namespace, iks *meta, int child_of_complete);
SWITCH_DECLARE(void) rayo_component_api_execute_async(struct rayo_component *component, const char *cmd, const char *args);
#define RAYO_COMPONENT_LOCATE(id) rayo_component_locate(id, __FILE__, __LINE__)
SWITCH_DECLARE(struct rayo_component *) rayo_component_locate(const char *id, const char *file, int line);
#endif
/* 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 noet
*/
@@ -1,408 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2014, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_cpa_component.c -- input component "cpa" mode implementation
*/
#include <switch.h>
#include "rayo_cpa_component.h"
#include "mod_rayo.h"
#include "rayo_components.h"
#include "rayo_cpa_detector.h"
/**
* Module globals
*/
static struct {
/** signal subscribers */
switch_hash_t *subscribers;
/** synchronizes access to subscribers */
switch_mutex_t *subscribers_mutex;
/** module pool */
switch_memory_pool_t *pool;
} globals;
/**
* A CPA signal monitored by this component
*/
struct cpa_signal {
/** name of this signal */
const char *name;
/** true if signal causes component termination */
int terminate;
};
/**
* CPA component state
*/
struct cpa_component {
/** component base class */
struct rayo_component base;
/** true if ready to forward detector events */
int ready;
/** signals this component wants */
switch_hash_t *signals;
};
#define CPA_COMPONENT(x) ((struct cpa_component *)x)
typedef void (* subscriber_execute_fn)(const char *jid, void *user_data);
/**
* Request signals
*/
static void subscribe(const char *uuid, const char *signal_type, const char *jid)
{
char *key = switch_mprintf("%s:%s", uuid, signal_type);
switch_mutex_lock(globals.subscribers_mutex);
{
switch_hash_t *signal_subscribers = switch_core_hash_find(globals.subscribers, key);
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Subscribe %s => %s\n", signal_type, jid);
if (!signal_subscribers) {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Create %s subscriber hash\n", signal_type);
switch_core_hash_init(&signal_subscribers);
switch_core_hash_insert(globals.subscribers, key, signal_subscribers);
}
switch_core_hash_insert(signal_subscribers, jid, "1");
}
switch_mutex_unlock(globals.subscribers_mutex);
switch_safe_free(key);
}
/**
* Stop receiving signals
*/
static void unsubscribe(const char *uuid, const char *signal_type, const char *jid)
{
char *key = switch_mprintf("%s:%s", uuid, signal_type);
switch_mutex_lock(globals.subscribers_mutex);
{
switch_hash_t *signal_subscribers = switch_core_hash_find(globals.subscribers, key);
if (signal_subscribers) {
switch_core_hash_delete(signal_subscribers, jid);
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Unsubscribe %s => %s\n", signal_type, jid);
/* clean up hash if empty */
if (switch_core_hash_empty(signal_subscribers)) {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Destroy %s subscriber hash\n", signal_type);
switch_core_hash_destroy(&signal_subscribers);
switch_core_hash_delete(globals.subscribers, key);
}
}
}
switch_mutex_unlock(globals.subscribers_mutex);
switch_safe_free(key);
}
/**
* Execute function for each subscriber
*/
static void subscriber_execute(const char *uuid, const char *signal_type, subscriber_execute_fn callback, void *user_data)
{
switch_event_t *subscriber_list = NULL;
switch_event_header_t *subscriber = NULL;
/* fetch list of subscribers */
char *key = switch_mprintf("%s:%s", uuid, signal_type);
switch_event_create_subclass(&subscriber_list, SWITCH_EVENT_CLONE, NULL);
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Subscriber execute %s\n", signal_type);
switch_mutex_lock(globals.subscribers_mutex);
{
switch_hash_index_t *hi = NULL;
switch_hash_t *signal_subscribers = switch_core_hash_find(globals.subscribers, key);
if (signal_subscribers) {
for (hi = switch_core_hash_first(signal_subscribers); hi; hi = switch_core_hash_next(&hi)) {
const void *jid;
void *dont_care;
switch_core_hash_this(hi, &jid, NULL, &dont_care);
switch_event_add_header_string(subscriber_list, SWITCH_STACK_BOTTOM, "execute", (const char *)jid);
}
} else {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "No subscribers for %s\n", signal_type);
}
}
switch_mutex_unlock(globals.subscribers_mutex);
switch_safe_free(key);
/* execute function for each subscriber */
for (subscriber = subscriber_list->headers; subscriber; subscriber = subscriber->next) {
callback(subscriber->value, user_data);
}
switch_event_destroy(&subscriber_list);
}
/**
* Stop all CPA detectors
*/
static void stop_cpa_detectors(struct cpa_component *cpa)
{
if (cpa->signals) {
switch_hash_index_t *hi = NULL;
for (hi = switch_core_hash_first(cpa->signals); hi; hi = switch_core_hash_next(&hi)) {
const void *signal_type;
void *cpa_signal = NULL;
switch_core_hash_this(hi, &signal_type, NULL, &cpa_signal);
if (cpa_signal) {
rayo_cpa_detector_stop(RAYO_ACTOR(cpa)->parent->id, ((struct cpa_signal *)cpa_signal)->name);
unsubscribe(RAYO_ACTOR(cpa)->parent->id, ((struct cpa_signal *)cpa_signal)->name, RAYO_JID(cpa));
}
}
switch_core_hash_destroy(&cpa->signals);
cpa->signals = NULL;
}
unsubscribe(RAYO_ACTOR(cpa)->parent->id, "hangup", RAYO_JID(cpa));
}
/**
* Stop execution of CPA component
*/
static iks *stop_cpa_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
{
stop_cpa_detectors(CPA_COMPONENT(component));
rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_STOP);
return iks_new_iq_result(msg->payload);
}
/**
* Forward CPA signal to client
*/
static void rayo_cpa_detector_event(const char *jid, void *user_data)
{
struct rayo_actor *component = RAYO_LOCATE(jid);
if (component) {
if (CPA_COMPONENT(component)->ready) {
switch_event_t *event = (switch_event_t *)user_data;
const char *signal_type = switch_event_get_header(event, "signal-type");
struct cpa_signal *cpa_signal = switch_core_hash_find(CPA_COMPONENT(component)->signals, signal_type);
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(component->parent->id), SWITCH_LOG_DEBUG, "Handling CPA event\n");
if (cpa_signal) {
const char *value = switch_event_get_header(event, "value");
const char *duration = switch_event_get_header(event, "duration");
if (cpa_signal->terminate) {
iks *complete_event;
iks *signal_xml;
stop_cpa_detectors(CPA_COMPONENT(component));
/* send complete event to client */
complete_event = rayo_component_create_complete_event(RAYO_COMPONENT(component), "signal", RAYO_CPA_NS);
signal_xml = iks_find(complete_event, "complete");
signal_xml = iks_find(signal_xml, "signal");
iks_insert_attrib(signal_xml, "type", signal_type);
if (!zstr(value)) {
iks_insert_attrib(signal_xml, "value", value);
}
if (!zstr(duration)) {
iks_insert_attrib(signal_xml, "duration", duration);
}
rayo_component_send_complete_event(RAYO_COMPONENT(component), complete_event);
} else {
/* send event to client */
iks *signal_event = iks_new_presence("signal", RAYO_CPA_NS, RAYO_JID(component), RAYO_COMPONENT(component)->client_jid);
iks *signal_xml = iks_find(signal_event, "signal");
iks_insert_attrib(signal_xml, "type", signal_type);
if (!zstr(value)) {
iks_insert_attrib(signal_xml, "value", value);
}
if (!zstr(duration)) {
iks_insert_attrib(signal_xml, "duration", duration);
}
RAYO_SEND_REPLY(component, RAYO_COMPONENT(component)->client_jid, signal_event);
}
}
} else {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(component->parent->id), SWITCH_LOG_DEBUG, "Skipping CPA event\n");
}
RAYO_RELEASE(component);
}
}
/**
* Handle CPA signal-type event
*/
static void on_rayo_cpa_detector_event(switch_event_t *event)
{
subscriber_execute(switch_event_get_header(event, "Unique-ID"), switch_event_get_header(event, "signal-type"), rayo_cpa_detector_event, event);
}
/**
* Handle CPA completion because of hangup
*/
static void rayo_cpa_component_hangup(const char *jid, void *user_data)
{
struct rayo_actor *component = RAYO_LOCATE(jid);
if (component) {
stop_cpa_detectors(CPA_COMPONENT(component));
rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_HANGUP);
RAYO_RELEASE(component);
}
}
/**
* Handle hungup call event
*/
static void on_channel_hangup_complete_event(switch_event_t *event)
{
subscriber_execute(switch_event_get_header(event, "Unique-ID"), "hangup", rayo_cpa_component_hangup, event);
}
/**
* Start CPA
*/
iks *rayo_cpa_component_start(struct rayo_actor *call, struct rayo_message *msg, void *session_data)
{
iks *iq = msg->payload;
switch_core_session_t *session = (switch_core_session_t *)session_data;
iks *input = iks_find(iq, "input");
switch_memory_pool_t *pool = NULL;
struct cpa_component *component = NULL;
int have_grammar = 0;
iks *grammar = NULL;
/* create CPA component */
switch_core_new_memory_pool(&pool);
component = switch_core_alloc(pool, sizeof(*component));
component = CPA_COMPONENT(rayo_component_init((struct rayo_component *)component, pool, RAT_CALL_COMPONENT, "cpa", NULL, call, iks_find_attrib(iq, "from")));
if (!component) {
switch_core_destroy_memory_pool(&pool);
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create CPA entity");
}
switch_core_hash_init(&component->signals);
/* start CPA detectors */
for (grammar = iks_find(input, "grammar"); grammar; grammar = iks_next_tag(grammar)) {
if (!strcmp("grammar", iks_name(grammar))) {
const char *error_str = "";
const char *url = iks_find_attrib_soft(grammar, "url");
char *url_dup;
char *url_params;
if (zstr(url)) {
stop_cpa_detectors(component);
RAYO_RELEASE(component);
RAYO_DESTROY(component);
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Missing grammar URL");
}
have_grammar = 1;
url_dup = strdup(url);
switch_assert(url_dup);
if ((url_params = strchr(url_dup, '?'))) {
*url_params = '\0';
url_params++;
}
if (switch_core_hash_find(component->signals, url)) {
free(url_dup);
stop_cpa_detectors(component);
RAYO_RELEASE(component);
RAYO_DESTROY(component);
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Duplicate URL");
}
/* start detector */
/* TODO return better reasons... */
if (rayo_cpa_detector_start(switch_core_session_get_uuid(session), url_dup, &error_str)) {
struct cpa_signal *cpa_signal = switch_core_alloc(pool, sizeof(*cpa_signal));
cpa_signal->terminate = !zstr(url_params) && strstr(url_params, "terminate=true");
cpa_signal->name = switch_core_strdup(pool, url_dup);
switch_core_hash_insert(component->signals, cpa_signal->name, cpa_signal);
subscribe(switch_core_session_get_uuid(session), cpa_signal->name, RAYO_JID(component));
} else {
free(url_dup);
stop_cpa_detectors(component);
RAYO_RELEASE(component);
RAYO_DESTROY(component);
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, error_str);
}
free(url_dup);
}
}
if (!have_grammar) {
stop_cpa_detectors(component);
RAYO_RELEASE(component);
RAYO_DESTROY(component);
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "No grammar defined");
}
/* acknowledge command */
rayo_component_send_start(RAYO_COMPONENT(component), iq);
/* TODO hangup race condition */
subscribe(switch_core_session_get_uuid(session), "hangup", RAYO_JID(component));
/* ready to forward detector events */
component->ready = 1;
return NULL;
}
/**
* Load input CPA
* @param module_interface
* @param pool memory pool
* @param config_file
* @return SWITCH_STATUS_SUCCESS if successfully loaded
*/
switch_status_t rayo_cpa_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
{
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "cpa", "set:"RAYO_EXT_NS":stop", stop_cpa_component);
switch_event_bind("rayo_cpa_component", SWITCH_EVENT_CUSTOM, "rayo::cpa", on_rayo_cpa_detector_event, NULL);
switch_event_bind("rayo_cpa_component", SWITCH_EVENT_CHANNEL_HANGUP_COMPLETE, NULL, on_channel_hangup_complete_event, NULL);
globals.pool = pool;
switch_core_hash_init(&globals.subscribers);
switch_mutex_init(&globals.subscribers_mutex, SWITCH_MUTEX_NESTED, pool);
return rayo_cpa_detector_load(module_interface, pool, config_file);
}
/**
* Stop input CPA
*/
void rayo_cpa_component_shutdown(void)
{
switch_event_unbind_callback(on_rayo_cpa_detector_event);
switch_event_unbind_callback(on_channel_hangup_complete_event);
rayo_cpa_detector_shutdown();
if (globals.subscribers) {
switch_core_hash_destroy(&globals.subscribers);
}
}
/* 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 noet
*/
@@ -1,53 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2014, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_cpa_component.h -- Rayo call progress analysis component
*
*/
#ifndef RAYO_CPA_COMPONENT_H
#define RAYO_CPA_COMPONENT_H
#include <switch.h>
#include <iksemel.h>
#include "mod_rayo.h"
SWITCH_DECLARE(switch_status_t) rayo_cpa_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(void) rayo_cpa_component_shutdown(void);
SWITCH_DECLARE(iks *) rayo_cpa_component_start(struct rayo_actor *call, struct rayo_message *msg, void *session_data);
#endif
/* 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 noet
*/
@@ -1,482 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2014-2015, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_cpa_detector.c -- Glue to normalize events from and to allow multiple instantiation of various detectors in FreeSWITCH
*/
#include "rayo_cpa_detector.h"
static struct {
/** detectors supported by this module mapped by signal-type */
switch_hash_t *detectors;
/** synchronizes access to detectors */
switch_mutex_t *detectors_mutex;
} globals;
struct rayo_cpa_detector;
/**
* Detector definition
*/
struct rayo_cpa_detector {
/** unique internal name of this detector */
const char *name;
/** detector ID */
const char *uuid;
/** start detection APP */
const char *start_app;
/** args to pass to start detection app */
const char *start_app_args;
/** stop detection APP */
const char *stop_app;
/** args to pass to stop detection app */
const char *stop_app_args;
/** (optional) name of header to get the signal type from */
const char *signal_type_header;
/** (optional) where to get the signal value from the event */
const char *signal_value_header;
/** (optional) where to get the signal duration from the event */
const char *signal_duration_header;
/** detector event to signal type mapping */
switch_hash_t *signal_type_map;
};
/**
* Detection state
*/
struct rayo_cpa_detector_state {
/** reference count */
int refs;
};
/**
* Start detecting
* @param call_uuid call to detect signal on
* @param signal_ns namespace of signal to detect
* @param error_detail on failure, describes the error
* @return 1 if successful, 0 if failed
*/
int rayo_cpa_detector_start(const char *call_uuid, const char *signal_ns, const char **error_detail)
{
struct rayo_cpa_detector *detector = switch_core_hash_find(globals.detectors, signal_ns);
switch_core_session_t *session;
if (detector) {
if (zstr(detector->start_app)) {
/* nothing to do */
return 1;
}
session = switch_core_session_locate(call_uuid);
if (session) {
struct rayo_cpa_detector_state *detector_state = switch_channel_get_private(switch_core_session_get_channel(session), detector->uuid);
if (detector_state) {
/* detector is already running */
detector_state->refs++;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Start detector %s, refs = %d\n", detector->name, detector_state->refs);
switch_core_session_rwunlock(session);
return 1;
}
detector_state = switch_core_session_alloc(session, sizeof(*detector_state));
detector_state->refs = 1;
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Starting detector %s, refs = 1\n", detector->name);
switch_channel_set_private(switch_core_session_get_channel(session), detector->uuid, detector_state);
switch_core_session_execute_application_async(session, detector->start_app, zstr(detector->start_app_args) ? NULL : detector->start_app_args);
switch_core_session_rwunlock(session);
return 1;
} else {
*error_detail = "session gone";
return 0;
}
}
*error_detail = "detector not supported";
return 0;
}
/**
* Stop detecting
* @param call_uuid call to stop detecting signal on
* @param signal_ns name of signal to stop detecting
*/
void rayo_cpa_detector_stop(const char *call_uuid, const char *signal_ns)
{
struct rayo_cpa_detector *detector = switch_core_hash_find(globals.detectors, signal_ns);
switch_core_session_t *session;
if (detector) {
if (zstr(detector->stop_app)) {
/* nothing to do */
return;
}
session = switch_core_session_locate(call_uuid);
if (session) {
struct rayo_cpa_detector_state *detector_state = switch_channel_get_private(switch_core_session_get_channel(session), detector->uuid);
if (detector_state) {
detector_state->refs--;
if (detector_state->refs < 0) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Stop detector %s refs = %d\n", detector->name, detector_state->refs);
} else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Stop detector %s refs = %d\n", detector->name, detector_state->refs);
}
if (detector_state->refs == 0) {
/* nobody interested in detector events- shut it down */
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Stopping detector %s\n", detector->name);
switch_core_session_execute_application_async(session, detector->stop_app, zstr(detector->stop_app_args) ? NULL : detector->stop_app_args);
switch_channel_set_private(switch_core_session_get_channel(session), detector->uuid, NULL);
}
} else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Detector %s is already stopped\n", detector->name);
}
switch_core_session_rwunlock(session);
}
}
}
/**
* Handle event from detector
*/
static void rayo_cpa_detector_event(switch_event_t *event)
{
struct rayo_cpa_detector *detector = (struct rayo_cpa_detector *)event->bind_user_data;
if (detector) {
const char *signal_type = "rayo_default";
if (!zstr(detector->signal_type_header)) {
signal_type = switch_event_get_header(event, detector->signal_type_header);
}
if (!zstr(signal_type)) {
signal_type = switch_core_hash_find(detector->signal_type_map, signal_type);
}
if (!zstr(signal_type)) {
switch_event_t *cpa_event;
const char *uuid = switch_event_get_header(event, "Unique-ID");
if (zstr(uuid)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s %s event is missing call UUID!\n", detector->name, signal_type);
return;
}
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Got Rayo CPA event %s\n", signal_type);
if (switch_event_create_subclass(&cpa_event, SWITCH_EVENT_CUSTOM, "rayo::cpa") == SWITCH_STATUS_SUCCESS) {
switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "Unique-ID", uuid);
switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "detector-name", detector->name);
switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "detector-uuid", detector->uuid);
switch_event_add_header(cpa_event, SWITCH_STACK_BOTTOM, "signal-type", "%s%s:%s", RAYO_CPA_BASE, signal_type, RAYO_VERSION);
if (!zstr(detector->signal_value_header)) {
const char *value = switch_event_get_header(event, detector->signal_value_header);
if (!zstr(value)) {
switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "value", value);
}
}
if (!zstr(detector->signal_duration_header)) {
const char *duration = switch_event_get_header(event, detector->signal_duration_header);
if (!zstr(duration)) {
switch_event_add_header_string(cpa_event, SWITCH_STACK_BOTTOM, "duration", duration);
}
}
switch_event_fire(&cpa_event);
}
} else {
/* couldn't map event to Rayo signal-type */
const char *event_name = switch_event_get_header(event, "Event-Name");
const char *event_subclass = switch_event_get_header(event, "Event-Subclass");
if (zstr(event_subclass)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to find Rayo signal-type for event %s\n", event_name);
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to find Rayo signal-type for event %s %s\n", event_name, event_subclass);
}
}
}
}
#define RAYO_CPA_DETECTOR_SYNTAX "rayo_cpa <uuid> <signal-type> <start|stop>"
SWITCH_STANDARD_API(rayo_cpa_detector_api)
{
char *cmd_dup = NULL;
char *argv[4] = { 0 };
int argc = 0;
if (zstr(cmd)) {
stream->write_function(stream, "-ERR: USAGE %s\n", RAYO_CPA_DETECTOR_SYNTAX);
goto done;
}
cmd_dup = strdup(cmd);
argc = switch_separate_string(cmd_dup, ' ', argv, sizeof(argv) / sizeof(argv[0]));
if (argc != 3) {
stream->write_function(stream, "-ERR: USAGE %s\n", RAYO_CPA_DETECTOR_SYNTAX);
} else {
const char *err_reason = NULL;
if (!strcmp(argv[2], "stop")) {
rayo_cpa_detector_stop(argv[0], argv[1]);
stream->write_function(stream, "+OK\n");
} else if (!strcmp(argv[2], "start")) {
if (!rayo_cpa_detector_start(argv[0], argv[1], &err_reason)) {
if (err_reason) {
stream->write_function(stream, "-ERR: %s\n", err_reason);
} else {
stream->write_function(stream, "-ERR\n");
}
} else {
stream->write_function(stream, "+OK\n");
}
} else {
stream->write_function(stream, "-ERR: USAGE %s\n", RAYO_CPA_DETECTOR_SYNTAX);
}
}
done:
switch_safe_free(cmd_dup);
return SWITCH_STATUS_SUCCESS;
}
/**
* Detector definition destructor
*/
static void destroy_detector(void *ptr)
{
struct rayo_cpa_detector *detector = (struct rayo_cpa_detector *) ptr;
if (detector->signal_type_map) {
switch_core_hash_destroy(&detector->signal_type_map);
}
}
/**
* Configure CPA
*/
static switch_status_t do_config(switch_memory_pool_t *pool, const char *config_file)
{
switch_xml_t cfg, xml, cpa_xml;
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_hash_t *bound_events;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Configuring CPA\n");
if (!(xml = switch_xml_open_cfg(config_file, &cfg, NULL))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", config_file);
return SWITCH_STATUS_TERM;
}
switch_core_hash_init(&bound_events);
cpa_xml = switch_xml_child(cfg, "cpa");
if (cpa_xml) {
switch_xml_t detector_xml;
for (detector_xml = switch_xml_child(cpa_xml, "detector"); detector_xml; detector_xml = detector_xml->next) {
switch_xml_t start_xml, stop_xml, event_xml;
struct rayo_cpa_detector *detector;
char id[SWITCH_UUID_FORMATTED_LENGTH + 1] = { 0 };
const char *name = switch_xml_attr_soft(detector_xml, "name");
if (zstr(name)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing name of CPA detector!\n");
status = SWITCH_STATUS_TERM;
goto done;
}
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "CPA detector: %s\n", name);
detector = switch_core_alloc(pool, sizeof(*detector));
switch_core_hash_init(&detector->signal_type_map);
detector->name = switch_core_strdup(pool, name);
switch_uuid_str(id, sizeof(id));
detector->uuid = switch_core_strdup(pool, id);
start_xml = switch_xml_child(detector_xml, "start");
if (start_xml) {
detector->start_app = switch_core_strdup(pool, switch_xml_attr_soft(start_xml, "application"));
detector->start_app_args = switch_core_strdup(pool, switch_xml_attr_soft(start_xml, "data"));
}
stop_xml = switch_xml_child(detector_xml, "stop");
if (stop_xml) {
detector->stop_app = switch_core_strdup(pool, switch_xml_attr_soft(stop_xml, "application"));
detector->stop_app_args = switch_core_strdup(pool, switch_xml_attr_soft(stop_xml, "data"));
}
event_xml = switch_xml_child(detector_xml, "event");
if (event_xml) {
int event_ok = 0;
switch_xml_t signal_type_xml;
const char *event_class = switch_xml_attr_soft(event_xml, "class");
const char *event_subclass = switch_xml_attr_soft(event_xml, "subclass");
switch_event_types_t event_type;
if (zstr(event_class)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing event class for CPA detector: %s\n", detector->name);
status = SWITCH_STATUS_TERM;
goto done;
}
if (switch_name_event(event_class, &event_type) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid event class %s for CPA detector: %s\n", event_class, detector->name);
status = SWITCH_STATUS_TERM;
goto done;
}
/* bind detector to event if not already done... */
{
struct rayo_cpa_detector *bound_detector;
const char *event_name = switch_core_sprintf(pool, "%s %s", event_class, event_subclass);
if (!(bound_detector = switch_core_hash_find(bound_events, event_name))) {
/* not yet bound */
if (zstr(event_subclass)) {
event_subclass = NULL;
}
switch_event_bind("rayo_cpa_detector", event_type, event_subclass, rayo_cpa_detector_event, detector);
switch_core_hash_insert(bound_events, event_name, detector); /* mark as bound */
} else if (bound_detector != detector) {
/* can't have multiple detectors generating the same event! */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s attempted to bind to event %s that is already bound by %s\n", detector->name, event_name, bound_detector->name);
status = SWITCH_STATUS_TERM;
goto done;
}
}
/* configure native event -> rayo CPA event mapping */
detector->signal_type_header = switch_core_strdup(pool, switch_xml_attr_soft(event_xml, "type-header"));
detector->signal_value_header = switch_core_strdup(pool, switch_xml_attr_soft(event_xml, "value-header"));
detector->signal_duration_header = switch_core_strdup(pool, switch_xml_attr_soft(event_xml, "duration-header"));
/* configure native event type -> rayo CPA signal type mapping */
for (signal_type_xml = switch_xml_child(event_xml, "signal-type"); signal_type_xml; signal_type_xml = signal_type_xml->next) {
const char *header_value = switch_core_strdup(pool, switch_xml_attr_soft(signal_type_xml, "header-value"));
const char *signal_type = switch_core_strdup(pool, switch_xml_attr_soft(signal_type_xml, "value"));
if (zstr(signal_type)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s missing signal-type value!\n", detector->name);
status = SWITCH_STATUS_TERM;
goto done;
} else {
/* add signal-type to detector mapping if not already done for this detector */
const char *signal_type_ns = switch_core_sprintf(pool, "%s%s:%s", RAYO_CPA_BASE, signal_type, RAYO_VERSION);
struct rayo_cpa_detector *bound_detector = switch_core_hash_find(globals.detectors, signal_type_ns);
if (!bound_detector) {
switch_core_hash_insert_destructor(globals.detectors, signal_type_ns, detector, destroy_detector);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Adding CPA %s => %s\n", signal_type_ns, detector->name);
event_ok = 1;
} else if (bound_detector == detector) {
/* detector has multiple signal-type configs w/ same value */
event_ok = 1;
} else {
/* multiple detectors with same signal-type value */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s configured to handle signal-type %s that is already handled by %s\n", detector->name, signal_type, bound_detector->name);
status = SWITCH_STATUS_TERM;
goto done;
}
}
/* map event value to signal-type */
if (zstr(header_value)) {
switch_core_hash_insert(detector->signal_type_map, "rayo_default", signal_type);
} else {
switch_core_hash_insert(detector->signal_type_map, header_value, signal_type);
}
}
if (!event_ok) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Detector %s is missing Rayo signal-type for event\n", detector->name);
status = SWITCH_STATUS_TERM;
goto done;
}
}
}
}
done:
switch_core_hash_destroy(&bound_events);
switch_xml_free(xml);
return status;
}
/**
* Console auto-completion for signal types
*/
static switch_status_t rayo_cpa_detector_signal_types(const char *line, const char *cursor, switch_console_callback_match_t **matches)
{
switch_status_t status = SWITCH_STATUS_FALSE;
switch_hash_index_t *hi;
void *val;
const void *vvar;
switch_console_callback_match_t *my_matches = NULL;
switch_mutex_lock(globals.detectors_mutex);
for (hi = switch_core_hash_first(globals.detectors); hi; hi = switch_core_hash_next(&hi)) {
switch_core_hash_this(hi, &vvar, NULL, &val);
switch_console_push_match(&my_matches, (const char *) vvar);
}
switch_mutex_unlock(globals.detectors_mutex);
if (my_matches) {
*matches = my_matches;
status = SWITCH_STATUS_SUCCESS;
}
return status;
}
/**
* Load CPA signal detection features
* @param module_interface
* @param pool memory pool
* @param config_file
* @return SWITCH_STATUS_SUCCESS if successfully loaded
*/
switch_status_t rayo_cpa_detector_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
{
switch_api_interface_t *api_interface;
switch_core_hash_init(&globals.detectors);
switch_mutex_init(&globals.detectors_mutex, SWITCH_MUTEX_NESTED, pool);
if (do_config(pool, config_file) != SWITCH_STATUS_SUCCESS) {
return SWITCH_STATUS_TERM;
}
SWITCH_ADD_API(api_interface, "rayo_cpa", "Query rayo status", rayo_cpa_detector_api, RAYO_CPA_DETECTOR_SYNTAX);
switch_console_set_complete("add rayo_cpa ::console::list_uuid ::rayo_cpa::list_signal_types start");
switch_console_set_complete("add rayo_cpa ::console::list_uuid ::rayo_cpa::list_signal_types stop");
switch_console_add_complete_func("::rayo_cpa::list_signal_types", rayo_cpa_detector_signal_types);
return SWITCH_STATUS_SUCCESS;
}
/**
* Disable CPA signal detection features
*/
void rayo_cpa_detector_shutdown(void)
{
switch_console_set_complete("del rayo_cpa");
switch_console_del_complete_func("::rayo_cpa::list_signal_types");
if (globals.detectors) {
switch_core_hash_destroy(&globals.detectors);
}
switch_event_unbind_callback(rayo_cpa_detector_event);
}
/* 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 noet
*/
@@ -1,53 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2014, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_cpa_detector.h -- Rayo call progress analysis
*
*/
#ifndef RAYO_CPA_DETECTOR_H
#define RAYO_CPA_DETECTOR_H
#include <switch.h>
#include "mod_rayo.h"
SWITCH_DECLARE(switch_status_t) rayo_cpa_detector_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
SWITCH_DECLARE(void) rayo_cpa_detector_shutdown(void);
SWITCH_DECLARE(int) rayo_cpa_detector_start(const char *call_uuid, const char *signal_ns, const char **error_detail);
SWITCH_DECLARE(void) rayo_cpa_detector_stop(const char *call_uuid, const char *signal_ns);
#endif
/* 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 noet
*/
@@ -1,146 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_elements.c -- Rayo XML element definition
*
*/
#include "rayo_elements.h"
/**
* <input> component validation
*/
ELEMENT(RAYO_INPUT)
ATTRIB(xmlns,, any)
STRING_ATTRIB(mode, any, "any,dtmf,voice,cpa")
OPTIONAL_ATTRIB(terminator,, dtmf_digit)
ATTRIB(recognizer,, any)
ATTRIB(language, en-US, any)
ATTRIB(initial-timeout, -1, positive_or_neg_one)
ATTRIB(inter-digit-timeout, -1, positive_or_neg_one)
ATTRIB(sensitivity, 0.5, decimal_between_zero_and_one)
ATTRIB(min-confidence, 0, decimal_between_zero_and_one)
ATTRIB(max-silence, -1, positive_or_neg_one)
STRING_ATTRIB(match-content-type, application/nlsml+xml, "application/nlsml+xml")
/* internal attribs for prompt support */
ATTRIB(barge-event, false, bool)
ATTRIB(start-timers, true, bool)
ELEMENT_END
/**
* <join> command validation
*/
ELEMENT(RAYO_JOIN)
ATTRIB(xmlns,, any)
STRING_ATTRIB(direction, duplex, "send,recv,duplex")
STRING_ATTRIB(media, bridge, "bridge,direct")
ATTRIB(call-uri,, any)
ATTRIB(mixer-name,, any)
ELEMENT_END
/**
* <output> component validation
*/
ELEMENT(RAYO_OUTPUT)
ATTRIB(xmlns,, any)
ATTRIB(start-offset, 0, not_negative)
ATTRIB(start-paused, false, bool)
ATTRIB(repeat-interval, 0, not_negative)
ATTRIB(repeat-times, 1, not_negative)
ATTRIB(max-time, -1, positive_or_neg_one)
ATTRIB(renderer,, any)
ATTRIB(voice,, any)
STRING_ATTRIB(direction, out, "out,in")
ELEMENT_END
/**
* <output><seek> validation
*/
ELEMENT(RAYO_OUTPUT_SEEK)
ATTRIB(xmlns,, any)
STRING_ATTRIB(direction,, "forward,back")
ATTRIB(amount,-1, positive)
ELEMENT_END
/**
* <prompt> component validation
*/
ELEMENT(RAYO_PROMPT)
ATTRIB(xmlns,, any)
ATTRIB(barge-in, true, bool)
ELEMENT_END
/**
* <receivefax> command validation
*/
ELEMENT(RAYO_RECEIVEFAX)
ATTRIB(xmlns,, any)
ELEMENT_END
/**
* <record> component validation
*/
ELEMENT(RAYO_RECORD)
ATTRIB(xmlns,, any)
ATTRIB(format, wav, any)
ATTRIB(start-beep, false, bool)
ATTRIB(stop-beep, false, bool)
ATTRIB(start-paused, false, bool)
ATTRIB(max-duration, -1, positive_or_neg_one)
ATTRIB(initial-timeout, -1, positive_or_neg_one)
ATTRIB(final-timeout, -1, positive_or_neg_one)
STRING_ATTRIB(direction, duplex, "duplex,send,recv")
ATTRIB(mix, false, bool)
ELEMENT_END
/**
* <sendfax> command validation
*/
ELEMENT(RAYO_SENDFAX)
ATTRIB(xmlns,, any)
ELEMENT_END
/**
* <app> command validation
*/
ELEMENT(RAYO_APP)
ATTRIB(xmlns,, any)
ATTRIB(app,, any)
OPTIONAL_ATTRIB(args,, any)
ELEMENT_END
/* 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 noet
*/
@@ -1,56 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_elements.h -- Rayo XML elements
*
*/
#ifndef RAYO_ELEMENTS_H
#define RAYO_ELEMENTS_H
#include "iks_helpers.h"
ELEMENT_DECL(RAYO_INPUT)
ELEMENT_DECL(RAYO_JOIN)
ELEMENT_DECL(RAYO_OUTPUT)
ELEMENT_DECL(RAYO_OUTPUT_SEEK)
ELEMENT_DECL(RAYO_PROMPT)
ELEMENT_DECL(RAYO_RECEIVEFAX)
ELEMENT_DECL(RAYO_RECORD)
ELEMENT_DECL(RAYO_SENDFAX)
ELEMENT_DECL(RAYO_APP)
#endif
/* 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 noet
*/
@@ -1,176 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_exec_component.c -- Rayo call application execution component
*
*/
#include "rayo_components.h"
#include "rayo_elements.h"
/**
* An exec component
*/
struct exec_component {
/** component base class */
struct rayo_component base;
/** Dialplan app */
const char *app;
/** Dialplan app args */
char *args;
};
#define EXEC_COMPONENT(x) ((struct exec_component *)x)
/**
* Wrapper for executing dialplan app
*/
SWITCH_STANDARD_APP(rayo_app_exec)
{
if (!zstr(data)) {
struct rayo_component *component = RAYO_COMPONENT_LOCATE(data);
if (component) {
switch_status_t status;
switch_channel_set_variable(switch_core_session_get_channel(session), SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "");
status = switch_core_session_execute_application(session, EXEC_COMPONENT(component)->app, EXEC_COMPONENT(component)->args);
if (status != SWITCH_STATUS_SUCCESS) {
rayo_component_send_complete(component, COMPONENT_COMPLETE_ERROR);
} else {
const char *resp = switch_channel_get_variable(switch_core_session_get_channel(session), SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE);
if (zstr(resp)) {
rayo_component_send_complete(component, COMPONENT_COMPLETE_DONE);
} else {
/* send complete event to client */
iks *response = iks_new("app");
iks_insert_attrib(response, "xmlns", RAYO_EXEC_COMPLETE_NS);
iks_insert_attrib(response, "response", resp);
rayo_component_send_complete_with_metadata(component, COMPONENT_COMPLETE_DONE, response, 1);
iks_delete(response);
}
}
RAYO_RELEASE(component);
}
} else {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Missing rayo exec component JID\n");
}
switch_channel_set_variable(switch_core_session_get_channel(session), SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "");
}
/**
* Create a record component
*/
static struct rayo_component *exec_component_create(struct rayo_actor *call, const char *client_jid, iks *exec)
{
switch_memory_pool_t *pool;
struct exec_component *exec_component = NULL;
switch_core_new_memory_pool(&pool);
exec_component = switch_core_alloc(pool, sizeof(*exec_component));
exec_component = EXEC_COMPONENT(rayo_component_init(RAYO_COMPONENT(exec_component), pool, RAT_CALL_COMPONENT, "exec", NULL, call, client_jid));
if (exec_component) {
exec_component->app = switch_core_strdup(pool, iks_find_attrib_soft(exec, "app"));
exec_component->args = switch_core_strdup(pool, iks_find_attrib_soft(exec, "args"));
} else {
switch_core_destroy_memory_pool(&pool);
return NULL;
}
return RAYO_COMPONENT(exec_component);
}
/**
* Execute dialplan APP on rayo call
*/
static iks *start_exec_app_component(struct rayo_actor *call, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
iks *exec = iks_find(iq, "app");
struct rayo_component *exec_component = NULL;
switch_core_session_t *session = NULL;
/* validate record attributes */
if (!VALIDATE_RAYO_APP(exec)) {
return iks_new_error(iq, STANZA_ERROR_BAD_REQUEST);
}
exec_component = exec_component_create(call, iks_find_attrib(iq, "from"), exec);
if (!exec_component) {
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create exec entity");
}
session = switch_core_session_locate(call->id);
if (session) {
if (switch_core_session_execute_application_async(session, switch_core_session_strdup(session, "rayo-app-exec"), switch_core_session_strdup(session, RAYO_JID(exec_component))) != SWITCH_STATUS_SUCCESS) {
switch_core_session_rwunlock(session);
RAYO_RELEASE(exec_component);
RAYO_DESTROY(exec_component);
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "failed to execute app");
}
switch_core_session_rwunlock(session);
} else {
RAYO_RELEASE(exec_component);
RAYO_DESTROY(exec_component);
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Call is gone");
}
rayo_component_send_start(exec_component, iq);
return NULL;
}
/**
* Initialize exec component
* @param module_interface
* @param pool memory pool to allocate from
* @param config_file to use
* @return SWITCH_STATUS_SUCCESS if successful
*/
switch_status_t rayo_exec_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
{
switch_application_interface_t *app_interface;
SWITCH_ADD_APP(app_interface, "rayo-app-exec", "Wrapper dialplan app for internal use only", "", rayo_app_exec, "", SAF_SUPPORT_NOMEDIA | SAF_ZOMBIE_EXEC);
rayo_actor_command_handler_add(RAT_CALL, "", "set:"RAYO_EXEC_NS":app", start_exec_app_component);
return SWITCH_STATUS_SUCCESS;
}
/**
* Shutdown exec component
* @return SWITCH_STATUS_SUCCESS if successful
*/
switch_status_t rayo_exec_component_shutdown(void)
{
return SWITCH_STATUS_SUCCESS;
}
/* 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 noet
*/
@@ -1,561 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2014, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_fax_components.c -- Rayo receivefax and sendfax components implementation
*
*/
#include "rayo_components.h"
#include "rayo_elements.h"
/**
* settings
*/
static struct {
const char *file_prefix;
} globals;
struct fax_component {
/** component base class */
struct rayo_component base;
/** Flag to stop fax */
int stop;
};
#define FAX_COMPONENT(x) ((struct fax_component *)x)
struct receivefax_component {
/** fax component base class */
struct fax_component base;
/** true if HTTP PUT needs to be done after fax is received */
int http_put_after_receive;
/** fax stored on local filesystem */
const char *local_filename;
/** fax final target (may be same as local filename) */
const char *filename;
};
#define RECEIVEFAX_COMPONENT(x) ((struct receivefax_component *)x)
#define FAX_FINISH "finish", RAYO_FAX_COMPLETE_NS
/**
* Start execution of call sendfax component
* @param call the call to send fax to
* @param msg the original request
* @param session_data the call's session
*/
static iks *start_sendfax_component(struct rayo_actor *call, struct rayo_message *msg, void *session_data)
{
iks *iq = msg->payload;
switch_core_session_t *session = (switch_core_session_t *)session_data;
struct fax_component *sendfax_component = NULL;
iks *sendfax = iks_find(iq, "sendfax");
iks *response = NULL;
switch_event_t *execute_event = NULL;
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_memory_pool_t *pool;
iks *document;
const char *fax_document;
const char *fax_header;
const char *fax_identity;
const char *pages;
/* validate attributes */
if (!VALIDATE_RAYO_SENDFAX(sendfax)) {
return iks_new_error(iq, STANZA_ERROR_BAD_REQUEST);
}
/* fax is only allowed if the call is not currently joined */
if (rayo_call_is_joined(RAYO_CALL(call))) {
return iks_new_error_detailed(iq, STANZA_ERROR_UNEXPECTED_REQUEST, "can't send fax on a joined call");
}
if (rayo_call_is_faxing(RAYO_CALL(call))) {
return iks_new_error_detailed(iq, STANZA_ERROR_UNEXPECTED_REQUEST, "fax already in progress");
}
/* get fax document */
document = iks_find(sendfax, "document");
if (!document) {
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "missing document");
}
fax_document = iks_find_attrib_soft(document, "url");
if (zstr(fax_document)) {
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "missing document url");
}
/* is valid URL type? */
if (!strncasecmp(fax_document, "http://", 7) || !strncasecmp(fax_document, "https://", 8)) {
switch_stream_handle_t stream = { 0 };
SWITCH_STANDARD_STREAM(stream);
/* need to fetch document from server... */
switch_api_execute("http_get", fax_document, session, &stream);
if (!zstr(stream.data) && !strncmp(fax_document, SWITCH_PATH_SEPARATOR, strlen(SWITCH_PATH_SEPARATOR))) {
fax_document = switch_core_session_strdup(session, stream.data);
} else {
switch_safe_free(stream.data);
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "failed to fetch document");
}
switch_safe_free(stream.data);
} else if (!strncasecmp(fax_document, "file://", 7)) {
fax_document = fax_document + 7;
if (zstr(fax_document)) {
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "invalid file:// url");
}
} else if (strncasecmp(fax_document, SWITCH_PATH_SEPARATOR, strlen(SWITCH_PATH_SEPARATOR))) {
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "unsupported url type");
}
/* does document exist? */
if (switch_file_exists(fax_document, NULL) != SWITCH_STATUS_SUCCESS) {
return iks_new_error_detailed_printf(iq, STANZA_ERROR_BAD_REQUEST, "file not found: %s", fax_document);
}
/* get fax identity and header */
fax_identity = iks_find_attrib_soft(document, "identity");
if (!zstr(fax_identity)) {
switch_channel_set_variable(channel, "fax_ident", fax_identity);
} else {
switch_channel_set_variable(channel, "fax_ident", NULL);
}
fax_header = iks_find_attrib_soft(document, "header");
if (!zstr(fax_header)) {
switch_channel_set_variable(channel, "fax_header", fax_header);
} else {
switch_channel_set_variable(channel, "fax_header", NULL);
}
/* get pages to send */
pages = iks_find_attrib_soft(document, "pages");
if (!zstr(pages)) {
if (switch_regex_match(pages, "[1-9][0-9]*(-[1-9][0-9]*)?") == SWITCH_STATUS_FALSE) {
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "invalid pages value");
} else {
int start = 0;
int end = 0;
char *pages_dup = switch_core_session_strdup(session, pages);
char *sep = strchr(pages_dup, '-');
if (sep) {
*sep = '\0';
sep++;
end = atoi(sep);
}
start = atoi(pages_dup);
if (end && end < start) {
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "invalid pages value");
}
switch_channel_set_variable(channel, "fax_start_page", pages_dup);
switch_channel_set_variable(channel, "fax_end_page", sep);
}
} else {
switch_channel_set_variable(channel, "fax_start_page", NULL);
switch_channel_set_variable(channel, "fax_end_page", NULL);
}
/* create sendfax component */
switch_core_new_memory_pool(&pool);
sendfax_component = switch_core_alloc(pool, sizeof(*sendfax_component));
sendfax_component = FAX_COMPONENT(rayo_component_init((struct rayo_component *)sendfax_component, pool, RAT_CALL_COMPONENT, "sendfax", NULL, call, iks_find_attrib(iq, "from")));
if (!sendfax_component) {
switch_core_destroy_memory_pool(&pool);
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create sendfax entity");
}
/* add channel variable so that fax component can be located from fax events */
switch_channel_set_variable(channel, "rayo_fax_jid", RAYO_JID(sendfax_component));
/* clear fax result variables */
switch_channel_set_variable(channel, "fax_success", NULL);
switch_channel_set_variable(channel, "fax_result_code", NULL);
switch_channel_set_variable(channel, "fax_result_text", NULL);
switch_channel_set_variable(channel, "fax_document_transferred_pages", NULL);
switch_channel_set_variable(channel, "fax_document_total_pages", NULL);
switch_channel_set_variable(channel, "fax_image_resolution", NULL);
switch_channel_set_variable(channel, "fax_image_size", NULL);
switch_channel_set_variable(channel, "fax_bad_rows", NULL);
switch_channel_set_variable(channel, "fax_transfer_rate", NULL);
switch_channel_set_variable(channel, "fax_ecm_used", NULL);
switch_channel_set_variable(channel, "fax_local_station_id", NULL);
switch_channel_set_variable(channel, "fax_remote_station_id", NULL);
rayo_call_set_faxing(RAYO_CALL(call), 1);
/* execute txfax APP */
if (switch_event_create(&execute_event, SWITCH_EVENT_COMMAND) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header_string(execute_event, SWITCH_STACK_BOTTOM, "call-command", "execute");
switch_event_add_header_string(execute_event, SWITCH_STACK_BOTTOM, "execute-app-name", "txfax");
switch_event_add_header_string(execute_event, SWITCH_STACK_BOTTOM, "execute-app-arg", fax_document);
if (!switch_channel_test_flag(channel, CF_PROXY_MODE)) {
switch_channel_set_flag(channel, CF_BLOCK_BROADCAST_UNTIL_MEDIA);
}
if (switch_core_session_queue_private_event(session, &execute_event, SWITCH_FALSE) != SWITCH_STATUS_SUCCESS) {
response = iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "failed to txfax (queue event failed)");
if (execute_event) {
switch_event_destroy(&execute_event);
}
rayo_call_set_faxing(RAYO_CALL(call), 0);
RAYO_RELEASE(sendfax_component);
} else {
/* component starting... */
rayo_component_send_start(RAYO_COMPONENT(sendfax_component), iq);
}
} else {
response = iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "failed to create txfax event");
rayo_call_set_faxing(RAYO_CALL(call), 0);
RAYO_RELEASE(sendfax_component);
}
return response;
}
/**
* Start execution of call receivefax component
* @param call the call to receive fax from
* @param msg the original request
* @param session_data the call's session
*/
static iks *start_receivefax_component(struct rayo_actor *call, struct rayo_message *msg, void *session_data)
{
iks *iq = msg->payload;
switch_core_session_t *session = (switch_core_session_t *)session_data;
struct receivefax_component *receivefax_component = NULL;
iks *receivefax = iks_find(iq, "receivefax");
iks *response = NULL;
switch_event_t *execute_event = NULL;
switch_channel_t *channel = switch_core_session_get_channel(session);
switch_memory_pool_t *pool;
int file_no;
/* validate attributes */
if (!VALIDATE_RAYO_RECEIVEFAX(receivefax)) {
return iks_new_error(iq, STANZA_ERROR_BAD_REQUEST);
}
/* fax is only allowed if the call is not currently joined */
if (rayo_call_is_joined(RAYO_CALL(call))) {
return iks_new_error_detailed(iq, STANZA_ERROR_UNEXPECTED_REQUEST, "can't receive fax on a joined call");
}
if (rayo_call_is_faxing(RAYO_CALL(call))) {
return iks_new_error_detailed(iq, STANZA_ERROR_UNEXPECTED_REQUEST, "fax already in progress");
}
/* create receivefax component */
switch_core_new_memory_pool(&pool);
receivefax_component = switch_core_alloc(pool, sizeof(*receivefax_component));
receivefax_component = RECEIVEFAX_COMPONENT(rayo_component_init((struct rayo_component *)receivefax_component, pool, RAT_CALL_COMPONENT, "receivefax", NULL, call, iks_find_attrib(iq, "from")));
if (!receivefax_component) {
switch_core_destroy_memory_pool(&pool);
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create sendfax entity");
}
file_no = rayo_actor_seq_next(call);
receivefax_component->filename = switch_core_sprintf(pool, "%s%s%s-%d.tif",
globals.file_prefix, SWITCH_PATH_SEPARATOR, switch_core_session_get_uuid(session), file_no);
if (!strncmp(receivefax_component->filename, "http://", 7) || !strncmp(receivefax_component->filename, "https://", 8)) {
/* This is an HTTP URL, need to PUT after fax is received */
receivefax_component->local_filename = switch_core_sprintf(pool, "%s%s%s-%d",
SWITCH_GLOBAL_dirs.temp_dir, SWITCH_PATH_SEPARATOR, switch_core_session_get_uuid(session), file_no);
receivefax_component->http_put_after_receive = 1;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s save fax to HTTP URL\n", RAYO_JID(receivefax_component));
} else {
/* assume file.. */
receivefax_component->local_filename = receivefax_component->filename;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s save fax to local file\n", RAYO_JID(receivefax_component));
}
/* add channel variable so that fax component can be located from fax events */
switch_channel_set_variable(channel, "rayo_fax_jid", RAYO_JID(receivefax_component));
/* clear fax result variables */
switch_channel_set_variable(channel, "fax_success", NULL);
switch_channel_set_variable(channel, "fax_result_code", NULL);
switch_channel_set_variable(channel, "fax_result_text", NULL);
switch_channel_set_variable(channel, "fax_document_transferred_pages", NULL);
switch_channel_set_variable(channel, "fax_document_total_pages", NULL);
switch_channel_set_variable(channel, "fax_image_resolution", NULL);
switch_channel_set_variable(channel, "fax_image_size", NULL);
switch_channel_set_variable(channel, "fax_bad_rows", NULL);
switch_channel_set_variable(channel, "fax_transfer_rate", NULL);
switch_channel_set_variable(channel, "fax_ecm_used", NULL);
switch_channel_set_variable(channel, "fax_local_station_id", NULL);
switch_channel_set_variable(channel, "fax_remote_station_id", NULL);
rayo_call_set_faxing(RAYO_CALL(call), 1);
/* execute rxfax APP */
if (switch_event_create(&execute_event, SWITCH_EVENT_COMMAND) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header_string(execute_event, SWITCH_STACK_BOTTOM, "call-command", "execute");
switch_event_add_header_string(execute_event, SWITCH_STACK_BOTTOM, "execute-app-name", "rxfax");
switch_event_add_header_string(execute_event, SWITCH_STACK_BOTTOM, "execute-app-arg", receivefax_component->local_filename);
if (!switch_channel_test_flag(channel, CF_PROXY_MODE)) {
switch_channel_set_flag(channel, CF_BLOCK_BROADCAST_UNTIL_MEDIA);
}
if (switch_core_session_queue_private_event(session, &execute_event, SWITCH_FALSE) != SWITCH_STATUS_SUCCESS) {
response = iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "failed to rxfax (queue event failed)");
if (execute_event) {
switch_event_destroy(&execute_event);
}
rayo_call_set_faxing(RAYO_CALL(call), 0);
RAYO_RELEASE(receivefax_component);
} else {
/* component starting... */
rayo_component_send_start(RAYO_COMPONENT(receivefax_component), iq);
}
} else {
response = iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "failed to create rxfax event");
rayo_call_set_faxing(RAYO_CALL(call), 0);
RAYO_RELEASE(receivefax_component);
}
return response;
}
/**
* Stop execution of fax component
*/
static iks *stop_fax_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
switch_core_session_t *session = switch_core_session_locate(component->parent->id);
FAX_COMPONENT(component)->stop = 1;
if (session) {
switch_core_session_execute_application_async(session, "stopfax", "");
switch_core_session_rwunlock(session);
}
return iks_new_iq_result(iq);
}
/**
* Add fax metadata to result
* @param event source of metadata
* @param name of metadata
* @param result to add metadata to
*/
static void insert_fax_metadata(switch_event_t *event, const char *name, iks *result)
{
char actual_name[256];
const char *value;
snprintf(actual_name, sizeof(actual_name), "variable_%s", name);
actual_name[sizeof(actual_name) - 1] = '\0';
value = switch_event_get_header(event, actual_name);
if (!zstr(value)) {
iks *metadata = iks_insert(result, "metadata");
iks_insert_attrib(metadata, "xmlns", RAYO_FAX_COMPLETE_NS);
iks_insert_attrib(metadata, "name", name);
iks_insert_attrib(metadata, "value", value);
}
}
/**
* Handle fax completion event from FreeSWITCH core
* @param event received from FreeSWITCH core. It will be destroyed by the core after this function returns.
*/
static void on_execute_complete_event(switch_event_t *event)
{
const char *application = switch_event_get_header(event, "Application");
if (!zstr(application) && (!strcmp(application, "rxfax") || !strcmp(application, "txfax"))) {
int is_rxfax = !strcmp(application, "rxfax");
const char *uuid = switch_event_get_header(event, "Unique-ID");
const char *fax_jid = switch_event_get_header(event, "variable_rayo_fax_jid");
struct rayo_actor *component;
if (!zstr(fax_jid) && (component = RAYO_LOCATE(fax_jid))) {
iks *result;
iks *complete;
iks *fax;
int have_fax_document = 1;
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Got result for %s\n", fax_jid);
/* RX only: transfer HTTP document and delete local copy */
if (is_rxfax && RECEIVEFAX_COMPONENT(component)->http_put_after_receive && switch_file_exists(RECEIVEFAX_COMPONENT(component)->local_filename, RAYO_POOL(component)) == SWITCH_STATUS_SUCCESS) {
char *cmd = switch_core_sprintf(RAYO_POOL(component), "%s %s", RECEIVEFAX_COMPONENT(component)->filename, RECEIVEFAX_COMPONENT(component)->local_filename);
switch_stream_handle_t stream = { 0 };
SWITCH_STANDARD_STREAM(stream);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s PUT fax to %s\n", RAYO_JID(component), RECEIVEFAX_COMPONENT(component)->filename);
switch_api_execute("http_put", cmd, NULL, &stream);
/* check if successful */
if (!zstr(stream.data) && strncmp(stream.data, "+OK", 3)) {
/* PUT failed */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "%s PUT fax %s to %s failed: %s\n", RAYO_JID(component), RECEIVEFAX_COMPONENT(component)->local_filename, RECEIVEFAX_COMPONENT(component)->filename, (char *)stream.data);
have_fax_document = 0;
}
switch_safe_free(stream.data)
switch_file_remove(RECEIVEFAX_COMPONENT(component)->local_filename, RAYO_POOL(component));
}
/* successful fax? */
if (have_fax_document && switch_true(switch_event_get_header(event, "variable_fax_success"))) {
result = rayo_component_create_complete_event(RAYO_COMPONENT(component), FAX_FINISH);
} else if (have_fax_document && FAX_COMPONENT(component)->stop) {
result = rayo_component_create_complete_event(RAYO_COMPONENT(component), COMPONENT_COMPLETE_STOP);
} else {
result = rayo_component_create_complete_event(RAYO_COMPONENT(component), COMPONENT_COMPLETE_ERROR);
}
complete = iks_find(result, "complete");
/* RX only: add fax document information */
if (is_rxfax && have_fax_document) {
const char *pages = switch_event_get_header(event, "variable_fax_document_transferred_pages");
if (!zstr(pages) && switch_is_number(pages) && atoi(pages) > 0) {
const char *resolution = switch_event_get_header(event, "variable_fax_file_image_resolution");
const char *size = switch_event_get_header(event, "variable_fax_image_size");
fax = iks_insert(complete, "fax");
iks_insert_attrib(fax, "xmlns", RAYO_FAX_COMPLETE_NS);
if (RECEIVEFAX_COMPONENT(component)->http_put_after_receive) {
iks_insert_attrib(fax, "url", RECEIVEFAX_COMPONENT(component)->filename);
} else {
/* convert absolute path to file:// URI */
iks_insert_attrib_printf(fax, "url", "file://%s", RECEIVEFAX_COMPONENT(component)->filename);
}
if (!zstr(resolution)) {
iks_insert_attrib(fax, "resolution", resolution);
}
if (!zstr(size)) {
iks_insert_attrib(fax, "size", size);
}
iks_insert_attrib(fax, "pages", pages);
}
}
/* add metadata from event */
insert_fax_metadata(event, "fax_success", complete);
insert_fax_metadata(event, "fax_result_code", complete);
insert_fax_metadata(event, "fax_result_text", complete);
insert_fax_metadata(event, "fax_document_transferred_pages", complete);
insert_fax_metadata(event, "fax_document_total_pages", complete);
insert_fax_metadata(event, "fax_image_resolution", complete);
insert_fax_metadata(event, "fax_image_size", complete);
insert_fax_metadata(event, "fax_bad_rows", complete);
insert_fax_metadata(event, "fax_transfer_rate", complete);
insert_fax_metadata(event, "fax_ecm_used", complete);
insert_fax_metadata(event, "fax_local_station_id", complete);
insert_fax_metadata(event, "fax_remote_station_id", complete);
/* flag faxing as done */
rayo_call_set_faxing(RAYO_CALL(component->parent), 0);
rayo_component_send_complete_event(RAYO_COMPONENT(component), result);
RAYO_RELEASE(component);
}
}
}
/**
* Process module XML configuration
* @param pool memory pool to allocate from
* @param config_file to use
* @return SWITCH_STATUS_SUCCESS on successful configuration
*/
static switch_status_t do_config(switch_memory_pool_t *pool, const char *config_file)
{
switch_xml_t cfg, xml;
/* set defaults */
globals.file_prefix = switch_core_sprintf(pool, "%s%s", SWITCH_GLOBAL_dirs.recordings_dir, SWITCH_PATH_SEPARATOR);
if (!(xml = switch_xml_open_cfg(config_file, &cfg, NULL))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", config_file);
return SWITCH_STATUS_TERM;
}
/* get params */
{
switch_xml_t settings = switch_xml_child(cfg, "fax");
if (settings) {
switch_xml_t param;
for (param = switch_xml_child(settings, "param"); param; param = param->next) {
const char *var = switch_xml_attr_soft(param, "name");
const char *val = switch_xml_attr_soft(param, "value");
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "param: %s = %s\n", var, val);
if (!strcasecmp(var, "receivefax-file-prefix")) {
if (!zstr(val)) {
globals.file_prefix = switch_core_strdup(pool, val);
}
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported param: %s\n", var);
}
}
}
}
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "receivefax-file-prefix = %s\n", globals.file_prefix);
switch_xml_free(xml);
return SWITCH_STATUS_SUCCESS;
}
/**
* Initialize fax components
* @param module_interface
* @param pool memory pool to allocate from
* @param config_file to use
* @return SWITCH_STATUS_SUCCESS if successful
*/
switch_status_t rayo_fax_components_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
{
if (do_config(pool, config_file) != SWITCH_STATUS_SUCCESS) {
return SWITCH_STATUS_TERM;
}
switch_event_bind("rayo_fax_components", SWITCH_EVENT_CHANNEL_EXECUTE_COMPLETE, NULL, on_execute_complete_event, NULL);
rayo_actor_command_handler_add(RAT_CALL, "", "set:"RAYO_FAX_NS":receivefax", start_receivefax_component);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "receivefax", "set:"RAYO_EXT_NS":stop", stop_fax_component);
rayo_actor_command_handler_add(RAT_CALL, "", "set:"RAYO_FAX_NS":sendfax", start_sendfax_component);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "sendfax", "set:"RAYO_EXT_NS":stop", stop_fax_component);
return SWITCH_STATUS_SUCCESS;
}
/**
* Shutdown fax components
* @return SWITCH_STATUS_SUCCESS if successful
*/
switch_status_t rayo_fax_components_shutdown(void)
{
switch_event_unbind_callback(on_execute_complete_event);
return SWITCH_STATUS_SUCCESS;
}
/* 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 noet
*/
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,731 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2015, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* rayo_prompt_component.c -- Rayo prompt component implementation
*
*/
#include "rayo_components.h"
#include "rayo_elements.h"
enum prompt_component_state {
/* initial state - no barge */
PCS_START_OUTPUT,
/* playing prompt */
PCS_OUTPUT,
/* start input - no barge */
PCS_START_INPUT,
/* input starting - start timers needed */
PCS_START_INPUT_TIMERS,
/* initial state - barge in */
PCS_START_OUTPUT_BARGE,
/* start input for barge-in */
PCS_START_INPUT_OUTPUT,
/* playing and detecting */
PCS_INPUT_OUTPUT,
/* barge in on output */
PCS_STOP_OUTPUT,
/* detecting */
PCS_INPUT,
/* finishing, stop output */
PCS_DONE_STOP_OUTPUT,
/* finished */
PCS_DONE
};
/**
* Prompt state
*/
struct prompt_component {
struct rayo_component base;
enum prompt_component_state state;
iks *iq;
iks *complete;
const char *input_jid;
const char *output_jid;
const char *start_timers_request_id;
};
#define PROMPT_COMPONENT(x) ((struct prompt_component *)x)
static const char *prompt_component_state_to_string(enum prompt_component_state state)
{
switch(state) {
case PCS_START_OUTPUT_BARGE: return "START_OUTPUT_BARGE";
case PCS_START_OUTPUT: return "START_OUTPUT";
case PCS_START_INPUT_OUTPUT: return "START_INPUT_OUTPUT";
case PCS_START_INPUT: return "START_INPUT";
case PCS_START_INPUT_TIMERS: return "START_INPUT_TIMERS";
case PCS_INPUT_OUTPUT: return "INPUT_OUTPUT";
case PCS_STOP_OUTPUT: return "STOP_OUTPUT";
case PCS_INPUT: return "INPUT";
case PCS_OUTPUT: return "OUTPUT";
case PCS_DONE_STOP_OUTPUT: return "DONE_STOP_OUTPUT";
case PCS_DONE: return "DONE";
}
return "UNKNOWN";
}
/**
* Send input-timers-started event
*/
void rayo_component_send_input_timers_started_event(struct rayo_component *component)
{
iks *event = iks_new("presence");
iks *x;
iks_insert_attrib(event, "from", RAYO_JID(component));
iks_insert_attrib(event, "to", component->client_jid);
x = iks_insert(event, "input-timers-started");
iks_insert_attrib(x, "xmlns", RAYO_PROMPT_NS);
RAYO_SEND_REPLY(component, component->client_jid, event);
}
/**
* Send stop to component
*/
static void rayo_component_send_stop(struct rayo_actor *from, const char *to)
{
iks *stop = iks_new("iq");
iks *x;
iks_insert_attrib(stop, "from", RAYO_JID(from));
iks_insert_attrib(stop, "to", to);
iks_insert_attrib(stop, "type", "set");
iks_insert_attrib_printf(stop, "id", "mod_rayo-prompt-%d", RAYO_SEQ_NEXT(from));
x = iks_insert(stop, "stop");
iks_insert_attrib(x, "xmlns", RAYO_EXT_NS);
RAYO_SEND_MESSAGE(from, to, stop);
}
/**
* Start input component
*/
static void start_input(struct prompt_component *prompt, int start_timers, int barge_event)
{
iks *iq = iks_new("iq");
iks *input = iks_find(PROMPT_COMPONENT(prompt)->iq, "prompt");
input = iks_find(input, "input");
iks_insert_attrib(iq, "from", RAYO_JID(prompt));
iks_insert_attrib(iq, "to", RAYO_JID(RAYO_ACTOR(prompt)->parent));
iks_insert_attrib_printf(iq, "id", "mod_rayo-prompt-%d", RAYO_SEQ_NEXT(prompt));
iks_insert_attrib(iq, "type", "set");
input = iks_copy_within(input, iks_stack(iq));
iks_insert_attrib(input, "start-timers", start_timers ? "true" : "false");
iks_insert_attrib(input, "barge-event", barge_event ? "true" : "false");
iks_insert_node(iq, input);
RAYO_SEND_MESSAGE(prompt, RAYO_JID(RAYO_ACTOR(prompt)->parent), iq);
}
/**
* Start input component timers
*/
static void start_input_timers(struct prompt_component *prompt)
{
iks *x;
iks *iq = iks_new("iq");
iks_insert_attrib(iq, "from", RAYO_JID(prompt));
iks_insert_attrib(iq, "to", prompt->input_jid);
iks_insert_attrib(iq, "type", "set");
prompt->start_timers_request_id = switch_core_sprintf(RAYO_POOL(prompt), "mod_rayo-prompt-%d", RAYO_SEQ_NEXT(prompt));
iks_insert_attrib(iq, "id", prompt->start_timers_request_id);
x = iks_insert(iq, "start-timers");
iks_insert_attrib(x, "xmlns", RAYO_INPUT_NS);
RAYO_SEND_MESSAGE(prompt, prompt->input_jid, iq);
}
/**
* Handle start of output.
*/
static iks *prompt_component_handle_output_start(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) output start\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_START_OUTPUT:
PROMPT_COMPONENT(prompt)->output_jid = switch_core_strdup(RAYO_POOL(prompt), msg->from_jid);
PROMPT_COMPONENT(prompt)->state = PCS_OUTPUT;
/* send ref to client */
rayo_component_send_start(RAYO_COMPONENT(prompt), PROMPT_COMPONENT(prompt)->iq);
break;
case PCS_START_OUTPUT_BARGE:
PROMPT_COMPONENT(prompt)->output_jid = switch_core_strdup(RAYO_POOL(prompt), msg->from_jid);
PROMPT_COMPONENT(prompt)->state = PCS_START_INPUT_OUTPUT;
/* start input without timers and with barge events */
start_input(PROMPT_COMPONENT(prompt), 0, 1);
break;
case PCS_OUTPUT:
case PCS_START_INPUT_OUTPUT:
case PCS_START_INPUT:
case PCS_START_INPUT_TIMERS:
case PCS_INPUT_OUTPUT:
case PCS_STOP_OUTPUT:
case PCS_INPUT:
case PCS_DONE_STOP_OUTPUT:
case PCS_DONE:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, unexpected start output event\n", RAYO_JID(prompt));
break;
}
return NULL;
}
/**
* Handle start of input.
*/
static iks *prompt_component_handle_input_start(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) input start\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_START_INPUT:
PROMPT_COMPONENT(prompt)->input_jid = switch_core_strdup(RAYO_POOL(prompt), msg->from_jid);
PROMPT_COMPONENT(prompt)->state = PCS_INPUT;
rayo_component_send_input_timers_started_event(RAYO_COMPONENT(prompt));
break;
case PCS_START_INPUT_OUTPUT:
PROMPT_COMPONENT(prompt)->input_jid = switch_core_strdup(RAYO_POOL(prompt), msg->from_jid);
PROMPT_COMPONENT(prompt)->state = PCS_INPUT_OUTPUT;
/* send ref to client */
rayo_component_send_start(RAYO_COMPONENT(prompt), PROMPT_COMPONENT(prompt)->iq);
break;
case PCS_START_INPUT_TIMERS:
PROMPT_COMPONENT(prompt)->input_jid = switch_core_strdup(RAYO_POOL(prompt), msg->from_jid);
PROMPT_COMPONENT(prompt)->state = PCS_INPUT;
/* send ref to client */
rayo_component_send_start(RAYO_COMPONENT(prompt), PROMPT_COMPONENT(prompt)->iq);
start_input_timers(PROMPT_COMPONENT(prompt));
break;
case PCS_DONE:
/* stopped by client */
PROMPT_COMPONENT(prompt)->input_jid = switch_core_strdup(RAYO_POOL(prompt), msg->from_jid);
rayo_component_send_stop(prompt, msg->from_jid);
break;
case PCS_START_OUTPUT:
case PCS_START_OUTPUT_BARGE:
case PCS_INPUT_OUTPUT:
case PCS_INPUT:
case PCS_STOP_OUTPUT:
case PCS_OUTPUT:
case PCS_DONE_STOP_OUTPUT:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, unexpected start input event\n", RAYO_JID(prompt));
break;
}
return NULL;
}
/**
* Handle start of input/output.
*/
static iks *prompt_component_handle_io_start(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, got <ref> from %s: %s\n",
RAYO_JID(prompt), msg->from_jid, iks_string(iks_stack(iq), iq));
if (!strcmp("input", msg->from_subtype)) {
return prompt_component_handle_input_start(prompt, msg, data);
} else if (!strcmp("output", msg->from_subtype)) {
return prompt_component_handle_output_start(prompt, msg, data);
}
return NULL;
}
/**
* Handle failure to start timers
*/
static iks *prompt_component_handle_input_start_timers_error(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
/* this is only expected if input component is gone */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) start timers error\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
return NULL;
}
/**
* Handle input failure.
*/
static iks *prompt_component_handle_input_error(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
iks *error = iks_find(iq, "error");
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) input error\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_START_INPUT_TIMERS:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, <input> error: %s\n", RAYO_JID(prompt), iks_string(iks_stack(iq), iq));
PROMPT_COMPONENT(prompt)->state = PCS_DONE;
/* forward IQ error to client */
iq = PROMPT_COMPONENT(prompt)->iq;
iks_insert_attrib(iq, "from", RAYO_JID(prompt->parent));
iks_insert_attrib(iq, "to", RAYO_COMPONENT(prompt)->client_jid);
iks_insert_attrib(iq, "type", "error");
iks_insert_node(iq, iks_copy_within(error, iks_stack(iq)));
RAYO_SEND_REPLY(prompt, RAYO_COMPONENT(prompt)->client_jid, iq);
/* done */
PROMPT_COMPONENT(prompt)->iq = NULL;
RAYO_RELEASE(prompt);
RAYO_DESTROY(prompt);
break;
case PCS_START_INPUT:
PROMPT_COMPONENT(prompt)->state = PCS_DONE;
iks_delete(PROMPT_COMPONENT(prompt)->iq);
if (iks_find(error, "item-not-found")) {
/* call is gone (hangup) */
rayo_component_send_complete(RAYO_COMPONENT(prompt), COMPONENT_COMPLETE_HANGUP);
} else {
/* send presence error to client */
rayo_component_send_complete(RAYO_COMPONENT(prompt), COMPONENT_COMPLETE_ERROR);
}
break;
case PCS_START_INPUT_OUTPUT:
PROMPT_COMPONENT(prompt)->state = PCS_DONE_STOP_OUTPUT;
/* forward IQ error to client */
iq = PROMPT_COMPONENT(prompt)->iq;
iks_insert_attrib(iq, "from", RAYO_JID(prompt->parent));
iks_insert_attrib(iq, "to", RAYO_COMPONENT(prompt)->client_jid);
iks_insert_attrib(iq, "type", "error");
iks_insert_node(iq, iks_copy_within(error, iks_stack(iq)));
PROMPT_COMPONENT(prompt)->complete = iks_copy(iq);
rayo_component_send_stop(prompt, PROMPT_COMPONENT(prompt)->output_jid);
break;
case PCS_START_OUTPUT:
case PCS_START_OUTPUT_BARGE:
case PCS_INPUT_OUTPUT:
case PCS_STOP_OUTPUT:
case PCS_INPUT:
case PCS_OUTPUT:
case PCS_DONE_STOP_OUTPUT:
case PCS_DONE:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, unexpected start input error event\n", RAYO_JID(prompt));
break;
}
return NULL;
}
/**
* Handle output failure.
*/
static iks *prompt_component_handle_output_error(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
iks *error = iks_find(iq, "error");
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) output error\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_START_OUTPUT:
case PCS_START_OUTPUT_BARGE:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, <output> error: %s\n", RAYO_JID(prompt), iks_string(iks_stack(iq), iq));
PROMPT_COMPONENT(prompt)->state = PCS_DONE;
/* forward IQ error to client */
iq = PROMPT_COMPONENT(prompt)->iq;
iks_insert_attrib(iq, "from", RAYO_JID(prompt->parent));
iks_insert_attrib(iq, "to", RAYO_COMPONENT(prompt)->client_jid);
iks_insert_attrib(iq, "type", "error");
iks_insert_node(iq, iks_copy_within(error, iks_stack(iq)));
RAYO_SEND_REPLY(prompt, RAYO_COMPONENT(prompt)->client_jid, iq);
/* done */
PROMPT_COMPONENT(prompt)->iq = NULL;
RAYO_RELEASE(prompt);
RAYO_DESTROY(prompt);
break;
case PCS_START_INPUT_OUTPUT:
case PCS_START_INPUT_TIMERS:
case PCS_START_INPUT:
case PCS_INPUT_OUTPUT:
case PCS_STOP_OUTPUT:
case PCS_INPUT:
case PCS_OUTPUT:
case PCS_DONE_STOP_OUTPUT:
case PCS_DONE:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, unexpected start output error event\n", RAYO_JID(prompt));
break;
}
return NULL;
}
/**
* Handle barge event
*/
static iks *prompt_component_handle_input_barge(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
iks *presence = msg->payload;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) input barge\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_INPUT_OUTPUT:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, got <start-of-input> from %s: %s\n",
RAYO_JID(prompt), msg->from_jid, iks_string(iks_stack(presence), presence));
PROMPT_COMPONENT(prompt)->state = PCS_STOP_OUTPUT;
rayo_component_send_stop(prompt, PROMPT_COMPONENT(prompt)->output_jid);
break;
case PCS_STOP_OUTPUT:
case PCS_INPUT:
/* don't care */
break;
case PCS_OUTPUT:
case PCS_START_OUTPUT:
case PCS_START_OUTPUT_BARGE:
case PCS_START_INPUT:
case PCS_START_INPUT_OUTPUT:
case PCS_START_INPUT_TIMERS:
case PCS_DONE_STOP_OUTPUT:
case PCS_DONE:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s, unexpected start output error event\n", RAYO_JID(prompt));
break;
}
return NULL;
}
/**
* Handle completion event
*/
static iks *prompt_component_handle_input_complete(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
iks *presence = msg->payload;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) input complete\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_INPUT_OUTPUT:
PROMPT_COMPONENT(prompt)->state = PCS_DONE_STOP_OUTPUT;
presence = iks_copy(presence);
iks_insert_attrib(presence, "from", RAYO_JID(prompt));
iks_insert_attrib(presence, "to", RAYO_COMPONENT(prompt)->client_jid);
PROMPT_COMPONENT(prompt)->complete = presence;
rayo_component_send_stop(prompt, PROMPT_COMPONENT(prompt)->output_jid);
break;
case PCS_STOP_OUTPUT:
PROMPT_COMPONENT(prompt)->state = PCS_DONE_STOP_OUTPUT;
presence = iks_copy(presence);
iks_insert_attrib(presence, "from", RAYO_JID(prompt));
iks_insert_attrib(presence, "to", RAYO_COMPONENT(prompt)->client_jid);
PROMPT_COMPONENT(prompt)->complete = presence;
break;
case PCS_INPUT:
PROMPT_COMPONENT(prompt)->state = PCS_DONE;
/* pass through */
case PCS_DONE:
presence = iks_copy(presence);
iks_insert_attrib(presence, "from", RAYO_JID(prompt));
iks_insert_attrib(presence, "to", RAYO_COMPONENT(prompt)->client_jid);
iks_delete(PROMPT_COMPONENT(prompt)->iq);
rayo_component_send_complete_event(RAYO_COMPONENT(prompt), presence);
break;
case PCS_OUTPUT:
case PCS_START_OUTPUT:
case PCS_START_OUTPUT_BARGE:
case PCS_START_INPUT:
case PCS_START_INPUT_OUTPUT:
case PCS_START_INPUT_TIMERS:
case PCS_DONE_STOP_OUTPUT:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "%s, unexpected start output error event\n", RAYO_JID(prompt));
break;
}
return NULL;
}
/**
* Forward result
*/
static iks *prompt_component_handle_result(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
/* forward all results, except for internal ones... */
const char *id = iks_find_attrib_soft(iq, "id");
if (strncmp("mod_rayo-prompt", id, 15)) {
iks_insert_attrib(iq, "from", RAYO_JID(prompt));
iks_insert_attrib(iq, "to", RAYO_COMPONENT(prompt)->client_jid);
RAYO_SEND_REPLY_DUP(prompt, RAYO_COMPONENT(prompt)->client_jid, iq);
} else if (!zstr(PROMPT_COMPONENT(prompt)->start_timers_request_id) && !strcmp(PROMPT_COMPONENT(prompt)->start_timers_request_id, id)) {
rayo_component_send_input_timers_started_event(RAYO_COMPONENT(prompt));
}
return NULL;
}
/**
* Handle completion event
*/
static iks *prompt_component_handle_output_complete(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) output complete\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_OUTPUT:
PROMPT_COMPONENT(prompt)->state = PCS_START_INPUT;
/* start input with timers enabled and barge events disabled */
start_input(PROMPT_COMPONENT(prompt), 1, 0);
break;
case PCS_START_INPUT_OUTPUT:
/* output finished before input started */
PROMPT_COMPONENT(prompt)->state = PCS_START_INPUT_TIMERS;
break;
case PCS_INPUT_OUTPUT:
PROMPT_COMPONENT(prompt)->state = PCS_INPUT;
start_input_timers(PROMPT_COMPONENT(prompt));
break;
case PCS_STOP_OUTPUT:
PROMPT_COMPONENT(prompt)->state = PCS_INPUT;
start_input_timers(PROMPT_COMPONENT(prompt));
break;
case PCS_DONE_STOP_OUTPUT:
if (PROMPT_COMPONENT(prompt)->complete) {
iks_delete(PROMPT_COMPONENT(prompt)->iq);
rayo_component_send_complete_event(RAYO_COMPONENT(prompt), PROMPT_COMPONENT(prompt)->complete);
}
break;
case PCS_INPUT:
break;
case PCS_START_OUTPUT:
case PCS_START_OUTPUT_BARGE:
/* output most likely failed w/ error */
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s, prompt output finished way too quickly (possible failure), continuing w/ input\n", RAYO_JID(prompt));
/* start input with timers enabled and barge events disabled */
rayo_component_send_start(RAYO_COMPONENT(prompt), PROMPT_COMPONENT(prompt)->iq);
PROMPT_COMPONENT(prompt)->state = PCS_START_INPUT;
start_input(PROMPT_COMPONENT(prompt), 1, 0);
break;
case PCS_START_INPUT:
case PCS_START_INPUT_TIMERS:
case PCS_DONE:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "%s, unexpected start output complete event\n", RAYO_JID(prompt));
break;
}
return NULL;
}
/**
* Start execution of prompt component
*/
static iks *start_call_prompt_component(struct rayo_actor *call, struct rayo_message *msg, void *session_data)
{
iks *iq = msg->payload;
switch_core_session_t *session = (switch_core_session_t *)session_data;
switch_memory_pool_t *pool;
struct prompt_component *prompt_component = NULL;
iks *prompt = iks_find(iq, "prompt");
iks *input;
iks *output;
iks *cmd;
if (!VALIDATE_RAYO_PROMPT(prompt)) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Bad <prompt> attrib\n");
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Bad <prompt> attrib value");
}
output = iks_find(prompt, "output");
if (!output) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Missing <output>\n");
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Missing <output>");
}
input = iks_find(prompt, "input");
if (!input) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Missing <input>\n");
return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Missing <input>");
}
/* create prompt component, linked to call */
switch_core_new_memory_pool(&pool);
prompt_component = switch_core_alloc(pool, sizeof(*prompt_component));
prompt_component = PROMPT_COMPONENT(rayo_component_init(RAYO_COMPONENT(prompt_component), pool, RAT_CALL_COMPONENT, "prompt", NULL, call, iks_find_attrib(iq, "from")));
if (!prompt_component) {
switch_core_destroy_memory_pool(&pool);
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create prompt entity");
}
prompt_component->iq = iks_copy(iq);
/* start output */
if (iks_find_bool_attrib(prompt, "barge-in")) {
prompt_component->state = PCS_START_OUTPUT_BARGE;
} else {
prompt_component->state = PCS_START_OUTPUT;
}
cmd = iks_new("iq");
iks_insert_attrib(cmd, "from", RAYO_JID(prompt_component));
iks_insert_attrib(cmd, "to", RAYO_JID(call));
iks_insert_attrib(cmd, "id", iks_find_attrib(iq, "id"));
iks_insert_attrib(cmd, "type", "set");
output = iks_copy_within(output, iks_stack(cmd));
iks_insert_node(cmd, output);
RAYO_SEND_MESSAGE(prompt_component, RAYO_JID(call), cmd);
return NULL;
}
/**
* Stop execution of prompt component
*/
static iks *stop_call_prompt_component(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
iks *reply = NULL;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) stop prompt\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_OUTPUT:
/* input hasn't started yet */
PROMPT_COMPONENT(prompt)->state = PCS_DONE_STOP_OUTPUT;
PROMPT_COMPONENT(prompt)->complete = rayo_component_create_complete_event(RAYO_COMPONENT(prompt), COMPONENT_COMPLETE_STOP);
rayo_component_send_stop(prompt, PROMPT_COMPONENT(prompt)->output_jid);
break;
case PCS_INPUT_OUTPUT:
case PCS_INPUT:
case PCS_STOP_OUTPUT:
/* stopping input will trigger completion */
rayo_component_send_stop(prompt, PROMPT_COMPONENT(prompt)->input_jid);
break;
case PCS_START_INPUT:
/* stop input as soon as it starts */
PROMPT_COMPONENT(prompt)->state = PCS_DONE;
break;
case PCS_DONE_STOP_OUTPUT:
case PCS_DONE:
/* already done */
break;
case PCS_START_OUTPUT:
case PCS_START_OUTPUT_BARGE:
case PCS_START_INPUT_OUTPUT:
case PCS_START_INPUT_TIMERS:
/* ref hasn't been sent yet */
reply = iks_new_error(iq, STANZA_ERROR_UNEXPECTED_REQUEST);
break;
}
if (!reply) {
reply = iks_new_iq_result(iq);
}
return reply;
}
/**
* Pass output component command
*/
static iks *forward_output_component_request(struct rayo_actor *prompt, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s (%s) %s prompt\n",
RAYO_JID(prompt), prompt_component_state_to_string(PROMPT_COMPONENT(prompt)->state), iks_name(iks_first_tag(iq)));
switch (PROMPT_COMPONENT(prompt)->state) {
case PCS_OUTPUT:
case PCS_START_INPUT_OUTPUT:
case PCS_INPUT_OUTPUT: {
/* forward request to output component */
iks_insert_attrib(iq, "from", RAYO_JID(prompt));
iks_insert_attrib(iq, "to", PROMPT_COMPONENT(prompt)->output_jid);
RAYO_SEND_MESSAGE_DUP(prompt, PROMPT_COMPONENT(prompt)->output_jid, iq);
return NULL;
}
case PCS_START_INPUT_TIMERS:
case PCS_START_OUTPUT:
case PCS_START_OUTPUT_BARGE:
/* ref hasn't been sent yet */
return iks_new_error_detailed(iq, STANZA_ERROR_UNEXPECTED_REQUEST, "too soon");
break;
case PCS_START_INPUT:
case PCS_STOP_OUTPUT:
case PCS_DONE_STOP_OUTPUT:
case PCS_INPUT:
case PCS_DONE:
return iks_new_error_detailed(iq, STANZA_ERROR_UNEXPECTED_REQUEST, "output is finished");
}
return NULL;
}
/**
* Initialize prompt component
* @param module_interface
* @param pool memory pool to allocate from
* @param config_file to use
* @return SWITCH_STATUS_SUCCESS if successful
*/
switch_status_t rayo_prompt_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
{
/* Prompt is a convenience component that wraps <input> and <output> */
rayo_actor_command_handler_add(RAT_CALL, "", "set:"RAYO_PROMPT_NS":prompt", start_call_prompt_component);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "set:"RAYO_EXT_NS":stop", stop_call_prompt_component);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "result:"RAYO_NS":ref", prompt_component_handle_io_start);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "result::", prompt_component_handle_result);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "error:"RAYO_OUTPUT_NS":output", prompt_component_handle_output_error);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "error:"RAYO_INPUT_NS":input", prompt_component_handle_input_error);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "error:"RAYO_INPUT_NS":start-timers", prompt_component_handle_input_start_timers_error);
rayo_actor_event_handler_add(RAT_CALL_COMPONENT, "input", RAT_CALL_COMPONENT, "prompt", ":"RAYO_INPUT_NS":start-of-input", prompt_component_handle_input_barge);
rayo_actor_event_handler_add(RAT_CALL_COMPONENT, "input", RAT_CALL_COMPONENT, "prompt", "unavailable:"RAYO_EXT_NS":complete", prompt_component_handle_input_complete);
rayo_actor_event_handler_add(RAT_CALL_COMPONENT, "output", RAT_CALL_COMPONENT, "prompt", "unavailable:"RAYO_EXT_NS":complete", prompt_component_handle_output_complete);
/* wrap output commands */
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "set:"RAYO_OUTPUT_NS":pause", forward_output_component_request);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "set:"RAYO_OUTPUT_NS":resume", forward_output_component_request);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "set:"RAYO_OUTPUT_NS":speed-up", forward_output_component_request);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "set:"RAYO_OUTPUT_NS":speed-down", forward_output_component_request);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "set:"RAYO_OUTPUT_NS":volume-up", forward_output_component_request);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "set:"RAYO_OUTPUT_NS":volume-down", forward_output_component_request);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "prompt", "set:"RAYO_OUTPUT_NS":seek", forward_output_component_request);
return SWITCH_STATUS_SUCCESS;
}
/**
* Shutdown prompt component
* @return SWITCH_STATUS_SUCCESS if successful
*/
switch_status_t rayo_prompt_component_shutdown(void)
{
return SWITCH_STATUS_SUCCESS;
}
/* 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 noet
*/
@@ -1,549 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2014, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* record_component.c -- Rayo record component implementation
*
*/
#include "rayo_components.h"
#include "rayo_elements.h"
/* TODO timeouts / durations are affected by pause/resume */
/**
* settings
*/
static struct {
const char *record_file_prefix;
const char *record_file_format;
} globals;
/**
* A record component
*/
struct record_component {
/** component base class */
struct rayo_component base;
/** maximum duration allowed */
int max_duration;
/** timeout for total silence */
int initial_timeout;
/** timeout for silence after initial utterance */
int final_timeout;
/** duplex/send/recv */
const char *direction;
/** true if mixed (mono) */
int mix;
/** true if start beep to be played */
int start_beep;
/** true if stop beep to be played */
int stop_beep;
/** time recording started */
switch_time_t start_time;
/** duration of this recording */
int duration_ms;
/** path on local filesystem */
char *local_file_path;
/** true if recording was stopped */
int stop;
};
#define RECORD_COMPONENT(x) ((struct record_component *)x)
/* 1000 Hz beep for 250ms */
#define RECORD_BEEP "tone_stream://%(250,0,1000)"
#define RECORD_COMPLETE_MAX_DURATION "max-duration", RAYO_RECORD_COMPLETE_NS
#define RECORD_COMPLETE_INITIAL_TIMEOUT "initial-timeout", RAYO_RECORD_COMPLETE_NS
#define RECORD_COMPLETE_FINAL_TIMEOUT "final-timeout", RAYO_RECORD_COMPLETE_NS
/**
* Notify completion of record component
*/
static void complete_record(struct rayo_component *component, const char *reason, const char *reason_namespace)
{
switch_core_session_t *session = NULL;
const char *uuid = RAYO_ACTOR(component)->parent->id;
const char *uri = RECORD_COMPONENT(component)->local_file_path;
iks *recording;
switch_size_t file_size = 0;
/* TODO this doesn't work with HTTP, improve core RECORD_STOP event so that file size and duration is reported */
#if 0
switch_file_t *file;
if (switch_file_open(&file, uri, SWITCH_FOPEN_READ, SWITCH_FPROT_UREAD, RAYO_POOL(component)) == SWITCH_STATUS_SUCCESS) {
file_size = switch_file_get_size(file);
switch_file_close(file);
} else {
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_INFO, "Failed to open %s.\n", uri);
}
#endif
switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Recording %s done.\n", uri);
if (RECORD_COMPONENT(component)->stop_beep && (session = switch_core_session_locate(uuid))) {
switch_ivr_displace_session(session, RECORD_BEEP, 0, "");
switch_core_session_rwunlock(session);
}
/* send complete event to client */
recording = iks_new("recording");
iks_insert_attrib(recording, "xmlns", RAYO_RECORD_COMPLETE_NS);
if (strlen(uri) > strlen(SWITCH_PATH_SEPARATOR) && !strncmp(uri, SWITCH_PATH_SEPARATOR, strlen(SWITCH_PATH_SEPARATOR))) {
/* convert absolute path to file:// URI */
iks_insert_attrib_printf(recording, "uri", "file://%s", uri);
} else {
/* is already a URI (hopefully) */
iks_insert_attrib(recording, "uri", uri);
}
iks_insert_attrib_printf(recording, "duration", "%i", RECORD_COMPONENT(component)->duration_ms);
iks_insert_attrib_printf(recording, "size", "%"SWITCH_SIZE_T_FMT, file_size);
rayo_component_send_complete_with_metadata(component, reason, reason_namespace, recording, 1);
iks_delete(recording);
}
/**
* Handle RECORD_STOP event from FreeSWITCH.
* @param event received from FreeSWITCH core. It will be destroyed by the core after this function returns.
*/
static void on_call_record_stop_event(switch_event_t *event)
{
const char *file_path = switch_event_get_header(event, "Record-File-Path");
struct rayo_component *component = RAYO_COMPONENT_LOCATE(file_path);
if (component) {
const char *completion_cause = switch_event_get_header(event, "Record-Completion-Cause");
completion_cause = zstr(completion_cause) ? "" : completion_cause;
RECORD_COMPONENT(component)->duration_ms += (switch_micro_time_now() - RECORD_COMPONENT(component)->start_time) / 1000;
if (RECORD_COMPONENT(component)->stop) {
complete_record(component, COMPONENT_COMPLETE_STOP);
} else if (!strcmp(completion_cause, "no-input-timeout")) {
complete_record(component, RECORD_COMPLETE_INITIAL_TIMEOUT);
} else if (!strcmp(completion_cause, "success-maxtime")) {
complete_record(component, RECORD_COMPLETE_MAX_DURATION);
} else {
/* assume final timeout */
complete_record(component, RECORD_COMPLETE_FINAL_TIMEOUT);
}
RAYO_RELEASE(component);
}
}
/**
* Create a record component
*/
static struct rayo_component *record_component_create(struct rayo_actor *actor, const char *type, const char *client_jid, iks *record)
{
switch_memory_pool_t *pool;
struct record_component *record_component = NULL;
char *local_file_path;
char *fs_file_path;
switch_bool_t start_paused;
start_paused = iks_find_bool_attrib(record, "start-paused");
/* create record filename from session UUID and ref */
/* for example: prefix/1234-1234-1234-1234-30.wav */
local_file_path = switch_mprintf("%s%s-%i.%s",
globals.record_file_prefix,
actor->id, rayo_actor_seq_next(actor), iks_find_attrib(record, "format"));
fs_file_path = switch_mprintf("{pause=%s}fileman://%s",
start_paused ? "true" : "false",
local_file_path);
switch_core_new_memory_pool(&pool);
record_component = switch_core_alloc(pool, sizeof(*record_component));
record_component = RECORD_COMPONENT(rayo_component_init(RAYO_COMPONENT(record_component), pool, type, "record", fs_file_path, actor, client_jid));
if (record_component) {
record_component->max_duration = iks_find_int_attrib(record, "max-duration");
record_component->initial_timeout = iks_find_int_attrib(record, "initial-timeout");
record_component->final_timeout = iks_find_int_attrib(record, "final-timeout");
record_component->direction = switch_core_strdup(RAYO_POOL(record_component), iks_find_attrib_soft(record, "direction"));
record_component->mix = iks_find_bool_attrib(record, "mix");
record_component->start_beep = iks_find_bool_attrib(record, "start-beep");
record_component->stop_beep = iks_find_bool_attrib(record, "stop-beep");
record_component->start_time = start_paused ? 0 : switch_micro_time_now();
record_component->local_file_path = switch_core_strdup(RAYO_POOL(record_component), local_file_path);
} else {
switch_core_destroy_memory_pool(&pool);
}
switch_safe_free(local_file_path);
switch_safe_free(fs_file_path);
return RAYO_COMPONENT(record_component);
}
/**
* Start recording call
* @param session the session to record
* @param record the record component
*/
static int start_call_record(switch_core_session_t *session, struct rayo_component *component)
{
struct record_component *record_component = RECORD_COMPONENT(component);
switch_channel_t *channel = switch_core_session_get_channel(session);
int max_duration_sec = 0;
switch_channel_set_variable(channel, "RECORD_HANGUP_ON_ERROR", "false");
switch_channel_set_variable(channel, "RECORD_TOGGLE_ON_REPEAT", "");
switch_channel_set_variable(channel, "RECORD_CHECK_BRIDGE", "");
switch_channel_set_variable(channel, "RECORD_MIN_SEC", "0");
switch_channel_set_variable(channel, "RECORD_STEREO", "");
switch_channel_set_variable(channel, "RECORD_READ_ONLY", "");
switch_channel_set_variable(channel, "RECORD_WRITE_ONLY", "");
switch_channel_set_variable(channel, "RECORD_APPEND", "");
switch_channel_set_variable(channel, "RECORD_WRITE_OVER", "true");
switch_channel_set_variable(channel, "RECORD_ANSWER_REQ", "");
switch_channel_set_variable(channel, "RECORD_SILENCE_THRESHOLD", "200");
if (record_component->initial_timeout > 0) {
switch_channel_set_variable_printf(channel, "RECORD_INITIAL_TIMEOUT_MS", "%i", record_component->initial_timeout);
} else {
switch_channel_set_variable(channel, "RECORD_INITIAL_TIMEOUT_MS", "");
}
if (record_component->final_timeout > 0) {
switch_channel_set_variable_printf(channel, "RECORD_FINAL_TIMEOUT_MS", "%i", record_component->final_timeout);
} else {
switch_channel_set_variable(channel, "RECORD_FINAL_TIMEOUT_MS", "");
}
/* allow dialplan override for these variables */
//switch_channel_set_variable(channel, "RECORD_PRE_BUFFER_FRAMES", "");
//switch_channel_set_variable(channel, "record_sample_rate", "");
//switch_channel_set_variable(channel, "enable_file_write_buffering", "");
/* max duration attribute is in milliseconds- convert to seconds */
if (record_component->max_duration > 0) {
max_duration_sec = ceil((double)(record_component->max_duration - record_component->duration_ms) / 1000.0);
}
if (!strcmp(record_component->direction, "duplex")) {
if (!record_component->mix) {
/* STEREO */
switch_channel_set_variable(channel, "RECORD_STEREO", "true");
} /* else MONO (default) */
} else if (!strcmp(record_component->direction, "send")) {
/* record audio sent from the caller */
switch_channel_set_variable(channel, "RECORD_READ_ONLY", "true");
} else if (!strcmp(record_component->direction, "recv")) {
/* record audio received by the caller */
switch_channel_set_variable(channel, "RECORD_WRITE_ONLY", "true");
};
if (record_component->start_beep) {
switch_ivr_displace_session(session, RECORD_BEEP, 0, "");
record_component->start_time = switch_micro_time_now();
}
if (switch_ivr_record_session(session, (char *)RAYO_ID(component), max_duration_sec, NULL) == SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Recording started: file = %s\n", RAYO_ID(component));
return 1;
}
return 0;
}
/**
* Start execution of call record component
*/
static iks *start_call_record_component(struct rayo_actor *call, struct rayo_message *msg, void *session_data)
{
iks *iq = msg->payload;
switch_core_session_t *session = (switch_core_session_t *)session_data;
struct rayo_component *component = NULL;
iks *record = iks_find(iq, "record");
/* validate record attributes */
if (!VALIDATE_RAYO_RECORD(record)) {
return iks_new_error(iq, STANZA_ERROR_BAD_REQUEST);
}
component = record_component_create(call, RAT_CALL_COMPONENT, iks_find_attrib(iq, "from"), record);
if (!component) {
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create record entity");
}
if (start_call_record(session, component)) {
rayo_component_send_start(component, iq);
} else {
RAYO_RELEASE(component);
RAYO_DESTROY(component);
return iks_new_error(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR);
}
return NULL;
}
/**
* Stop execution of record component
*/
static iks *stop_call_record_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
switch_core_session_t *session = switch_core_session_locate(component->parent->id);
if (session) {
RECORD_COMPONENT(component)->stop = 1;
switch_ivr_stop_record_session(session, RAYO_ID(component));
switch_core_session_rwunlock(session);
}
return iks_new_iq_result(iq);
}
/**
* Pause execution of record component
*/
static iks *pause_record_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
struct record_component *record = RECORD_COMPONENT(component);
switch_stream_handle_t stream = { 0 };
char *command = switch_mprintf("%s pause", record->local_file_path);
SWITCH_STANDARD_STREAM(stream);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s pausing\n", RAYO_ID(component));
if (record->start_time) {
record->duration_ms += (switch_micro_time_now() - record->start_time) / 1000;
record->start_time = 0;
}
switch_api_execute("fileman", command, NULL, &stream);
switch_safe_free(stream.data);
switch_safe_free(command);
return iks_new_iq_result(iq);
}
/**
* Resume execution of record component
*/
static iks *resume_record_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
struct record_component *record = RECORD_COMPONENT(component);
switch_stream_handle_t stream = { 0 };
char *command = switch_mprintf("%s resume", record->local_file_path);
SWITCH_STANDARD_STREAM(stream);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "%s resuming\n", RAYO_ID(component));
if (!record->start_time) {
record->start_time = switch_micro_time_now();
}
switch_api_execute("fileman", command, NULL, &stream);
switch_safe_free(stream.data);
switch_safe_free(command);
return iks_new_iq_result(iq);
}
/**
* Handle conference events from FreeSWITCH.
* @param event received from FreeSWITCH core. It will be destroyed by the core after this function returns.
*/
static void on_mixer_record_event(switch_event_t *event)
{
const char *file_path = switch_event_get_header(event, "Path");
const char *action = switch_event_get_header(event, "Action");
struct rayo_component *component = RAYO_COMPONENT_LOCATE(file_path);
if (component) {
struct record_component *record = RECORD_COMPONENT(component);
if (!strcmp("stop-recording", action)) {
record->duration_ms += (switch_micro_time_now() - record->start_time) / 1000;
if (record->stop) {
complete_record(component, COMPONENT_COMPLETE_STOP);
} else {
/* TODO assume final timeout, for now */
complete_record(component, RECORD_COMPLETE_FINAL_TIMEOUT);
}
}
RAYO_RELEASE(component);
}
}
/**
* Start recording mixer
* @param record the record component
*/
static int start_mixer_record(struct rayo_component *component)
{
switch_stream_handle_t stream = { 0 };
char *args;
SWITCH_STANDARD_STREAM(stream);
args = switch_mprintf("%s recording start %s", RAYO_ACTOR(component)->parent->id, RAYO_ID(component));
switch_api_execute("conference", args, NULL, &stream);
switch_safe_free(args);
switch_safe_free(stream.data);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Recording started: file = %s\n", RAYO_ID(component));
return 1;
}
/**
* Start execution of mixer record component
*/
static iks *start_mixer_record_component(struct rayo_actor *mixer, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
struct rayo_component *component = NULL;
iks *record = iks_find(iq, "record");
/* validate record attributes */
if (!VALIDATE_RAYO_RECORD(record)) {
return iks_new_error(iq, STANZA_ERROR_BAD_REQUEST);
}
component = record_component_create(mixer, RAT_MIXER_COMPONENT, iks_find_attrib(iq, "from"), record);
if (!component) {
return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create record entity");
}
/* mixer doesn't allow "send" */
if (!strcmp("send", iks_find_attrib_soft(record, "direction"))) {
RAYO_RELEASE(component);
RAYO_DESTROY(component);
return iks_new_error(iq, STANZA_ERROR_BAD_REQUEST);
}
if (start_mixer_record(component)) {
rayo_component_send_start(component, iq);
} else {
RAYO_RELEASE(component);
RAYO_DESTROY(component);
return iks_new_error(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR);
}
return NULL;
}
/**
* Stop execution of record component
*/
static iks *stop_mixer_record_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
{
iks *iq = msg->payload;
char *args;
switch_stream_handle_t stream = { 0 };
SWITCH_STANDARD_STREAM(stream);
RECORD_COMPONENT(component)->stop = 1;
args = switch_mprintf("%s recording stop %s", component->parent->id, RAYO_ID(component));
switch_api_execute("conference", args, NULL, &stream);
switch_safe_free(args);
switch_safe_free(stream.data);
return iks_new_iq_result(iq);
}
/**
* Process module XML configuration
* @param pool memory pool to allocate from
* @param config_file to use
* @return SWITCH_STATUS_SUCCESS on successful configuration
*/
static switch_status_t do_config(switch_memory_pool_t *pool, const char *config_file)
{
switch_xml_t cfg, xml;
/* set defaults */
globals.record_file_prefix = switch_core_sprintf(pool, "%s%s", SWITCH_GLOBAL_dirs.recordings_dir, SWITCH_PATH_SEPARATOR);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Configuring module\n");
if (!(xml = switch_xml_open_cfg(config_file, &cfg, NULL))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", config_file);
return SWITCH_STATUS_TERM;
}
/* get params */
{
switch_xml_t settings = switch_xml_child(cfg, "record");
if (settings) {
switch_xml_t param;
for (param = switch_xml_child(settings, "param"); param; param = param->next) {
const char *var = switch_xml_attr_soft(param, "name");
const char *val = switch_xml_attr_soft(param, "value");
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "param: %s = %s\n", var, val);
if (!strcasecmp(var, "record-file-prefix")) {
if (!zstr(val)) {
globals.record_file_prefix = switch_core_strdup(pool, val);
}
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported param: %s\n", var);
}
}
}
}
switch_xml_free(xml);
return SWITCH_STATUS_SUCCESS;
}
/**
* Initialize record component
* @param module_interface
* @param pool memory pool to allocate from
* @param config_file to use
* @return SWITCH_STATUS_SUCCESS if successful
*/
switch_status_t rayo_record_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
{
if (do_config(pool, config_file) != SWITCH_STATUS_SUCCESS) {
return SWITCH_STATUS_TERM;
}
switch_event_bind("rayo_record_component", SWITCH_EVENT_RECORD_STOP, NULL, on_call_record_stop_event, NULL);
rayo_actor_command_handler_add(RAT_CALL, "", "set:"RAYO_RECORD_NS":record", start_call_record_component);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "record", "set:"RAYO_RECORD_NS":pause", pause_record_component);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "record", "set:"RAYO_RECORD_NS":resume", resume_record_component);
rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "record", "set:"RAYO_EXT_NS":stop", stop_call_record_component);
switch_event_bind("rayo_record_component", SWITCH_EVENT_CUSTOM, "conference::maintenance", on_mixer_record_event, NULL);
rayo_actor_command_handler_add(RAT_MIXER, "", "set:"RAYO_RECORD_NS":record", start_mixer_record_component);
rayo_actor_command_handler_add(RAT_MIXER_COMPONENT, "record", "set:"RAYO_RECORD_NS":pause", pause_record_component);
rayo_actor_command_handler_add(RAT_MIXER_COMPONENT, "record", "set:"RAYO_RECORD_NS":resume", resume_record_component);
rayo_actor_command_handler_add(RAT_MIXER_COMPONENT, "record", "set:"RAYO_EXT_NS":stop", stop_mixer_record_component);
return SWITCH_STATUS_SUCCESS;
}
/**
* Shutdown record component
* @return SWITCH_STATUS_SUCCESS if successful
*/
switch_status_t rayo_record_component_shutdown(void)
{
switch_event_unbind_callback(on_call_record_stop_event);
switch_event_unbind_callback(on_mixer_record_event);
return SWITCH_STATUS_SUCCESS;
}
/* 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 noet
*/
-80
View File
@@ -1,80 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* sasl.c -- SASL functions
*
*/
#include <switch.h>
#include <iksemel.h>
#include "sasl.h"
/**
* Parse authzid, authcid, and password tokens from base64 PLAIN auth message.
* @param message the base-64 encoded authentication message
* @param authzid the authorization id in the message - free this string when done with parsed message
* @param authcid the authentication id in the message
* @param password the password in the message
*/
void parse_plain_auth_message(const char *message, char **authzid, char **authcid, char **password)
{
char *decoded = iks_base64_decode(message);
int maxlen = strlen(message) * 6 / 8 + 1;
int pos = 0;
*authzid = NULL;
*authcid = NULL;
*password = NULL;
if (decoded == NULL) {
goto end;
}
pos = strlen(decoded) + 1;
if (pos >= maxlen) {
goto end;
}
*authcid = strdup(decoded + pos);
pos += strlen(*authcid) + 1;
if (pos >= maxlen) {
goto end;
}
*password = strdup(decoded + pos);
if (zstr(decoded)) {
*authzid = strdup(*authcid);
} else {
*authzid = strdup(decoded);
}
end:
switch_safe_free(decoded);
}
/* 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 noet
*/
-45
View File
@@ -1,45 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* sasl.h -- SASL
*
*/
#ifndef SASL_H
#define SASL_H
SWITCH_DECLARE(void) parse_plain_auth_message(const char *message, char **authzid, char **authcid, char **password);
#endif
/* 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 noet
*/
File diff suppressed because it is too large Load Diff
-69
View File
@@ -1,69 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* srgs.h -- Transforms SRGS into regex rules
*
*/
#ifndef SRGS_H
#define SRGS_H
#include <switch.h>
struct srgs_parser;
struct srgs_grammar;
enum srgs_match_type {
/** invalid input */
SMT_NO_MATCH,
/** matches, can accept more input */
SMT_MATCH,
/** not yet a match, but valid input so far */
SMT_MATCH_PARTIAL,
/** matches, cannot accept more input */
SMT_MATCH_END
};
SWITCH_DECLARE(int) srgs_init(void);
SWITCH_DECLARE(void) srgs_destroy(void);
SWITCH_DECLARE(struct srgs_parser *) srgs_parser_new(const char *uuid);
SWITCH_DECLARE(struct srgs_grammar *) srgs_parse(struct srgs_parser *parser, const char *document);
SWITCH_DECLARE(const char *) srgs_grammar_to_regex(struct srgs_grammar *grammar);
SWITCH_DECLARE(const char *) srgs_grammar_to_jsgf(struct srgs_grammar *grammar);
SWITCH_DECLARE(const char *) srgs_grammar_to_jsgf_file(struct srgs_grammar *grammar, const char *basedir, const char *ext);
SWITCH_DECLARE(enum srgs_match_type) srgs_grammar_match(struct srgs_grammar *grammar, const char *input, const char **interpretation);
SWITCH_DECLARE(void) srgs_parser_destroy(struct srgs_parser *parser);
#endif
/* 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 noet
*/
@@ -1,216 +0,0 @@
#include <switch.h>
#include <iksemel.h>
#include <test/switch_test.h>
#include <iks_helpers.h>
static const char *voxeo_grammar =
"<iq id='8847' type='set' from='usera@192.168.1.10/voxeo3' to='e7632f74-8c55-11e2-84b0-e538fa88a1ef@192.168.1.10'><input xmlns='urn:xmpp:rayo:input:1' min-confidence='0.3' mode='DTMF' sensitivity='0.5'><grammar content-type='application/grammar+voxeo'><![CDATA[[1 DIGITS]]]></grammar></input></iq>";
static const char *repeating_bracket =
"<iq id='8847' type='set' from='usera@192.168.1.10/voxeo3' to='e7632f74-8c55-11e2-84b0-e538fa88a1ef@192.168.1.10'><input xmlns='urn:xmpp:rayo:input:1' min-confidence='0.3' mode='DTMF' sensitivity='0.5'><grammar content-type='application/grammar+voxeo'><![CDATA[[1 DIGITS]>]]]]]]]]] ]] ]]></grammar></input></iq>";
static const char *normal_cdata =
"<iq id='8847' type='set' from='usera@192.168.1.10/voxeo3' to='e7632f74-8c55-11e2-84b0-e538fa88a1ef@192.168.1.10'><input xmlns='urn:xmpp:rayo:input:1' min-confidence='0.3' mode='DTMF' sensitivity='0.5'><grammar content-type='application/grammar+voxeo'><![CDATA[1 DIGITS]]></grammar></input></iq>";
static const char *empty_cdata =
"<iq id='8847' type='set' from='usera@192.168.1.10/voxeo3' to='e7632f74-8c55-11e2-84b0-e538fa88a1ef@192.168.1.10'><input xmlns='urn:xmpp:rayo:input:1' min-confidence='0.3' mode='DTMF' sensitivity='0.5'><grammar content-type='application/grammar+voxeo'><![CDATA[]]></grammar></input></iq>";
static const char *rayo_test_srgs =
"<grammar xmlns=\"http://www.w3.org/2001/06/grammar\" root=\"MAINRULE\">\n"
" <rule id=\"MAINRULE\">\n"
" <one-of>\n"
" <item>\n"
" <item repeat=\"0-1\"> need a</item>\n"
" <item repeat=\"0-1\"> i need a</item>\n"
" <one-of>\n"
" <item> clue </item>\n"
" </one-of>\n"
" <tag> out.concept = \"clue\";</tag>\n"
" </item>\n"
" <item>\n"
" <item repeat=\"0-1\"> have an</item>\n"
" <item repeat=\"0-1\"> i have an</item>\n"
" <one-of>\n"
" <item> answer </item>\n"
" </one-of>\n"
" <tag> out.concept = \"answer\";</tag>\n"
" </item>\n"
" </one-of>\n"
" </rule>\n"
"</grammar>";
#define MATCH 1
#define NO_MATCH 0
/**
* main program
*/
FST_BEGIN()
FST_SUITE_BEGIN(iks)
FST_SETUP_BEGIN()
{
}
FST_SETUP_END()
FST_TEARDOWN_BEGIN()
{
}
FST_TEARDOWN_END()
FST_TEST_BEGIN(iks_cdata_bug)
{
iks *iq = NULL;
iks *input = NULL;
iksparser *p = iks_dom_new(&iq);
const char *cdata;
fst_check(IKS_OK == iks_parse(p, voxeo_grammar, 0, 1));
iks_parser_delete(p);
fst_check((input = iks_find(iq, "input")));
fst_check((cdata = iks_find_cdata(input, "grammar")));
fst_check_string_equals("[1 DIGITS]", cdata);
iks_delete(iq);
}
FST_TEST_END()
FST_TEST_BEGIN(repeating_bracket)
{
iks *iq = NULL;
iks *input = NULL;
iksparser *p = iks_dom_new(&iq);
const char *cdata;
fst_check(IKS_OK == iks_parse(p, repeating_bracket, 0, 1));
iks_parser_delete(p);
fst_check((input = iks_find(iq, "input")));
fst_check((cdata = iks_find_cdata(input, "grammar")));
fst_check_string_equals("[1 DIGITS]>]]]]]]]]] ]] ", cdata);
iks_delete(iq);
}
FST_TEST_END()
FST_TEST_BEGIN(normal_cdata)
{
iks *iq = NULL;
iks *input = NULL;
iksparser *p = iks_dom_new(&iq);
const char *cdata;
fst_check(IKS_OK == iks_parse(p, normal_cdata, 0, 1));
iks_parser_delete(p);
fst_check((input = iks_find(iq, "input")));
fst_check((cdata = iks_find_cdata(input, "grammar")));
fst_check_string_equals("1 DIGITS", cdata);
iks_delete(iq);
}
FST_TEST_END()
FST_TEST_BEGIN(empty_cdata)
{
iks *iq = NULL;
iks *input = NULL;
iksparser *p = iks_dom_new(&iq);
const char *cdata;
fst_check(IKS_OK == iks_parse(p, empty_cdata, 0, 1));
iks_parser_delete(p);
fst_check((input = iks_find(iq, "input")));
fst_check(NULL == (cdata = iks_find_cdata(input, "grammar")));
iks_delete(iq);
}
FST_TEST_END()
FST_TEST_BEGIN(rayo_test_srgs)
{
iks *grammar = NULL;
iksparser *p = iks_dom_new(&grammar);
fst_check(IKS_OK == iks_parse(p, rayo_test_srgs, 0, 1));
iks_parser_delete(p);
iks_delete(grammar);
}
FST_TEST_END()
FST_TEST_BEGIN(iks_helper_value_matches)
{
fst_check(MATCH == value_matches("1", "1,2,3"));
fst_check(MATCH == value_matches("2", "1,2,3"));
fst_check(MATCH == value_matches("3", "1,2,3"));
fst_check(NO_MATCH == value_matches("4", "1,2,3"));
fst_check(NO_MATCH == value_matches("1,2", "1,2,3"));
fst_check(NO_MATCH == value_matches(NULL, "1,2,3"));
fst_check(NO_MATCH == value_matches(NULL, NULL));
fst_check(NO_MATCH == value_matches("1", NULL));
fst_check(NO_MATCH == value_matches("", "1,2,3"));
fst_check(NO_MATCH == value_matches("", ""));
fst_check(NO_MATCH == value_matches("1", ""));
fst_check(MATCH == value_matches("duplex", "duplex,send,recv"));
fst_check(MATCH == value_matches("send", "duplex,send,recv"));
fst_check(MATCH == value_matches("recv", "duplex,send,recv"));
fst_check(NO_MATCH == value_matches("sendrecv", "duplex,send,recv"));
fst_check(MATCH == value_matches("duplex1", "duplex1,duplex2,duplex3"));
fst_check(MATCH == value_matches("duplex2", "duplex1,duplex2,duplex3"));
fst_check(MATCH == value_matches("duplex3", "duplex1,duplex2,duplex3"));
fst_check(NO_MATCH == value_matches("duplex4", "duplex1,duplex2,duplex3"));
fst_check(NO_MATCH == value_matches("duplex", "duplex1,duplex2,duplex3"));
}
FST_TEST_END()
FST_TEST_BEGIN(dialback_key)
{
char *dialback_key;
dialback_key = iks_server_dialback_key("s3cr3tf0rd14lb4ck", "xmpp.example.com", "example.org", "D60000229F");
fst_check_string_equals("37c69b1cf07a3f67c04a5ef5902fa5114f2c76fe4a2686482ba5b89323075643", dialback_key);
switch_safe_free(dialback_key);
fst_check(NULL == (dialback_key = iks_server_dialback_key("", "xmpp.example.com", "example.org", "D60000229F")));
switch_safe_free(dialback_key);
fst_check(NULL == (dialback_key = iks_server_dialback_key("s3cr3tf0rd14lb4ck", "", "example.org", "D60000229F")));
switch_safe_free(dialback_key);
fst_check(NULL == (dialback_key = iks_server_dialback_key("s3cr3tf0rd14lb4ck", "xmpp.example.com", "", "D60000229F")));
switch_safe_free(dialback_key);
fst_check(NULL == (dialback_key = iks_server_dialback_key("s3cr3tf0rd14lb4ck", "xmpp.example.com", "example.org", "")));
switch_safe_free(dialback_key);
fst_check(NULL == (dialback_key = iks_server_dialback_key(NULL, "xmpp.example.com", "example.org", "D60000229F")));
switch_safe_free(dialback_key);
fst_check(NULL == (dialback_key = iks_server_dialback_key("s3cr3tf0rd14lb4ck", NULL, "example.org", "D60000229F")));
switch_safe_free(dialback_key);
fst_check(NULL == (dialback_key = iks_server_dialback_key("s3cr3tf0rd14lb4ck", "xmpp.example.com", NULL, "D60000229F")));
switch_safe_free(dialback_key);
fst_check(NULL == (dialback_key = iks_server_dialback_key("s3cr3tf0rd14lb4ck", "xmpp.example.com", "example.org", NULL)));
switch_safe_free(dialback_key);
}
FST_TEST_END()
FST_TEST_BEGIN(validate_dtmf)
{
fst_check(SWITCH_TRUE == iks_attrib_is_dtmf_digit("1"));
fst_check(SWITCH_TRUE == iks_attrib_is_dtmf_digit("A"));
fst_check(SWITCH_TRUE == iks_attrib_is_dtmf_digit("a"));
fst_check(SWITCH_TRUE == iks_attrib_is_dtmf_digit("D"));
fst_check(SWITCH_TRUE == iks_attrib_is_dtmf_digit("d"));
fst_check(SWITCH_TRUE == iks_attrib_is_dtmf_digit("*"));
fst_check(SWITCH_TRUE == iks_attrib_is_dtmf_digit("#"));
fst_check(SWITCH_FALSE == iks_attrib_is_dtmf_digit("E"));
fst_check(SWITCH_FALSE == iks_attrib_is_dtmf_digit(NULL));
fst_check(SWITCH_FALSE == iks_attrib_is_dtmf_digit(""));
fst_check(SWITCH_FALSE == iks_attrib_is_dtmf_digit("11"));
fst_check(SWITCH_TRUE == validate_optional_attrib(iks_attrib_is_dtmf_digit, "A"));
fst_check(SWITCH_TRUE == validate_optional_attrib(iks_attrib_is_dtmf_digit, "1"));
fst_check(SWITCH_FALSE == validate_optional_attrib(iks_attrib_is_dtmf_digit, "Z"));
fst_check(SWITCH_FALSE == validate_optional_attrib(iks_attrib_is_dtmf_digit, "11"));
fst_check(SWITCH_TRUE == validate_optional_attrib(iks_attrib_is_dtmf_digit, NULL));
fst_check(SWITCH_TRUE == validate_optional_attrib(iks_attrib_is_dtmf_digit, ""));
}
FST_TEST_END()
FST_SUITE_END()
FST_END()
@@ -1,343 +0,0 @@
#include <switch.h>
#include <test/switch_test.h>
#include <nlsml.h>
static const char *nlsml_good =
"<result x-model=\"http://theYesNoModel\""
" xmlns:xf=\"http://www.w3.org/2000/xforms\""
" grammar=\"http://theYesNoGrammar\">"
"<interpretation>"
"<xf:instance>"
"<myApp:yes_no>"
"<response>yes</response>"
"</myApp:yes_no>"
"</xf:instance>"
"<input>ok</input>"
"</interpretation>"
"</result>";
static const char *nlsml_bad =
"<result grammar=\"http://grammar\" x-model=\"http://dataModel\"\n"
"xmlns:xf=\"http://www.w3.org/2000/xforms\"\n"
" <interpretation/>\n"
"</result>\n";
static const char *nlsml_match_with_model_instance =
"<result grammar=\"http://grammar\" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\"\n"
" <interpretation confidence=\"75\" grammar=\"http://grammar\"\n"
" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\n"
"\n"
" <model>\n"
" <xf:group name=\"nameAddress\">\n"
" <string name=\"name\"/>\n"
" <string name=\"street\"/>\n"
" <string name=\"city\"/>\n"
" <string name=\"state\"/>\n"
" <string name=\"zip\">\n"
" <mask>ddddd</mask>\n"
" </string>\n"
" </xf:group>\n"
" </model>\n"
"\n"
" <xf:instance name=\"nameAddress\">\n"
" <nameAddress>\n"
" <street confidence=\"75\">123 Maple Street</street>\n"
" <city>Mill Valley</city>\n"
" <state>CA</state>\n"
" <zip>90952</zip>\n"
" </nameAddress>\n"
" </xf:instance>\n"
" <input>\n"
" My address is 123 Maple Street,\n"
" Mill Valley, California, 90952\n"
" </input>n"
" </interpretation>\n"
"</result>\n";
static const char *nlsml_multi_input =
"<result grammar=\"http://grammar\" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\"\n"
" <interpretation confidence=\"75\" grammar=\"http://grammar\"\n"
" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\n"
"\n"
" <input>\n"
" <input mode=\"speech\" confidence=\"50\"\n"
" timestamp-start=\"2000-04-03T0:00:00\"\n"
" timestamp-end=\"2000-04-03T0:00:00.2\">fried</input>\n"
" <input mode=\"speech\" confidence=\"100\"\n"
" timestamp-start=\"2000-04-03T0:00:00.25\"\n"
" timestamp-end=\"2000-04-03T0:00:00.6\">onions</input>\n"
" </input>\n"
" </interpretation>\n"
"</result>\n";
static const char *nlsml_no_input =
"<result grammar=\"http://grammar\" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\"\n"
" <interpretation confidence=\"100\" grammar=\"http://grammar\"\n"
" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\n"
"\n"
" <input>\n"
" <noinput/>\n"
" </input>\n"
" </interpretation>\n"
"</result>\n";
static const char *nlsml_multi_input_dtmf =
"<result grammar=\"http://grammar\" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\"\n"
" <interpretation confidence=\"100\" grammar=\"http://grammar\"\n"
" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\n"
"\n"
" <input>\n"
" <input mode=\"speech\"><nomatch/></input>\n"
" <input mode=\"dtmf\">1 2 3 4</input>\n"
" </input>\n"
" </interpretation>\n"
"</result>\n";
static const char *nlsml_meta =
"<result grammar=\"http://grammar\" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\n"
"<interpretation grammar=\"http://toppings\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\n"
" <input mode=\"speech\">\n"
" what toppings do you have?\n"
" </input>\n"
" <xf:model>\n"
" <xf:group xf:name=\"question\">\n"
" <xf:string xf:name=\"questioned_item\"/>\n"
" <xf:string xf:name=\"questioned_property\"/>\n"
" </xf:group>\n"
" </xf:model>\n"
" <xf:instance>\n"
" <xf:question>\n"
" <xf:questioned_item>toppings</xf:questioned_item>\n"
" <xf:questioned_property>\n"
" availability\n"
" </xf:questioned_property>\n"
" </xf:question>\n"
" </xf:instance>\n"
"</interpretation>\n"
"</result>\n";
static const char *nlsml_simple_ambiguity =
"<result xmlns:xf=\"http://www.w3.org/2000/xforms\"\n"
" grammar=\"http://flight\">\n"
" <interpretation confidence=\"60\">\n"
" <input mode=\"speech\">\n"
" I want to go to Pittsburgh\n"
" </input>\n"
" <xf:model>\n"
" <group name=\"airline\">\n"
" <string name=\"to_city\"/>\n"
" </group>\n"
" </xf:model>\n"
" <xf:instance>\n"
" <myApp:airline>\n"
" <to_city>Pittsburgh</to_city>\n"
" </myApp:airline>\n"
" </xf:instance>\n"
" </interpretation>\n"
" <interpretation confidence=\"40\">\n"
" <input>I want to go to Stockholm</input>\n"
" <xf:model>\n"
" <group name=\"airline\">\n"
" <string name=\"to_city\"/>\n"
" </group>\n"
" </xf:model>\n"
" <xf:instance>\n"
" <myApp:airline>\n"
" <to_city>Stockholm</to_city>\n"
" </myApp:airline>\n"
" </xf:instance>\n"
" </interpretation>\n"
"</result>\n";
const char *nlsml_mixed_initiative =
"<result xmlns:xf=\"http://www.w3.org/2000/xforms\"\n"
" grammar=\"http://foodorder\">\n"
" <interpretation confidence=\"100\" >\n"
" <xf:model>\n"
" <group name=\"order\">\n"
" <group name=\"food_item\" maxOccurs=\"*\">\n"
" <group name=\"pizza\" >\n"
" <string name=\"ingredients\" maxOccurs=\"*\"/>\n"
" </group>\n"
" <group name=\"burger\">\n"
" <string name=\"ingredients\" maxOccurs=\"*/\">\n"
" </group>\n"
" </group>\n"
" <group name=\"drink_item\" maxOccurs=\"*\">\n"
" <string name=\"size\">\n"
" <string name=\"type\">\n"
" </group>\n"
" <string name=\"delivery_method\"/>\n"
" </group>\n"
" </xf:model>\n"
" <xf:instance>\n"
" <myApp:order>\n"
" <food_item confidence=\"100\">\n"
" <pizza>\n"
" <xf:ingredients confidence=\"100\">\n"
" pepperoni\n"
" </xf:ingredients>\n"
" <xf:ingredients confidence=\"100\">\n"
" cheese\n"
" </xf:ingredients>\n"
" </pizza>\n"
" <pizza>\n"
" <ingredients>sausage</ingredients>\n"
" </pizza>\n"
" </food_item>\n"
" <drink_item confidence=\"100\">\n"
" <size>2-liter</size>\n"
" </drink_item>\n"
" <delivery_method>to go</delivery_method>\n"
" </myApp:order>\n"
" </xf:instance>\n"
" <input mode=\"speech\">I would like 2 pizzas,\n"
" one with pepperoni and cheese, one with sausage\n"
" and a bottle of coke, to go.\n"
" </input>\n"
" </interpretation>\n"
"</result>\n";
static const char *nlsml_no_match =
"<result grammar=\"http://grammar\" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\"\n"
" <interpretation confidence=\"100\" grammar=\"http://grammar\"\n"
" x-model=\"http://dataModel\"\n"
" xmlns:xf=\"http://www.w3.org/2000/xforms\">\n"
"\n"
" <input>\n"
" <input mode=\"speech\"><nomatch/></input>\n"
" <input mode=\"dtmf\"><nomatch/></input>\n"
" </input>\n"
" </interpretation>\n"
"</result>\n";
static const char *nlsml_dtmf_result =
"<result xmlns='http://www.ietf.org/xml/ns/mrcpv2' "
"xmlns:xf='http://www.w3.org/2000/xforms'><interpretation>"
"<input mode='dtmf' confidence='100'>1 2 3 4</input>"
"<instance>1 2 3 4</instance>"
"</interpretation></result>";
static const char *nlsml_good_normalized =
"<result x-model='http://theYesNoModel'"
" xmlns:xf='http://www.w3.org/2000/xforms'"
" grammar='http://theYesNoGrammar'"
" xmlns='http://www.ietf.org/xml/ns/mrcpv2'>"
"<interpretation>"
"<xf:instance>"
"<myApp:yes_no>"
"<response>yes</response>"
"</myApp:yes_no>"
"</xf:instance>"
"<input>ok</input>"
"</interpretation>"
"</result>";
static const char *nlsml_dtmf_instance_result =
"<result xmlns='http://www.ietf.org/xml/ns/mrcpv2' "
"xmlns:xf='http://www.w3.org/2000/xforms'><interpretation>"
"<input mode='dtmf' confidence='100'>1</input>"
"<instance>foo</instance>"
"</interpretation></result>";
FST_BEGIN()
FST_SUITE_BEGIN(nlsml)
FST_SETUP_BEGIN()
{
fst_requires(nlsml_init());
}
FST_SETUP_END()
FST_TEARDOWN_BEGIN()
{
}
FST_TEARDOWN_END()
/**
* Test parsing NLSML example results
*/
FST_TEST_BEGIN(parse_nlsml_examples)
{
fst_check(NMT_MATCH == nlsml_parse(nlsml_good, "1234"));
fst_check(NMT_BAD_XML == nlsml_parse(nlsml_bad, "1234"));
fst_check(NMT_MATCH == nlsml_parse(nlsml_match_with_model_instance, "1234"));
fst_check(NMT_MATCH == nlsml_parse(nlsml_multi_input, "1234"));
fst_check(NMT_NOINPUT == nlsml_parse(nlsml_no_input, "1234"));
fst_check(NMT_MATCH == nlsml_parse(nlsml_multi_input_dtmf, "1234"));
fst_check(NMT_MATCH == nlsml_parse(nlsml_meta, "1234"));
fst_check(NMT_MATCH == nlsml_parse(nlsml_simple_ambiguity, "1234"));
fst_check(NMT_MATCH == nlsml_parse(nlsml_mixed_initiative, "1234"));
fst_check(NMT_NOMATCH == nlsml_parse(nlsml_no_match, "1234"));
}
FST_TEST_END()
/**
* Test creating DTMF match result
*/
FST_TEST_BEGIN(create_dtmf_match)
{
iks *result = nlsml_create_dtmf_match("1234", NULL);
char *result_str;
fst_requires(result);
result_str = iks_string(NULL, result);
fst_check_string_equals(nlsml_dtmf_result, result_str);
iks_free(result_str);
iks_delete(result);
}
FST_TEST_END()
/**
* Test creating DTMF match result with instance interpretation
*/
FST_TEST_BEGIN(create_dtmf_instance)
{
iks *result = nlsml_create_dtmf_match("1", "foo");
char *result_str;
fst_requires(result);
result_str = iks_string(NULL, result);
fst_check_string_equals(nlsml_dtmf_instance_result, result_str);
iks_free(result_str);
iks_delete(result);
}
FST_TEST_END()
/**
* Test NLSML normalization
*/
FST_TEST_BEGIN(normalize)
{
iks *result = nlsml_normalize(nlsml_good);
char *result_str;
fst_requires(result);
result_str = iks_string(NULL, result);
fst_check_string_equals(nlsml_good_normalized, result_str);
iks_free(result_str);
iks_delete(result);
}
FST_TEST_END()
FST_SUITE_END()
FST_END()
File diff suppressed because it is too large Load Diff
@@ -1,20 +0,0 @@
XMPP_ERROR(STANZA_ERROR_BAD_REQUEST, "bad-request", "modify")
XMPP_ERROR(STANZA_ERROR_CONFLICT, "conflict", "cancel")
XMPP_ERROR(STANZA_ERROR_FEATURE_NOT_IMPLEMENTED, "feature-not-implemented", "modify")
XMPP_ERROR(STANZA_ERROR_FORBIDDEN, "forbidden", "auth")
XMPP_ERROR(STANZA_ERROR_GONE, "gone", "modify")
XMPP_ERROR(STANZA_ERROR_INTERNAL_SERVER_ERROR, "internal-server-error", "wait")
XMPP_ERROR(STANZA_ERROR_ITEM_NOT_FOUND, "item-not-found", "cancel")
XMPP_ERROR(STANZA_ERROR_JID_MALFORMED, "jid-malformed", "modify")
XMPP_ERROR(STANZA_ERROR_NOT_ACCEPTABLE, "not-acceptable", "modify")
XMPP_ERROR(STANZA_ERROR_NOT_ALLOWED, "not-allowed", "cancel")
XMPP_ERROR(STANZA_ERROR_NOT_AUTHORIZED, "not-authorized", "auth")
XMPP_ERROR(STANZA_ERROR_RECIPIENT_UNAVAILABLE, "recipient-unavailable", "wait")
XMPP_ERROR(STANZA_ERROR_REDIRECT, "redirect", "modify")
XMPP_ERROR(STANZA_ERROR_REGISTRATION_REQUIRED, "registration-required", "auth")
XMPP_ERROR(STANZA_ERROR_REMOTE_SERVER_NOT_FOUND, "remote-server-not-found", "cancel")
XMPP_ERROR(STANZA_ERROR_REMOTE_SERVER_TIMEOUT, "remote-server-timeout", "wait")
XMPP_ERROR(STANZA_ERROR_RESOURCE_CONSTRAINT, "resource-constraint", "wait")
XMPP_ERROR(STANZA_ERROR_SERVICE_UNAVAILABLE, "service-unavailable", "cancel")
XMPP_ERROR(STANZA_ERROR_UNDEFINED_CONDITION, "undefined-condition", "wait")
XMPP_ERROR(STANZA_ERROR_UNEXPECTED_REQUEST, "unexpected-request", "wait")
File diff suppressed because it is too large Load Diff
@@ -1,68 +0,0 @@
/*
* mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2013-2018, Grasshopper
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is Grasshopper
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Rienzo <chris.rienzo@grasshopper.com>
*
* xmpp_streams.h -- XMPP in/out s2s and in c2s streams
*
*/
#ifndef XMPP_STREAMS_H
#define XMPP_STREAMS_H
struct xmpp_stream;
struct xmpp_stream_context;
typedef int (* xmpp_stream_bind_callback)(struct xmpp_stream *stream);
typedef int (* xmpp_stream_ready_callback)(struct xmpp_stream *stream);
typedef void (* xmpp_stream_recv_callback)(struct xmpp_stream *stream, iks *stanza);
typedef void (* xmpp_stream_destroy_callback)(struct xmpp_stream *stream);
SWITCH_DECLARE(struct xmpp_stream_context *) xmpp_stream_context_create(const char *domain, const char *domain_secret, xmpp_stream_bind_callback bind_cb, xmpp_stream_ready_callback ready, xmpp_stream_recv_callback recv, xmpp_stream_destroy_callback destroy);
SWITCH_DECLARE(void) xmpp_stream_context_add_cert(struct xmpp_stream_context *context, const char *cert_pem_file);
SWITCH_DECLARE(void) xmpp_stream_context_add_key(struct xmpp_stream_context *context, const char *key_pem_file);
SWITCH_DECLARE(void) xmpp_stream_context_add_user(struct xmpp_stream_context *context, const char *user, const char *password);
SWITCH_DECLARE(void) xmpp_stream_context_dump(struct xmpp_stream_context *context, switch_stream_handle_t *stream);
SWITCH_DECLARE(void) xmpp_stream_context_destroy(struct xmpp_stream_context *context);
SWITCH_DECLARE(void) xmpp_stream_context_send(struct xmpp_stream_context *context, const char *jid, iks *stanza);
SWITCH_DECLARE(switch_status_t) xmpp_stream_context_listen(struct xmpp_stream_context *context, const char *addr, int port, int is_s2s, const char *acl);
SWITCH_DECLARE(switch_status_t) xmpp_stream_context_connect(struct xmpp_stream_context *context, const char *peer_domain, const char *peer_address, int peer_port);
SWITCH_DECLARE(int) xmpp_stream_is_s2s(struct xmpp_stream *stream);
SWITCH_DECLARE(int) xmpp_stream_is_incoming(struct xmpp_stream *stream);
SWITCH_DECLARE(const char *) xmpp_stream_get_jid(struct xmpp_stream *stream);
SWITCH_DECLARE(void) xmpp_stream_set_private(struct xmpp_stream *stream, void *user_private);
SWITCH_DECLARE(void *) xmpp_stream_get_private(struct xmpp_stream *stream);
#endif
/* 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 noet
*/
-24
View File
@@ -1,24 +0,0 @@
include $(top_srcdir)/build/modmake.rulesam
MODNAME=mod_ssml
IKS_DIR=$(switch_srcdir)/libs/iksemel
IKS_BUILDDIR=$(switch_builddir)/libs/iksemel
IKS_LA=$(IKS_BUILDDIR)/src/libiksemel.la
noinst_LTLIBRARIES = libssmlmod.la
libssmlmod_la_SOURCES = mod_ssml.c
libssmlmod_la_CFLAGS = $(AM_CFLAGS) -I$(IKS_DIR)/include
mod_LTLIBRARIES = mod_ssml.la
mod_ssml_la_SOURCES = mod_ssml.c
mod_ssml_la_CFLAGS = $(AM_CFLAGS) -I$(IKS_DIR)/include
mod_ssml_la_LIBADD = $(switch_builddir)/libfreeswitch.la $(IKS_LA)
mod_ssml_la_LDFLAGS = -avoid-version -module -no-undefined -shared
BUILT_SOURCES=$(IKS_LA)
$(IKS_LA): $(IKS_BUILDDIR) $(IKS_DIR) $(IKS_DIR)/.update
@cd $(IKS_DIR) && $(MAKE)
@$(TOUCH_TARGET)
deps: $(IKS_LA)
@@ -1,29 +0,0 @@
<configuration name="ssml.conf" description="SSML parser config">
<!-- voices in order of preference -->
<tts-voices>
<voice name="slt" language="en-US" gender="female" prefix="tts://flite|slt|"/>
<voice name="kal" language="en-US" gender="male" prefix="tts://flite|kal|"/>
<voice name="rms" language="en-US" gender="male" prefix="tts://flite|rms|"/>
<voice name="awb" language="en-US" gender="male" prefix="tts://flite|awb|"/>
</tts-voices>
<!-- maps ISO language to say module -->
<language-map>
<language iso="en-US" say-module="en" language="en"/>
</language-map>
<!-- say voices in order of preference -->
<say-voices>
<voice name="callie" language="en-US" gender="female" prefix="$${sounds_dir}/en/us/callie/"/>
</say-voices>
<!-- map interpret-as to say macros -->
<macros>
<macro name="ordinal" type="number" method="counted"/>
<macro name="cardinal" type="number" method="pronounced"/>
<macro name="characters" type="name_spelled" method="pronounced"/>
<macro name="telephone" type="telephone_number" method="pronounced"/>
</macros>
</configuration>
File diff suppressed because it is too large Load Diff