]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
FS-11555: [mod_signalwire] Initial commit.
authorAndrey Volk <andrey@signalwire.com>
Fri, 7 Dec 2018 10:37:01 +0000 (14:37 +0400)
committerAndrey Volk <andywolk@gmail.com>
Fri, 21 Dec 2018 17:45:59 +0000 (21:45 +0400)
22 files changed:
Freeswitch.2017.sln
build/modules.conf.in
build/modules.conf.most
conf/insideout/autoload_configs/modules.conf.xml
conf/minimal/autoload_configs/modules.conf.xml
conf/rayo/autoload_configs/modules.conf.xml
conf/sbc/autoload_configs/modules.conf.xml
conf/softphone/freeswitch.xml
conf/vanilla/autoload_configs/modules.conf.xml
configure.ac
debian/control-modules
freeswitch.spec
libs/.gitignore
src/mod/applications/mod_signalwire/Makefile.am [new file with mode: 0644]
src/mod/applications/mod_signalwire/conf/autoload_configs/signalwire.conf.xml [new file with mode: 0644]
src/mod/applications/mod_signalwire/mod_signalwire.2017.vcxproj [new file with mode: 0644]
src/mod/applications/mod_signalwire/mod_signalwire.c [new file with mode: 0644]
w32/Setup/Setup.2017.wixproj
w32/libks-version.props [new file with mode: 0644]
w32/libks.props [new file with mode: 0644]
w32/signalwire-client-c-version.props [new file with mode: 0644]
w32/signalwire-client-c.props [new file with mode: 0644]

index 1962a9b283ad8a4a3814e106050c9abbeb0ce6d1..be107fde3e0af332ff22725dcf91c8d31ba94bd9 100644 (file)
@@ -576,6 +576,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "48khz", "libs\win32\Sound_F
 EndProject\r
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "48khz music", "libs\win32\Sound_Files\48khzmusic.2017.vcxproj", "{EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31}"\r
 EndProject\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mod_signalwire", "src\mod\applications\mod_signalwire\mod_signalwire.2017.vcxproj", "{B19AE6FC-BFFF-428D-B483-3BBEAECCC618}"\r
+EndProject\r
 Global\r
        GlobalSection(SolutionConfigurationPlatforms) = preSolution\r
                All|Win32 = All|Win32\r
@@ -2627,6 +2629,18 @@ Global
                {EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31}.Debug|x64.Build.0 = Debug|x64\r
                {EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31}.Release|Win32.ActiveCfg = Release|Win32\r
                {EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31}.Release|x64.ActiveCfg = Release|x64\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.All|Win32.ActiveCfg = Release|Win32\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.All|Win32.Build.0 = Release|Win32\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.All|x64.ActiveCfg = Release|x64\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.All|x64.Build.0 = Release|x64\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Debug|Win32.ActiveCfg = Debug|Win32\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Debug|Win32.Build.0 = Debug|Win32\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Debug|x64.ActiveCfg = Debug|x64\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Debug|x64.Build.0 = Debug|x64\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Release|Win32.ActiveCfg = Release|Win32\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Release|Win32.Build.0 = Release|Win32\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Release|x64.ActiveCfg = Release|x64\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618}.Release|x64.Build.0 = Release|x64\r
        EndGlobalSection\r
        GlobalSection(SolutionProperties) = preSolution\r
                HideSolutionNode = FALSE\r
@@ -2836,6 +2850,7 @@ Global
                {EF62B845-A0CE-44FD-B8E6-475FE87D06C3} = {9388C266-C3FC-468A-92EF-0CBC35941412}\r
                {8154C82D-58EE-4145-9DEC-A445A5AA3D6B} = {4F227C26-768F-46A3-8684-1D08A46FB374}\r
                {EBD0B6B4-C5CA-46B0-BBC7-DBA71DF05D31} = {4F227C26-768F-46A3-8684-1D08A46FB374}\r
+               {B19AE6FC-BFFF-428D-B483-3BBEAECCC618} = {E72B5BCB-6462-4D23-B419-3AF1A4AC3D78}\r
        EndGlobalSection\r
        GlobalSection(ExtensibilityGlobals) = postSolution\r
                SolutionGuid = {09840DE7-9208-45AA-9667-1A71EE93BD1E}\r
index d998e5dba83dfdc6681989e264ea0614a8cd4d0f..a3297e744c3eebc181da0c82a089c323f7afd200 100644 (file)
@@ -39,6 +39,7 @@ applications/mod_httapi
 #applications/mod_rad_auth
 #applications/mod_redis
 #applications/mod_rss
+applications/mod_signalwire
 applications/mod_sms
 #applications/mod_sms_flowroute
 #applications/mod_snapshot
index b442b966e6d70495687177028e6305ad3bc7de7d..a4946004d2e4546a2322070825aace21a71f4512 100644 (file)
@@ -38,6 +38,7 @@ applications/mod_prefix
 #applications/mod_rad_auth
 applications/mod_redis
 applications/mod_rss
+applications/mod_signalwire
 applications/mod_sms
 applications/mod_snapshot
 applications/mod_snom
index 3e2b09cdcbcb0406023d0949c50cdbc3bdddd3d0..507850348972e39c4824ea32aa781d3401f93590 100644 (file)
@@ -37,6 +37,7 @@
     <!-- <load module="mod_freetdm"/> -->
 
     <!-- Applications -->
+    <!--load module="mod_signalwire"/-->
     <load module="mod_commands"/>
     <load module="mod_conference"/>
     <load module="mod_dptools"/>
index 37ece9d202534107e2837699f693ff9ce2e6d043..a57544f2fe0db6478b5cd3a42c159ed8c567481e 100644 (file)
@@ -17,6 +17,7 @@
     <load module="mod_loopback"/>
 
     <!-- Applications -->
+    <!--load module="mod_signalwire"/-->
     <load module="mod_commands"/>
     <load module="mod_conference"/>
     <load module="mod_db"/>
index f6c58680bc80809d26a6178d43caa3b83547b597..ee44e06c57bd29b8c3b2cd69296a4aebfc65dc49 100644 (file)
@@ -14,6 +14,7 @@
     <load module="mod_loopback"/>
 
     <!-- Applications -->
+    <!--load module="mod_signalwire"-->
     <load module="mod_commands"/>
     <load module="mod_conference"/>
     <load module="mod_dptools"/>
index 579b1d33bebb33f1aef68e4f1eab91551a723bc6..31f46a41283c2d3500ceaed37db9801ab88db9ce 100644 (file)
@@ -26,6 +26,7 @@
     <!-- <load module="mod_woomera"/> -->
 
     <!-- Applications -->
+    <!--load module="mod_signalwire"/-->
     <load module="mod_commands"/>
     <load module="mod_dptools"/>
     <load module="mod_expr"/>
index 771efd9544dd5a6497264a81f0afeb2834538dae..5fbc0cbd39c926eab6edb844e04f2948533c417d 100644 (file)
@@ -89,6 +89,7 @@
        <load module="mod_sndfile"/>
        <load module="mod_tone_stream"/>
        <load module="mod_local_stream"/>
+       <!--load module="mod_signalwire"/-->
       </modules>
     </configuration>
 
index 2c465d1231c732a70abf8cf23ddb768fd9c55e43..0b336a168db08d898bf59bb2d4a6e82403fa0f76 100644 (file)
@@ -50,6 +50,7 @@
     <load module="mod_verto"/>
 
     <!-- Applications -->
+    <load module="mod_signalwire"/>
     <load module="mod_commands"/>
     <load module="mod_conference"/>
     <!-- <load module="mod_curl"/> -->
index 1144a5faac3d0e9225deada9a96e04a9847d3c03..4045e72f62674b47a7b02ac629de5a3efaa60052 100644 (file)
@@ -1498,6 +1498,14 @@ PKG_CHECK_MODULES([V8FS_STATIC], [v8-6.1_static >= 6.1.298],[
   ])
 ])
 
+PKG_CHECK_MODULES([KS], [libks >= 1.1.0],[
+  AM_CONDITIONAL([HAVE_KS],[true])],[
+  AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_KS],[false])])
+
+PKG_CHECK_MODULES([SIGNALWIRE_CLIENT], [signalwire_client >= 1.0.0],[
+  AM_CONDITIONAL([HAVE_SIGNALWIRE_CLIENT],[true])],[
+  AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_SIGNALWIRE_CLIENT],[false])])
+
 PKG_CHECK_MODULES([AMQP], [librabbitmq >= 0.5.2],[
   AM_CONDITIONAL([HAVE_AMQP],[true])],[
   AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_AMQP],[false])])
@@ -1853,6 +1861,7 @@ AC_CONFIG_FILES([Makefile
                src/mod/applications/mod_redis/Makefile
                src/mod/applications/mod_rss/Makefile
                src/mod/applications/mod_skel/Makefile
+               src/mod/applications/mod_signalwire/Makefile
                src/mod/applications/mod_sms/Makefile
                src/mod/applications/mod_sms_flowroute/Makefile
                src/mod/applications/mod_snapshot/Makefile
index b19e12e57525cabe331a2cb7c7c665ebd99527ae..d27ebbbc37a7669a0137f9f97aac9a8780ff0507 100644 (file)
@@ -206,6 +206,11 @@ Module: applications/mod_skel
 Description: Adds mod_skel
  Adds mod_skel.
 
+Module: applications/mod_signalwire
+Description: mod_signalwire
+ Adds mod_signalwire.
+Build-Depends: libks, signalwire-client-c
+
 Module: applications/mod_sms
 Description: Astract SMS
  This module provides an abstract facility for interfacing with SMS
index 0cc0462323331d8418546ca785af7e6490874eba..f0fee3ca72ab4bff9a79a6e8161ed8815650b2b3 100644 (file)
@@ -3,7 +3,7 @@
 # spec file for package freeswitch
 #
 # includes module(s): freeswitch-devel freeswitch-codec-passthru-amr freeswitch-codec-passthru-amrwb freeswitch-codec-passthru-g729 
-#                     freeswitch-codec-passthru-g7231 freeswitch-lua freeswitch-perl freeswitch-python freeswitch-v8
+#                     freeswitch-codec-passthru-g7231 freeswitch-lua freeswitch-perl freeswitch-python freeswitch-v8 freeswitch-signalwire
 #                     freeswitch-lan-de freeswitch-lang-en freeswitch-lang-fr freeswitch-lang-hu freeswitch-lang-ru freeswitch-freetdm
 #                    and others
 #
@@ -536,6 +536,14 @@ Requires:       %{name} = %{version}-%{release}
 Provides FreeSWITCH mod_rss, edisrse and read an XML based RSS feed, then read
 the entries aloud via a TTS engine
 
+%package application-signalwire
+Summary:       FreeSWITCH mod_signalwire
+Group:          System/Libraries
+Requires:       %{name} = %{version}-%{release}
+
+%description application-signalwire
+Provides FreeSWITCH mod_signalwire
+
 %package application-sms
 Summary:       FreeSWITCH mod_sms
 Group:          System/Libraries
@@ -1373,6 +1381,7 @@ Requires: freeswitch-application-memcache
 Requires:      freeswitch-application-nibblebill
 Requires:      freeswitch-application-redis
 Requires:      freeswitch-application-rss
+Requires:      freeswitch-application-signalwire
 Requires:      freeswitch-application-sms
 Requires:      freeswitch-application-snapshot
 Requires:      freeswitch-application-snom
@@ -1458,7 +1467,7 @@ APPLICATION_MODULES_FR="applications/mod_fifo applications/mod_fsk applications/
                        applications/mod_memcache applications/mod_mongo applications/mod_nibblebill applications/mod_rad_auth \
                        applications/mod_redis applications/mod_rss "
 
-APPLICATION_MODULES_SZ="applications/mod_sms applications/mod_snapshot applications/mod_snom applications/mod_soundtouch \
+APPLICATION_MODULES_SZ="applications/mod_signalwire applications/mod_sms applications/mod_snapshot applications/mod_snom applications/mod_soundtouch \
                        applications/mod_spandsp applications/mod_spy applications/mod_stress \
                        applications/mod_valet_parking applications/mod_translate applications/mod_voicemail \
                        applications/mod_voicemail_ivr applications/mod_video_filter"
@@ -2115,6 +2124,9 @@ fi
 %files application-rss
 %{MODINSTDIR}/mod_rss.so*
 
+%files application-signalwire
+%{MODINSTDIR}/mod_signalwire.so*
+
 %files application-sms
 %{MODINSTDIR}/mod_sms.so*
 
@@ -2541,6 +2553,8 @@ fi
 #
 ######################################################################################################################
 %changelog
+* Tue Dec 11 2018 - Andrey Volk
+- add mod_signalwire
 * Sun Mar 13 2016 - Matthew Vale
 - add perl and python ESL language module packages
 * Thu Jul 09 2015 - Artur ZaprzaÅ‚a
index 8555f1a4f12f865282ea15fe65995159c4a95afa..10b50dda73d7b6bb4da2b1c32b5823119ec5129d 100644 (file)
@@ -858,3 +858,8 @@ libsndfile-*/
 libsndfile-*
 opencv-*/
 opencv-*
+libks-*/
+libks*
+signalwire-client-c-*/
+signalwire-client-c-*
+
diff --git a/src/mod/applications/mod_signalwire/Makefile.am b/src/mod/applications/mod_signalwire/Makefile.am
new file mode 100644 (file)
index 0000000..d46251b
--- /dev/null
@@ -0,0 +1,27 @@
+include $(top_srcdir)/build/modmake.rulesam
+MODNAME=mod_signalwire
+
+if HAVE_KS
+if HAVE_SIGNALWIRE_CLIENT
+
+mod_LTLIBRARIES = mod_signalwire.la
+mod_signalwire_la_SOURCES  = mod_signalwire.c
+mod_signalwire_la_CFLAGS   = $(AM_CFLAGS)
+mod_signalwire_la_CFLAGS  += $(KS_CFLAGS) $(SIGNALWIRE_CLIENT_CFLAGS)
+mod_signalwire_la_LIBADD   = $(switch_builddir)/libfreeswitch.la
+mod_signalwire_la_LDFLAGS  = -avoid-version -module -no-undefined -shared $(KS_LIBS) $(SIGNALWIRE_CLIENT_LIBS)
+
+else
+install: error
+all: error
+error:
+       $(error You must install signalwire-client-c to build mod_signalwire)
+endif
+
+else
+install: error
+all: error
+error:
+       $(error You must install libks to build mod_signalwire)
+endif
+
diff --git a/src/mod/applications/mod_signalwire/conf/autoload_configs/signalwire.conf.xml b/src/mod/applications/mod_signalwire/conf/autoload_configs/signalwire.conf.xml
new file mode 100644 (file)
index 0000000..c62ad72
--- /dev/null
@@ -0,0 +1,10 @@
+<configuration name="signalwire.conf" description="SignalWire">
+  <settings>
+    <!-- on/off/file-path -->
+    <!--param name="kslog" value="on"/-->
+    <!--param name="blade-bootstrap" value="blade://switchblade:2100"/-->
+    <!--param name="adoption-service" value="https://adopt.signalwire.com/adoption"/-->
+    <!--param name="stun-server" value="stun.freeswitch.org"/-->
+    <!-- <authentication></authentication> -->
+  </settings>
+</configuration>
diff --git a/src/mod/applications/mod_signalwire/mod_signalwire.2017.vcxproj b/src/mod/applications/mod_signalwire/mod_signalwire.2017.vcxproj
new file mode 100644 (file)
index 0000000..192c82d
--- /dev/null
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ItemGroup Label="ProjectConfigurations">\r
+    <ProjectConfiguration Include="Debug|Win32">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Debug|x64">\r
+      <Configuration>Debug</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|Win32">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>Win32</Platform>\r
+    </ProjectConfiguration>\r
+    <ProjectConfiguration Include="Release|x64">\r
+      <Configuration>Release</Configuration>\r
+      <Platform>x64</Platform>\r
+    </ProjectConfiguration>\r
+  </ItemGroup>\r
+  <PropertyGroup Label="Globals">\r
+    <ProjectName>mod_signalwire</ProjectName>\r
+    <RootNamespace>mod_signalwire</RootNamespace>\r
+    <Keyword>Win32Proj</Keyword>\r
+    <ProjectGuid>{B19AE6FC-BFFF-428D-B483-3BBEAECCC618}</ProjectGuid>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v141</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v141</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v141</PlatformToolset>\r
+  </PropertyGroup>\r
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">\r
+    <ConfigurationType>DynamicLibrary</ConfigurationType>\r
+    <CharacterSet>MultiByte</CharacterSet>\r
+    <PlatformToolset>v141</PlatformToolset>\r
+  </PropertyGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />\r
+  <Import Project="$(SolutionDir)\w32\libks.props" Condition=" '$(libksPropsImported)' == '' " />\r
+  <Import Project="$(SolutionDir)\w32\signalwire-client-c.props" Condition=" '$(signalwire-client-cPropsImported)' == '' " />\r
+  <Import Project="$(SolutionDir)\w32\openssl.props" Condition=" '$(OpensslPropsImported)' == '' " />\r
+  <ImportGroup Label="ExtensionSettings">\r
+  </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="..\..\..\..\w32\module_release.props" />\r
+  </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="..\..\..\..\w32\module_debug.props" />\r
+  </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="..\..\..\..\w32\module_release.props" />\r
+  </ImportGroup>\r
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">\r
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />\r
+    <Import Project="..\..\..\..\w32\module_debug.props" />\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros" />\r
+  <PropertyGroup>\r
+    <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\r
+  </PropertyGroup>\r
+  <ItemDefinitionGroup>\r
+    <ClCompile>\r
+      <AdditionalIncludeDirectories>.;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+    </ClCompile>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">\r
+    <ClCompile>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+    </ClCompile>\r
+    <Link>\r
+      <RandomizedBaseAddress>false</RandomizedBaseAddress>\r
+      <DataExecutionPrevention>\r
+      </DataExecutionPrevention>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">\r
+    <Midl>\r
+      <TargetEnvironment>X64</TargetEnvironment>\r
+    </Midl>\r
+    <ClCompile>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PreprocessorDefinitions>_DEBUG;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+    </ClCompile>\r
+    <Link>\r
+      <RandomizedBaseAddress>false</RandomizedBaseAddress>\r
+      <DataExecutionPrevention>\r
+      </DataExecutionPrevention>\r
+      <TargetMachine>MachineX64</TargetMachine>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">\r
+    <ClCompile>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+    </ClCompile>\r
+    <Link>\r
+      <RandomizedBaseAddress>false</RandomizedBaseAddress>\r
+      <DataExecutionPrevention>\r
+      </DataExecutionPrevention>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">\r
+    <Midl>\r
+      <TargetEnvironment>X64</TargetEnvironment>\r
+    </Midl>\r
+    <ClCompile>\r
+      <PrecompiledHeader>\r
+      </PrecompiledHeader>\r
+      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+    </ClCompile>\r
+    <Link>\r
+      <RandomizedBaseAddress>false</RandomizedBaseAddress>\r
+      <DataExecutionPrevention>\r
+      </DataExecutionPrevention>\r
+      <TargetMachine>MachineX64</TargetMachine>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+  <ItemGroup>\r
+    <ClCompile Include="mod_signalwire.c" />\r
+  </ItemGroup>\r
+  <ItemGroup>\r
+    <ProjectReference Include="..\..\..\..\w32\Library\FreeSwitchCore.2017.vcxproj">\r
+      <Project>{202d7a4e-760d-4d0e-afa1-d7459ced30ff}</Project>\r
+      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>\r
+    </ProjectReference>\r
+  </ItemGroup>\r
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />\r
+  <ImportGroup Label="ExtensionTargets">\r
+  </ImportGroup>\r
+</Project>\r
diff --git a/src/mod/applications/mod_signalwire/mod_signalwire.c b/src/mod/applications/mod_signalwire/mod_signalwire.c
new file mode 100644 (file)
index 0000000..5fd2fe8
--- /dev/null
@@ -0,0 +1,1308 @@
+/*
+ * mod_signalwire.c -- SignalWire module
+ *
+ * Copyright (c) 2018 SignalWire, Inc
+ *
+ * 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.
+ */
+
+#include <switch.h>
+#include <switch_curl.h>
+#include <switch_stun.h>
+#include <signalwire-client-c/client.h>
+
+#ifndef WIN32
+#include <sys/utsname.h>
+#endif
+
+#define SW_KS_JSON_PRINT(_h, _j) do { \
+               char *_json = ks_json_print(_j); \
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ALERT, "--- %s ---\n%s\n---\n", _h, _json); \
+               ks_json_free(&_json); \
+       } while (0)
+
+static int debug_level = 7;
+
+static int signalwire_gateway_exists(void);
+
+/* Prototypes */
+SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_signalwire_shutdown);
+SWITCH_MODULE_LOAD_FUNCTION(mod_signalwire_load);
+SWITCH_MODULE_RUNTIME_FUNCTION(mod_signalwire_runtime);
+
+/* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime)
+ * Defines a switch_loadable_module_function_table_t and a static const char[] modname
+ */
+SWITCH_MODULE_DEFINITION(mod_signalwire, mod_signalwire_load, mod_signalwire_shutdown, mod_signalwire_runtime);
+
+typedef enum {
+       SW_STATE_ADOPTION,
+       SW_STATE_OFFLINE,
+       SW_STATE_ONLINE,
+       SW_STATE_CONFIGURE,
+       SW_STATE_START_PROFILE,
+       SW_STATE_REGISTER,
+       SW_STATE_READY,
+} sw_state_t;
+
+static struct {
+       int ssl_verify;
+       ks_bool_t shutdown;
+       ks_bool_t restarting;
+       swclt_config_t *config;
+       char blade_bootstrap[1024];
+       char adoption_service[1024];
+       char stun_server[1024];
+       char adoption_token[64];
+       ks_size_t adoption_backoff;
+       ks_time_t adoption_next;
+
+       char adoption_data_local_ip[256];
+       char adoption_data_external_ip[256];
+       char adoption_data_uname[1024];
+
+       char relay_connector_id[256];
+       
+       swclt_sess_t signalwire_session;
+       swclt_hmon_t signalwire_session_monitor;
+       sw_state_t state;
+       ks_bool_t profile_update;
+       ks_bool_t profile_reload;
+       ks_bool_t signalwire_reconnected;
+       switch_xml_t signalwire_profile;
+       char signalwire_profile_md5[SWITCH_MD5_DIGEST_STRING_SIZE];
+
+       ks_bool_t kslog_on;
+
+       switch_mutex_t *mutex; // general mutex for this mod
+       char gateway_ip[80];
+       char gateway_port[10];
+} globals;
+
+static void mod_signalwire_kslogger(const char *file, const char *func, int line, int level, const char *fmt, ...)
+{
+       const char *fp;
+       va_list ap;
+       char buf[32768];
+
+       if (level > debug_level) return;
+
+       fp = switch_cut_path(file);
+
+       va_start(ap, fmt);
+
+       vsnprintf(buf, sizeof(buf) - 1, fmt, ap);
+       buf[sizeof(buf) - 1] = '\0';
+
+       va_end(ap);
+       
+       switch_log_printf(SWITCH_CHANNEL_ID_LOG, fp, func, line, NULL, level, "%s\n", buf);
+}
+
+static switch_status_t switch_find_available_port(switch_port_t *port, const char *ip, int type)
+{
+       switch_status_t ret = SWITCH_STATUS_SUCCESS;
+       switch_memory_pool_t *pool = NULL;
+       switch_sockaddr_t *addr = NULL;
+       switch_socket_t *sock = NULL;
+       switch_bool_t found = SWITCH_FALSE;
+
+       if ((ret = switch_core_new_memory_pool(&pool)) != SWITCH_STATUS_SUCCESS) {
+               goto done;
+       }
+       
+       while (!found) {
+               if ((ret = switch_sockaddr_info_get(&addr, ip, SWITCH_UNSPEC, *port, 0, pool)) != SWITCH_STATUS_SUCCESS) {
+                       goto done;
+               }
+       
+               if ((ret = switch_socket_create(&sock, switch_sockaddr_get_family(addr), type, 0, pool)) != SWITCH_STATUS_SUCCESS) {
+                       goto done;
+               }
+
+               if (!(found = (switch_socket_bind(sock, addr) == SWITCH_STATUS_SUCCESS))) {
+                       *port = *port + 1;
+               }
+               
+               switch_socket_close(sock);
+       }
+
+done:
+       if (pool) switch_core_destroy_memory_pool(&pool);
+
+       return ret;
+}
+
+struct response_data {
+       char *data;
+       size_t size;
+};
+
+static size_t response_data_handler(void *contents, size_t size, size_t nmemb, void *userp)
+{
+       size_t received = size * nmemb;
+       struct response_data *rd = (struct response_data *)userp;
+
+       if (!rd->data) rd->data = ks_pool_alloc(NULL, received + 1);
+       else rd->data = ks_pool_resize(rd->data, rd->size + received + 1);
+
+       memcpy(rd->data + rd->size, contents, received);
+       rd->size += received;
+       rd->data[rd->size] = 0;
+
+       return received;
+}
+
+static void save_sip_config(const char *config)
+{
+       char confpath[1024];
+       FILE *fp = NULL;
+
+       switch_snprintf(confpath, sizeof(confpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat");
+       fp = fopen(confpath, "w");
+       if (!fp) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire SIP configuration\n", confpath);
+               return;
+       }
+
+       fputs(config, fp);
+       fclose(fp);
+}
+
+static void load_sip_config(void)
+{
+       char confpath[1024];
+       char data[32767] = { 0 };
+       FILE *fp = NULL;
+
+       switch_snprintf(confpath, sizeof(confpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat");
+       if (!(fp = fopen(confpath, "r"))) return;
+
+       if (!fread(data, 1, sizeof(data), fp)) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire SIP configuration from %s\n", confpath);
+       }
+       fclose(fp);
+       if (!zstr_buf(data)) {
+               switch_md5_string(globals.signalwire_profile_md5, (void *) data, strlen(data));
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "saved profile MD5 = \"%s\"\n", globals.signalwire_profile_md5);
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "saved profile = \"%s\"\n", (char *)data);
+               globals.signalwire_profile = switch_xml_parse_str_dynamic((char *)data, SWITCH_TRUE);
+       }
+}
+
+static ks_status_t load_credentials_from_json(ks_json_t *json)
+{
+       ks_status_t status = KS_STATUS_SUCCESS;
+       ks_json_t *authentication = NULL;
+       const char *authentication_str = NULL;
+       const char *bootstrap = NULL;
+       const char *relay_connector_id = NULL;
+
+       if ((bootstrap = ks_json_get_object_cstr(json, "bootstrap")) == NULL) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing bootstrap URL\n");
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+
+       if ((relay_connector_id = ks_json_get_object_cstr(json, "relay_connector_id")) == NULL) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing relay_connector_id\n");
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+
+       if ((authentication = ks_json_get_object_item(json, "authentication")) == NULL) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Unable to connect to SignalWire: missing authentication\n");
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+
+       // update the internal connection target, which is normally assigned in swclt_sess_create()
+       if (swclt_sess_target_set(globals.signalwire_session, bootstrap) != KS_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to connect to SignalWire at %s\n", bootstrap);
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+
+       // update the relay_connector_id passed to profile configuration
+       strncpy(globals.relay_connector_id, relay_connector_id, sizeof(globals.relay_connector_id) - 1);
+       strncpy(globals.blade_bootstrap, bootstrap, sizeof(globals.blade_bootstrap) - 1);
+
+       // got adopted, update the client config authentication
+       authentication_str = ks_json_pprint_unformatted(NULL, authentication);
+       swclt_config_set_authentication(globals.config, authentication_str);
+
+       ks_pool_free(&authentication_str);
+done:
+
+       return status;
+}
+
+static ks_status_t mod_signalwire_adoption_post(void)
+{
+       ks_status_t status = KS_STATUS_SUCCESS;
+       switch_memory_pool_t *pool = NULL;
+       switch_CURL *curl = NULL;
+       switch_curl_slist_t *headers = NULL;
+       char url[1024];
+       char errbuf[CURL_ERROR_SIZE];
+       CURLcode res;
+       long rescode;
+       ks_json_t *json = ks_json_create_object();
+       struct response_data rd = { 0 };
+       char *jsonstr = NULL;
+
+       // Determine and cache adoption data values that are heavier to figure out
+       if (!globals.adoption_data_local_ip[0]) {
+               switch_find_local_ip(globals.adoption_data_local_ip, sizeof(globals.adoption_data_local_ip), NULL, AF_INET);
+       }
+
+       if (!globals.adoption_data_external_ip[0]) {
+               switch_port_t local_port = 6050;
+               char *error = NULL;
+               char *external_ip;
+               switch_port_t external_port;
+
+               if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "SignalWire adoption failed: could not allocate memory pool\n");
+                       status = KS_STATUS_FAIL;
+                       goto done;
+               }
+               if (switch_find_available_port(&local_port, globals.adoption_data_local_ip, SOCK_STREAM) != SWITCH_STATUS_SUCCESS) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: could not get available local port\n");
+                       status = KS_STATUS_FAIL;
+                       goto done;
+               }
+
+               external_ip = globals.adoption_data_local_ip;
+               external_port = local_port;
+               if (switch_stun_lookup(&external_ip, &external_port, globals.stun_server, SWITCH_STUN_DEFAULT_PORT, &error, pool) != SWITCH_STATUS_SUCCESS) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: stun [%s] lookup error: %s\n", globals.stun_server, error);
+                       status = KS_STATUS_FAIL;
+                       goto done;
+               }
+               strncpy(globals.adoption_data_external_ip, external_ip, sizeof(globals.adoption_data_external_ip));
+       }
+
+       if (!globals.adoption_data_uname[0]) {
+#ifndef WIN32
+               struct utsname buf;
+               if (uname(&buf)) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed: could not get uname\n");
+                       status = KS_STATUS_FAIL;
+                       goto done;
+               }
+               switch_snprintf(globals.adoption_data_uname,
+                                               sizeof(globals.adoption_data_uname),
+                                               "%s %s %s %s %s",
+                                               buf.sysname,
+                                               buf.nodename,
+                                               buf.release,
+                                               buf.version,
+                                               buf.machine);
+#else
+               // @todo set globals.adoption_data_uname from GetVersion Win32API
+#endif
+       }
+
+
+       ks_json_add_string_to_object(json, "client_uuid", globals.adoption_token);
+       ks_json_add_string_to_object(json, "hostname", switch_core_get_hostname());
+       ks_json_add_string_to_object(json, "ip", globals.adoption_data_local_ip);
+       ks_json_add_string_to_object(json, "ext_ip", globals.adoption_data_external_ip);
+       ks_json_add_string_to_object(json, "version", switch_version_full());
+       ks_json_add_string_to_object(json, "uname", globals.adoption_data_uname);
+
+       jsonstr = ks_json_print_unformatted(json);
+       ks_json_delete(&json);
+
+       switch_snprintf(url, sizeof(url), "%s/%s", globals.adoption_service, globals.adoption_token);
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG10, "Checking %s for SignalWire adoption of this FreeSWITCH\n", url);
+
+       curl = switch_curl_easy_init();
+
+       headers = switch_curl_slist_append(headers, "Accept: application/json");
+       headers = switch_curl_slist_append(headers, "Accept-Charset: utf-8");
+       headers = switch_curl_slist_append(headers, "Content-Type: application/json");
+
+       switch_curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
+       switch_curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5);
+
+       if (!strncasecmp(url, "https", 5)) {
+               switch_curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, globals.ssl_verify);
+               switch_curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, globals.ssl_verify);
+       }
+
+       switch_curl_easy_setopt(curl, CURLOPT_URL, url);
+       switch_curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+       switch_curl_easy_setopt(curl, CURLOPT_USERAGENT, "mod_signalwire/1");
+       switch_curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonstr);
+       switch_curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
+       switch_curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&rd);
+       switch_curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, response_data_handler);
+
+       if ((res = switch_curl_easy_perform(curl))) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Curl Result %d, Error: %s\n", res, errbuf);
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+
+       switch_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &rescode);
+
+       if (rescode == 404) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE,
+                                 "Go to https://signalwire.com to set up your Connector now! Enter connection token %s\n", globals.adoption_token);
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+
+       if (rescode != 200) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire adoption failed with HTTP code %ld, %s\n", rescode, rd.data);
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+
+       json = ks_json_parse(rd.data);
+       if (!json) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received bad SignalWire adoption response\n%s\n", rd.data);
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+
+       if ((status = load_credentials_from_json(json)) != KS_STATUS_SUCCESS) {
+               goto done;
+       }
+
+       ks_json_delete(&json);
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire adoption of this FreeSWITCH completed\n");
+
+       // write out the data to save it for reloading in the future
+       {
+               char authpath[1024];
+               FILE *fp = NULL;
+
+               switch_snprintf(authpath, sizeof(authpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat");
+               fp = fopen(authpath, "w");
+               if (!fp) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire creds\n", authpath);
+                       status = KS_STATUS_FAIL;
+                       goto done;
+               }
+
+               fputs(rd.data, fp);
+               fclose(fp);
+       }
+
+       globals.state = SW_STATE_OFFLINE;
+       swclt_sess_connect(globals.signalwire_session);
+
+done:
+       if (rd.data) ks_pool_free(&rd.data);
+       if (jsonstr) ks_json_free_ex((void **)&jsonstr);
+       if (json) ks_json_delete(&json);
+       if (curl) {
+               curl_easy_cleanup(curl);
+               if (headers) curl_slist_free_all(headers);
+       }
+       if (pool) switch_core_destroy_memory_pool(&pool);
+       return status;
+}
+
+#define SIGNALWIRE_SYNTAX "token | adoption | adopted | reload | update | debug <level> | kslog <on|off|logfile e.g. /tmp/ks.log>"
+SWITCH_STANDARD_API(mod_signalwire_api_function)
+{
+       int argc = 0;
+       char *argv[2] = { 0 };
+       char *buf = NULL;
+
+
+       if (!cmd || !(buf = strdup(cmd))) {
+               stream->write_function(stream, "-USAGE: signalwire %s\n", SIGNALWIRE_SYNTAX);
+               return SWITCH_STATUS_SUCCESS;
+       }
+
+    if ((argc = switch_separate_string(buf, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
+               if (!strcmp(argv[0], "token")) {
+                       if (globals.adoption_token[0]) {
+                               stream->write_function(stream,
+                                       "     _____ _                   ___       ___\n"
+                                       "    / ___/(_)___ _____  ____ _/ / |     / (_)_______\n"
+                                       "    \\__ \\/ / __ `/ __ \\/ __ `/ /| | /| / / / ___/ _ \\\n"
+                                       "   ___/ / / /_/ / / / / /_/ / / | |/ |/ / / /  /  __/\n"
+                                       "  /____/_/\\__, /_/ /_/\\__,_/_/  |__/|__/_/_/   \\___/\n"
+                                       "         /____/\n"
+                                       "\n /=====================================================================\\\n"
+                                       "| Connection Token: %s               |\n"
+                                       " \\=====================================================================/\n"
+                                       " Go to https://signalwire.com to set up your Connector now!\n", globals.adoption_token);
+                       } else {
+                               stream->write_function(stream, "-ERR connection token not available\n");
+                       }
+                       goto done;
+               }
+               else if (!strcmp(argv[0], "adoption")) {
+                       if (globals.state == SW_STATE_ADOPTION) {
+                               globals.adoption_next = ks_time_now();
+                               stream->write_function(stream, "+OK\n");
+                       } else {
+                               stream->write_function(stream, "-ERR adoption not currently pending\n");
+                       }
+                       goto done;
+               }
+               else if (!strcmp(argv[0], "adopted")) {
+                       stream->write_function(stream, "+OK %s\n", globals.state == SW_STATE_ADOPTION ? "Not Adopted" : "Adopted");
+                       goto done;
+               }
+               else if (!strcmp(argv[0], "debug")) {
+                       if (argv[1]) {
+                               debug_level = atoi(argv[1]);
+                       }
+
+                       stream->write_function(stream, "+OK debug %d\n", debug_level);
+                       goto done;
+               } else if (!strcmp(argv[0], "kslog")) {
+                       if (argv[1]) {
+                               if (!strcmp(argv[1], "on")) {
+                                       ks_global_set_logger(mod_signalwire_kslogger);
+                               } else if (!strcmp(argv[1], "off")) {
+                                       ks_global_set_logger(NULL);
+                               }
+                       }
+
+                       stream->write_function(stream, "+OK %s\n", argv[1]);
+                       goto done;
+               } else if (!strcmp(argv[0], "reload")) {
+                       globals.profile_reload = KS_TRUE;
+                       stream->write_function(stream, "+OK\n");
+                       goto done;
+               } else if (!strcmp(argv[0], "update")) {
+                       globals.profile_update = KS_TRUE;
+                       stream->write_function(stream, "+OK\n");
+                       goto done;
+               }
+       }
+
+       stream->write_function(stream, "-USAGE: signalwire %s\n", SIGNALWIRE_SYNTAX);
+       
+done:
+       switch_safe_free(buf);
+       
+       return SWITCH_STATUS_SUCCESS;
+}
+
+static void mod_signalwire_session_state_handler(swclt_sess_t sess, swclt_hstate_change_t *state_change_info, const char *cb_data)
+{
+       SWCLT_HSTATE new_state = state_change_info->new_state;
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire Session State Change: %s\n", swclt_hstate_describe_change(state_change_info));
+
+       if (new_state == SWCLT_HSTATE_ONLINE) {
+               // Connected with NEW or RESTORED session
+               globals.signalwire_reconnected = KS_TRUE;
+       } else if (new_state == SWCLT_HSTATE_OFFLINE) {
+               // Disconnected
+       }
+}
+
+static void __on_provisioning_events(swclt_sess_t sess, blade_broadcast_rqu_t *rqu, void *cb_data)
+{
+       if (!strcmp(rqu->event, "update")) {
+               globals.profile_update = KS_TRUE;
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update requested\n");
+       }
+}
+
+static switch_xml_t xml_config_handler(const char *section, const char *tag_name, const char *key_name, const char *key_value, switch_event_t *params,
+                                                                          void *user_data)
+{
+       char *profileName = NULL;
+       char *reconfigValue = NULL;
+       switch_xml_t signalwire_profile_dup = NULL;
+       
+       if (!section || strcmp(section, "configuration")) return NULL;
+       if (!key_name || strcmp(key_name, "name")) return NULL;
+       if (!key_value || strcmp(key_value, "sofia.conf")) return NULL;
+       if (!params) return NULL;
+       profileName = switch_event_get_header(params, "profile");
+       if (!profileName || strcmp(profileName, "signalwire")) return NULL;
+       reconfigValue = switch_event_get_header(params, "reconfig");
+       if (!reconfigValue || strcmp(reconfigValue, "true")) return NULL;
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Received XML lookup for SignalWire SIP profile\n");
+
+       if (globals.signalwire_profile) {
+               signalwire_profile_dup = switch_xml_dup(globals.signalwire_profile);
+       }
+       return signalwire_profile_dup;
+}
+
+static switch_status_t mod_signalwire_load_or_generate_token(void)
+{
+       switch_status_t status = SWITCH_STATUS_SUCCESS;
+       char tokenpath[1024];
+       
+       switch_snprintf(tokenpath, sizeof(tokenpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-token.dat");
+       if (switch_file_exists(tokenpath, NULL) != SWITCH_STATUS_SUCCESS) {
+               // generate first time uuid
+               ks_uuid_t uuid;
+               const char *token;
+               FILE *fp = fopen(tokenpath, "w");
+               if (!fp) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to save SignalWire connection token\n", tokenpath);
+                       status = SWITCH_STATUS_TERM;
+                       goto done;
+               }
+
+               ks_uuid(&uuid);
+               token = ks_uuid_str(NULL, &uuid);
+
+               fputs(token, fp);
+               fclose(fp);
+               
+               strncpy(globals.adoption_token, token, sizeof(globals.adoption_token) - 1);
+               
+               ks_pool_free(&token);
+       } else {
+               char token[64];
+               FILE *fp = fopen(tokenpath, "r");
+               if (!fp) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to open %s to read SignalWire connection token\n", tokenpath);
+                       status = SWITCH_STATUS_TERM;
+                       goto done;
+               }
+               if (!fgets(token, sizeof(token), fp)) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire connection token from %s\n", tokenpath);
+                       fclose(fp);
+                       status = SWITCH_STATUS_TERM;
+                       goto done;
+               }
+               fclose(fp);
+
+               // trim newline markers in case they exist, only want the token
+               for (size_t len = strlen(token); len > 0 && (token[len - 1] == '\r' || token[len - 1] == '\n'); --len) {
+                       token[len - 1] = '\0';
+               }
+
+               strncpy(globals.adoption_token, token, sizeof(globals.adoption_token) - 1);
+       }
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,
+                                         "\n /=====================================================================\\\n"
+                                         "| Connection Token: %s               |\n"
+                                         " \\=====================================================================/\n"
+                                         " Go to https://signalwire.com to set up your Connector now!\n", globals.adoption_token);
+
+done:
+       return status;
+}
+
+static switch_status_t load_config()
+{
+       char *cf = "signalwire.conf";
+       switch_xml_t cfg, xml;
+       const char *data;
+
+       globals.ssl_verify = 1;
+       switch_set_string(globals.blade_bootstrap, "edge.<space>.signalwire.com/api/relay/wss");
+       switch_set_string(globals.adoption_service, "https://adopt.signalwire.com/adoption");
+       switch_set_string(globals.stun_server, "stun.freeswitch.org");
+
+       if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "open of %s failed\n", cf);
+               // don't need the config
+       } else {
+               switch_xml_t settings, param, tmp;
+               if ((settings = switch_xml_child(cfg, "settings"))) {
+                       for (param = switch_xml_child(settings, "param"); param; param = param->next) {
+                               char *var = (char *) switch_xml_attr_soft(param, "name");
+                               char *val = (char *) switch_xml_attr_soft(param, "value");
+
+                               if (!strcasecmp(var, "kslog") && !ks_zstr(val)) {
+                                       if (!strcmp(val, "off")) {
+                                               globals.kslog_on = KS_FALSE;
+                                       } else if (!strcmp(val, "on")) {
+                                               globals.kslog_on = KS_TRUE;
+                                       }
+                               } else if (!strcasecmp(var, "blade-bootstrap") && !ks_zstr(val)) {
+                                       switch_set_string(globals.blade_bootstrap, val);
+                               } else if (!strcasecmp(var, "adoption-service") && !ks_zstr(val)) {
+                                       switch_set_string(globals.adoption_service, val);
+                               } else if (!strcasecmp(var, "stun-server") && !ks_zstr(val)) {
+                                       switch_set_string(globals.stun_server, val);
+                               } else if (!strcasecmp(var, "ssl-verify")) {
+                                       globals.ssl_verify = switch_true(val) ? 1 : 0;
+                               }
+                       }
+                       if ((tmp = switch_xml_child(settings, "authentication"))) {
+                               const char *txt = switch_xml_txt(tmp);
+                               if (!ks_zstr(txt)) {
+                                       swclt_config_set_authentication(globals.config, txt);
+                               }
+                       }
+               }
+               switch_xml_free(xml);
+       }
+
+       if ((data = getenv("SW_BLADE_BOOTSTRAP"))) {
+               switch_set_string(globals.blade_bootstrap, data);
+       }
+
+       if ((data = getenv("SW_ADOPTION_SERVICE"))) {
+               strncpy(globals.adoption_service, data, sizeof(globals.adoption_service));
+       }
+
+       swclt_config_load_from_env(globals.config);
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+static ks_status_t load_credentials(void)
+{
+       ks_status_t status = KS_STATUS_SUCCESS;
+       char authpath[1024];
+       char data[2048];
+       FILE *fp = NULL;
+       ks_json_t *json = NULL;
+       
+       switch_snprintf(authpath, sizeof(authpath), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat");
+       if (!(fp = fopen(authpath, "r"))) goto done;
+
+       if (!fgets(data, sizeof(data), fp)) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to read SignalWire authentication data from %s\n", authpath);
+               fclose(fp);
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+       fclose(fp);
+
+       json = ks_json_parse(data);
+       if (!json) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to parse SignalWire authentication data from %s\n", authpath);
+               status = KS_STATUS_FAIL;
+               goto done;
+       }
+       status = load_credentials_from_json(json);
+       ks_json_delete(&json);
+
+done:
+       return status;
+}
+
+static void mod_signalwire_session_auth_failed_handler(swclt_sess_t sess)
+{
+       char path[1024];
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire authentication failed\n");
+
+       switch_snprintf(path, sizeof(path), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "adoption-auth.dat");
+       unlink(path);
+
+       switch_snprintf(path, sizeof(path), "%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR, "signalwire-conf.dat");
+       unlink(path);
+
+       globals.restarting = KS_TRUE;
+
+       globals.adoption_backoff = 0;
+       globals.adoption_next = 0;
+
+       globals.state = SW_STATE_ADOPTION;
+}
+
+/* Dialplan INTERFACE */
+SWITCH_STANDARD_DIALPLAN(dialplan_hunt)
+{
+       switch_caller_extension_t *extension = NULL;
+       switch_channel_t *channel = switch_core_session_get_channel(session);
+       const char *network_ip = switch_channel_get_variable(channel, "sip_network_ip");
+       const char *network_port = switch_channel_get_variable(channel, "sip_network_port");
+
+       if (!caller_profile) {
+               if (!(caller_profile = switch_channel_get_caller_profile(channel))) {
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Error obtaining caller profile!\n");
+                       goto done;
+               }
+       }
+
+       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Processing %s <%s>->%s in context %s\n",
+                                         caller_profile->caller_id_name, caller_profile->caller_id_number, caller_profile->destination_number, caller_profile->context);
+
+       if ((extension = switch_caller_extension_new(session, "signalwire", caller_profile->destination_number)) == NULL) {
+               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Memory Error!\n");
+               goto done;
+       }
+
+       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "call from %s:%s\n", network_ip, network_port);
+
+       switch_mutex_lock(globals.mutex);
+
+       if (network_ip &&
+               !zstr_buf(globals.gateway_ip) && !strcmp(globals.gateway_ip, network_ip)) {
+                       // good to go
+                       char transfer_to[1024];
+
+                       switch_snprintf(transfer_to, sizeof(transfer_to), "%s %s %s", caller_profile->destination_number, "XML", caller_profile->context);
+                       switch_caller_extension_add_application(session, extension, "transfer", transfer_to);
+       } else {
+               switch_caller_extension_add_application(session, extension, "respond", "500");
+       }
+
+       switch_mutex_unlock(globals.mutex);
+
+done:
+       return extension;
+}
+
+/**
+ * Module load or unload callback from core
+ * @param event the event
+ */
+static void on_module_load_unload(switch_event_t *event)
+{
+       const char *type = switch_event_get_header(event, "type");
+       const char *name = switch_event_get_header(event, "name");
+       if (!zstr(type) && !zstr(name) && !strcmp(type, "endpoint") && !strcmp(name, "sofia")) {
+               globals.profile_reload = KS_TRUE;
+       }
+}
+
+/**
+ * Sofia sofia::gateway_state change callback
+ * @param event the event
+ */
+static void on_sofia_gateway_state(switch_event_t *event)
+{
+       const char *ip = switch_event_get_header(event, "Register-Network-IP");
+       const char *port = switch_event_get_header(event, "Register-Network-Port");
+       const char *state = switch_event_get_header(event, "State");
+       const char *gateway = switch_event_get_header(event, "Gateway");
+
+       if (!ip || !port || !state || !gateway) {
+               return;
+       }
+
+       if (!strcmp(gateway, "signalwire")) {
+               switch_mutex_lock(globals.mutex);
+
+               if (!strcmp(state, "REGED")) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP Gateway registered to %s:%s\n", ip, port);
+                       switch_set_string(globals.gateway_ip, ip);
+                       switch_set_string(globals.gateway_port, port);
+               } else if (!strcmp(state, "NOREG")) {
+                       globals.gateway_ip[0] = '\0';
+                       globals.gateway_port[0] = '\0';
+               }
+
+               switch_mutex_unlock(globals.mutex);
+       }
+}
+
+/* Macro expands to: switch_status_t mod_signalwire_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */
+SWITCH_MODULE_LOAD_FUNCTION(mod_signalwire_load)
+{
+       switch_api_interface_t *api_interface = NULL;
+       switch_dialplan_interface_t *dialplan_interface;
+       const char *kslog_env = NULL;
+       switch_status_t status = SWITCH_STATUS_SUCCESS;
+
+       memset(&globals, 0, sizeof(globals));
+
+       kslog_env = getenv("KSLOG");
+       if (kslog_env && kslog_env[0] && kslog_env[0] != '0') globals.kslog_on = KS_TRUE;
+
+       /* connect my internal structure to the blank pointer passed to me */
+       *module_interface = switch_loadable_module_create_module_interface(pool, modname);
+
+       ks_global_set_logger(mod_signalwire_kslogger);
+
+       SWITCH_ADD_API(api_interface, "signalwire", "SignalWire API", mod_signalwire_api_function, SIGNALWIRE_SYNTAX);
+       switch_console_set_complete("add signalwire debug");
+       switch_console_set_complete("add signalwire debug 1");
+       switch_console_set_complete("add signalwire debug 2");
+       switch_console_set_complete("add signalwire debug 3");
+       switch_console_set_complete("add signalwire debug 4");
+       switch_console_set_complete("add signalwire debug 5");
+       switch_console_set_complete("add signalwire debug 6");
+       switch_console_set_complete("add signalwire debug 7");
+       switch_console_set_complete("add signalwire kslog");
+       switch_console_set_complete("add signalwire kslog on");
+       switch_console_set_complete("add signalwire kslog off");
+       switch_console_set_complete("add signalwire token");
+       switch_console_set_complete("add signalwire adoption");
+       switch_console_set_complete("add signalwire adopted");
+       switch_console_set_complete("add signalwire update");
+       switch_console_set_complete("add signalwire reload");
+
+       switch_xml_bind_search_function(xml_config_handler, SWITCH_XML_SECTION_CONFIG, NULL);
+
+       ks_ssl_init_skip(KS_TRUE);
+
+       swclt_init(KS_LOG_LEVEL_DEBUG);
+
+       if (globals.kslog_on == KS_FALSE) {
+               ks_global_set_logger(NULL);
+       } else {
+               ks_global_set_logger(mod_signalwire_kslogger);
+       }
+       
+       // Configuration
+       swclt_config_create(&globals.config);
+       load_config();
+
+       switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, pool);
+
+       switch_event_bind("mod_signalwire", SWITCH_EVENT_MODULE_LOAD, NULL, on_module_load_unload, NULL);
+       switch_event_bind("mod_signalwire", SWITCH_EVENT_MODULE_UNLOAD, NULL, on_module_load_unload, NULL);
+       switch_event_bind("mod_signalwire", SWITCH_EVENT_CUSTOM, "sofia::gateway_state", on_sofia_gateway_state, NULL);
+
+       SWITCH_ADD_DIALPLAN(dialplan_interface, "signalwire", dialplan_hunt);
+
+       // Load credentials if they exist from a prior adoption
+       load_credentials();
+
+       // SignalWire
+       swclt_sess_create(&globals.signalwire_session,
+                                         globals.blade_bootstrap,
+                                         globals.config);
+       swclt_sess_set_auth_failed_cb(globals.signalwire_session, mod_signalwire_session_auth_failed_handler);
+
+       if (!globals.signalwire_session) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "signalwire_session create error\n");
+               switch_goto_status(SWITCH_STATUS_TERM, err);
+       }
+
+       swclt_hmon_register(&globals.signalwire_session_monitor, globals.signalwire_session, mod_signalwire_session_state_handler, NULL);
+
+       // @todo register nodestore callbacks here if needed
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Welcome to\n"
+       "     _____ _                   ___       ___\n"
+       "    / ___/(_)___ _____  ____ _/ / |     / (_)_______\n"
+       "    \\__ \\/ / __ `/ __ \\/ __ `/ /| | /| / / / ___/ _ \\\n"
+       "   ___/ / / /_/ / / / / /_/ / / | |/ |/ / / /  /  __/\n"
+       "  /____/_/\\__, /_/ /_/\\__,_/_/  |__/|__/_/_/   \\___/\n"
+       "         /____/\n");
+
+       // storage_dir was missing in clean install
+       switch_dir_make_recursive(SWITCH_GLOBAL_dirs.storage_dir, SWITCH_DEFAULT_DIR_PERMS, pool);
+
+       if ((status = mod_signalwire_load_or_generate_token()) != SWITCH_STATUS_SUCCESS) {
+               goto err;
+       }
+
+       if (swclt_sess_has_authentication(globals.signalwire_session)) {
+               // Load cached profile if we already have one.  We'll still connect to SignalWire and
+               // fetch a new profile in the background.
+               load_sip_config();
+               if (globals.signalwire_profile) {
+                       globals.state = SW_STATE_START_PROFILE;
+               } else {
+                       globals.state = SW_STATE_OFFLINE;
+               }
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CONSOLE, "Connecting to SignalWire\n");
+               swclt_sess_connect(globals.signalwire_session);
+       } else {
+               globals.state = SW_STATE_ADOPTION;
+       }
+
+       goto done;
+
+err:
+       if (globals.signalwire_session) ks_handle_destroy(&globals.signalwire_session);
+       swclt_config_destroy(&globals.config);
+       ks_global_set_logger(NULL);
+
+done:
+       
+       return status;
+}
+
+/*
+  Called when the system shuts down
+  Macro expands to: switch_status_t mod_signalwire_shutdown() */
+SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_signalwire_shutdown)
+{
+       /* Cleanup dynamically allocated config settings */
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Disconnecting from SignalWire\n");
+
+       switch_event_unbind_callback(on_module_load_unload);
+       switch_event_unbind_callback(on_sofia_gateway_state);
+
+       // stop things that might try to use blade or kafka while they are shutting down
+       globals.shutdown = KS_TRUE;
+
+       swclt_sess_disconnect(globals.signalwire_session);
+       while (swclt_hstate_current_get(globals.signalwire_session) == SWCLT_HSTATE_ONLINE) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sleeping for pending disconnect\n");
+               ks_sleep_ms(1000);
+       }
+       
+       //signalwire_dialplan_shutdown();
+       // @todo signalwire profile unbinding and unloading
+       switch_xml_unbind_search_function_ptr(xml_config_handler);
+       
+       // kill signalwire, so nothing more can come into the system
+       ks_handle_destroy(&globals.signalwire_session);
+
+       // cleanup config
+       swclt_config_destroy(&globals.config);
+
+       // shutdown libblade (but not libks?)
+       swclt_shutdown();
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
+static void mod_signalwire_state_adoption(void)
+{
+       // keep trying to check adoption token for authentication
+       if (ks_time_now() >= globals.adoption_next) {
+               // Use a very very simple backoff algorithm, every time we try, backoff another minute
+               // so that after first try we wait 1 minute, after next try we wait 2 minutes, at third
+               // try we are waiting 3 minutes, upto a max backoff of 15 minutes between adoption checks
+               if (globals.adoption_backoff < 15) globals.adoption_backoff++;
+               globals.adoption_next = ks_time_now() + (globals.adoption_backoff * 60 * KS_USEC_PER_SEC);
+               if (mod_signalwire_adoption_post() != KS_STATUS_SUCCESS) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Next SignalWire adoption check in %"SWITCH_SIZE_T_FMT" minutes\n", globals.adoption_backoff);
+               }
+       }
+       if (globals.signalwire_reconnected) {
+               // OK to continue as is
+               globals.signalwire_reconnected = KS_FALSE;
+       }
+}
+
+static void mod_signalwire_state_offline(void)
+{
+       if (globals.signalwire_reconnected) {
+               globals.signalwire_reconnected = KS_FALSE;
+               globals.state = SW_STATE_ONLINE;
+       }
+}
+
+static void mod_signalwire_state_online(void)
+{
+       globals.signalwire_reconnected = KS_FALSE;
+       if (!swclt_sess_provisioning_setup(globals.signalwire_session, __on_provisioning_events, NULL)) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connected to SignalWire\n");
+               globals.state = SW_STATE_CONFIGURE;
+       } else {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Failed to connect to SignalWire\n");
+               ks_sleep_ms(4000);
+               globals.state = SW_STATE_OFFLINE;
+               globals.restarting = KS_TRUE;
+       }
+}
+
+static void mod_signalwire_state_configure(void)
+{
+       switch_memory_pool_t *pool = NULL;
+       char local_ip[256];
+       switch_port_t local_port = 6050;
+       char local_endpoint[256];
+       char *external_ip;
+       switch_port_t external_port;
+       char external_endpoint[256];
+       char *error = NULL;
+       swclt_cmd_t cmd;
+
+       if (globals.signalwire_reconnected) {
+               globals.signalwire_reconnected = KS_FALSE;
+               globals.state = SW_STATE_ONLINE;
+       }
+
+       // already restarting/updating...
+       globals.profile_reload = KS_FALSE;
+       globals.profile_update = KS_FALSE;
+
+       if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "SignalWire configure failed: could not allocate memory pool\n");
+               goto done;
+       }
+
+       switch_find_local_ip(local_ip, sizeof(local_ip), NULL, AF_INET);
+
+       if (switch_find_available_port(&local_port, local_ip, SOCK_STREAM) != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire configure failed: could not get available local port\n");
+               ks_sleep_ms(4000);
+               goto done;
+       }
+
+       snprintf(local_endpoint, sizeof(local_endpoint), "%s:%u", local_ip, local_port);
+
+       external_ip = local_ip;
+       external_port = local_port;
+
+       if (switch_stun_lookup(&external_ip, &external_port, globals.stun_server, SWITCH_STUN_DEFAULT_PORT, &error, pool) != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SignalWire configure failed: stun [%s] lookup error: %s\n", globals.stun_server, error);
+               ks_sleep_ms(4000);
+               goto done;
+       }
+
+       snprintf(external_endpoint, sizeof(external_endpoint), "%s:%u", external_ip, external_port);
+
+       if (!swclt_sess_provisioning_configure(globals.signalwire_session, "freeswitch", local_endpoint, external_endpoint, globals.relay_connector_id, &cmd)) {
+               SWCLT_CMD_TYPE cmd_type;
+               swclt_cmd_type(cmd, &cmd_type);
+               if (cmd_type == SWCLT_CMD_TYPE_RESULT) {
+                       const ks_json_t *result;
+                       signalwire_provisioning_configure_response_t *configure_res;
+
+                       swclt_cmd_result(cmd, &result);
+                       result = ks_json_get_object_item(result, "result");
+                       if (!SIGNALWIRE_PROVISIONING_CONFIGURE_RESPONSE_PARSE(ks_handle_pool(cmd), result, &configure_res)) {
+                               const ks_json_t *configuration = configure_res->configuration;
+                               const char *configuration_profile = ks_json_get_object_cstr(configuration, "profile");
+                               if (globals.signalwire_profile) {
+                                       switch_xml_free(globals.signalwire_profile);
+                                       globals.signalwire_profile = NULL;
+                               }
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "\"%s\"\n", configuration_profile);
+                               globals.signalwire_profile = switch_xml_parse_str_dynamic((char *)configuration_profile, SWITCH_TRUE);
+                               if (!globals.signalwire_profile) {
+                                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to parse configuration profile\n");
+                               } else {
+                                       char digest[SWITCH_MD5_DIGEST_STRING_SIZE] = { 0 };
+                                       switch_md5_string(digest, (void *) configuration_profile, strlen(configuration_profile));
+                                       save_sip_config(configuration_profile);
+                                       if (!signalwire_gateway_exists() || zstr_buf(globals.signalwire_profile_md5) || strcmp(globals.signalwire_profile_md5, digest)) {
+                                               // not registered or new profile - update md5 and load it
+                                               strcpy(globals.signalwire_profile_md5, digest);
+                                               globals.state = SW_STATE_START_PROFILE;
+                                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "profile MD5 = \"%s\"\n", globals.signalwire_profile_md5);
+                                       } else {
+                                               // already registered
+                                               globals.state = SW_STATE_READY;
+                                       }
+                               }
+                       }
+               }
+       }
+       ks_handle_destroy(&cmd);
+       if (globals.state == SW_STATE_CONFIGURE) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Failed to receive valid configuration from SignalWire\n");
+               ks_sleep_ms(4000);
+       } else {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Received configuration from SignalWire\n");
+       }
+
+done:
+       if (pool) switch_core_destroy_memory_pool(&pool);
+}
+
+static int signalwire_gateway_exists(void)
+{
+       int exists = 0;
+       switch_stream_handle_t stream = { 0 };
+       SWITCH_STANDARD_STREAM(stream);
+       if (switch_api_execute("sofia", "profile signalwire gwlist", NULL, &stream) == SWITCH_STATUS_SUCCESS && stream.data) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "gwlist = \"%s\"\n", (char *)stream.data);
+               exists = (strstr((char *)stream.data, "Invalid Profile") == NULL) &&
+                       (strstr((char*)stream.data, "signalwire") != NULL);
+       }
+       switch_safe_free(stream.data);
+       return exists;
+}
+
+static int signalwire_profile_is_started(void)
+{
+       int started = 0;
+       switch_stream_handle_t stream = { 0 };
+       SWITCH_STANDARD_STREAM(stream);
+       if (switch_api_execute("sofia", "status profile signalwire", NULL, &stream) == SWITCH_STATUS_SUCCESS && stream.data) {
+               started = (strstr((char *)stream.data, "Invalid Profile") == NULL) &&
+                       (strstr((char *)stream.data, "signalwire") != NULL);
+       }
+       switch_safe_free(stream.data);
+       return started;
+}
+
+static int signalwire_profile_rescan(void)
+{
+       int success = 0;
+       switch_stream_handle_t stream = { 0 };
+       SWITCH_STANDARD_STREAM(stream);
+       if (switch_api_execute("sofia", "profile signalwire rescan", NULL, &stream) == SWITCH_STATUS_SUCCESS) {
+               success = signalwire_profile_is_started();
+       }
+       switch_safe_free(stream.data);
+       return success;
+}
+
+static int signalwire_profile_start(void)
+{
+       int success = 0;
+       switch_stream_handle_t stream = { 0 };
+       SWITCH_STANDARD_STREAM(stream);
+       if (switch_api_execute("sofia", "profile signalwire start", NULL, &stream) == SWITCH_STATUS_SUCCESS) {
+               success = signalwire_profile_is_started();
+       }
+       switch_safe_free(stream.data);
+       return success;
+}
+
+static void signalwire_profile_killgw(void)
+{
+       switch_stream_handle_t stream = { 0 };
+       SWITCH_STANDARD_STREAM(stream);
+       switch_api_execute("sofia", "profile signalwire killgw signalwire", NULL, &stream);
+       switch_safe_free(stream.data);
+}
+
+static void mod_signalwire_state_start_profile(void)
+{
+       if (globals.profile_update) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update initiated\n");
+               globals.state = SW_STATE_CONFIGURE;
+               globals.profile_update = KS_FALSE;
+               return;
+       }
+       globals.profile_reload = KS_FALSE; // already here
+
+       // ignore SignalWire reconnections until register is attempted
+
+       if (switch_loadable_module_exists("mod_sofia") != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for mod_sofia to load\n");
+       } else if (signalwire_profile_is_started()) {
+               // kill gateway if already up and rescan the profile
+               if (signalwire_gateway_exists()) {
+                       signalwire_profile_killgw();
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP gateway killed\n");
+               }
+               if (signalwire_profile_rescan()) {
+                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP profile rescanned\n");
+                       globals.state = SW_STATE_REGISTER;
+               }
+       } else {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting SignalWire SIP profile\n");
+               signalwire_profile_start(); // assume success - it gets checked in next state
+               globals.state = SW_STATE_REGISTER;
+       }
+}
+
+static void mod_signalwire_state_register(void)
+{
+       if (globals.profile_update) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile update initiated\n");
+               globals.state = SW_STATE_CONFIGURE;
+               globals.profile_update = KS_FALSE;
+               return;
+       } else if (globals.profile_reload) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile reload initiated\n");
+               globals.state = SW_STATE_START_PROFILE;
+               globals.profile_reload = KS_FALSE;
+               return;
+       }
+       // ignore SignalWire reconnections until register is attempted
+
+       if (switch_loadable_module_exists("mod_sofia") != SWITCH_STATUS_SUCCESS) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Waiting for mod_sofia to load\n");
+               globals.state = SW_STATE_START_PROFILE;
+       } else if (signalwire_gateway_exists() || signalwire_profile_rescan()) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SignalWire SIP gateway started\n");
+               globals.state = SW_STATE_READY;
+       } else {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Failed to start SignalWire SIP gateway\n");
+               globals.state = SW_STATE_CONFIGURE;
+               ks_sleep_ms(5000);
+       }
+}
+
+static void mod_signalwire_state_ready()
+{
+       if (globals.profile_update) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Signalwire SIP profile update initiated\n");
+               globals.state = SW_STATE_CONFIGURE;
+               globals.profile_update = KS_FALSE;
+       } else if (globals.profile_reload) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "SignalWire SIP profile reload initiated\n");
+               globals.state = SW_STATE_START_PROFILE;
+               globals.profile_reload = KS_FALSE;
+       } else if (globals.signalwire_reconnected) {
+               globals.signalwire_reconnected = KS_FALSE;
+               globals.state = SW_STATE_ONLINE;
+       }
+}
+
+SWITCH_MODULE_RUNTIME_FUNCTION(mod_signalwire_runtime)
+{
+       while (!globals.shutdown) {
+               if (globals.restarting) {
+                       swclt_sess_disconnect(globals.signalwire_session);
+                       while (swclt_hstate_current_get(globals.signalwire_session) == SWCLT_HSTATE_ONLINE) {
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sleeping for pending disconnect\n");
+                               ks_sleep_ms(1000);
+                       }
+
+                       // kill signalwire, so nothing more can come into the system
+                       ks_handle_destroy(&globals.signalwire_session);
+
+                       // Create a new session and start over
+                       swclt_sess_create(&globals.signalwire_session,
+                                         globals.blade_bootstrap,
+                                         globals.config);
+                       swclt_sess_set_auth_failed_cb(globals.signalwire_session, mod_signalwire_session_auth_failed_handler);
+
+                       swclt_hmon_register(&globals.signalwire_session_monitor, globals.signalwire_session, mod_signalwire_session_state_handler, NULL);
+
+                       globals.restarting = KS_FALSE;
+                       continue;
+               }
+
+               switch(globals.state) {
+               case SW_STATE_ADOPTION: // waiting for adoption to occur
+                       mod_signalwire_state_adoption();
+                       break;
+               case SW_STATE_OFFLINE: // waiting for session to go online
+                       mod_signalwire_state_offline();
+                       break;
+               case SW_STATE_ONLINE: // provisioning service setup
+                       mod_signalwire_state_online();
+                       break;
+               case SW_STATE_CONFIGURE: // provisioning configuration
+                       mod_signalwire_state_configure();
+                       break;
+               case SW_STATE_START_PROFILE:
+                       mod_signalwire_state_start_profile();
+                       break;
+               case SW_STATE_REGISTER:
+                       mod_signalwire_state_register();
+                       break;
+               case SW_STATE_READY: // ready for runtime
+                       mod_signalwire_state_ready();
+                       break;
+               default: break;
+               }
+               ks_sleep_ms(1000);
+       }
+
+       return SWITCH_STATUS_TERM;
+}
+
+/* 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
+ */
index 0017c5acef7ac5161531603a5d5f126877f799d9..cb65aa947c8748b7b64df052dc42613fd3501c55 100644 (file)
       <RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
       <RefTargetDir>INSTALLFOLDER</RefTargetDir>
     </ProjectReference>
+    <ProjectReference Include="..\..\src\mod\applications\mod_signalwire\mod_signalwire.2017.vcxproj">
+      <Name>mod_signalwire</Name>
+      <Project>{b19ae6fc-bfff-428d-b483-3bbeaeccc618}</Project>
+      <Private>True</Private>
+      <DoNotHarvest>True</DoNotHarvest>
+      <RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
+      <RefTargetDir>INSTALLFOLDER</RefTargetDir>
+    </ProjectReference>
     <ProjectReference Include="..\..\src\mod\applications\mod_sms\mod_sms.2017.vcxproj">
       <Name>mod_sms</Name>
       <Project>{2469b306-b027-4ff2-8815-c9c1ea2cae79}</Project>
diff --git a/w32/libks-version.props b/w32/libks-version.props
new file mode 100644 (file)
index 0000000..3b9fe4b
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ImportGroup Label="PropertySheets">\r
+    <Import Project="basedir.props" Condition=" '$(BaseDirImported)' == ''"/>\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros">\r
+    <libksVersion>0.1.1</libksVersion>\r
+  </PropertyGroup>\r
+  <PropertyGroup>\r
+    <libksVersionImported>true</libksVersionImported>\r
+  </PropertyGroup>\r
+  <PropertyGroup />\r
+  <ItemDefinitionGroup />\r
+  <ItemGroup>\r
+    <BuildMacro Include="libksVersion">\r
+      <Value>$(libksVersion)</Value>\r
+    </BuildMacro>\r
+  </ItemGroup>\r
+</Project>\r
diff --git a/w32/libks.props b/w32/libks.props
new file mode 100644 (file)
index 0000000..c2e2565
--- /dev/null
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ImportGroup Label="PropertySheets">\r
+    <Import Project="libks-version.props" Condition=" '$(libksVersionImported)' == ''"/>\r
+    <Import Project="downloadpackage.task" Condition=" '$(downloadpackagetask_Imported)' == '' " />\r
+  </ImportGroup>\r
+\r
+  <PropertyGroup>\r
+    <libksPropsImported>true</libksPropsImported>\r
+  </PropertyGroup>\r
+\r
+  <PropertyGroup Label="UserMacros">\r
+    <libksDir>$(BaseDir)libs\libks-$(libksVersion)</libksDir>\r
+  </PropertyGroup>\r
+\r
+\r
+  <!-- \r
+       Download Target.\r
+       Name must be unique. \r
+       By design, targets are executed only once per project.\r
+       \r
+       Usage:\r
+       \r
+       package: URI\r
+\r
+       expectfileordirectory: Skips the download and extraction if exists\r
+\r
+       outputfolder: Folder to store a downloaded file. \r
+                     By default "$(BaseDir)libs", if empty\r
+\r
+       outputfilename: If not empty, overrides filename from URI.\r
+                       .exe files don't get extracted\r
+\r
+       extractto: Folder to extract an archive to\r
+   -->\r
+\r
+  <Target Name="libksBinariesDownloadTarget" BeforeTargets="CustomBuild" DependsOnTargets="7za">  \r
+      <DownloadPackageTask \r
+           package="http://files.freeswitch.org/windows/packages/libks/$(libksVersion)/libks-$(libksVersion)-binaries-$(Platform.ToLower())-$(Configuration.ToLower()).zip"\r
+           expectfileordirectory="$(libksDir)\binaries\$(Platform)\$(Configuration)\ks.dll" \r
+           outputfolder=""\r
+           outputfilename="" \r
+           extractto="$(BaseDir)libs\"\r
+      />\r
+  </Target> \r
+  <Target Name="libksHeadersDownloadTarget" BeforeTargets="CustomBuild" DependsOnTargets="7za">  \r
+      <DownloadPackageTask \r
+           package="http://files.freeswitch.org/windows/packages/libks/$(libksVersion)/libks-$(libksVersion)-headers.zip"\r
+           expectfileordirectory="$(libksDir)\src\include\libks\ks.h" \r
+           outputfolder=""\r
+           outputfilename="" \r
+           extractto="$(BaseDir)libs\"\r
+      />\r
+  </Target> \r
+\r
+  <Target Name="libkscopyTarget" BeforeTargets="CustomBuild" DependsOnTargets="libksBinariesDownloadTarget">  \r
+        <Message Text="Copying libks libraries to the freeswitch output folder." Importance="High" />\r
+        <ItemGroup>  \r
+             <libksFiles Include="$(libksDir)\binaries\$(Platform)\$(Configuration)\*.dll"/>  \r
+        </ItemGroup>  \r
+        <Copy Condition="!exists('$(BaseDir)\$(Platform)\$(Configuration)\ks.dll')"\r
+            SourceFiles="@(libksFiles)"  \r
+            DestinationFiles="@(libksFiles->'$(BaseDir)\$(Platform)\$(Configuration)\%(Filename)%(Extension)')"  \r
+        />  \r
+  </Target>   \r
+\r
+\r
+  <ItemDefinitionGroup>\r
+    <ClCompile>\r
+      <AdditionalIncludeDirectories>$(libksDir)\src\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>__PRETTY_FUNCTION__=__FUNCSIG__;WIN32;_WINDOWS;SWCLT_VERSION_MAJOR=1;SWCLT_VERSION_MINOR=0;SWCLT_VERSION_REVISION=0;_WIN32_WINNT=0x0600;_WINSOCK_DEPRECATED_NO_WARNINGS=1;WIN32_LEAN_AND_MEAN=1;KS_PLAT_WIN=1;NOMAXMIN=1;_CRT_SECURE_NO_WARNINGS=1;SWCLT_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+    </ClCompile>\r
+    <Link>\r
+      <AdditionalLibraryDirectories>$(libksDir)\binaries\$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r
+      <AdditionalDependencies>ks.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+</Project>\r
diff --git a/w32/signalwire-client-c-version.props b/w32/signalwire-client-c-version.props
new file mode 100644 (file)
index 0000000..0b0de97
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ImportGroup Label="PropertySheets">\r
+    <Import Project="basedir.props" Condition=" '$(BaseDirImported)' == ''"/>\r
+  </ImportGroup>\r
+  <PropertyGroup Label="UserMacros">\r
+    <signalwire-client-cVersion>1.0.0</signalwire-client-cVersion>\r
+  </PropertyGroup>\r
+  <PropertyGroup>\r
+    <signalwire-client-cVersionImported>true</signalwire-client-cVersionImported>\r
+  </PropertyGroup>\r
+  <PropertyGroup />\r
+  <ItemDefinitionGroup />\r
+  <ItemGroup>\r
+    <BuildMacro Include="signalwire-client-cVersion">\r
+      <Value>$(signalwire-client-cVersion)</Value>\r
+    </BuildMacro>\r
+  </ItemGroup>\r
+</Project>\r
diff --git a/w32/signalwire-client-c.props b/w32/signalwire-client-c.props
new file mode 100644 (file)
index 0000000..e7e40e2
--- /dev/null
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
+  <ImportGroup Label="PropertySheets">\r
+    <Import Project="signalwire-client-c-version.props" Condition=" '$(signalwire-client-cVersionImported)' == ''"/>\r
+  </ImportGroup>\r
+\r
+  <PropertyGroup>\r
+    <signalwire-client-cPropsImported>true</signalwire-client-cPropsImported>\r
+  </PropertyGroup>\r
+\r
+  <PropertyGroup Label="UserMacros">\r
+    <signalwire-client-cDir>$(BaseDir)libs\signalwire-client-c-$(signalwire-client-cVersion)</signalwire-client-cDir>\r
+  </PropertyGroup>\r
+\r
+  <!-- \r
+       Download Target.\r
+       Name must be unique. \r
+       By design, targets are executed only once per project.\r
+       \r
+       Usage:\r
+       \r
+       package: URI\r
+\r
+       expectfileordirectory: Skips the download and extraction if exists\r
+\r
+       outputfolder: Folder to store a downloaded file. \r
+                     By default "$(BaseDir)libs", if empty\r
+\r
+       outputfilename: If not empty, overrides filename from URI.\r
+                       .exe files don't get extracted\r
+\r
+       extractto: Folder to extract an archive to\r
+   -->\r
+\r
+  <Target Name="signalwire-client-cBinariesDownloadTarget" BeforeTargets="CustomBuild" DependsOnTargets="7za">  \r
+      <DownloadPackageTask \r
+           package="http://files.freeswitch.org/windows/packages/signalwire-client-c/$(signalwire-client-cVersion)/signalwire-client-c-$(signalwire-client-cVersion)-binaries-$(Platform.ToLower())-$(Configuration.ToLower()).zip"\r
+           expectfileordirectory="$(signalwire-client-cDir)\binaries\$(Platform)\$(Configuration)\signalwire_client.dll" \r
+           outputfolder=""\r
+           outputfilename="" \r
+           extractto="$(BaseDir)libs\"\r
+      />\r
+  </Target> \r
+  <Target Name="signalwire-client-cHeadersDownloadTarget" BeforeTargets="CustomBuild" DependsOnTargets="7za">  \r
+      <DownloadPackageTask \r
+           package="http://files.freeswitch.org/windows/packages/signalwire-client-c/$(signalwire-client-cVersion)/signalwire-client-c-$(signalwire-client-cVersion)-headers.zip"\r
+           expectfileordirectory="$(signalwire-client-cDir)\include\signalwire-client-c\client.h" \r
+           outputfolder=""\r
+           outputfilename="" \r
+           extractto="$(BaseDir)libs\"\r
+      />\r
+  </Target> \r
+\r
+  <Target Name="signalwire-client-ccopyTarget" BeforeTargets="CustomBuild" DependsOnTargets="signalwire-client-cBinariesDownloadTarget">  \r
+        <Message Text="Copying signalwire-client-c libraries to the freeswitch output folder." Importance="High" />\r
+        <ItemGroup>  \r
+             <signalwire-client-cFiles Include="$(signalwire-client-cDir)\binaries\$(Platform)\$(Configuration)\*.dll"/>  \r
+        </ItemGroup>  \r
+        <Copy Condition="!exists('$(BaseDir)\$(Platform)\$(Configuration)\signalwire_client.dll')"\r
+            SourceFiles="@(signalwire-client-cFiles)"  \r
+            DestinationFiles="@(signalwire-client-cFiles->'$(BaseDir)\$(Platform)\$(Configuration)\%(Filename)%(Extension)')"  \r
+        />  \r
+  </Target>   \r
+\r
+\r
+  <ItemDefinitionGroup>\r
+    <ClCompile>\r
+      <AdditionalIncludeDirectories>$(signalwire-client-cDir)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\r
+      <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>\r
+    </ClCompile>\r
+    <Link>\r
+      <AdditionalLibraryDirectories>$(signalwire-client-cDir)\binaries\$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>\r
+      <AdditionalDependencies>signalwire_client.lib;%(AdditionalDependencies)</AdditionalDependencies>\r
+    </Link>\r
+  </ItemDefinitionGroup>\r
+</Project>\r