/// <summary>
 /// Default constructor.
 /// </summary>
 /// <param name="stack">Reference to SIP stack.</param>
 /// <param name="flow">SIP data flow.</param>
 /// <param name="request">Recieved request.</param>
 /// <param name="transaction">SIP server transaction which must be used to send response back to request maker.</param>
 internal SIP_RequestReceivedEventArgs(SIP_Stack stack, SIP_Flow flow, SIP_Request request, SIP_ServerTransaction transaction)
 {
     m_pStack       = stack;
     m_pFlow        = flow;
     m_pRequest     = request;
     m_pTransaction = transaction;
 }
        /// <summary>
        /// Creates new SIP server transaction for specified request.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to receive request.</param>
        /// <param name="request">SIP request.</param>
        /// <returns>Returns added server transaction.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        public SIP_ServerTransaction CreateServerTransaction(SIP_Flow flow, SIP_Request request)
        {
            if (m_IsDisposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            lock (m_pServerTransactions){
                SIP_ServerTransaction transaction = new SIP_ServerTransaction(m_pStack, flow, request);
                m_pServerTransactions.Add(transaction.Key, transaction);
                transaction.StateChanged += new EventHandler(delegate(object s, EventArgs e){
                    if (transaction.State == SIP_TransactionState.Terminated)
                    {
                        lock (m_pClientTransactions){
                            m_pServerTransactions.Remove(transaction.Key);
                        }
                    }
                });

                return(transaction);
            }
        }
Example #3
0
        /// <summary>
        /// Default incoming call constructor.
        /// </summary>
        /// <param name="ua">Owner UA.</param>
        /// <param name="invite">INVITE server transaction.</param>
        /// <exception cref="ArgumentNullException">Is riased when <b>ua</b> or <b>invite</b> is null reference.</exception>
        internal SIP_UA_Call(SIP_UA ua,SIP_ServerTransaction invite)
        {
            if(ua == null){
                throw new ArgumentNullException("ua");
            }
            if(invite == null){
                throw new ArgumentNullException("invite");
            }

            m_pUA = ua;
            m_pInitialInviteTransaction = invite;
            m_pLocalUri  = invite.Request.To.Address.Uri;
            m_pRemoteUri = invite.Request.From.Address.Uri;
            m_pInitialInviteTransaction.Canceled += new EventHandler(delegate(object sender,EventArgs e){
                // If transaction canceled, terminate call.
                SetState(SIP_UA_CallState.Terminated);
            });

            // Parse SDP if INVITE contains SDP.
            // RFC 3261 13.2.1. INVITE may be offerless, we must thne send offer and remote party sends sdp in ACK.
            if(invite.Request.ContentType != null && invite.Request.ContentType.ToLower().IndexOf("application/sdp") > -1){
                m_pRemoteSDP = SDP_Message.Parse(Encoding.UTF8.GetString(invite.Request.Data));
            }

            m_pTags = new Dictionary<string,object>();

            m_State = SIP_UA_CallState.WaitingToAccept;
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="stack">Owner stack.</param>
        /// <param name="request">Request to forward.</param>
        /// <param name="forkingMode">Specifies how proxy context must handle forking.</param>
        /// <param name="noCancel">Specifies if proxy should not send Cancel to forked requests.</param>
        /// <param name="noRecurse">Specifies what proxy server does when it gets 3xx response. If true proxy will forward
        /// request to new specified address if false, proxy will return 3xx response to caller.</param>
        /// <param name="destinations">Possible destinations. NOTE: These values must be in priority order !</param>
        /// <exception cref="ArgumentNullException">Is raised when any of the reference type prameters is null.</exception>
        public SIP_ProxyContext(SIP_Stack stack,SIP_Request request,SIP_ForkingMode forkingMode,bool noCancel,bool noRecurse,SIP_Destination[] destinations)
        {
            if(stack == null){
                throw new ArgumentNullException("stack");
            }
            if(request == null){
                throw new ArgumentNullException("serverTransaction");
            }
            if(destinations == null){
                throw new ArgumentNullException("destinations");
            }

            m_pStack = stack;

            m_pServerTransaction = stack.TransactionLayer.CreateServerTransaction(request);
            m_pServerTransaction.CanCreateDialog = false;
            m_pServerTransaction.Canceled += new EventHandler(m_pServerTransaction_Canceled);
            m_pServerTransaction.Terminated += new EventHandler(m_pServerTransaction_Terminated);

            m_ForkingMode = forkingMode;
            m_NoCancel    = noCancel;
            m_NoRecurse   = noRecurse;

            m_pClientTransactions = new List<SIP_ClientTransaction>();
            m_pResponses          = new List<SIP_Response>();
            m_CreateTime          = DateTime.Now;

            // Queue destinations up, higest to lowest.
            m_pRemainingDestinations = new Queue<SIP_Destination>();
            foreach(SIP_Destination destination in destinations){
                m_pRemainingDestinations.Enqueue(destination);
            }
        }
Example #5
0
 /// <summary>
 /// Raises <b>Reinvite</b> event.
 /// </summary>
 /// <param name="reinvite">Re-INVITE server transaction.</param>
 private void OnReinvite(SIP_ServerTransaction reinvite)
 {
     if (this.Reinvite != null)
     {
         this.Reinvite(this, new EventArgs());
     }
 }
 /// <summary>
 /// Default constructor.
 /// </summary>
 /// <param name="stack">Reference to SIP stack.</param>
 /// <param name="flow">SIP data flow.</param>
 /// <param name="request">Recieved request.</param>
 /// <param name="transaction">SIP server transaction which must be used to send response back to request maker.</param>
 internal SIP_RequestReceivedEventArgs(SIP_Stack stack,SIP_Flow flow,SIP_Request request,SIP_ServerTransaction transaction)
 {
     m_pStack       = stack;
     m_pFlow        = flow;
     m_pRequest     = request;
     m_pTransaction = transaction;
 }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="transaction">Server transaction.</param>
        /// <param name="response">SIP response.</param>
        /// <exception cref="ArgumentNullException">Is raised when any of the arguments is null.</exception>
        public SIP_ResponseSentEventArgs(SIP_ServerTransaction transaction,SIP_Response response)
        {
            if(transaction == null){
                throw new ArgumentNullException("transaction");
            }
            if(response == null){
                throw new ArgumentNullException("response");
            }

            m_pTransaction = transaction;
            m_pResponse    = response;
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="invite">SIP INVITE server transaction.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>invite</b> is null reference.</exception>
        public wfrm_IncomingCall(SIP_ServerTransaction invite)
        {
            if(invite == null){
                throw new ArgumentNullException("invite");
            }

            InitUI();

            m_pTransaction = invite;
            m_pTransaction.Canceled += new EventHandler(m_pTransaction_Canceled);

            m_pFrom.Text = invite.Request.To.Address.ToStringValue();
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="transaction">Server transaction.</param>
        /// <param name="response">SIP response.</param>
        /// <exception cref="ArgumentNullException">Is raised when any of the arguments is null.</exception>
        public SIP_ResponseSentEventArgs(SIP_ServerTransaction transaction, SIP_Response response)
        {
            if (transaction == null)
            {
                throw new ArgumentNullException("transaction");
            }
            if (response == null)
            {
                throw new ArgumentNullException("response");
            }

            m_pTransaction = transaction;
            m_pResponse    = response;
        }
        /// <summary>
        /// Matches SIP request to server transaction. If not matching transaction found, returns null.
        /// </summary>
        /// <param name="request">SIP request to match.</param>
        /// <returns>Returns matching transaction or null if no match.</returns>
        internal SIP_ServerTransaction MatchServerTransaction(SIP_Request request)
        {
            /* RFC 3261 17.2.3 Matching Requests to Server Transactions.
             *  This matching rule applies to both INVITE and non-INVITE transactions.
             *
             *  1. the branch parameter in the request is equal to the one in the top Via header
             *     field of the request that created the transaction, and
             *
             *  2. the sent-by value in the top Via of the request is equal to the
             *     one in the request that created the transaction, and
             *
             *  3. the method of the request matches the one that created the transaction, except
             *     for ACK, where the method of the request that created the transaction is INVITE.
             *
             *  Internal implementation notes:
             *      Inernally we use branch + '-' + sent-by for non-CANCEL and for CANCEL
             *      branch + '-' + sent-by + '-' CANCEL. This is because method matching is actually
             *      needed for CANCEL only (CANCEL shares cancelable transaction branch ID).
             */

            SIP_ServerTransaction retVal = null;

            /*
             *  We use branch and sent-by as indexing key for transaction, the only special what we need to
             *  do is to handle CANCEL, because it has same branch as transaction to be canceled.
             *  For avoiding key collision, we add branch + '-' + 'sent-by' + CANCEL for cancel index key.
             *  ACK has also same branch, but we won't do transaction for ACK, so it isn't problem.
             */
            string key = request.Via.GetTopMostValue().Branch + '-' + request.Via.GetTopMostValue().SentBy;

            if (request.RequestLine.Method == SIP_Methods.CANCEL)
            {
                key += "-CANCEL";
            }

            lock (m_pServerTransactions){
                m_pServerTransactions.TryGetValue(key, out retVal);
            }

            // Don't match ACK for terminated transaction, in that case ACK must be passed to "core".
            if (retVal != null && request.RequestLine.Method == SIP_Methods.ACK && retVal.State == SIP_TransactionState.Terminated)
            {
                retVal = null;
            }

            return(retVal);
        }
Example #11
0
        /// <summary>
        /// Default incoming call constructor.
        /// </summary>
        /// <param name="ua">Owner UA.</param>
        /// <param name="invite">INVITE server transaction.</param>
        /// <exception cref="ArgumentNullException">Is riased when <b>ua</b> or <b>invite</b> is null reference.</exception>
        internal SIP_UA_Call(SIP_UA ua,SIP_ServerTransaction invite)
        {
            if(ua == null){
                throw new ArgumentNullException("ua");
            }
            if(invite == null){
                throw new ArgumentNullException("invite");
            }

            m_pUA = ua;
            m_pInitialInviteTransaction = invite;
            m_pLocalUri  = invite.Request.To.Address.Uri;
            m_pRemoteUri = invite.Request.From.Address.Uri;
            m_pInitialInviteTransaction.Canceled += new EventHandler(delegate(object sender,EventArgs e){
                // If transaction canceled, terminate call.
                SetState(SIP_UA_CallState.Terminated);
            });

            m_State = SIP_UA_CallState.WaitingToAccept;
        }
        /// <summary>
        /// Ensures that specified request has matching server transaction. If server transaction doesn't exist,
        /// it will be created, otherwise existing transaction will be returned.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to receive request.</param>
        /// <param name="request">SIP request.</param>
        /// <returns>Returns matching transaction.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null.</exception>
        /// <exception cref="InvalidOperationException">Is raised when request.Method is ACK request.</exception>
        public SIP_ServerTransaction EnsureServerTransaction(SIP_Flow flow, SIP_Request request)
        {
            if (flow == null)
            {
                throw new ArgumentNullException("flow");
            }
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            if (request.RequestLine.Method == SIP_Methods.ACK)
            {
                throw new InvalidOperationException("ACK request is transaction less request, can't create transaction for it.");
            }

            /*
             *  We use branch and sent-by as indexing key for transaction, the only special what we need to
             *  do is to handle CANCEL, because it has same branch as transaction to be canceled.
             *  For avoiding key collision, we add branch + '-' + 'sent-by' + CANCEL for cancel index key.
             *  ACK has also same branch, but we won't do transaction for ACK, so it isn't problem.
             */
            string key = request.Via.GetTopMostValue().Branch + '-' + request.Via.GetTopMostValue().SentBy;

            if (request.RequestLine.Method == SIP_Methods.CANCEL)
            {
                key += "-CANCEL";
            }

            lock (m_pServerTransactions){
                SIP_ServerTransaction retVal = null;
                m_pServerTransactions.TryGetValue(key, out retVal);
                // We don't have transaction, create it.
                if (retVal == null)
                {
                    retVal = CreateServerTransaction(flow, request);
                }

                return(retVal);
            }
        }
        /// <summary>
        /// Matches CANCEL requst to SIP server non-CANCEL transaction. Returns null if no match.
        /// </summary>
        /// <param name="cancelRequest">SIP CANCEL request.</param>
        /// <returns>Returns CANCEL matching server transaction or null if no match.</returns>
        /// <exception cref="ArgumentNullException">Is raised when <b>cancelTransaction</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when <b>cancelTransaction</b> has invalid.</exception>
        public SIP_ServerTransaction MatchCancelToTransaction(SIP_Request cancelRequest)
        {
            if (cancelRequest == null)
            {
                throw new ArgumentNullException("cancelRequest");
            }
            if (cancelRequest.RequestLine.Method != SIP_Methods.CANCEL)
            {
                throw new ArgumentException("Argument 'cancelRequest' is not SIP CANCEL request.");
            }

            SIP_ServerTransaction retVal = null;

            // NOTE: There we don't add '-CANCEL' because we want to get CANCEL matching transaction, not CANCEL
            //       transaction itself.
            string key = cancelRequest.Via.GetTopMostValue().Branch + '-' + cancelRequest.Via.GetTopMostValue().SentBy;

            lock (m_pServerTransactions){
                m_pServerTransactions.TryGetValue(key, out retVal);
            }

            return(retVal);
        }
        /// <summary>
        /// Creates new SIP server transaction for specified request.
        /// </summary>
        /// <param name="request">SIP request.</param>
        /// <returns>Returns added server transaction.</returns>
        public SIP_ServerTransaction CreateServerTransaction(SIP_Request request)
        {
            string transactionID = request.Via.GetTopMostValue().Branch + "-" + request.CSeq.RequestMethod;

            // TODO: use Dictionary ? that works much faster.
            // FIX ME:
            SIP_ServerTransaction transaction = new SIP_ServerTransaction(m_pSipStack,request);
            m_pServerTransactions.Add(transaction);

            return transaction;
        }
 /// <summary>
 /// Removes specified transaction from SIP server transactions collection.
 /// </summary>
 /// <param name="transaction">Transaction to remove.</param>
 internal void RemoveServerTransaction(SIP_ServerTransaction transaction)
 {
     m_pServerTransactions.Remove(transaction);
 }
        /// <summary>
        /// Creates new SIP UAS dialog.
        /// </summary>
        /// <param name="transaction">Owner transaction what forces to create dialog.</param>
        /// <param name="request">Server transaction request what response it is.</param>
        /// <param name="response">SIP response what causes dialog creation.</param>
        /// <returns>Returns new SIP dialog.</returns>
        internal SIP_Dialog CreateDialog(SIP_ServerTransaction transaction,SIP_Request request,SIP_Response response)
        {
            // TODO: ren EnsureDialog

            SIP_Dialog dialog = new SIP_Dialog(m_pSipStack,request,response);
            m_pDialogs.Add(dialog);

            return dialog;
        }
Example #17
0
        internal SIP_ProxyContext CreateProxyContext(SIP_RequestContext requestContext,SIP_ServerTransaction transaction,SIP_Request request,bool addRecordRoute)
        {            
            // Create proxy context that will be responsible for forwarding request.
            SIP_ProxyContext proxyContext = new SIP_ProxyContext(
                this,
                transaction,
                request,
                addRecordRoute,
                m_ForkingMode,
                (this.ProxyMode & SIP_ProxyMode.B2BUA) != 0,
                false,
                false,
                requestContext.Targets.ToArray()
            );
            m_pProxyContexts.Add(proxyContext);

            return proxyContext;
        }
Example #18
0
        private void Handle2xx(SIP_Dialog dialog, SIP_ServerTransaction transaction)
        {
            if (dialog == null)
            {
                throw new ArgumentNullException("dialog");
            }
            if (transaction == null)
            {
                throw new ArgumentException("transaction");
            }

            /* RFC 6026 8.1.
                Once the response has been constructed, it is passed to the INVITE
                server transaction.  In order to ensure reliable end-to-end
                transport of the response, it is necessary to periodically pass
                the response directly to the transport until the ACK arrives.  The
                2xx response is passed to the transport with an interval that
                starts at T1 seconds and doubles for each retransmission until it
                reaches T2 seconds (T1 and T2 are defined in Section 17).
                Response retransmissions cease when an ACK request for the
                response is received.  This is independent of whatever transport
                protocols are used to send the response.
             
                If the server retransmits the 2xx response for 64*T1 seconds without
                receiving an ACK, the dialog is confirmed, but the session SHOULD be
                terminated.  This is accomplished with a BYE, as described in Section
                15.
              
                 T1 - 500
                 T2 - 4000
            */

            TimerEx timer = null;

            EventHandler<SIP_RequestReceivedEventArgs> callback = delegate(object s1, SIP_RequestReceivedEventArgs e)
            {
                try
                {
                    if (e.Request.RequestLine.Method == SIP_Methods.ACK)
                    {
                        // ACK for INVITE 2xx response received, stop retransmitting INVITE 2xx response.
                        if (transaction.Request.CSeq.SequenceNumber == e.Request.CSeq.SequenceNumber)
                        {
                            if (timer != null)
                            {
                                timer.Dispose();
                            }
                        }
                    }
                }
                catch
                {
                    // We don't care about errors here.
                }
            };
            dialog.RequestReceived += callback;

            // Create timer and sart retransmitting INVITE 2xx response.
            timer = new TimerEx(500);
            timer.AutoReset = false;
            timer.Elapsed += delegate(object s, System.Timers.ElapsedEventArgs e)
            {
                try
                {
                    lock (transaction.SyncRoot)
                    {
                        if (transaction.State == SIP_TransactionState.Accpeted)
                        {
                            transaction.SendResponse(transaction.FinalResponse);
                        }
                        else
                        {
                            timer.Dispose();

                            return;
                        }
                    }

                    timer.Interval = Math.Min(timer.Interval * 2, 4000);
                    timer.Enabled = true;
                }
                catch
                {
                    // We don't care about errors here.
                }
            };
            timer.Disposed += delegate(object s1, EventArgs e1)
            {
                try
                {
                    dialog.RequestReceived -= callback;
                }
                catch
                {
                    // We don't care about errors here.
                }
            };
            timer.Enabled = true;
        }
Example #19
0
        private void ProcessMediaOffer(SIP_Dialog dialog, SIP_ServerTransaction transaction, RTP_MultimediaSession rtpMultimediaSession, SDP_Message offer, SDP_Message localSDP)
        {
            if (dialog == null)
            {
                throw new ArgumentNullException("dialog");
            }
            if (transaction == null)
            {
                throw new ArgumentNullException("transaction");
            }
            if (rtpMultimediaSession == null)
            {
                throw new ArgumentNullException("rtpMultimediaSession");
            }
            if (offer == null)
            {
                throw new ArgumentNullException("offer");
            }
            if (localSDP == null)
            {
                throw new ArgumentNullException("localSDP");
            }

            try
            {
                #region SDP basic validation

                // Version field must exist.
                if (offer.Version == null)
                {
                    transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Required 'v'(Protocol Version) field is missing.", transaction.Request));

                    return;
                }

                // Origin field must exist.
                if (offer.Origin == null)
                {
                    transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Required 'o'(Origin) field is missing.", transaction.Request));

                    return;
                }

                // Session Name field.

                // Check That global 'c' connection attribute exists or otherwise each enabled media stream must contain one.
                if (offer.Connection == null)
                {
                    for (int i = 0; i < offer.MediaDescriptions.Count; i++)
                    {
                        if (offer.MediaDescriptions[i].Connection == null)
                        {
                            transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": Invalid SDP answer: Global or per media stream no: " + i + " 'c'(Connection) attribute is missing.", transaction.Request));

                            return;
                        }
                    }
                }

                #endregion

                // Re-INVITE media streams count must be >= current SDP streams.
                if (localSDP.MediaDescriptions.Count > offer.MediaDescriptions.Count)
                {
                    transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": re-INVITE SDP offer media stream count must be >= current session stream count.", transaction.Request));

                    return;
                }

                bool audioAccepted = false;
                // Process media streams info.
                for (int i = 0; i < offer.MediaDescriptions.Count; i++)
                {
                    SDP_MediaDescription offerMedia = offer.MediaDescriptions[i];
                    SDP_MediaDescription answerMedia = (localSDP.MediaDescriptions.Count > i ? localSDP.MediaDescriptions[i] : null);

                    // Disabled stream.
                    if (offerMedia.Port == 0)
                    {
                        // Remote-party offered new disabled stream.
                        if (answerMedia == null)
                        {
                            // Just copy offer media stream data to answer and set port to zero.
                            localSDP.MediaDescriptions.Add(offerMedia);
                            localSDP.MediaDescriptions[i].Port = 0;
                        }
                        // Existing disabled stream or remote party disabled it.
                        else
                        {
                            answerMedia.Port = 0;

                            #region Cleanup active RTP stream and it's resources, if it exists

                            // Dispose existing RTP session.
                            if (answerMedia.Tags.ContainsKey("rtp_session"))
                            {
                                ((RTP_Session)offerMedia.Tags["rtp_session"]).Dispose();
                                answerMedia.Tags.Remove("rtp_session");
                            }

                            // Release UPnPports if any.
                            if (answerMedia.Tags.ContainsKey("upnp_rtp_map"))
                            {
                                try
                                {
                                    m_pUPnP.DeletePortMapping((UPnP_NAT_Map)answerMedia.Tags["upnp_rtp_map"]);
                                }
                                catch
                                {
                                }
                                answerMedia.Tags.Remove("upnp_rtp_map");
                            }
                            if (answerMedia.Tags.ContainsKey("upnp_rtcp_map"))
                            {
                                try
                                {
                                    m_pUPnP.DeletePortMapping((UPnP_NAT_Map)answerMedia.Tags["upnp_rtcp_map"]);
                                }
                                catch
                                {
                                }
                                answerMedia.Tags.Remove("upnp_rtcp_map");
                            }

                            #endregion
                        }
                    }
                    // Remote-party wants to communicate with this stream.
                    else
                    {
                        // See if we can support this stream.
                        if (!audioAccepted && CanSupportMedia(offerMedia))
                        {
                            // New stream.
                            if (answerMedia == null)
                            {
                                answerMedia = new SDP_MediaDescription(SDP_MediaTypes.audio, 0, 2, "RTP/AVP", null);
                                localSDP.MediaDescriptions.Add(answerMedia);
                            }

                            #region Build audio codec map with codecs which we support

                            Dictionary<int, AudioCodec> audioCodecs = GetOurSupportedAudioCodecs(offerMedia);
                            answerMedia.MediaFormats.Clear();
                            answerMedia.Attributes.Clear();
                            foreach (KeyValuePair<int, AudioCodec> entry in audioCodecs)
                            {
                                answerMedia.Attributes.Add(new SDP_Attribute("rtpmap", entry.Key + " " + entry.Value.Name + "/" + entry.Value.CompressedAudioFormat.SamplesPerSecond));
                                answerMedia.MediaFormats.Add(entry.Key.ToString());
                            }
                            answerMedia.Attributes.Add(new SDP_Attribute("ptime", "20"));
                            answerMedia.Tags["audio_codecs"] = audioCodecs;

                            #endregion

                            #region Create/modify RTP session

                            // RTP session doesn't exist, create it.
                            if (!answerMedia.Tags.ContainsKey("rtp_session"))
                            {
                                RTP_Session rtpSess = CreateRtpSession(rtpMultimediaSession);
                                // RTP session creation failed,disable this stream.
                                if (rtpSess == null)
                                {
                                    answerMedia.Port = 0;

                                    break;
                                }
                                answerMedia.Tags.Add("rtp_session", rtpSess);

                                rtpSess.NewReceiveStream += delegate(object s, RTP_ReceiveStreamEventArgs e)
                                {
                                    if (answerMedia.Tags.ContainsKey("rtp_audio_out"))
                                    {
                                        ((AudioOut_RTP)answerMedia.Tags["rtp_audio_out"]).Dispose();
                                    }

                                    AudioOut_RTP audioOut = new AudioOut_RTP(m_pAudioOutDevice, e.Stream, audioCodecs);
                                    audioOut.Start();
                                    answerMedia.Tags["rtp_audio_out"] = audioOut;
                                };

                                // NAT
                                if (!HandleNAT(answerMedia, rtpSess))
                                {
                                    // NAT handling failed,disable this stream.
                                    answerMedia.Port = 0;

                                    break;
                                }
                            }

                            RTP_StreamMode offerStreamMode = GetRtpStreamMode(offer, offerMedia);
                            if (offerStreamMode == RTP_StreamMode.Inactive)
                            {
                                answerMedia.SetStreamMode("inactive");
                            }
                            else if (offerStreamMode == RTP_StreamMode.Receive)
                            {
                                answerMedia.SetStreamMode("sendonly");
                            }
                            else if (offerStreamMode == RTP_StreamMode.Send)
                            {
                                answerMedia.SetStreamMode("recvonly");
                            }
                            else if (offerStreamMode == RTP_StreamMode.SendReceive)
                            {
                                answerMedia.SetStreamMode("sendrecv");
                            }

                            RTP_Session rtpSession = (RTP_Session)answerMedia.Tags["rtp_session"];
                            rtpSession.Payload = Convert.ToInt32(answerMedia.MediaFormats[0]);
                            rtpSession.StreamMode = GetRtpStreamMode(localSDP, answerMedia);
                            rtpSession.RemoveTargets();
                            if (GetSdpHost(offer, offerMedia) != "0.0.0.0")
                            {
                                rtpSession.AddTarget(GetRtpTarget(offer, offerMedia));
                            }
                            rtpSession.Start();

                            #endregion

                            #region Create/modify audio-in source

                            if (!answerMedia.Tags.ContainsKey("rtp_audio_in"))
                            {
                                AudioIn_RTP rtpAudioIn = new AudioIn_RTP(m_pAudioInDevice, 20, audioCodecs, rtpSession.CreateSendStream());
                                rtpAudioIn.Start();
                                answerMedia.Tags.Add("rtp_audio_in", rtpAudioIn);
                            }
                            else
                            {
                                ((AudioIn_RTP)answerMedia.Tags["rtp_audio_in"]).AudioCodecs = audioCodecs;
                            }

                            #endregion

                            audioAccepted = true;
                        }
                        // We don't accept this stream, so disable it.
                        else
                        {
                            // Just copy offer media stream data to answer and set port to zero.

                            // Delete exisiting media stream.
                            if (answerMedia != null)
                            {
                                localSDP.MediaDescriptions.RemoveAt(i);
                            }
                            localSDP.MediaDescriptions.Add(offerMedia);
                            localSDP.MediaDescriptions[i].Port = 0;
                        }
                    }
                }

                #region Create and send 2xx response

                SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, transaction.Request, transaction.Flow);
                //response.Contact = SIP stack will allocate it as needed;
                response.ContentType = "application/sdp";
                response.Data = localSDP.ToByte();

                transaction.SendResponse(response);

                // Start retransmitting 2xx response, while ACK receives.
                Handle2xx(dialog, transaction);

                // REMOVE ME: 27.11.2010
                // Start retransmitting 2xx response, while ACK receives.
                //m_pInvite2xxMgr.Add(dialog,transaction);

                #endregion
            }
            catch (Exception x)
            {
                transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x.Message, transaction.Request));
            }
        }
        /// <summary>
        /// Sends response to request maker using RFC 3261 18. rules.
        /// </summary>
        /// <param name="transaction">Owner server transaction. Can be null if stateless response sending.</param>
        /// <param name="response">SIP response to send.</param>
        /// <param name="localEP">Local IP end point to use for sending resposne. Value null means system will allocate it.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when stack ahs not been started and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        /// <exception cref="SIP_TransportException">Is raised when <b>response</b> sending has failed.</exception>
        private void SendResponseInternal(SIP_ServerTransaction transaction,SIP_Response response,IPEndPoint localEP)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(!m_IsRunning){
                throw new InvalidOperationException("Stack has not been started.");
            }
            if(response == null){
                throw new ArgumentNullException("response");
            }

            /* RFC 3261 18.2.2.
                The server transport uses the value of the top Via header field in
                order to determine where to send a response.  It MUST follow the
                following process:

                    o  If the "sent-protocol" is a reliable transport protocol such as
                       TCP or SCTP, or TLS over those, the response MUST be sent using
                       the existing connection to the source of the original request
                       that created the transaction, if that connection is still open.
                       This requires the server transport to maintain an association
                       between server transactions and transport connections.  If that
                       connection is no longer open, the server SHOULD open a
                       connection to the IP address in the "received" parameter, if
                       present, using the port in the "sent-by" value, or the default
                       port for that transport, if no port is specified.  If that
                       connection attempt fails, the server SHOULD use the procedures
                       in [4] for servers in order to determine the IP address and
                       port to open the connection and send the response to.

                    o  Otherwise, if the Via header field value contains a "maddr"
                       parameter, the response MUST be forwarded to the address listed
                       there, using the port indicated in "sent-by", or port 5060 if
                       none is present.  If the address is a multicast address, the
                       response SHOULD be sent using the TTL indicated in the "ttl"
                       parameter, or with a TTL of 1 if that parameter is not present.

                    o  Otherwise (for unreliable unicast transports), if the top Via
                       has a "received" parameter, the response MUST be sent to the
                       address in the "received" parameter, using the port indicated
                       in the "sent-by" value, or using port 5060 if none is specified
                       explicitly.  If this fails, for example, elicits an ICMP "port
                       unreachable" response, the procedures of Section 5 of [4]
                       SHOULD be used to determine where to send the response.

                    o  Otherwise, if it is not receiver-tagged, the response MUST be
                       sent to the address indicated by the "sent-by" value, using the
                       procedures in Section 5 of [4].
            */

            /* RFC 3581 4. (Adds new processing between RFC 3261 18.2.2. bullet 2 and 3)
                When a server attempts to send a response, it examines the topmost
                Via header field value of that response.  If the "sent-protocol"
                component indicates an unreliable unicast transport protocol, such as
                UDP, and there is no "maddr" parameter, but there is both a
                "received" parameter and an "rport" parameter, the response MUST be
                sent to the IP address listed in the "received" parameter, and the
                port in the "rport" parameter.  The response MUST be sent from the
                same address and port that the corresponding request was received on.
                This effectively adds a new processing step between bullets two and
                three in Section 18.2.2 of SIP [1].

                The response must be sent from the same address and port that the
                request was received on in order to traverse symmetric NATs.  When a
                server is listening for requests on multiple ports or interfaces, it
                will need to remember the one on which the request was received.  For
                a stateful proxy, storing this information for the duration of the
                transaction is not an issue.  However, a stateless proxy does not
                store state between a request and its response, and therefore cannot
                remember the address and port on which a request was received.  To
                properly implement this specification, a stateless proxy can encode
                the destination address and port of a request into the Via header
                field value that it inserts.  When the response arrives, it can
                extract this information and use it to forward the response.
            */

            SIP_t_ViaParm via = response.Via.GetTopMostValue();
            if(via == null){
                throw new ArgumentException("Argument 'response' does not contain required Via: header field.");
            }

            // TODO: If transport is not supported.
            //throw new SIP_TransportException("Not supported transport '" + via.ProtocolTransport + "'.");

            string logID         = Guid.NewGuid().ToString();
            string transactionID = transaction == null ? "" : transaction.ID;

            // Try to get local IP end point which we should use to send response back.
            if(transaction != null && transaction.Request.LocalEndPoint != null){
                localEP = transaction.Request.LocalEndPoint;
            }

            // TODO: no "localEP" at moment

            // TODO: Stateless should use flowID instead.

            // Our stateless proxy add 'localEP' parameter to Via: if so normally we can get it from there.
            else if(via.Parameters["localEP"] != null){
                localEP = Net_Utils.ParseIPEndPoint(via.Parameters["localEP"].Value);
            }

            byte[] responseData = response.ToByteData();

            #region Try existing flow first

            /* First try active flow to send response, thats not 100% as RFC says, but works better in any case.
               RFC says that for TCP and TLS only, we do it for any transport.
            */

            if(transaction != null){
                try{
                    SIP_Flow flow = transaction.Flow;
                    flow.Send(response);

                    if(m_pStack.Logger != null){
                            m_pStack.Logger.AddWrite(
                                logID,
                                null,
                                0,
                                "Response [flowReuse=true; transactionID='" + transactionID + "'; method='" + response.CSeq.RequestMethod + "'; cseq='" + response.CSeq.SequenceNumber + "'; " +
                                    "transport='" + flow.Transport + "'; size='" + responseData.Length + "'; statusCode='" + response.StatusCode + "'; " +
                                    "reason='" + response.ReasonPhrase + "'; sent '" + flow.LocalEP + "' -> '" + flow.RemoteEP + "'.",
                                localEP,
                                flow.RemoteEP,
                                responseData
                            );
                        }

                    return;
                }
                catch{
                    // Do nothing, processing will continue.
                }
            }

            #endregion

            #region Reliable TCP,TLS, ...

            if(SIP_Utils.IsReliableTransport(via.ProtocolTransport)){
                // Get original request remote end point.
                IPEndPoint remoteEP = null;
                if(transaction != null && transaction.Request.RemoteEndPoint != null){
                    remoteEP = transaction.Request.RemoteEndPoint;
                }
                else if(via.Received != null){
                    remoteEP = new IPEndPoint(via.Received,via.SentBy.Port == -1 ? 5060 : via.SentBy.Port);
                }

                #region If original request connection alive, use it

                try{
                    SIP_Flow flow = null;

                    // Statefull
                    if(transaction != null){
                        if(transaction.Request.Flow != null && !transaction.Request.Flow.IsDisposed){
                            flow = transaction.Request.Flow;
                        }
                    }
                    // Stateless
                    else{
                        string flowID = via.Parameters["connectionID"].Value;
                        if(flowID != null){
                            flow = m_pFlowManager[flowID];
                        }
                    }

                    if(flow != null){
                        flow.Send(response);

                        if(m_pStack.Logger != null){
                            m_pStack.Logger.AddWrite(
                                logID,
                                null,
                                0,
                                "Response [flowReuse=true; transactionID='" + transactionID + "'; method='" + response.CSeq.RequestMethod + "'; cseq='" + response.CSeq.SequenceNumber + "'; " +
                                    "transport='" + flow.Transport + "'; size='" + responseData.Length + "'; statusCode='" + response.StatusCode + "'; " +
                                    "reason='" + response.ReasonPhrase + "'; sent '" + flow.RemoteEP + "' -> '" + flow.LocalEP + "'.",
                                localEP,
                                remoteEP,
                                responseData
                            );
                        }

                        return;
                    }
                }
                catch{
                    // Do nothing, processing will continue.
                    // Override RFC, if there is any existing connection and it gives error, try always RFC 3261 18.2.2(recieved) and 3265 5.
                }

                #endregion

                #region Send RFC 3261 18.2.2(recieved)

                if(remoteEP != null){
                    try{
                        SendResponseToHost(logID,transactionID,null,remoteEP.Address.ToString(),remoteEP.Port,via.ProtocolTransport,response);
                    }
                    catch{
                        // Do nothing, processing will continue -> "RFC 3265 5.".
                    }
                }

                #endregion

                #region Send RFC 3265 5.

                SendResponse_RFC_3263_5(logID,transactionID,localEP,response);

                #endregion

            }

            #endregion

            #region UDP Via: maddr parameter

            else if(via.Maddr != null){
                throw new SIP_TransportException("Sending responses to multicast address(Via: 'maddr') is not supported.");
            }

            #endregion

            #region RFC 3581 4. UDP Via: received and rport parameters

            else if(via.Maddr == null && via.Received != null && via.RPort > 0){
                SendResponseToHost(logID,transactionID,localEP,via.Received.ToString(),via.RPort,via.ProtocolTransport,response);
            }

            #endregion

            #region UDP Via: received parameter

            else if(via.Received != null){
                SendResponseToHost(logID,transactionID,localEP,via.Received.ToString(),via.SentByPortWithDefault,via.ProtocolTransport,response);
            }

            #endregion

            #region UDP

            else{
                SendResponse_RFC_3263_5(logID,transactionID,localEP,response);
            }

            #endregion
        }
        /// <summary>
        /// Sends specified response back to request maker using RFC 3261 18. rules.
        /// </summary>
        /// <param name="transaction">SIP server transaction which response to send.</param>
        /// <param name="response">SIP response.</param>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception>
        /// <exception cref="InvalidOperationException">Is raised when stack ahs not been started and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>transaction</b> or <b>response</b> is null reference.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        /// <exception cref="SIP_TransportException">Is raised when <b>response</b> sending has failed.</exception>
        internal void SendResponse(SIP_ServerTransaction transaction,SIP_Response response)
        {
            if(transaction == null){
                throw new ArgumentNullException("transaction");
            }
            // NOTE: all other paramter / state validations are done in SendResponseInternal.

            SendResponseInternal(transaction,response,null);
        }
        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public void Dispose()
        {
            if(m_Disposed){
                return;
            }
            m_Disposed = true;

            m_pStack                 = null;
            m_pServerTransaction     = null;
            m_pClientTransactions    = null;
            m_pResponses             = null;
            m_pRemainingDestinations = null;
        }
        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public void Dispose()
        {
            lock(m_pLock){
                if(m_IsDisposed){
                    return;
                }
                m_IsDisposed = true;

                m_pProxy.Stack.Logger.AddText("ProxyContext(id='" + m_ID + "') disposed.");

                m_pProxy.m_pProxyContexts.Remove(this);

                m_pProxy             = null;
                m_pServerTransaction = null;
                m_pTargetsHandlers   = null;
                m_pResponses         = null;
                m_pTargets           = null;
            }
        }
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="proxy">Owner proxy.</param>
        /// <param name="transaction">Server transaction what is used to send SIP responses back to caller.</param>
        /// <param name="request">Request to forward.</param>
        /// <param name="addRecordRoute">If true, Record-Route header field will be added.</param>
        /// <param name="forkingMode">Specifies how proxy context must handle forking.</param>
        /// <param name="isB2BUA">Specifies if proxy context is in B2BUA or just transaction satefull mode.</param>
        /// <param name="noCancel">Specifies if proxy should not send Cancel to forked requests.</param>
        /// <param name="noRecurse">Specifies what proxy server does when it gets 3xx response. If true proxy will forward
        /// request to new specified address if false, proxy will return 3xx response to caller.</param>
        /// <param name="targets">Possible remote targets. NOTE: These values must be in priority order !</param>
        /// <param name="credentials">Target set credentials.</param>
        /// <exception cref="ArgumentNullException">Is raised when any of the reference type prameters is null.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        internal SIP_ProxyContext(SIP_ProxyCore proxy,SIP_ServerTransaction transaction,SIP_Request request,bool addRecordRoute,SIP_ForkingMode forkingMode,bool isB2BUA,bool noCancel,bool noRecurse,SIP_ProxyTarget[] targets,NetworkCredential[] credentials)
        {
            if(proxy == null){
                throw new ArgumentNullException("proxy");
            }
            if(transaction == null){
                throw new ArgumentNullException("transaction");
            }
            if(request == null){
                throw new ArgumentNullException("request");
            }
            if(targets == null){
                throw new ArgumentNullException("targets");
            }
            if(targets.Length == 0){
                throw new ArgumentException("Argumnet 'targets' must contain at least 1 value.");
            }

            m_pProxy = proxy;

            m_pServerTransaction = transaction;
            m_pServerTransaction.Canceled += new EventHandler(m_pServerTransaction_Canceled);
            m_pServerTransaction.Disposed += new EventHandler(m_pServerTransaction_Disposed);

            m_pRequest       = request;
            m_AddRecordRoute = addRecordRoute;
            m_ForkingMode    = forkingMode;
            m_IsB2BUA        = isB2BUA;
            m_NoCancel       = noCancel;
            m_NoRecurse      = noRecurse;

            m_pTargetsHandlers = new List<TargetHandler>();
            m_pResponses       = new List<SIP_Response>();
            m_ID               = Guid.NewGuid().ToString();
            m_CreateTime       = DateTime.Now;

            // Queue targets up, higest to lowest.
            m_pTargets = new Queue<TargetHandler>();
            foreach(SIP_ProxyTarget target in targets){
                m_pTargets.Enqueue(new TargetHandler(this,target.Flow,target.TargetUri,m_AddRecordRoute,false));
            }

            m_pCredentials = new List<NetworkCredential>();
            m_pCredentials.AddRange(credentials);

            /*  RFC 3841 9.1.
                The Request-Disposition header field specifies caller preferences for
                how a server should process a request.

                Override SIP proxy default value.
            */
            foreach(SIP_t_Directive directive in request.RequestDisposition.GetAllValues()){
                if(directive.Directive == SIP_t_Directive.DirectiveType.NoFork){
                    m_ForkingMode = SIP_ForkingMode.None;
                }
                else if(directive.Directive == SIP_t_Directive.DirectiveType.Parallel){
                    m_ForkingMode = SIP_ForkingMode.Parallel;
                }
                else if(directive.Directive == SIP_t_Directive.DirectiveType.Sequential){
                    m_ForkingMode = SIP_ForkingMode.Sequential;
                }
                else if(directive.Directive == SIP_t_Directive.DirectiveType.NoCancel){
                    m_NoCancel = true;
                }
                else if(directive.Directive == SIP_t_Directive.DirectiveType.NoRecurse){
                    m_NoRecurse = true;
                }
            }

            m_pProxy.Stack.Logger.AddText("ProxyContext(id='" + m_ID + "') created.");
        }
        /// <summary>
        /// Creates new SIP server transaction for specified request.
        /// </summary>
        /// <param name="flow">SIP data flow which is used to receive request.</param>
        /// <param name="request">SIP request.</param>
        /// <returns>Returns added server transaction.</returns>
        /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
        /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception>
        public SIP_ServerTransaction CreateServerTransaction(SIP_Flow flow,SIP_Request request)
        {
            if(m_IsDisposed){
                throw new ObjectDisposedException(this.GetType().Name);
            }
            if(flow == null){
                throw new ArgumentNullException("flow");
            }
            if(request == null){
                throw new ArgumentNullException("request");
            }

            lock(m_pServerTransactions){
                SIP_ServerTransaction transaction = new SIP_ServerTransaction(m_pStack,flow,request);
                m_pServerTransactions.Add(transaction.Key,transaction);
                transaction.StateChanged += new EventHandler(delegate(object s,EventArgs e){
                    if(transaction.State == SIP_TransactionState.Terminated){
                        lock(m_pClientTransactions){
                            m_pServerTransactions.Remove(transaction.Key);
                        }
                    }
                });

                return transaction;
            }
        }