PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/sipsorcery-core/SIPSorcery.AppServer.DialPlan/DialPlanApps/CallbackApp.cs

https://github.com/thecc4re/sipsorcery-mono
C# | 277 lines | 194 code | 37 blank | 46 comment | 12 complexity | c5f282bdfad9b955a725d40205a5d6a7 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. //-----------------------------------------------------------------------------
  2. // Filename: CallbackApp.cs
  3. //
  4. // Description: The framework for creating a new dial plan application.
  5. //
  6. // History:
  7. // 04 Jun 2009 Aaron Clauson Created.
  8. //
  9. // License:
  10. // This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
  11. //
  12. // Copyright (c) 2009 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
  13. // All rights reserved.
  14. //
  15. // Redistribution and use in source and binary forms, with or without modification, are permitted provided that
  16. // the following conditions are met:
  17. //
  18. // Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  19. // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  20. // disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd.
  21. // nor the names of its contributors may be used to endorse or promote products derived from this software without specific
  22. // prior written permission.
  23. //
  24. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
  25. // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  26. // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
  27. // OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  28. // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  29. // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30. // POSSIBILITY OF SUCH DAMAGE.
  31. //-----------------------------------------------------------------------------
  32. using System;
  33. using System.Collections.Generic;
  34. using System.Linq;
  35. using System.Net;
  36. using System.Net.Sockets;
  37. using System.Security;
  38. using System.Text;
  39. using System.Threading;
  40. using SIPSorcery.Net;
  41. using SIPSorcery.SIP;
  42. using SIPSorcery.SIP.App;
  43. using SIPSorcery.Sys;
  44. using log4net;
  45. namespace SIPSorcery.AppServer.DialPlan
  46. {
  47. public class CallbackApp
  48. {
  49. private const int MAXCALLBACK_DELAY_SECONDS = 15; // The maximum seconds a callback method can be delayed for.
  50. private const int MAXCALLBACK_RINGTIME_SECONDS = 120; // Set ring time for calls being created by dial plan. There is nothing that can cancel the call.
  51. private string CRLF = SIPConstants.CRLF;
  52. private static int m_maxRingTime = SIPTimings.MAX_RING_TIME;
  53. private static ILog logger = AppState.logger;
  54. private SIPMonitorLogDelegate Log_External;
  55. private SIPTransport m_sipTransport;
  56. private ISIPCallManager m_callManager;
  57. private DialStringParser m_dialStringParser;
  58. private string m_username;
  59. private string m_adminMemberId;
  60. private SIPEndPoint m_outboundProxy;
  61. private SIPDialogue m_firstLegDialogue;
  62. private bool m_firstLegEarlyMediaSet;
  63. public CallbackApp(
  64. SIPTransport sipTransport,
  65. ISIPCallManager callManager,
  66. DialStringParser dialStringParser,
  67. SIPMonitorLogDelegate logDelegate,
  68. string username,
  69. string adminMemberId,
  70. SIPEndPoint outboundProxy)
  71. {
  72. m_sipTransport = sipTransport;
  73. m_callManager = callManager;
  74. m_dialStringParser = dialStringParser;
  75. Log_External = logDelegate;
  76. m_username = username;
  77. m_adminMemberId = adminMemberId;
  78. m_outboundProxy = outboundProxy;
  79. }
  80. /// <summary>
  81. /// Establishes a new call with the client end tied to the proxy. Since the proxy will not be sending any audio the idea is that once
  82. /// the call is up it should be re-INVITED off somewhere else pronto to avoid the callee sitting their listening to dead air.
  83. /// </summary>
  84. /// <param name="dest1">The dial string of the first call to place.</param>
  85. /// <param name="dest2">The dial string of the second call to place.</param>
  86. /// <param name="delaySeconds">Delay in seconds before placing the first call. Gives the user a chance to hangup their phone if they are calling themselves back.</param>
  87. /// <returns>The result of the call.</returns>
  88. public void Callback(string dest1, string dest2, int delaySeconds)
  89. {
  90. try
  91. {
  92. if (delaySeconds > 0)
  93. {
  94. delaySeconds = (delaySeconds > MAXCALLBACK_DELAY_SECONDS) ? MAXCALLBACK_DELAY_SECONDS : delaySeconds;
  95. Log("Callback app delaying by " + delaySeconds + "s.");
  96. Thread.Sleep(delaySeconds * 1000);
  97. }
  98. Log("Callback app commencing first leg to " + dest1 + ".");
  99. SIPEndPoint defaultUDPEP = m_sipTransport.GetDefaultSIPEndPoint(SIPProtocolsEnum.udp);
  100. SIPRequest firstLegDummyInviteRequest = GetCallbackInviteRequest(defaultUDPEP.GetIPEndPoint(), null);
  101. m_firstLegDialogue = Dial(dest1, MAXCALLBACK_RINGTIME_SECONDS, 0, firstLegDummyInviteRequest);
  102. if (m_firstLegDialogue == null)
  103. {
  104. Log("The first call leg to " + dest1 + " was unsuccessful.");
  105. return;
  106. }
  107. SDP firstLegSDP = SDP.ParseSDPDescription(m_firstLegDialogue.RemoteSDP);
  108. string call1SDPIPAddress = firstLegSDP.Connection.ConnectionAddress;
  109. int call1SDPPort = firstLegSDP.Media[0].Port;
  110. Log("The first call leg to " + dest1 + " was successful, audio socket=" + call1SDPIPAddress + ":" + call1SDPPort + ".");
  111. Log("Callback app commencing second leg to " + dest2 + ".");
  112. SIPRequest secondLegDummyInviteRequest = GetCallbackInviteRequest(defaultUDPEP.GetIPEndPoint(), m_firstLegDialogue.RemoteSDP);
  113. SIPDialogue secondLegDialogue = Dial(dest2, MAXCALLBACK_RINGTIME_SECONDS, 0, secondLegDummyInviteRequest);
  114. if (secondLegDialogue == null)
  115. {
  116. Log("The second call leg to " + dest2 + " was unsuccessful.");
  117. m_firstLegDialogue.Hangup(m_sipTransport, m_outboundProxy);
  118. return;
  119. }
  120. SDP secondLegSDP = SDP.ParseSDPDescription(secondLegDialogue.RemoteSDP);
  121. string call2SDPIPAddress = secondLegSDP.Connection.ConnectionAddress;
  122. int call2SDPPort = secondLegSDP.Media[0].Port;
  123. Log("The second call leg to " + dest2 + " was successful, audio socket=" + call2SDPIPAddress + ":" + call2SDPPort + ".");
  124. m_callManager.CreateDialogueBridge(m_firstLegDialogue, secondLegDialogue, m_username);
  125. Log("Re-inviting Callback dialogues to each other.");
  126. m_callManager.ReInvite(m_firstLegDialogue, secondLegDialogue);
  127. //m_callManager.ReInvite(secondLegDialogue, m_firstLegDialogue.RemoteSDP);
  128. SendRTPPacket(call2SDPIPAddress + ":" + call2SDPPort, call1SDPIPAddress + ":" + call1SDPPort);
  129. SendRTPPacket(call1SDPIPAddress + ":" + call1SDPPort, call2SDPIPAddress + ":" + call2SDPPort);
  130. }
  131. catch (Exception excp)
  132. {
  133. logger.Error("Exception CallbackApp. " + excp);
  134. Log("Exception in Callback. " + excp);
  135. }
  136. }
  137. private SIPDialogue Dial(
  138. string data,
  139. int ringTimeout,
  140. int answeredCallLimit,
  141. SIPRequest clientRequest) {
  142. SIPDialogue answeredDialogue = null;
  143. ManualResetEvent waitForCallCompleted = new ManualResetEvent(false);
  144. ForkCall call = new ForkCall(m_sipTransport, Log_External, m_callManager.QueueNewCall, null, m_username, m_adminMemberId, m_outboundProxy, null);
  145. //call.CallProgress += (s, r, h, t, b) => { Log("Progress response of " + s + " received on CallBack Dial" + "."); };
  146. call.CallProgress += CallProgress;
  147. call.CallFailed += (s, r, h) => { waitForCallCompleted.Set(); };
  148. call.CallAnswered += (s, r, toTag, h, t, b, d, transferMode) => { answeredDialogue = d; waitForCallCompleted.Set(); };
  149. try {
  150. Queue<List<SIPCallDescriptor>> callsQueue = m_dialStringParser.ParseDialString(DialPlanContextsEnum.Script, clientRequest, data, null, null, null, null, null, null, null, null);
  151. call.Start(callsQueue);
  152. // Wait for an answer.
  153. ringTimeout = (ringTimeout > m_maxRingTime) ? m_maxRingTime : ringTimeout;
  154. if (waitForCallCompleted.WaitOne(ringTimeout * 1000, false)) {
  155. // Call timed out.
  156. call.CancelNotRequiredCallLegs(CallCancelCause.TimedOut);
  157. }
  158. return answeredDialogue;
  159. }
  160. catch (Exception excp) {
  161. logger.Error("Exception CallbackApp Dial. " + excp);
  162. return null;
  163. }
  164. }
  165. private void CallProgress(SIPResponseStatusCodesEnum progressStatus, string reasonPhrase, string[] customHeaders, string progressContentType, string progressBody, ISIPClientUserAgent uac)
  166. {
  167. try
  168. {
  169. Log("Progress response of " + progressStatus + " received on CallBack Dial" + ".");
  170. if (m_firstLegDialogue != null && !progressBody.IsNullOrBlank() && !m_firstLegEarlyMediaSet)
  171. {
  172. m_firstLegEarlyMediaSet = true;
  173. // The first leg is up and a call on the second leg has some early media that can be passed on.
  174. //m_callManager.ReInvite(m_firstLegDialogue, progressBody);
  175. }
  176. }
  177. catch (Exception excp)
  178. {
  179. logger.Error("Exception CallbackApp. " + excp.Message);
  180. }
  181. }
  182. private SIPRequest GetCallbackInviteRequest(IPEndPoint localSIPEndPoint, string sdp)
  183. {
  184. string callBackURI = "sip:callback@sipsorcery.com";
  185. string callBackUserField = "<" + callBackURI + ">";
  186. SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, callBackURI);
  187. SIPHeader inviteHeader = new SIPHeader(callBackUserField, callBackUserField, 1, CallProperties.CreateNewCallId());
  188. inviteHeader.CSeqMethod = SIPMethodsEnum.INVITE;
  189. inviteHeader.ContentType = "application/sdp";
  190. SIPViaHeader viaHeader = new SIPViaHeader(localSIPEndPoint, CallProperties.CreateBranchId());
  191. inviteHeader.Vias.PushViaHeader(viaHeader);
  192. inviteRequest.Header = inviteHeader;
  193. if (sdp == null)
  194. {
  195. sdp =
  196. "v=0" + CRLF +
  197. "o=- " + Crypto.GetRandomInt(1000, 5000).ToString() + " 2 IN IP4 " + localSIPEndPoint.Address.ToString() + CRLF +
  198. "s=session" + CRLF +
  199. "c=IN IP4 " + localSIPEndPoint.Address.ToString() + CRLF +
  200. "t=0 0" + CRLF +
  201. "m=audio " + Crypto.GetRandomInt(10000, 20000).ToString() + " RTP/AVP 0 18 101" + CRLF +
  202. "a=rtpmap:0 PCMU/8000" + CRLF +
  203. "a=rtpmap:18 G729/8000" + CRLF +
  204. "a=rtpmap:101 telephone-event/8000" + CRLF +
  205. "a=fmtp:101 0-16" + CRLF +
  206. "a=recvonly";
  207. }
  208. inviteHeader.ContentLength = sdp.Length;
  209. inviteRequest.Body = sdp;
  210. return inviteRequest;
  211. }
  212. private void SendRTPPacket(string sourceSocket, string destinationSocket)
  213. {
  214. try
  215. {
  216. //logger.Debug("Attempting to send RTP packet from " + sourceSocket + " to " + destinationSocket + ".");
  217. Log("Attempting to send RTP packet from " + sourceSocket + " to " + destinationSocket + ".");
  218. IPEndPoint sourceEP = IPSocket.GetIPEndPoint(sourceSocket);
  219. IPEndPoint destEP = IPSocket.GetIPEndPoint(destinationSocket);
  220. RTPPacket rtpPacket = new RTPPacket(80);
  221. rtpPacket.Header.SequenceNumber = (UInt16)6500;
  222. rtpPacket.Header.Timestamp = 100000;
  223. UDPPacket udpPacket = new UDPPacket(sourceEP.Port, destEP.Port, rtpPacket.GetBytes());
  224. IPv4Header ipHeader = new IPv4Header(ProtocolType.Udp, Crypto.GetRandomInt(6), sourceEP.Address, destEP.Address);
  225. IPv4Packet ipPacket = new IPv4Packet(ipHeader, udpPacket.GetBytes());
  226. byte[] data = ipPacket.GetBytes();
  227. Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
  228. rawSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, 1);
  229. rawSocket.SendTo(data, destEP);
  230. }
  231. catch (Exception excp)
  232. {
  233. logger.Error("Exception SendRTPPacket. " + excp.Message);
  234. }
  235. }
  236. private void Log(string message) {
  237. Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, message, m_username));
  238. }
  239. }
  240. }