private SIPRequest GetUpdateRequest(SIPRequest inviteRequest, CRMHeaders crmHeaders)
        {
            SIPRequest updateRequest = new SIPRequest(SIPMethodsEnum.UPDATE, inviteRequest.URI);

            updateRequest.SetSendFromHints(inviteRequest.LocalSIPEndPoint);

            SIPHeader inviteHeader = inviteRequest.Header;
            SIPHeader updateHeader = new SIPHeader(inviteHeader.From, inviteHeader.To, inviteHeader.CSeq + 1, inviteHeader.CallId);

            inviteRequest.Header.CSeq++;
            updateRequest.Header       = updateHeader;
            updateHeader.CSeqMethod    = SIPMethodsEnum.UPDATE;
            updateHeader.Routes        = inviteHeader.Routes;
            updateHeader.ProxySendFrom = inviteHeader.ProxySendFrom;

            SIPViaHeader viaHeader = new SIPViaHeader(new IPEndPoint(IPAddress.Any, 0), CallProperties.CreateBranchId());

            updateHeader.Vias.PushViaHeader(viaHeader);

            return(updateRequest);
        }
 //public DialPlanAppResult Dial(string data, CRMHeaders contact)
 //{
 //    return Dial(data, m_maxRingTime, 0, m_sipRequest, null);
 //}
 public DialPlanAppResult Dial(string data, int ringTimeout, int answeredCallLimit, CRMHeaders contact)
 {
     return Dial(data, ringTimeout, answeredCallLimit, m_sipRequest, contact);
 }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <param name="ringTimeout"></param>
        /// <param name="answeredCallLimit"></param>
        /// <param name="redirectMode"></param>
        /// <param name="clientTransaction"></param>
        /// <param name="keepScriptAlive">If false will let the dial plan engine know the script has finished and the call is answered. For applications
        /// like Callback which need to have two calls answered it will be true.</param>
        /// <returns></returns>
        private DialPlanAppResult Dial(
            string data,
            int ringTimeout,
            int answeredCallLimit,
            SIPRequest clientRequest,
            CRMHeaders contact)
        {
            if (m_dialPlanContext.IsAnswered)
            {
                Log("The call has already been answered the Dial command was not processed.");
                return DialPlanAppResult.AlreadyAnswered;
            }
            else if (data.IsNullOrBlank())
            {
                Log("The dial string cannot be empty when calling Dial.");
                return DialPlanAppResult.Error;
            }
            else if (m_callInitialisationCount > MAX_CALLS_ALLOWED)
            {
                Log("You have exceeded the maximum allowed calls for a dialplan execution.");
                return DialPlanAppResult.Error;
            }
            else
            {
                Log("Commencing Dial with: " + data + ".");

                DialPlanAppResult result = DialPlanAppResult.Unknown;
                m_waitForCallCompleted = new ManualResetEvent(false);

                SIPResponseStatusCodesEnum answeredStatus = SIPResponseStatusCodesEnum.None;
                string answeredReason = null;
                string answeredContentType = null;
                string answeredBody = null;
                SIPDialogue answeredDialogue = null;
                SIPDialogueTransferModesEnum uasTransferMode = SIPDialogueTransferModesEnum.Default;
                int numberLegs = 0;

                QueueNewCallDelegate queueNewCall = (m_callManager != null) ? m_callManager.QueueNewCall : (QueueNewCallDelegate)null;
                m_currentCall = new ForkCall(m_sipTransport, FireProxyLogEvent, queueNewCall, m_dialStringParser, Username, m_adminMemberId, m_outboundProxySocket, m_callManager, m_dialPlanContext, out LastDialled);
                m_currentCall.CallProgress += m_dialPlanContext.CallProgress;
                m_currentCall.CallFailed += (status, reason, headers) =>
                {
                    LastFailureStatus = status;
                    LastFailureReason = reason;
                    result = DialPlanAppResult.Failed;
                    m_waitForCallCompleted.Set();
                };
                m_currentCall.CallAnswered += (status, reason, toTag, headers, contentType, body, dialogue, transferMode) =>
                {
                    answeredStatus = status;
                    answeredReason = reason;
                    answeredContentType = contentType;
                    answeredBody = body;
                    answeredDialogue = dialogue;
                    uasTransferMode = transferMode;
                    result = DialPlanAppResult.Answered;
                    m_waitForCallCompleted.Set();
                };

                try
                {
                    Queue<List<SIPCallDescriptor>> callsQueue = m_dialStringParser.ParseDialString(
                        DialPlanContextsEnum.Script,
                        clientRequest,
                        data,
                        m_customSIPHeaders,
                        m_customContentType,
                        m_customContent,
                        m_dialPlanContext.CallersNetworkId,
                        m_customFromName,
                        m_customFromUser,
                        m_customFromHost,
                        contact,
                        ServiceLevel);

                    List<SIPCallDescriptor>[] callListArray = callsQueue.ToArray();
                    callsQueue.ToList().ForEach((list) => numberLegs += list.Count);

                    if (numberLegs == 0)
                    {
                        Log("The dial string did not result in any call legs.");
                        return DialPlanAppResult.Error;
                    }
                    else
                    {
                        m_callInitialisationCount += numberLegs;
                        if (m_callInitialisationCount > MAX_CALLS_ALLOWED)
                        {
                            Log("You have exceeded the maximum allowed calls for a dialplan execution.");
                            return DialPlanAppResult.Error;
                        }
                    }

                    m_currentCall.Start(callsQueue);

                    // Wait for an answer.
                    if (ringTimeout <= 0 || ringTimeout * 1000 > m_maxRingTime)
                    {
                        ringTimeout = m_maxRingTime;
                    }
                    else
                    {
                        ringTimeout = ringTimeout * 1000;
                    }
                    ExtendScriptTimeout(ringTimeout / 1000 + DEFAULT_CREATECALL_RINGTIME);
                    DateTime startTime = DateTime.Now;

                    if (m_waitForCallCompleted.WaitOne(ringTimeout, false))
                    {
                        if (!m_clientCallCancelled)
                        {
                            if (result == DialPlanAppResult.Answered)
                            {
                                // The call limit duration is only used if there hasn't already been a per leg duration set on the call.
                                if (answeredCallLimit > 0 && answeredDialogue.CallDurationLimit == 0)
                                {
                                    answeredDialogue.CallDurationLimit = answeredCallLimit;
                                }

                                m_dialPlanContext.CallAnswered(answeredStatus, answeredReason, null, null, answeredContentType, answeredBody, answeredDialogue, uasTransferMode);

                                // Dial plan script stops once there is an answered call to bridge to or the client call is cancelled.
                                Log("Dial command was successfully answered in " + DateTime.Now.Subtract(startTime).TotalSeconds.ToString("0.00") + "s.");

                                // Do some Google Analytics call tracking.
                                if (answeredDialogue.RemoteUserField != null)
                                {
                                    SendGoogleAnalyticsEvent("Call", "Answered", answeredDialogue.RemoteUserField.URI.Host, 1);
                                }

                                m_executingScript.StopExecution();
                            }
                            else if (result == DialPlanAppResult.Failed)
                            {
                                // Check whether any of the responses were redirects.
                                if (LastDialled != null && LastDialled.Count > 0)
                                {
                                    var redirect = (from trans in LastDialled
                                                    where trans.TransactionFinalResponse != null && trans.TransactionFinalResponse.StatusCode >= 300 &&
                                                        trans.TransactionFinalResponse.StatusCode <= 399 && trans.TransactionFinalResponse.Header.Contact != null
                                                        && trans.TransactionFinalResponse.Header.Contact.Count > 0
                                                    select trans.TransactionFinalResponse).FirstOrDefault();

                                    if (redirect != null)
                                    {
                                        m_redirectResponse = redirect;
                                        m_redirectURI = RedirectResponse.Header.Contact[0].ContactURI;
                                        result = DialPlanAppResult.Redirect;
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        if (!m_clientCallCancelled)
                        {
                            // Call timed out.
                            m_currentCall.CancelNotRequiredCallLegs(CallCancelCause.TimedOut);
                            result = DialPlanAppResult.TimedOut;
                        }
                    }

                    if (m_clientCallCancelled)
                    {
                        Log("Dial command was halted by cancellation of client call after " + DateTime.Now.Subtract(startTime).TotalSeconds.ToString("#.00") + "s.");
                        m_executingScript.StopExecution();
                    }

                    return result;
                }
                catch (ThreadAbortException)
                {
                    return DialPlanAppResult.Unknown;
                }
                catch (Exception excp)
                {
                    logger.Error("Exception DialPlanScriptFacade Dial. " + excp.Message);
                    return DialPlanAppResult.Error;
                }
            }
        }
 /// <summary>
 /// Sets CRM headers for the dialplan context. Typically the CRM headers will contain extra information about the caller
 /// that has been obtained from an external CRM system.
 /// </summary>
 public void SetCallerCRMDetails(CRMHeaders crmHeader)
 {
     m_dialPlanContext.SetCallerDetails(crmHeader);
 }
Beispiel #5
0
 public void Update(CRMHeaders crmHeaders)
 {
     throw new NotImplementedException();
 }
 public void Update(CRMHeaders crmHeaders)
 {
     throw new NotImplementedException();
 }
        /// <summary>
        /// Can't be used for local destinations!
        /// </summary>
        /// <param name="sipRequest"></param>
        /// <param name="command"></param>
        /// <returns></returns>
        private SIPCallDescriptor GetForwardsForExternalLeg(
            SIPRequest sipRequest,
            SIPURI callLegURI,
            string customContentType,
            string customContent,
            string fromDisplayName,
            string fromUsername,
            string fromHost,
            CRMHeaders contact)
        {
            try
            {
                SIPCallDescriptor sipCallDescriptor = null;

                Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Attempting to locate a provider for call leg: " + callLegURI.ToString() + ".", m_username));
                bool providerFound = false;

                string contentType = (sipRequest != null) ? sipRequest.Header.ContentType : null;
                string content = (sipRequest != null) ? sipRequest.Body : null;

                if (!customContentType.IsNullOrBlank())
                {
                    contentType = customContentType;
                }

                if (!customContent.IsNullOrBlank())
                {
                    content = customContent;
                }

                IPAddress publicSDPAddress = PublicIPAddress;
                if (publicSDPAddress == null && sipRequest != null)
                {
                    publicSDPAddress = SIPPacketMangler.GetRequestIPAddress(sipRequest);
                }

                if (m_sipProviders != null)
                {
                    foreach (SIPProvider provider in m_sipProviders)
                    {
                        if (callLegURI.Host.ToUpper() == provider.ProviderName.ToUpper())
                        {
                            if (provider.ProviderType == ProviderTypes.GoogleVoice)
                            {
                                sipCallDescriptor = new SIPCallDescriptor(
                                        provider.ProviderUsername,
                                        provider.ProviderPassword,
                                        callLegURI.User + "@" + provider.ProviderName,
                                        provider.GVCallbackNumber,
                                        provider.GVCallbackPattern,
                                        (provider.GVCallbackType != null) ? (int)provider.GVCallbackType.Value : DEFAULT_GVCALLBACK_TYPE,
                                        content,
                                        contentType);

                                sipCallDescriptor.CRMHeaders = contact;
                                providerFound = true;
                                break;
                            }
                            else
                            {
                                SIPURI providerURI = SIPURI.ParseSIPURI(provider.ProviderServer);
                                if (providerURI != null)
                                {
                                    providerURI.User = callLegURI.User;

                                    if (callLegURI.Parameters.Count > 0)
                                    {
                                        foreach (string parameterName in callLegURI.Parameters.GetKeys())
                                        {
                                            if (!providerURI.Parameters.Has(parameterName))
                                            {
                                                providerURI.Parameters.Set(parameterName, callLegURI.Parameters.Get(parameterName));
                                            }
                                        }
                                    }

                                    if (callLegURI.Headers.Count > 0)
                                    {
                                        foreach (string headerName in callLegURI.Headers.GetKeys())
                                        {
                                            if (!providerURI.Headers.Has(headerName))
                                            {
                                                providerURI.Headers.Set(headerName, callLegURI.Headers.Get(headerName));
                                            }
                                        }
                                    }

                                    SIPFromHeader fromHeader = ParseFromHeaderOption(provider.ProviderFrom, sipRequest, provider.ProviderUsername, providerURI.Host);

                                    sipCallDescriptor = new SIPCallDescriptor(
                                        provider.ProviderUsername,
                                        provider.ProviderPassword,
                                        providerURI.ToString(),
                                        fromHeader.ToString(),
                                        providerURI.ToString(),
                                        null,
                                        SIPCallDescriptor.ParseCustomHeaders(SubstituteRequestVars(sipRequest, provider.CustomHeaders)),
                                        provider.ProviderAuthUsername,
                                        SIPCallDirection.Out,
                                        contentType,
                                        content,
                                        publicSDPAddress);
                                    sipCallDescriptor.CRMHeaders = contact;

                                    if (!provider.ProviderOutboundProxy.IsNullOrBlank())
                                    {
                                        sipCallDescriptor.ProxySendFrom = provider.ProviderOutboundProxy.Trim();
                                    }

                                    providerFound = true;

                                    if (provider.ProviderFrom.IsNullOrBlank())
                                    {
                                        // If there is already a custom From header set on the provider that overrides the general settings.
                                        sipCallDescriptor.SetGeneralFromHeaderFields(fromDisplayName, fromUsername, fromHost);
                                    }

                                    break;
                                }
                                else
                                {
                                    Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Could not parse SIP URI from Provider Server " + provider.ProviderServer + " in GetForwardsForExternalLeg.", m_username));
                                }
                            }
                        }
                    }
                }

                if (!providerFound)
                {
                    // Treat as an anonymous SIP URI.

                    // Copy the From header so the tag can be removed before adding to the forwarded request.
                    string fromHeaderStr = (sipRequest != null) ? sipRequest.Header.From.ToString() : m_anonymousFromURI;
                    SIPFromHeader forwardedFromHeader = SIPFromHeader.ParseFromHeader(fromHeaderStr);
                    forwardedFromHeader.FromTag = null;

                    sipCallDescriptor = new SIPCallDescriptor(
                        m_anonymousUsername,
                        null,
                        callLegURI.ToString(),
                        forwardedFromHeader.ToString(),
                        callLegURI.ToString(),
                        null,
                        null,
                        null,
                        SIPCallDirection.Out,
                        contentType,
                        content,
                        publicSDPAddress);
                    sipCallDescriptor.CRMHeaders = contact;

                    sipCallDescriptor.SetGeneralFromHeaderFields(fromDisplayName, fromUsername, fromHost);
                }

                return sipCallDescriptor;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetForwardsForExternalLeg. " + excp.Message);
                return null;
            }
        }
        /// <summary>
        /// Creates a list of calls based on the registered contacts for a user registration.
        /// </summary>
        /// <param name="user"></param>
        /// <param name="domain"></param>
        /// <param name="from">The From header that will be set on the forwarded call leg.</param>
        /// <returns></returns>
        public List<SIPCallDescriptor> GetForwardsForLocalLeg(
            SIPRequest sipRequest,
            SIPAccount sipAccount,
            List<string> customHeaders,
            string customContentType,
            string customContent,
            string options,
            string callersNetworkId,
            string fromDisplayName,
            string fromUsername,
            string fromHost,
            CRMHeaders contact)
        {
            List<SIPCallDescriptor> localUserSwitchCalls = new List<SIPCallDescriptor>();

            try
            {
                if (sipAccount == null)
                {
                    throw new ApplicationException("Cannot lookup forwards for a null SIP account.");
                }

                List<SIPRegistrarBinding> bindings = GetRegistrarBindings_External(b => b.SIPAccountId == sipAccount.Id, null, 0, Int32.MaxValue);

                if (bindings != null)
                {
                    Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, bindings.Count + " found for " + sipAccount.SIPUsername + "@" + sipAccount.SIPDomain + ".", m_username));

                    // Build list of registered contacts.
                    for (int index = 0; index < bindings.Count; index++)
                    {
                        SIPRegistrarBinding binding = bindings[index];
                        SIPURI contactURI = binding.MangledContactSIPURI;

                        // Determine the content based on a custom request, caller's network id and whether mangling is required.
                        string contentType = (sipRequest != null) ? sipRequest.Header.ContentType : null;
                        string content = (sipRequest != null) ? sipRequest.Body : null;

                        if (!customContentType.IsNullOrBlank())
                        {
                            contentType = customContentType;
                        }

                        if (!customContent.IsNullOrBlank())
                        {
                            content = customContent;
                        }

                        IPAddress publicSDPAddress = PublicIPAddress;
                        if (publicSDPAddress == null && sipRequest != null)
                        {
                            publicSDPAddress = SIPPacketMangler.GetRequestIPAddress(sipRequest);
                        }

                        string fromHeader = (sipRequest != null) ? sipRequest.Header.From.ToString() : m_anonymousFromURI;

                        SIPCallDescriptor switchCall = new SIPCallDescriptor(
                            null,
                            null,
                            contactURI.ToString(),
                            fromHeader,
                            new SIPToHeader(null, SIPURI.ParseSIPURIRelaxed(sipAccount.SIPUsername + "@" + sipAccount.SIPDomain), null).ToString(),
                            null,
                            customHeaders,
                            null,
                            SIPCallDirection.In,
                            contentType,
                            content,
                            publicSDPAddress);
                        switchCall.CRMHeaders = contact;

                        // If the binding for the call is a switchboard add the custom switchboard headers.
                        if (!sipRequest.Header.SwitchboardFrom.IsNullOrBlank())
                        {
                            switchCall.SwitchboardHeaders.SwitchboardFrom = sipRequest.Header.SwitchboardFrom;
                        }

                        // If the binding has a proxy socket defined set the header to ask the upstream proxy to use it.
                        if (binding.ProxySIPEndPoint != null)
                        {
                            switchCall.ProxySendFrom = binding.ProxySIPEndPoint.ToString();
                        }
                        switchCall.ParseCallOptions(options);
                        if (sipAccount != null && !sipAccount.NetworkId.IsNullOrBlank() && sipAccount.NetworkId == callersNetworkId)
                        {
                            switchCall.MangleResponseSDP = false;
                        }
                        switchCall.SetGeneralFromHeaderFields(fromDisplayName, fromUsername, fromHost);
                        localUserSwitchCalls.Add(switchCall);
                    }
                }
                else
                {
                    Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No current bindings found for local SIP account " + sipAccount.SIPUsername + "@" + sipAccount.SIPDomain + ".", m_username));
                }

                return localUserSwitchCalls;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetForwardsForLocalLeg. " + excp);
                return localUserSwitchCalls;
            }
        }
        /// <summary>
        /// Processes dial strings using the multi-legged format. Each leg is separated from the proceeding one by the | character and each subsequent leg
        /// will only be used if all the forwards in the preceeding one fail. Within each leg the forwards are separated by the & character.
        /// 
        /// Example: 
        /// Dial(123@provider1&provider2|[email protected]|provider4&456@provider5[,trace])
        /// </summary>
        private Queue<List<SIPCallDescriptor>> ParseScriptDialString(
            SIPRequest sipRequest,
            string command,
            List<string> customHeaders,
            string customContentType,
            string customContent,
            string callersNetworkId,
            string fromDisplayName,
            string fromUsername,
            string fromHost,
            CRMHeaders contact)
        {
            try
            {
                Queue<List<SIPCallDescriptor>> callsQueue = new Queue<List<SIPCallDescriptor>>();
                string[] followonLegs = command.Split(CALLLEG_FOLLOWON_SEPARATOR);

                foreach (string followOnLeg in followonLegs)
                {
                    List<SIPCallDescriptor> switchCalls = new List<SIPCallDescriptor>();
                    string[] callLegs = followOnLeg.Split(CALLLEG_SIMULTANEOUS_SEPARATOR);

                    foreach (string callLeg in callLegs)
                    {
                        if (!callLeg.IsNullOrBlank())
                        {
                            // Strip off the options string if present.
                            string options = null;
                            string callLegDestination = callLeg;

                            int optionsStartIndex = callLeg.IndexOf(CALLLEG_OPTIONS_START_CHAR);
                            int optionsEndIndex = callLeg.IndexOf(CALLLEG_OPTIONS_END_CHAR);
                            if (optionsStartIndex != -1)
                            {
                                callLegDestination = callLeg.Substring(0, optionsStartIndex);
                                options = callLeg.Substring(optionsStartIndex, optionsEndIndex - optionsStartIndex);
                            }

                            // Determine whether the call forward is for a local domain or not.
                            SIPURI callLegSIPURI = SIPURI.ParseSIPURIRelaxed(SubstituteRequestVars(sipRequest, callLegDestination));
                            if (callLegSIPURI != null && callLegSIPURI.User == null)
                            {
                                callLegSIPURI.User = sipRequest.URI.User;
                            }

                            if (callLegSIPURI != null)
                            {
                                string localDomain = GetCanonicalDomain_External(callLegSIPURI.Host, false);
                                if (localDomain != null)
                                {
                                    SIPAccount calledSIPAccount = GetSIPAccount_External(s => s.SIPUsername == callLegSIPURI.User && s.SIPDomain == localDomain);
                                    if (calledSIPAccount == null && callLegSIPURI.User.Contains("."))
                                    {
                                        // A full lookup failed. Now try a partial lookup if the incoming username is in a dotted domain name format.
                                        string sipUsernameSuffix = callLegSIPURI.User.Substring(callLegSIPURI.User.LastIndexOf(".") + 1);
                                        calledSIPAccount = GetSIPAccount_External(s => s.SIPUsername == sipUsernameSuffix && s.SIPDomain == localDomain);
                                    }
                                    if (calledSIPAccount != null)
                                    {
                                        // An incoming dialplan won't be used if it's invoked from itself.
                                        if (calledSIPAccount.InDialPlanName.IsNullOrBlank() || (m_username == calledSIPAccount.Owner && m_dialPlanName == calledSIPAccount.InDialPlanName))
                                        {
                                            Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Call leg is for local domain looking up bindings for " + callLegSIPURI.User + "@" + localDomain + " for call leg " + callLegDestination + ".", m_username));
                                            switchCalls.AddRange(GetForwardsForLocalLeg(sipRequest, calledSIPAccount, customHeaders, customContentType, customContent, options, callersNetworkId, fromDisplayName, fromUsername, fromHost, contact));
                                        }
                                        else
                                        {
                                            // An incoming call for a SIP account that has an incoming dialplan specified.
                                            Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Call leg is for local domain forwarding to incoming dialplan for " + callLegSIPURI.User + "@" + localDomain + ".", m_username));
                                            string sipUsername = (m_sipAccount != null) ? m_sipAccount.SIPUsername : m_username;
                                            string sipDomain = (m_sipAccount != null) ? m_sipAccount.SIPDomain : GetCanonicalDomain_External(SIPDomainManager.DEFAULT_LOCAL_DOMAIN, false);
                                            SIPFromHeader fromHeader = ParseFromHeaderOption(null, sipRequest, sipUsername, sipDomain);

                                            string content = customContent;
                                            string contentType = customContentType;
                                            if (contentType.IsNullOrBlank())
                                            {
                                                contentType = sipRequest.Header.ContentType;
                                            }

                                            if (content.IsNullOrBlank())
                                            {
                                                content = sipRequest.Body;
                                            }

                                            SIPCallDescriptor loopbackCall = new SIPCallDescriptor(calledSIPAccount, callLegSIPURI.ToString(), fromHeader.ToString(), contentType, content);
                                            loopbackCall.SetGeneralFromHeaderFields(fromDisplayName, fromUsername, fromHost);
                                            loopbackCall.MangleIPAddress = (PublicIPAddress != null) ? PublicIPAddress : SIPPacketMangler.GetRequestIPAddress(sipRequest);
                                            loopbackCall.ParseCallOptions(options);
                                            switchCalls.Add(loopbackCall);
                                        }
                                    }
                                    else
                                    {
                                        Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No sip account could be found for local call leg " + callLeg + ".", m_username));
                                    }
                                }
                                else
                                {
                                    // Construct a call forward for a remote destination.
                                    SIPCallDescriptor sipCallDescriptor = GetForwardsForExternalLeg(sipRequest, callLegSIPURI, customContentType, customContent, fromDisplayName, fromUsername, fromHost, contact);

                                    if (sipCallDescriptor != null)
                                    {
                                        // Add and provided custom headers to those already present in the call descriptor and overwrite if an existing
                                        // header is already present.
                                        if (customHeaders != null && customHeaders.Count > 0)
                                        {
                                            foreach (string customHeader in customHeaders)
                                            {
                                                string customHeaderValue = SubstituteRequestVars(sipRequest, customHeader);
                                                sipCallDescriptor.CustomHeaders.Add(customHeaderValue);
                                            }
                                        }

                                        sipCallDescriptor.ParseCallOptions(options);
                                        switchCalls.Add(sipCallDescriptor);
                                    }
                                }
                            }
                            else
                            {
                                Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Could not parse a SIP URI from " + callLeg + " in ParseMultiDialString.", m_username));
                            }
                        }
                    }

                    // Calls will not be added if the URI is already in the queue!
                    List<SIPCallDescriptor> callList = new List<SIPCallDescriptor>();
                    callsQueue.Enqueue(callList);
                    foreach (SIPCallDescriptor callToAdd in switchCalls)
                    {
                        if (!IsURIInQueue(callsQueue, callToAdd.Uri))
                        {
                            callList.Add(callToAdd);
                        }
                        else
                        {
                            Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Call leg " + callToAdd.Uri.ToString() + " already added duplicate ignored.", m_username));
                        }
                    }
                }

                return callsQueue;
            }
            catch (Exception excp)
            {
                logger.Error("Exception ParseScriptDialString. " + excp.Message);
                throw excp;
            }
        }
        /// <summary>
        /// Parses a dial string that has been used in a dial plan Dial command. The format of the dial string is likely to continue to evolve, check the class
        /// summary for the different formats available. This method determines which format the dial string is in and passes off to the appropriate method to 
        /// build the call list.
        /// </summary>
        /// <param name="dialPlanType">The type of dialplan that generated the Dial command.</param>
        /// <param name="sipRequest">The SIP Request that originated this command. Can be null if the call was not initiated by a SIP request such as 
        /// by a HTTP web service.</param>
        /// <param name="command">The Dial command string being parsed.</param>
        /// <param name="customHeaders">If non-empty contains a list of custom SIP headers that will be added to the forwarded request.</param>
        /// <param name="customContentType">If set indicates a custom content type header is required on the forwarded request and it
        /// overrides any other value.</param>
        /// <param name="customContent">If set indicates a custom body is required on the forwarded request and it
        /// overrides any other value.</param>
        /// <param name="callersNetworkId">If the call originated from a locally administered account this will hold the account's
        /// networkid which is used to determine if two local accounts are on the same network and therefore should have their SDP
        /// left alone.</param>
        /// <param name="fromDisplayName">If set will be used the From header display name instead of the value from the originating SIP request.</param>
        /// <param name="fromUsername">If set will be used the From header user name instead of the value from the originating SIP request.</param>
        /// <param name="fromHost">If set will be used the From header host instead of the value from the originating SIP request.</param>
        /// <returns>A queue where each item is a list of calls. If there was only a single forward there would only be one item in the list which contained a 
        /// single call. For dial strings containing multiple forwards each queue item can be a list with multiple calls.</returns>
        public Queue<List<SIPCallDescriptor>> ParseDialString(
            DialPlanContextsEnum dialPlanType,
            SIPRequest sipRequest,
            string command,
            List<string> customHeaders,
            string customContentType,
            string customContent,
            string callersNetworkId,
            string fromDisplayName,
            string fromUsername,
            string fromHost,
            CRMHeaders contact)
        {
            try
            {
                if (command == null || command.Trim().Length == 0)
                {
                    throw new ArgumentException("The dial string cannot be empty.");
                }
                else
                {
                    Queue<List<SIPCallDescriptor>> prioritisedCallList = new Queue<List<SIPCallDescriptor>>();

                    if (dialPlanType == DialPlanContextsEnum.Line || (!command.Contains("[") && Regex.Match(command, @"\S+,.*,\S+").Success))
                    {
                        // Singled legged call (Asterisk format).
                        SIPCallDescriptor SIPCallDescriptor = ParseAsteriskDialString(command, sipRequest);
                        List<SIPCallDescriptor> callList = new List<SIPCallDescriptor>();
                        callList.Add(SIPCallDescriptor);
                        prioritisedCallList.Enqueue(callList);
                    }
                    else
                    {
                        // Multi legged call (Script sys.Dial format).
                        //string providersString = (command.IndexOf(',') == -1) ? command : command.Substring(0, command.IndexOf(','));
                        prioritisedCallList = ParseScriptDialString(sipRequest, command, customHeaders, customContentType, customContent, callersNetworkId, fromDisplayName, fromUsername, fromHost, contact);
                    }

                    return prioritisedCallList;
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception ParseDialString. " + excp);
                throw excp;
            }
        }
        public void SetCallerDetails(CRMHeaders crmHeaders)
        {
            try
            {
                CallerCRMDetails = crmHeaders;

                if (!CallerCRMDetails.Pending)
                {
                    lock (m_uacWaitingForCallDetails)
                    {
                        if (m_uacWaitingForCallDetails.Count > 0)
                        {
                            foreach (var waitingUAC in m_uacWaitingForCallDetails)
                            {
                                Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Sending CRM caller details to " + waitingUAC.CallDescriptor.Uri + ".", Owner));
                                waitingUAC.Update(crmHeaders);
                                m_uacCallDetailsSent.Add(waitingUAC);
                            }

                            m_uacWaitingForCallDetails.Clear();
                        }
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SetCallerDetails. " + excp.Message);
            }
        }
        private SIPRequest GetUpdateRequest(SIPRequest inviteRequest, CRMHeaders crmHeaders)
        {
            SIPRequest updateRequest = new SIPRequest(SIPMethodsEnum.UPDATE, inviteRequest.URI);
            updateRequest.LocalSIPEndPoint = inviteRequest.LocalSIPEndPoint;

            SIPHeader inviteHeader = inviteRequest.Header;
            SIPHeader updateHeader = new SIPHeader(inviteHeader.From, inviteHeader.To, inviteHeader.CSeq + 1, inviteHeader.CallId);
            inviteRequest.Header.CSeq++;
            updateRequest.Header = updateHeader;
            updateHeader.CSeqMethod = SIPMethodsEnum.UPDATE;
            updateHeader.Routes = inviteHeader.Routes;
            updateHeader.ProxySendFrom = inviteHeader.ProxySendFrom;

            SIPViaHeader viaHeader = new SIPViaHeader(inviteRequest.LocalSIPEndPoint, CallProperties.CreateBranchId());
            updateHeader.Vias.PushViaHeader(viaHeader);

            // Add custom CRM headers.
            if (crmHeaders != null)
            {
                updateHeader.CRMPersonName = crmHeaders.PersonName;
                updateHeader.CRMCompanyName = crmHeaders.CompanyName;
                updateHeader.CRMPictureURL = crmHeaders.AvatarURL;
            }

            return updateRequest;
        }
        public void Update(CRMHeaders crmHeaders)
        {
            Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.UserAgentClient, SIPMonitorEventTypesEnum.DialPlan, "Sending UPDATE to " + m_serverTransaction.TransactionRequest.URI.ToString() + ".", Owner));

            SIPRequest updateRequest = GetUpdateRequest(m_serverTransaction.TransactionRequest, crmHeaders);
            SIPNonInviteTransaction updateTransaction = m_sipTransport.CreateNonInviteTransaction(updateRequest, m_serverEndPoint, m_serverTransaction.LocalSIPEndPoint, m_outboundProxy);
            updateTransaction.TransactionTraceMessage += TransactionTraceMessage;
            updateTransaction.SendReliableRequest();
        }
        /// <summary>
        /// Looks up a person contact in the 37 Signals Highrise application.
        /// </summary>
        /// <param name="url">The URL of the Highrise account to attempt the lookup on.</param>
        /// <param name="authToken">The auth token for the Highrise account to attempt the lookup with.</param>
        /// <param name="name">The name of the person to attempt a match on.</param>
        /// <param name="addCallNote">If true it indicates a Highrise note should be created if a matching contact is found.</param>
        //public CRMHeaders LookupHighriseContact(string url, string authToken, string name, bool addCallNote, bool async)
        //{
        //    LogToMonitor(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Looking up Highrise contact on " + url + " for " + name + ".", m_context.Owner));
        //    if (async)
        //    {
        //        m_context.SetCallerDetails(new CRMHeaders() { Pending = true });
        //        ThreadPool.QueueUserWorkItem(delegate { DoLookup(url, authToken, name, addCallNote, (result) => { m_context.SetCallerDetails(result); }); });
        //        return null;
        //    }
        //    else
        //    {
        //        return DoLookup(url, authToken, name, addCallNote, null);
        //    }
        //}
        private CRMHeaders DoLookup(string url, string authToken, SIPFromHeader from, bool addCallNote, Action<CRMHeaders> callback)
        {
            try
            {
                string searchString = null;
                string lookupType = null;

                if (from.FromName != null && Regex.Match(from.FromName, @"\D").Success)
                {
                    // The From display name has a non-digit character do a name lookup.
                    lookupType = "name";
                    searchString = from.FromName.Trim();
                }
                else if (from.FromName != null)
                {
                    // The From display name is all digits do a phone number lookup.
                    lookupType = "phonenumber";
                    searchString = from.FromName.Trim();
                }
                else if (!Regex.Match(from.FromURI.User, @"\D").Success)
                {
                    // The From URI user is all digits do a phone number lookup.
                    lookupType = "phonenumber";
                    searchString = from.FromURI.User.Trim();
                }
                else
                {
                    // Last resort is to do a SIP URI lookup.
                    lookupType = "sipaddress";
                    searchString = from.FromURI.ToAOR();
                }

                LogToMonitor(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Highrise contact lookup type " + lookupType + " commencing for " + searchString + ".", m_context.Owner));

                CRMHeaders result = null;
                DateTime startLookup = DateTime.Now;

                PersonRequest personRequest = new PersonRequest(url, authToken);
                People people = null;

                if (lookupType == "name")
                {
                    people = personRequest.GetByName(searchString);
                }
                else if (lookupType == "phonenumber")
                {
                    people = personRequest.GetByPhoneNumber(searchString);
                }
                else if (lookupType == "sipaddress")
                {
                    people = personRequest.GetByCustomField("sip_address", searchString);
                }

                if (people != null && people.PersonList != null && people.PersonList.Count > 0)
                {
                    Person person = people.PersonList[0];
                    string companyName = null;

                    if (person.CompanyID != null)
                    {
                        CompanyRequest companyRequest = new CompanyRequest(url, authToken);
                        Company company = companyRequest.GetByID(person.CompanyID.Value);

                        if (company != null)
                        {
                            companyName = company.Name;
                        }
                    }

                    double secondsDuration = DateTime.Now.Subtract(startLookup).TotalSeconds;

                    if (companyName != null)
                    {
                        LogToMonitor(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Highrise contact match " + person.FirstName + " " + person.LastName + " of " + companyName + ", time taken " + secondsDuration.ToString("0.##") + "s.", m_context.Owner));
                    }
                    else
                    {
                        LogToMonitor(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Highrise contact match " + person.FirstName + " " + person.LastName + ", time taken " + secondsDuration.ToString("0.##") + "s.", m_context.Owner));
                    }
                    //m_context.SetCallerDetails(new CRMHeaders(person.FirstName + " " + person.LastName, companyName, person.AvatarURL));
                    string personName = (!person.LastName.IsNullOrBlank()) ? person.FirstName + " " + person.LastName : person.FirstName;
                    result = new CRMHeaders(personName, companyName, person.AvatarURL);

                    if (addCallNote)
                    {
                        ThreadPool.QueueUserWorkItem(delegate { AddHighriseCallNote(url, authToken, from, person); });
                    }
                }
                else
                {
                    double secondsDuration = DateTime.Now.Subtract(startLookup).TotalSeconds;

                    LogToMonitor(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No Highrise contact match, time taken " + secondsDuration.ToString("0.##") + "s.", m_context.Owner));
                    result = new CRMHeaders() { Pending = false, LookupError = "No Highrise contact match." };
                }

                if (callback != null)
                {
                    callback(result);
                }

                return result;
            }
            catch (Exception excp)
            {
                logger.Error("Exception LookupHighriseContact. " + excp.Message);
                LogToMonitor(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Error looking up Highrise contact.", m_context.Owner));

                var errorResult = new CRMHeaders() { Pending = false, LookupError = "Error looking up Highrise contact." };

                if (callback != null)
                {
                    callback(errorResult);
                }

                return errorResult;
            }
        }