mirror of
https://github.com/signalwire/freeswitch.git
synced 2026-07-24 05:02:10 +00:00
[mod_rayo, mod_ssml, iksemel] Remove from tree
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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
@@ -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
|
||||
*/
|
||||
@@ -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
Reference in New Issue
Block a user