PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/sipsorcery-core/SIPSorcery.Servers.Cores/SIPNotifier/NotifierSubscriptionsManager.cs

https://github.com/thecc4re/sipsorcery-mono
C# | 417 lines | 313 code | 48 blank | 56 comment | 41 complexity | f7c4745505216fb73d7835ea6a38bf1c MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. // ============================================================================
  2. // FileName: NotifierSubscriptionsManager.cs
  3. //
  4. // Description:
  5. // A client subscriptions manager for a SIP Notifier server as described in RFC3265
  6. // "Session Initiation Protocol (SIP)-Specific Event Notification". This class keeps track of subscriptions that
  7. // have been created by SIP client user agents and handles forwarding notifications for those subscriptions.
  8. //
  9. // Author(s):
  10. // Aaron Clauson
  11. //
  12. // History:
  13. // 23 Feb 2010 Aaron Clauson Created.
  14. //
  15. // License:
  16. // This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
  17. //
  18. // Copyright (c) 2010 Aaron Clauson (aaron@sipsorcery.com), SIPSorcery Ltd, London, UK (www.sipsorcery.com)
  19. // All rights reserved.
  20. //
  21. // Redistribution and use in source and binary forms, with or without modification, are permitted provided that
  22. // the following conditions are met:
  23. //
  24. // Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  25. // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  26. // disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd.
  27. // nor the names of its contributors may be used to endorse or promote products derived from this software without specific
  28. // prior written permission.
  29. //
  30. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
  31. // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  32. // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
  33. // OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  34. // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  35. // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  36. // POSSIBILITY OF SUCH DAMAGE.
  37. // ============================================================================
  38. using System;
  39. using System.Collections.Generic;
  40. using System.Data.Linq;
  41. using System.Linq;
  42. using System.Net;
  43. using System.Text;
  44. using System.Text.RegularExpressions;
  45. using System.Threading;
  46. using SIPSorcery.CRM;
  47. using SIPSorcery.Persistence;
  48. using SIPSorcery.Sys;
  49. using SIPSorcery.SIP;
  50. using SIPSorcery.SIP.App;
  51. using SIPSorcery.Web.Services;
  52. using log4net;
  53. namespace SIPSorcery.Servers
  54. {
  55. public class NotifierSubscriptionsManager
  56. {
  57. private const string GET_NOTIFICATIONS_THREAD_NAME = "subscriptionmanager-get";
  58. private static ILog logger = AppState.GetLogger("sipsubmngr");
  59. private static readonly int m_defaultSIPPort = SIPConstants.DEFAULT_SIP_PORT;
  60. private SIPMonitorLogDelegate MonitorLogEvent_External;
  61. private SIPAssetGetListDelegate<SIPDialogueAsset> GetDialogues_External;
  62. private SIPAssetGetByIdDelegate<SIPDialogueAsset> GetDialogue_External;
  63. private SIPAssetCountDelegate<SIPRegistrarBinding> GetSIPRegistrarBindingsCount_External;
  64. private SIPAssetPersistor<SIPAccount> m_sipAssetPersistor;
  65. private SIPTransport m_sipTransport;
  66. private SIPEndPoint m_outboundProxy;
  67. private ISIPMonitorPublisher m_publisher; // The SIP monitor event publisher, could be a memory or WPF boundary.
  68. private string m_notificationsAddress = Guid.NewGuid().ToString(); // The address used to subscribe to the SIP monitor event publisher.
  69. private Dictionary<string, SIPEventSubscription> m_subscriptions = new Dictionary<string, SIPEventSubscription>(); // [monitor session ID, subscription].
  70. public NotifierSubscriptionsManager(
  71. SIPMonitorLogDelegate logDelegate,
  72. SIPAssetGetListDelegate<SIPDialogueAsset> getDialogues,
  73. SIPAssetGetByIdDelegate<SIPDialogueAsset> getDialogue,
  74. SIPAssetPersistor<SIPAccount> sipAssetPersistor,
  75. SIPAssetCountDelegate<SIPRegistrarBinding> getBindingsCount,
  76. SIPTransport sipTransport,
  77. SIPEndPoint outboundProxy,
  78. ISIPMonitorPublisher publisher)
  79. {
  80. MonitorLogEvent_External = logDelegate;
  81. GetDialogues_External = getDialogues;
  82. GetDialogue_External = getDialogue;
  83. GetSIPRegistrarBindingsCount_External = getBindingsCount;
  84. m_sipAssetPersistor = sipAssetPersistor;
  85. m_sipTransport = sipTransport;
  86. m_outboundProxy = outboundProxy;
  87. m_publisher = publisher;
  88. m_publisher.MonitorEventReady += MonitorEventAvailable;
  89. }
  90. public string SubscribeClient(
  91. string owner,
  92. string adminID,
  93. SIPRequest subscribeRequest,
  94. string toTag,
  95. SIPURI canonicalResourceURI,
  96. out SIPResponseStatusCodesEnum errorResponse,
  97. out string errorReason)
  98. {
  99. try
  100. {
  101. errorResponse = SIPResponseStatusCodesEnum.None;
  102. errorReason = null;
  103. SIPURI resourceURI = subscribeRequest.URI.CopyOf();
  104. SIPEventPackage eventPackage = SIPEventPackage.Parse(subscribeRequest.Header.Event);
  105. int expiry = subscribeRequest.Header.Expires;
  106. if (!(eventPackage == SIPEventPackage.Dialog || eventPackage == SIPEventPackage.Presence))
  107. {
  108. throw new ApplicationException("Event package " + eventPackage.ToString() + " is not supported by the subscriptions manager.");
  109. }
  110. else
  111. {
  112. if (expiry > 0)
  113. {
  114. string subscribeError = null;
  115. string sessionID = Guid.NewGuid().ToString();
  116. SIPDialogue subscribeDialogue = new SIPDialogue(subscribeRequest, owner, adminID, toTag);
  117. if (eventPackage == SIPEventPackage.Dialog)
  118. {
  119. string monitorFilter = "dialog " + canonicalResourceURI.ToString();
  120. if (!subscribeRequest.Body.IsNullOrBlank())
  121. {
  122. monitorFilter += " and " + subscribeRequest.Body;
  123. }
  124. m_publisher.Subscribe(owner, adminID, m_notificationsAddress, sessionID, SIPMonitorClientTypesEnum.Machine.ToString(), monitorFilter, expiry, null, out subscribeError);
  125. if (subscribeError != null)
  126. {
  127. throw new ApplicationException(subscribeError);
  128. }
  129. else
  130. {
  131. SIPDialogEventSubscription subscription = new SIPDialogEventSubscription(MonitorLogEvent_External, sessionID, resourceURI, canonicalResourceURI, monitorFilter, subscribeDialogue, expiry, GetDialogues_External, GetDialogue_External);
  132. m_subscriptions.Add(sessionID, subscription);
  133. MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.SubscribeAccept, "New dialog subscription created for " + resourceURI.ToString() + ", expiry " + expiry + "s.", owner));
  134. }
  135. }
  136. else if (eventPackage == SIPEventPackage.Presence)
  137. {
  138. string monitorFilter = "presence " + canonicalResourceURI.ToString();
  139. m_publisher.Subscribe(owner, adminID, m_notificationsAddress, sessionID, SIPMonitorClientTypesEnum.Machine.ToString(), monitorFilter, expiry, null, out subscribeError);
  140. if (subscribeError != null)
  141. {
  142. throw new ApplicationException(subscribeError);
  143. }
  144. else
  145. {
  146. bool switchboardAccountsOnly = subscribeRequest.Body == SIPPresenceEventSubscription.SWITCHBOARD_FILTER;
  147. SIPPresenceEventSubscription subscription = new SIPPresenceEventSubscription(MonitorLogEvent_External, sessionID, resourceURI, canonicalResourceURI, monitorFilter, subscribeDialogue, expiry, m_sipAssetPersistor, GetSIPRegistrarBindingsCount_External, switchboardAccountsOnly);
  148. m_subscriptions.Add(sessionID, subscription);
  149. MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.SubscribeAccept, "New presence subscription created for " + resourceURI.ToString() + ", expiry " + expiry + "s.", owner));
  150. }
  151. }
  152. return sessionID;
  153. }
  154. return null;
  155. }
  156. }
  157. catch (Exception excp)
  158. {
  159. logger.Error("Exception NotifierSubscriptionsManager SubscribeClient. " + excp.Message);
  160. throw;
  161. }
  162. }
  163. /// <summary>
  164. /// Attempts to renew an existing subscription.
  165. /// </summary>
  166. /// <returns>The session ID if the renewal was successful or null if it wasn't.</returns>
  167. public string RenewSubscription(SIPRequest subscribeRequest, out SIPResponseStatusCodesEnum errorResponse, out string errorReason)
  168. {
  169. errorResponse = SIPResponseStatusCodesEnum.None;
  170. errorReason = null;
  171. try
  172. {
  173. int expiry = subscribeRequest.Header.Expires;
  174. string toTag = subscribeRequest.Header.To.ToTag;
  175. string fromTag = subscribeRequest.Header.From.FromTag;
  176. string callID = subscribeRequest.Header.CallId;
  177. int cseq = subscribeRequest.Header.CSeq;
  178. // Check for an existing subscription.
  179. SIPEventSubscription existingSubscription = (from sub in m_subscriptions.Values where sub.SubscriptionDialogue.CallId == callID select sub).FirstOrDefault();
  180. if (existingSubscription != null)
  181. {
  182. if (expiry == 0)
  183. {
  184. // Subsciption is being cancelled.
  185. StopSubscription(existingSubscription);
  186. return null;
  187. }
  188. else if (cseq > existingSubscription.SubscriptionDialogue.RemoteCSeq)
  189. {
  190. logger.Debug("Renewing subscription for " + existingSubscription.SessionID + " and " + existingSubscription.SubscriptionDialogue.Owner + ".");
  191. existingSubscription.SubscriptionDialogue.RemoteCSeq = cseq;
  192. //existingSubscription.ProxySendFrom = (!subscribeRequest.Header.ProxyReceivedOn.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(subscribeRequest.Header.ProxyReceivedOn) : null;
  193. string extensionResult = m_publisher.ExtendSession(m_notificationsAddress, existingSubscription.SessionID, expiry);
  194. if (extensionResult != null)
  195. {
  196. // One or more of the monitor servers could not extend the session. Close all the existing sessions and re-create.
  197. MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.SubscribeFailed, "Monitor session extension for " + existingSubscription.SubscriptionEventPackage.ToString() + " " + existingSubscription.ResourceURI.ToString() + " failed. " + extensionResult, existingSubscription.SubscriptionDialogue.Owner));
  198. m_publisher.CloseSession(m_notificationsAddress, existingSubscription.SessionID);
  199. // Need to re-establish the sessions with the notification servers.
  200. string subscribeError = null;
  201. string sessionID = Guid.NewGuid().ToString();
  202. m_publisher.Subscribe(existingSubscription.SubscriptionDialogue.Owner, existingSubscription.SubscriptionDialogue.AdminMemberId, m_notificationsAddress, sessionID, SIPMonitorClientTypesEnum.Machine.ToString(), existingSubscription.MonitorFilter, expiry, null, out subscribeError);
  203. if (subscribeError != null)
  204. {
  205. throw new ApplicationException(subscribeError);
  206. }
  207. else
  208. {
  209. lock (m_subscriptions)
  210. {
  211. m_subscriptions.Remove(existingSubscription.SessionID);
  212. existingSubscription.SessionID = sessionID;
  213. m_subscriptions.Add(sessionID, existingSubscription);
  214. }
  215. MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.SubscribeAccept, "Monitor session recreated for " + existingSubscription.SubscriptionEventPackage.ToString() + " " + existingSubscription.ResourceURI.ToString() + ".", existingSubscription.SubscriptionDialogue.Owner));
  216. }
  217. }
  218. MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.SubscribeRenew, "Monitor session successfully renewed for " + existingSubscription.SubscriptionEventPackage.ToString() + " " + existingSubscription.ResourceURI.ToString() + ".", existingSubscription.SubscriptionDialogue.Owner));
  219. return existingSubscription.SessionID;
  220. }
  221. else
  222. {
  223. throw new ApplicationException("A duplicate SUBSCRIBE request was received by NotifierSubscriptionsManager.");
  224. }
  225. }
  226. else
  227. {
  228. //throw new ApplicationException("No existing subscription could be found for a subscribe renewal request.");
  229. errorResponse = SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist;
  230. errorReason = "Subscription dialog not found";
  231. return null;
  232. }
  233. }
  234. catch (Exception excp)
  235. {
  236. logger.Error("Exception RenewSubscription. " + excp.Message);
  237. throw;
  238. }
  239. }
  240. public void SendFullStateNotify(string sessionID)
  241. {
  242. try
  243. {
  244. if (m_subscriptions.ContainsKey(sessionID))
  245. {
  246. SIPEventSubscription subscription = m_subscriptions[sessionID];
  247. lock (subscription)
  248. {
  249. subscription.GetFullState();
  250. SendNotifyRequestForSubscription(subscription);
  251. }
  252. }
  253. else
  254. {
  255. logger.Warn("No subscription could be found for " + sessionID + " when attempting to send a full state notification.");
  256. }
  257. }
  258. catch (Exception excp)
  259. {
  260. logger.Error("Exception NotifierSubscriptionsManager SendFullStateNotify. " + excp.Message);
  261. throw;
  262. }
  263. }
  264. private void StopSubscription(SIPEventSubscription subscription)
  265. {
  266. try
  267. {
  268. m_publisher.CloseSession(m_notificationsAddress, subscription.SessionID);
  269. lock (m_subscriptions)
  270. {
  271. m_subscriptions.Remove(subscription.SessionID);
  272. }
  273. MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.Warn, "Stopping subscription for " + subscription.SubscriptionEventPackage.ToString() + " " + subscription.ResourceURI.ToString() + ".", subscription.SubscriptionDialogue.Owner));
  274. }
  275. catch (Exception excp)
  276. {
  277. logger.Error("Exception StopSubscription. " + excp.Message);
  278. }
  279. }
  280. private void SendNotifyRequestForSubscription(SIPEventSubscription subscription)
  281. {
  282. try
  283. {
  284. subscription.SubscriptionDialogue.CSeq++;
  285. //logger.Debug(DateTime.Now.ToString("HH:mm:ss:fff") + " Sending NOTIFY request for " + subscription.SubscriptionDialogue.Owner + " event " + subscription.SubscriptionEventPackage.ToString()
  286. // + " and " + subscription.ResourceURI.ToString() + " to " + subscription.SubscriptionDialogue.RemoteTarget.ToString() + ", cseq=" + (subscription.SubscriptionDialogue.CSeq) + ".");
  287. int secondsRemaining = Convert.ToInt32(subscription.LastSubscribe.AddSeconds(subscription.Expiry).Subtract(DateTime.Now).TotalSeconds % Int32.MaxValue);
  288. SIPRequest notifyRequest = m_sipTransport.GetRequest(SIPMethodsEnum.NOTIFY, subscription.SubscriptionDialogue.RemoteTarget);
  289. notifyRequest.Header.From = SIPFromHeader.ParseFromHeader(subscription.SubscriptionDialogue.LocalUserField.ToString());
  290. notifyRequest.Header.To = SIPToHeader.ParseToHeader(subscription.SubscriptionDialogue.RemoteUserField.ToString());
  291. notifyRequest.Header.Event = subscription.SubscriptionEventPackage.ToString();
  292. notifyRequest.Header.CSeq = subscription.SubscriptionDialogue.CSeq;
  293. notifyRequest.Header.CallId = subscription.SubscriptionDialogue.CallId;
  294. notifyRequest.Body = subscription.GetNotifyBody();
  295. notifyRequest.Header.ContentLength = notifyRequest.Body.Length;
  296. notifyRequest.Header.SubscriptionState = "active;expires=" + secondsRemaining.ToString();
  297. notifyRequest.Header.ContentType = subscription.NotifyContentType;
  298. notifyRequest.Header.ProxySendFrom = subscription.SubscriptionDialogue.ProxySendFrom;
  299. // If the outbound proxy is a loopback address, as it will normally be for local deployments, then it cannot be overriden.
  300. SIPEndPoint dstEndPoint = m_outboundProxy;
  301. if (m_outboundProxy != null && IPAddress.IsLoopback(m_outboundProxy.Address))
  302. {
  303. dstEndPoint = m_outboundProxy;
  304. }
  305. else if (subscription.SubscriptionDialogue.ProxySendFrom != null)
  306. {
  307. // The proxy will always be listening on UDP port 5060 for requests from internal servers.
  308. dstEndPoint = new SIPEndPoint(SIPProtocolsEnum.udp, new IPEndPoint(SIPEndPoint.ParseSIPEndPoint(subscription.SubscriptionDialogue.ProxySendFrom).Address, m_defaultSIPPort));
  309. }
  310. SIPNonInviteTransaction notifyTransaction = m_sipTransport.CreateNonInviteTransaction(notifyRequest, dstEndPoint, m_sipTransport.GetDefaultSIPEndPoint(dstEndPoint), m_outboundProxy);
  311. notifyTransaction.NonInviteTransactionFinalResponseReceived += (local, remote, transaction, response) => { NotifyTransactionFinalResponseReceived(local, remote, transaction, response, subscription); };
  312. m_sipTransport.SendSIPReliable(notifyTransaction);
  313. //logger.Debug(notifyRequest.ToString());
  314. MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.NotifySent, "Notification sent for " + subscription.SubscriptionEventPackage.ToString() + " and " + subscription.ResourceURI.ToString() + " to " + subscription.SubscriptionDialogue.RemoteTarget.ToString() + ".", subscription.SubscriptionDialogue.Owner));
  315. subscription.NotificationSent();
  316. }
  317. catch (Exception excp)
  318. {
  319. logger.Error("Exception SendNotifyRequestForSubscription. " + excp.Message);
  320. throw;
  321. }
  322. }
  323. private void NotifyTransactionFinalResponseReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse, SIPEventSubscription subscription)
  324. {
  325. try
  326. {
  327. if (sipResponse.StatusCode >= 300)
  328. {
  329. // The NOTIFY request was met with an error response.
  330. MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.Warn, "A notify request received an error response of " + sipResponse.Status + " " + sipResponse.ReasonPhrase + ".", subscription.SubscriptionDialogue.Owner));
  331. StopSubscription(subscription);
  332. }
  333. }
  334. catch (Exception excp)
  335. {
  336. logger.Error("Exception NotifyTransactionFinalResponseReceived. " + excp.Message);
  337. }
  338. }
  339. private bool MonitorEventAvailable(SIPMonitorEvent sipMonitorEvent)
  340. {
  341. try
  342. {
  343. SIPMonitorMachineEvent machineEvent = sipMonitorEvent as SIPMonitorMachineEvent;
  344. if (machineEvent != null && !machineEvent.SessionID.IsNullOrBlank() && m_subscriptions.ContainsKey(machineEvent.SessionID))
  345. {
  346. SIPEventSubscription subscription = m_subscriptions[machineEvent.SessionID];
  347. lock (subscription)
  348. {
  349. string resourceURI = (machineEvent.ResourceURI != null) ? machineEvent.ResourceURI.ToString() : null;
  350. //logger.Debug("NotifierSubscriptionsManager received new " + machineEvent.MachineEventType + ", resource ID=" + machineEvent.ResourceID + ", resource URI=" + resourceURI + ".");
  351. //MonitorLogEvent_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.Notifier, SIPMonitorEventTypesEnum.Monitor, "NotifierSubscriptionsManager received new " + machineEvent.MachineEventType + ", resource ID=" + machineEvent.ResourceID + ", resource URI=" + resourceURI + ".", subscription.SubscriptionDialogue.Owner));
  352. if (subscription.AddMonitorEvent(machineEvent))
  353. {
  354. SendNotifyRequestForSubscription(subscription);
  355. }
  356. //logger.Debug("NotifierSubscriptionsManager completed " + machineEvent.MachineEventType + ", resource ID=" + machineEvent.ResourceID + ", resource URI=" + resourceURI + ".");
  357. }
  358. return true;
  359. }
  360. return false;
  361. }
  362. catch (Exception excp)
  363. {
  364. logger.Error("Exception NotifierSubscriptionsManager MonitorEventAvailable. " + excp.Message);
  365. return false;
  366. }
  367. }
  368. }
  369. }