/// <summary> /// Processes specified response through this dialog. /// </summary> /// <param name="response">SIP response to process.</param> /// <returns>Returns true if this dialog processed specified response, otherwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null.</exception> internal protected virtual bool ProcessResponse(SIP_Response response) { if (response == null) { throw new ArgumentNullException("response"); } return(false); }
/// <summary> /// Gets existing or creates new dialog. /// </summary> /// <param name="transaction">Owner transaction what forces to create dialog.</param> /// <param name="response">Response what forces to create dialog.</param> /// <returns>Returns dialog.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>transaction</b> or <b>response</b> is null.</exception> public SIP_Dialog GetOrCreateDialog(SIP_Transaction transaction, SIP_Response response) { if (transaction == null) { throw new ArgumentNullException("transaction"); } if (response == null) { throw new ArgumentNullException("response"); } string dialogID = ""; if (transaction is SIP_ServerTransaction) { dialogID = response.CallID + "-" + response.To.Tag + "-" + response.From.Tag; } else { dialogID = response.CallID + "-" + response.From.Tag + "-" + response.To.Tag; } lock (m_pDialogs){ SIP_Dialog dialog = null; m_pDialogs.TryGetValue(dialogID, out dialog); // Dialog doesn't exist, create it. if (dialog == null) { if (response.CSeq.RequestMethod.ToUpper() == SIP_Methods.INVITE) { dialog = new SIP_Dialog_Invite(); } else if (response.CSeq.RequestMethod.ToUpper() == SIP_Methods.REFER) { dialog = new SIP_Dialog_Refer(); } else { throw new ArgumentException("Method '" + response.CSeq.RequestMethod + "' has no dialog handler."); } dialog.Init(m_pStack, transaction, response); dialog.StateChanged += delegate(object s, EventArgs a){ if (dialog.State == SIP_DialogState.Terminated) { m_pDialogs.Remove(dialog.ID); } }; m_pDialogs.Add(dialog.ID, dialog); } return(dialog); } }
/// <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> /// Adds specified response to transaction responses collection. /// </summary> /// <param name="response">SIP response.</param> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception> protected void AddResponse(SIP_Response response) { if (response == null) { throw new ArgumentNullException("response"); } // Don't store more than 15 responses, otherwise hacker may try todo buffer overrun with provisional responses. if (m_pResponses.Count < 15 || response.StatusCode >= 200) { m_pResponses.Add(response); } }
/// <summary> /// Parses SIP_Response from stream. /// </summary> /// <param name="stream">Stream what contains valid SIP response.</param> /// <returns>Returns parsed SIP_Response obeject.</returns> /// <exception cref="ArgumentNullException">Raised when <b>stream</b> is null.</exception> /// <exception cref="SIP_ParseException">Raised when invalid SIP message.</exception> public static SIP_Response Parse(Stream stream) { /* Syntax: * SIP-Version SP Status-Code SP Reason-Phrase * SIP-Message */ if (stream == null) { throw new ArgumentNullException("stream"); } SIP_Response retVal = new SIP_Response(); // Parse Response-line StreamLineReader r = new StreamLineReader(stream); r.Encoding = "utf-8"; string[] version_code_text = r.ReadLineString().Split(new char[] { ' ' }, 3); if (version_code_text.Length != 3) { throw new SIP_ParseException("Invalid SIP Status-Line syntax ! Syntax: {SIP-Version SP Status-Code SP Reason-Phrase}."); } // SIP-Version try { retVal.SipVersion = Convert.ToDouble(version_code_text[0].Split('/')[1], System.Globalization.NumberFormatInfo.InvariantInfo); } catch { throw new SIP_ParseException("Invalid Status-Line SIP-Version value !"); } // Status-Code try { retVal.StatusCode = Convert.ToInt32(version_code_text[1]); } catch { throw new SIP_ParseException("Invalid Status-Line Status-Code value !"); } // Reason-Phrase retVal.ReasonPhrase = version_code_text[2]; // Parse SIP-Message retVal.InternalParse(stream); return(retVal); }
/// <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> /// Processes retransmited INVITE 2xx response. /// </summary> /// <param name="response">INVITE 2xx response.</param> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception> public void Process(SIP_Response response) { if (response == null) { throw new ArgumentNullException("response"); } lock (m_pLock){ SIP_Request ack = CreateAck(); try{ // Try existing flow. m_pDialog.Flow.Send(ack); // Log if (m_pDialog.Stack.Logger != null) { byte[] ackBytes = ack.ToByteData(); m_pDialog.Stack.Logger.AddWrite( m_pDialog.ID, null, ackBytes.Length, "Dialog [id='" + m_pDialog.ID + "] ACK sent for 2xx response.", m_pDialog.Flow.LocalEP, m_pDialog.Flow.RemoteEP, ackBytes ); } } catch { /* RFC 3261 13.2.2.4. * Once the ACK has been constructed, the procedures of [4] are used to * determine the destination address, port and transport. However, the * request is passed to the transport layer directly for transmission, * rather than a client transaction. */ try{ m_pDialog.Stack.TransportLayer.SendRequest(ack); } catch (Exception x) { // Log if (m_pDialog.Stack.Logger != null) { m_pDialog.Stack.Logger.AddText("Dialog [id='" + m_pDialog.ID + "'] ACK send for 2xx response failed: " + x.Message + "."); } } } } }
/// <summary> /// Creates and sends ACK for final(3xx - 6xx) failure response. /// </summary> /// <param name="response">SIP response.</param> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null.</exception> private void SendAck(SIP_Response response) { if (response == null) { throw new ArgumentNullException("resposne"); } /* RFC 3261 17.1.1.3 Construction of the ACK Request. * The ACK request constructed by the client transaction MUST contain * values for the Call-ID, From, and Request-URI that are equal to the * values of those header fields in the request passed to the transport * by the client transaction (call this the "original request"). The To * header field in the ACK MUST equal the To header field in the * response being acknowledged, and therefore will usually differ from * the To header field in the original request by the addition of the * tag parameter. The ACK MUST contain a single Via header field, and * this MUST be equal to the top Via header field of the original * request. The CSeq header field in the ACK MUST contain the same * value for the sequence number as was present in the original request, * but the method parameter MUST be equal to "ACK". * * If the INVITE request whose response is being acknowledged had Route * header fields, those header fields MUST appear in the ACK. This is * to ensure that the ACK can be routed properly through any downstream * stateless proxies. */ SIP_Request ackRequest = new SIP_Request(SIP_Methods.ACK); ackRequest.RequestLine.Uri = this.Request.RequestLine.Uri; ackRequest.Via.AddToTop(this.Request.Via.GetTopMostValue().ToStringValue()); ackRequest.CallID = this.Request.CallID; ackRequest.From = this.Request.From; ackRequest.To = response.To; ackRequest.CSeq = new SIP_t_CSeq(this.Request.CSeq.SequenceNumber, "ACK"); foreach (SIP_HeaderField h in response.Header.Get("Route:")) { ackRequest.Header.Add("Route:", h.Value); } ackRequest.MaxForwards = 70; try{ // Send request to target. this.Stack.TransportLayer.SendRequest(this.Flow, ackRequest, this); } catch (SIP_TransportException x) { OnTransportError(x); SetState(SIP_TransactionState.Terminated); } }
/// <summary> /// Sends specified response to flow remote end point. /// </summary> /// <param name="response">SIP response to send.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception> public void Send(SIP_Response response) { lock (m_pLock){ if (m_IsDisposed) { throw new ObjectDisposedException(this.GetType().Name); } if (response == null) { throw new ArgumentNullException("response"); } SendInternal(response.ToByteData()); } }
/// <summary> /// Checks if specified response matches this 2xx response retransmission wait entry. /// </summary> /// <param name="response">INVITE 2xx response.</param> /// <returns>Returns true if response matches, othwerwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference value.</exception> public bool Match(SIP_Response response) { if (response == null) { throw new ArgumentNullException("response"); } if (m_pInvite.CSeq.RequestMethod == response.CSeq.RequestMethod && m_pInvite.CSeq.SequenceNumber == response.CSeq.SequenceNumber) { return(true); } else { return(false); } }
/// <summary> /// Is raised when INVITE 100 (Trying) response must be sent if no response sent by transaction user. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pTimer100_Elapsed(object sender #if !NETSTANDARD , System.Timers.ElapsedEventArgs e #endif ) { lock (this.SyncRoot){ // RFC 3261 17.2.1. TU didn't generate response in 200 ms, send '100 Trying' to stop request retransmission. if (this.State == SIP_TransactionState.Proceeding && this.Responses.Length == 0) { /* RFC 3261 17.2.1. * The 100 (Trying) response is constructed according to the procedures in Section 8.2.6, except that the * insertion of tags in the To header field of the response (when none was present in the request) * is downgraded from MAY to SHOULD NOT. * * RFC 3261 8.2.6. * When a 100 (Trying) response is generated, any Timestamp header field present in the request MUST * be copied into this 100 (Trying) response. If there is a delay in generating the response, the UAS * SHOULD add a delay value into the Timestamp value in the response. This value MUST contain the difference * between the time of sending of the response and receipt of the request, measured in seconds. */ SIP_Response tryingResponse = this.Stack.CreateResponse(SIP_ResponseCodes.x100_Trying, this.Request); if (this.Request.Timestamp != null) { tryingResponse.Timestamp = new SIP_t_Timestamp(this.Request.Timestamp.Time, (DateTime.Now - this.CreateTime).Seconds); } try{ this.Stack.TransportLayer.SendResponse(this, tryingResponse); } catch (Exception x) { OnTransportError(x); SetState(SIP_TransactionState.Terminated); return; } } if (m_pTimer100 != null) { m_pTimer100.Dispose(); m_pTimer100 = null; } } }
/// <summary> /// Cleans up any resources being used. /// </summary> public void Dispose() { lock (m_pLock){ if (m_IsDisposed) { return; } m_IsDisposed = true; m_pDialog.m_pUasInvite2xxRetransmits.Remove(this); if (m_pTimer != null) { m_pTimer.Dispose(); m_pTimer = null; } m_pDialog = null; m_pResponse = null; } }
/// <summary> /// Processes specified response through this dialog. /// </summary> /// <param name="response">SIP response to process.</param> /// <returns>Returns true if this dialog processed specified response, otherwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null.</exception> internal protected override bool ProcessResponse(SIP_Response response) { if (response == null) { throw new ArgumentNullException("response"); } if (response.StatusCodeType == SIP_StatusCodeType.Success) { // Search pending INVITE 2xx response retransmission waite entry. foreach (UacInvite2xxRetransmissionWaiter w in m_pUacInvite2xxRetransmitWaits) { if (w.Match(response)) { w.Process(response); return(true); } } } return(false); }
/// <summary> /// Default constructor. /// </summary> /// <param name="dialog">Owner INVITE dialog.</param> /// <param name="response">INVITE 2xx response.</param> /// <exception cref="ArgumentNullException">Is raised when <b>dialog</b> or <b>response</b> is null reference.</exception> public UasInvite2xxRetransmit(SIP_Dialog_Invite dialog, SIP_Response response) { if (dialog == null) { throw new ArgumentNullException("dialog"); } if (response == null) { throw new ArgumentNullException("response"); } m_pDialog = dialog; m_pResponse = response; /* RFC 3261 13.3.1.4. * Once the response has been constructed, it is passed to the INVITE * server transaction. Note, however, that the INVITE server * transaction will be destroyed as soon as it receives this final * response and passes it to the transport. Therefore, 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. * * Since 2xx is retransmitted end-to-end, there may be hops between * UAS and UAC that are UDP. To ensure reliable delivery across * these hops, the response is retransmitted periodically even if the * transport at the UAS is reliable. */ m_pTimer = new TimerEx(SIP_TimerConstants.T1, false); m_pTimer.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimer_Elapsed); m_pTimer.Enabled = true; }
/// <summary> /// Matches SIP response to client transaction. If not matching transaction found, returns null. /// </summary> /// <param name="response">SIP response to match.</param> internal SIP_ClientTransaction MatchClientTransaction(SIP_Response response) { /* RFC 3261 17.1.3 Matching Responses to Client Transactions. * 1. If the response has the same value of the branch parameter in * the top Via header field as the branch parameter in the top * Via header field of the request that created the transaction. * * 2. If the method parameter in the CSeq header field matches the * method of the request that created the transaction. The * method is needed since a CANCEL request constitutes a * different transaction, but shares the same value of the branch * parameter. */ SIP_ClientTransaction retVal = null; string transactionID = response.Via.GetTopMostValue().Branch + "-" + response.CSeq.RequestMethod; lock (m_pClientTransactions){ m_pClientTransactions.TryGetValue(transactionID, out retVal); } return(retVal); }
/// <summary> /// Initializes dialog. /// </summary> /// <param name="stack">Owner stack.</param> /// <param name="transaction">Owner transaction.</param> /// <param name="response">SIP response what caused dialog creation.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>transaction</b> or <b>response</b>.</exception> internal protected override void Init(SIP_Stack stack, SIP_Transaction transaction, SIP_Response response) { if (stack == null) { throw new ArgumentNullException("stack"); } if (transaction == null) { throw new ArgumentNullException("transaction"); } if (response == null) { throw new ArgumentNullException("response"); } base.Init(stack, transaction, response); if (transaction is SIP_ServerTransaction) { if (response.StatusCodeType == SIP_StatusCodeType.Success) { SetState(SIP_DialogState.Early, false); // We need to retransmit 2xx response while we get ACK or timeout. (RFC 3261 13.3.1.4.) m_pUasInvite2xxRetransmits.Add(new UasInvite2xxRetransmit(this, response)); } else if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { SetState(SIP_DialogState.Early, false); m_pActiveInvite = transaction; m_pActiveInvite.StateChanged += delegate(object s, EventArgs a){ if (m_pActiveInvite != null && m_pActiveInvite.State == SIP_TransactionState.Terminated) { m_pActiveInvite = null; } }; // Once we send 2xx response, we need to retransmit it while get ACK or timeout. (RFC 3261 13.3.1.4.) ((SIP_ServerTransaction)m_pActiveInvite).ResponseSent += delegate(object s, SIP_ResponseSentEventArgs a){ if (a.Response.StatusCodeType == SIP_StatusCodeType.Success) { m_pUasInvite2xxRetransmits.Add(new UasInvite2xxRetransmit(this, a.Response)); } }; } else { throw new ArgumentException("Argument 'response' has invalid status code, 1xx - 2xx is only allowed."); } } else { if (response.StatusCodeType == SIP_StatusCodeType.Success) { SetState(SIP_DialogState.Confirmed, false); // Wait for retransmited 2xx responses. (RFC 3261 13.2.2.4.) m_pUacInvite2xxRetransmitWaits.Add(new UacInvite2xxRetransmissionWaiter(this, transaction.Request)); } else if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { SetState(SIP_DialogState.Early, false); m_pActiveInvite = transaction; m_pActiveInvite.StateChanged += delegate(object s, EventArgs a){ if (m_pActiveInvite != null && m_pActiveInvite.State == SIP_TransactionState.Terminated) { m_pActiveInvite = null; } }; // Once we receive 2xx response, we need to wait for retransmitted 2xx responses. (RFC 3261 13.2.2.4) ((SIP_ClientTransaction)m_pActiveInvite).ResponseReceived += delegate(object s, SIP_ResponseReceivedEventArgs a){ if (a.Response.StatusCodeType == SIP_StatusCodeType.Success) { UacInvite2xxRetransmissionWaiter waiter = new UacInvite2xxRetransmissionWaiter(this, m_pActiveInvite.Request); m_pUacInvite2xxRetransmitWaits.Add(waiter); // Force to send initial ACK to 2xx response. waiter.Process(a.Response); SetState(SIP_DialogState.Confirmed, true); } }; } else { throw new ArgumentException("Argument 'response' has invalid status code, 1xx - 2xx is only allowed."); } } }
/// <summary> /// Sends specified response to remote party. /// </summary> /// <param name="response">SIP response to send.</param> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception> public void SendResponse(SIP_Response response) { lock(this.SyncRoot){ if(this.State == SIP_TransactionState.Disposed){ throw new ObjectDisposedException(this.GetType().Name); } if(response == null){ throw new ArgumentNullException("response"); } try{ #region INVITE /* RFC 6026 7.1. INVITE server transaction. (Udpates RFC 3261) |INVITE |pass INV to TU INVITE V send 100 if TU won't in 200 ms send response+------------+ +--------| |--------+ 101-199 from TU | | | | send response +------->| |<-------+ | Proceeding | | |--------+ Transport Err. | | | Inform TU | |<-------+ +------------+ 300-699 from TU | |2xx from TU send response | |send response +--------------+ +------------+ | | INVITE V Timer G fires | send response +-----------+ send response | +--------| |--------+ | | | | | | +------->| Completed |<-------+ INVITE | Transport Err. | | - | Inform TU +--------| |----+ +-----+ | +---+ | +-----------+ | ACK | | v | v | ^ | | - | +------------+ | | | | | | |---+ ACK +----------+ | | +->| Accepted | | to TU Transport Err. | | | |<--+ Inform TU | V +------------+ | +-----------+ | ^ | | | | | | | | | Confirmed | | +-----+ | | | | 2xx from TU Timer H fires | +-----------+ | send response - | | | | | Timer I fires | | | - | Timer L fires | V | - | +------------+ | | | |<----+ +------->| Terminated | | | +------------+ */ if(this.Method == SIP_Methods.INVITE){ #region Proceeding if(this.State == SIP_TransactionState.Proceeding){ AddResponse(response); // 1xx if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ this.Stack.TransportLayer.SendResponse(this,response); OnResponseSent(response); } // 2xx else if(response.StatusCodeType == SIP_StatusCodeType.Success){ this.Stack.TransportLayer.SendResponse(this,response); OnResponseSent(response); SetState(SIP_TransactionState.Accpeted); /* RFC 6025 7.1. When the "Accepted" state is entered, timer L MUST be set to fire in 64*T1. */ m_pTimerL = new TimerEx(64 * SIP_TimerConstants.T1); m_pTimerL.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerL_Elapsed); m_pTimerL.Enabled = true; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer L(ACK wait) started, will trigger after " + m_pTimerL.Interval + "."); } } // 3xx - 6xx else{ this.Stack.TransportLayer.SendResponse(this,response); OnResponseSent(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.2.1. For unreliable transports, timer G is set to fire in T1 seconds, and is not set to fire for reliable transports. */ if(!this.Flow.IsReliable){ m_pTimerG = new TimerEx(SIP_TimerConstants.T1,false); m_pTimerG.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerG_Elapsed); m_pTimerG.Enabled = true; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer G(INVITE response(3xx - 6xx) retransmission) started, will trigger after " + m_pTimerG.Interval + "."); } } /* RFC 3261 17.2.1. When the "Completed" state is entered, timer H MUST be set to fire in 64*T1 seconds for all transports. */ m_pTimerH = new TimerEx(64 * SIP_TimerConstants.T1); m_pTimerH.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerH_Elapsed); m_pTimerH.Enabled = true; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer H(INVITE ACK wait) started, will trigger after " + m_pTimerH.Interval + "."); } } } #endregion #region Accepted else if(this.State == SIP_TransactionState.Accpeted){ this.Stack.TransportLayer.SendResponse(this,response); OnResponseSent(response); } #endregion #region Completed else if(this.State == SIP_TransactionState.Completed){ // We do nothing here, we just wait ACK to arrive. } #endregion #region Confirmed else if(this.State == SIP_TransactionState.Confirmed){ // We do nothing, just wait ACK retransmissions. } #endregion #region Terminated else if(this.State == SIP_TransactionState.Terminated){ // We should never rreach here, but if so, skip it. } #endregion } #endregion #region Non-INVITE /* RFC 3261 17.2.2. |Request received |pass to TU V +-----------+ | | | Trying |-------------+ | | | +-----------+ |200-699 from TU | |send response |1xx from TU | |send response | | | Request V 1xx from TU | send response+-----------+send response| +--------| |--------+ | | | Proceeding| | | +------->| |<-------+ | +<--------------| | | |Trnsprt Err +-----------+ | |Inform TU | | | | | | |200-699 from TU | | |send response | | Request V | | send response+-----------+ | | +--------| | | | | | Completed |<------------+ | +------->| | +<--------------| | |Trnsprt Err +-----------+ |Inform TU | | |Timer J fires | |- | | | V | +-----------+ | | | +-------------->| Terminated| | | +-----------+ */ else{ #region Trying if(this.State == SIP_TransactionState.Trying){ AddResponse(response); // 1xx if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ this.Stack.TransportLayer.SendResponse(this,response); OnResponseSent(response); SetState(SIP_TransactionState.Proceeding); } // 2xx - 6xx else{ this.Stack.TransportLayer.SendResponse(this,response); OnResponseSent(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.2.2. When the server transaction enters the "Completed" state, it MUST set Timer J to fire in 64*T1 seconds for unreliable transports, and zero seconds for reliable transports. */ m_pTimerJ = new TimerEx(64 * SIP_TimerConstants.T1,false); m_pTimerJ.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerJ_Elapsed); m_pTimerJ.Enabled = true; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer J(Non-INVITE request retransmission wait) started, will trigger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Proceeding else if(this.State == SIP_TransactionState.Proceeding){ AddResponse(response); // 1xx if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ this.Stack.TransportLayer.SendResponse(this,response); OnResponseSent(response); } // 2xx - 6xx else{ this.Stack.TransportLayer.SendResponse(this,response); OnResponseSent(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.2.2. When the server transaction enters the "Completed" state, it MUST set Timer J to fire in 64*T1 seconds for unreliable transports, and zero seconds for reliable transports. */ m_pTimerJ = new TimerEx(64 * SIP_TimerConstants.T1,false); m_pTimerJ.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerJ_Elapsed); m_pTimerJ.Enabled = true; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer J(Non-INVITE request retransmission wait) started, will trigger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Completed else if(this.State == SIP_TransactionState.Completed){ // Do nothing. } #endregion #region Terminated else if(this.State == SIP_TransactionState.Terminated){ // Do nothing. } #endregion } #endregion } catch(SIP_TransportException x){ // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] transport exception: " + x.Message); } OnTransportError(x); } } }
/// <summary> /// Parses SIP_Response from stream. /// </summary> /// <param name="stream">Stream what contains valid SIP response.</param> /// <returns>Returns parsed SIP_Response obeject.</returns> /// <exception cref="ArgumentNullException">Raised when <b>stream</b> is null.</exception> /// <exception cref="SIP_ParseException">Raised when invalid SIP message.</exception> public static SIP_Response Parse(Stream stream) { /* Syntax: SIP-Version SP Status-Code SP Reason-Phrase SIP-Message */ if(stream == null){ throw new ArgumentNullException("stream"); } SIP_Response retVal = new SIP_Response(); // Parse Response-line StreamLineReader r = new StreamLineReader(stream); r.Encoding = "utf-8"; string[] version_code_text = r.ReadLineString().Split(new char[]{' '},3); if(version_code_text.Length != 3){ throw new SIP_ParseException("Invalid SIP Status-Line syntax ! Syntax: {SIP-Version SP Status-Code SP Reason-Phrase}."); } // SIP-Version try{ retVal.SipVersion = Convert.ToDouble(version_code_text[0].Split('/')[1],System.Globalization.NumberFormatInfo.InvariantInfo); } catch{ throw new SIP_ParseException("Invalid Status-Line SIP-Version value !"); } // Status-Code try{ retVal.StatusCode = Convert.ToInt32(version_code_text[1]); } catch{ throw new SIP_ParseException("Invalid Status-Line Status-Code value !"); } // Reason-Phrase retVal.ReasonPhrase = version_code_text[2]; // Parse SIP-Message retVal.InternalParse(stream); return retVal; }
/// <summary> /// Raises <b>ResponseSent</b> event. /// </summary> /// <param name="response">SIP response.</param> private void OnResponseSent(SIP_Response response) { if(this.ResponseSent != null){ this.ResponseSent(this,new SIP_ResponseSentEventArgs(this,response)); } }
/// <summary> /// Sends specified response back to request maker using RFC 3261 18. rules. /// </summary> /// <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>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> /// <remarks>Use this method to send SIP responses from stateless SIP elements, like stateless proxy. /// Otherwise SIP_ServerTransaction.SendResponse method should be used.</remarks> public void SendResponse(SIP_Response response) { SendResponse(response,null); }
/// <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> /// Gets existing or creates new dialog. /// </summary> /// <param name="transaction">Owner transaction what forces to create dialog.</param> /// <param name="response">Response what forces to create dialog.</param> /// <returns>Returns dialog.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>transaction</b> or <b>response</b> is null.</exception> public SIP_Dialog GetOrCreateDialog(SIP_Transaction transaction,SIP_Response response) { if(transaction == null){ throw new ArgumentNullException("transaction"); } if(response == null){ throw new ArgumentNullException("response"); } string dialogID = ""; if(transaction is SIP_ServerTransaction){ dialogID = response.CallID + "-" + response.To.Tag + "-" + response.From.Tag; } else{ dialogID = response.CallID + "-" + response.From.Tag + "-" + response.To.Tag; } lock(m_pDialogs){ SIP_Dialog dialog = null; m_pDialogs.TryGetValue(dialogID,out dialog); // Dialog doesn't exist, create it. if(dialog == null){ if(response.CSeq.RequestMethod.ToUpper() == SIP_Methods.INVITE){ dialog = new SIP_Dialog_Invite(); dialog.Init(m_pStack,transaction,response); dialog.StateChanged += delegate(object s,EventArgs a){ if(dialog.State == SIP_DialogState.Terminated){ m_pDialogs.Remove(dialog.ID); } }; m_pDialogs.Add(dialog.ID,dialog); } else{ throw new ArgumentException("Method '" + response.CSeq.RequestMethod + "' has no dialog handler."); } } return dialog; } }
/// <summary> /// Sends response to the specified host. /// </summary> /// <param name="logID">Log ID.</param> /// <param name="transactionID">Transaction ID. If null, then stateless response sending.</param> /// <param name="localEP">UDP local end point to use for sending. If null, system will use default.</param> /// <param name="host">Host name or IP address where to send response.</param> /// <param name="port">Target host port.</param> /// <param name="transport">SIP transport to use.</param> /// <param name="response">SIP response to send.</param> private void SendResponseToHost(string logID,string transactionID,IPEndPoint localEP,string host,int port,string transport,SIP_Response response) { try{ IPAddress[] targets = null; if(Net_Utils.IsIPAddress(host)){ targets = new IPAddress[]{IPAddress.Parse(host)}; } else{ targets = m_pStack.Dns.GetHostAddresses(host); if(targets.Length == 0){ throw new SIP_TransportException("Invalid Via: Sent-By host name '" + host + "' could not be resolved."); } } byte[] responseData = response.ToByteData(); for(int i=0;i<targets.Length;i++){ IPEndPoint remoteEP = new IPEndPoint(targets[i],port); try{ SIP_Flow flow = GetOrCreateFlow(transport,localEP,remoteEP); flow.Send(response); // localEP = flow.LocalEP; if(m_pStack.Logger != null){ m_pStack.Logger.AddWrite( logID, null, 0, "Response [transactionID='" + transactionID + "'; method='" + response.CSeq.RequestMethod + "'; cseq='" + response.CSeq.SequenceNumber + "'; " + "transport='" + transport + "'; size='" + responseData.Length + "'; statusCode='" + response.StatusCode + "'; " + "reason='" + response.ReasonPhrase + "'; sent '" + localEP + "' -> '" + remoteEP + "'.", localEP, remoteEP, responseData ); } // If we reach so far, send succeeded. return; } catch{ // Generate error, all IP addresses has failed. if(i == (targets.Length - 1)){ if(m_pStack.Logger != null){ m_pStack.Logger.AddText(logID,"Failed to send response to host '" + host + "' IP end point '" + remoteEP + "'."); } throw new SIP_TransportException("Host '" + host + ":" + port + "' is not accessible."); } // For loop will try next IP address. else{ if(m_pStack.Logger != null){ m_pStack.Logger.AddText(logID,"Failed to send response to host '" + host + "' IP end point '" + remoteEP + "', will try next A record."); } } } } } catch(DNS_ClientException dnsX){ throw new SIP_TransportException("Dns error: " + dnsX.ErrorCode.ToString()); } }
/// <summary> /// Creates authorization for each challange in <b>response</b>. /// </summary> /// <param name="request">SIP request where to add authorization values.</param> /// <param name="response">SIP response which challanges to authorize.</param> /// <param name="credentials">Credentials for authorization.</param> /// <returns>Returns true if all challanges were authorized. If any of the challanges was not authorized, returns false.</returns> private bool Authorize(SIP_Request request, SIP_Response response, NetworkCredential[] credentials) { if (request == null) { throw new ArgumentNullException("request"); } if (response == null) { throw new ArgumentNullException("response"); } if (credentials == null) { throw new ArgumentNullException("credentials"); } bool allAuthorized = true; foreach (SIP_t_Challenge challange in response.WWWAuthenticate.GetAllValues()) { Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData, request.RequestLine.Method); // Serach credential for the specified challange. NetworkCredential credential = null; foreach (NetworkCredential c in credentials) { if (c.Domain.ToLower() == authDigest.Realm.ToLower()) { credential = c; break; } } // We don't have credential for this challange. if (credential == null) { allAuthorized = false; } // Authorize challange. else { authDigest.UserName = credential.UserName; authDigest.Password = credential.Password; authDigest.CNonce = Auth_HttpDigest.CreateNonce(); authDigest.Uri = request.RequestLine.Uri.ToString(); request.Authorization.Add(authDigest.ToAuthorization()); } } foreach (SIP_t_Challenge challange in response.ProxyAuthenticate.GetAllValues()) { Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData, request.RequestLine.Method); // Serach credential for the specified challange. NetworkCredential credential = null; foreach (NetworkCredential c in credentials) { if (c.Domain.ToLower() == authDigest.Realm.ToLower()) { credential = c; break; } } // We don't have credential for this challange. if (credential == null) { allAuthorized = false; } // Authorize challange. else { authDigest.UserName = credential.UserName; authDigest.Password = credential.Password; authDigest.CNonce = Auth_HttpDigest.CreateNonce(); authDigest.Uri = request.RequestLine.Uri.ToString(); request.ProxyAuthorization.Add(authDigest.ToAuthorization()); } } return(allAuthorized); }
/// <summary> /// Default constructor. /// </summary> /// <param name="stack">Reference to SIP stack.</param> /// <param name="transaction">Client transaction what response it is. This value can be null if no matching client response.</param> /// <param name="response">Received response.</param> internal SIP_ResponseReceivedEventArgs(SIP_Stack stack,SIP_ClientTransaction transaction,SIP_Response response) { m_pStack = stack; m_pResponse = response; m_pTransaction = transaction; }
/// <summary> /// Raises ResponseReceived event. /// </summary> /// <param name="response">SIP response received.</param> private void OnResponseReceived(SIP_Response response) { if(this.ResponseReceived != null){ this.ResponseReceived(this,new SIP_ResponseReceivedEventArgs(m_pStack,m_pTransaction,response)); } }
/// <summary> /// Clones this request. /// </summary> /// <returns>Returns new cloned request.</returns> public SIP_Response Copy() { SIP_Response retVal = SIP_Response.Parse(this.ToByteData()); return(retVal); }
/// <summary> /// Adds specified response to transaction responses collection. /// </summary> /// <param name="response">SIP response.</param> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception> protected void AddResponse(SIP_Response response) { if(response == null){ throw new ArgumentNullException("response"); } // Don't store more than 15 responses, otherwise hacker may try todo buffer overrun with provisional responses. if(m_pResponses.Count < 15 || response.StatusCode >= 200){ m_pResponses.Add(response); } }
/// <summary> /// Raises ResponseReceived event. /// </summary> /// <param name="response">SIP response received.</param> private void OnResponseReceived(SIP_Response response) { if(this.ResponseReceived != null){ this.ResponseReceived(this,new SIP_ResponseReceivedEventArgs(this.Stack,this,response)); } }
/// <summary> /// Matches SIP response to client transaction. If not matching transaction found, returns null. /// </summary> /// <param name="response">SIP response to match.</param> internal SIP_ClientTransaction MatchClientTransaction(SIP_Response response) { /* RFC 3261 17.1.3 Matching Responses to Client Transactions. 1. If the response has the same value of the branch parameter in the top Via header field as the branch parameter in the top Via header field of the request that created the transaction. 2. If the method parameter in the CSeq header field matches the method of the request that created the transaction. The method is needed since a CANCEL request constitutes a different transaction, but shares the same value of the branch parameter. */ SIP_ClientTransaction retVal = null; string transactionID = response.Via.GetTopMostValue().Branch + "-" + response.CSeq.RequestMethod; lock(m_pClientTransactions){ m_pClientTransactions.TryGetValue(transactionID,out retVal); } return retVal; }
/// <summary> /// Initializes dialog. /// </summary> /// <param name="stack">Owner stack.</param> /// <param name="transaction">Owner transaction.</param> /// <param name="response">SIP response what caused dialog creation.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>transaction</b> or <b>response</b>.</exception> internal protected override void Init(SIP_Stack stack,SIP_Transaction transaction,SIP_Response response) { if(stack == null){ throw new ArgumentNullException("stack"); } if(transaction == null){ throw new ArgumentNullException("transaction"); } if(response == null){ throw new ArgumentNullException("response"); } base.Init(stack,transaction,response); if(transaction is SIP_ServerTransaction){ if(response.StatusCodeType == SIP_StatusCodeType.Success){ SetState(SIP_DialogState.Early,false); } else if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ SetState(SIP_DialogState.Early,false); m_pActiveInvite = transaction; m_pActiveInvite.StateChanged += delegate(object s,EventArgs a){ if(m_pActiveInvite != null && m_pActiveInvite.State == SIP_TransactionState.Terminated){ m_pActiveInvite = null; /* RFC 3261 13.3.1.4. 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. */ if(this.State == SIP_DialogState.Early){ this.SetState(SIP_DialogState.Confirmed,true); Terminate("ACK was not received for initial INVITE 2xx response.",true); } else if(this.State == SIP_DialogState.Terminating){ this.SetState(SIP_DialogState.Confirmed,false); Terminate(m_TerminateReason,true); } } }; } else{ throw new ArgumentException("Argument 'response' has invalid status code, 1xx - 2xx is only allowed."); } } else{ if(response.StatusCodeType == SIP_StatusCodeType.Success){ SetState(SIP_DialogState.Confirmed,false); } else if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ SetState(SIP_DialogState.Early,false); m_pActiveInvite = transaction; m_pActiveInvite.StateChanged += delegate(object s,EventArgs a){ if(m_pActiveInvite != null && m_pActiveInvite.State == SIP_TransactionState.Terminated){ m_pActiveInvite = null; } }; // Once we receive 2xx response, dialog will switch to confirmed state. ((SIP_ClientTransaction)transaction).ResponseReceived += delegate(object s,SIP_ResponseReceivedEventArgs a){ if(a.Response.StatusCodeType == SIP_StatusCodeType.Success){ SetState(SIP_DialogState.Confirmed,true); } }; } else{ throw new ArgumentException("Argument 'response' has invalid status code, 1xx - 2xx is only allowed."); } } }
/// <summary> /// Matches specified SIP response to SIP dialog. If no matching dialog found, returns null. /// </summary> /// <param name="response">SIP response.</param> /// <returns>Returns matched SIP dialog or null in no match found.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null.</exception> internal SIP_Dialog MatchDialog(SIP_Response response) { if(response == null){ throw new ArgumentNullException("response"); } SIP_Dialog dialog = null; try{ string callID = response.CallID; string fromTag = response.From.Tag; string toTag = response.To.Tag; if(callID != null && fromTag != null && toTag != null){ string dialogID = callID + "-" + fromTag + "-" + toTag; lock(m_pDialogs){ m_pDialogs.TryGetValue(dialogID,out dialog); } } } catch{ } return dialog; }
/// <summary> /// Sends specified response back to request maker using RFC 3263 5. rules. /// </summary> /// <param name="logID">Log ID.</param> /// <param name="transactionID">Transaction ID. If null, then stateless response sending.</param> /// <param name="localEP">UDP local end point to use for sending. If null, system will use default.</param> /// <param name="response">SIP response.</param> /// <exception cref="SIP_TransportException">Is raised when <b>response</b> sending has failed.</exception> private void SendResponse_RFC_3263_5(string logID,string transactionID,IPEndPoint localEP,SIP_Response response) { /* RFC 3263 5. RFC 3261 [1] defines procedures for sending responses from a server back to the client. Typically, for unicast UDP requests, the response is sent back to the source IP address where the request came from, using the port contained in the Via header. For reliable transport protocols, the response is sent over the connection the request arrived on. However, it is important to provide failover support when the client element fails between sending the request and receiving the response. A server, according to RFC 3261 [1], will send a response on the connection it arrived on (in the case of reliable transport protocols), and for unreliable transport protocols, to the source address of the request, and the port in the Via header field. The procedures here are invoked when a server attempts to send to that location and that response fails (the specific conditions are detailed in RFC 3261). "Fails" is defined as any closure of the transport connection the request came in on before the response can be sent, or communication of a fatal error from the transport layer. In these cases, the server examines the value of the sent-by construction in the topmost Via header. If it contains a numeric IP address, the server attempts to send the response to that address, using the transport protocol from the Via header, and the port from sent-by, if present, else the default for that transport protocol. The transport protocol in the Via header can indicate "TLS", which refers to TLS over TCP. When this value is present, the server MUST use TLS over TCP to send the response. If, however, the sent-by field contained a domain name and a port number, the server queries for A or AAAA records with that name. It tries to send the response to each element on the resulting list of IP addresses, using the port from the Via, and the transport protocol from the Via (again, a value of TLS refers to TLS over TCP). As in the client processing, the next entry in the list is tried if the one before it results in a failure. If, however, the sent-by field contained a domain name and no port, the server queries for SRV records at that domain name using the service identifier "_sips" if the Via transport is "TLS", "_sip" otherwise, and the transport from the topmost Via header ("TLS" implies that the transport protocol in the SRV query is TCP). The resulting list is sorted as described in [2], and the response is sent to the topmost element on the new list described there. If that results in a failure, the next entry on the list is tried. */ SIP_t_ViaParm via = response.Via.GetTopMostValue(); #region Sent-By is IP address if(via.SentBy.IsIPAddress){ SendResponseToHost(logID,transactionID,localEP,via.SentBy.Host,via.SentByPortWithDefault,via.ProtocolTransport,response); } #endregion #region Sent-By is host name with port number else if(via.SentBy.Port != -1){ SendResponseToHost(logID,transactionID,localEP,via.SentBy.Host,via.SentByPortWithDefault,via.ProtocolTransport,response); } #endregion #region Sent-By is just host name else{ try{ // Query SRV records. string srvQuery = ""; if(via.ProtocolTransport == SIP_Transport.UDP){ srvQuery = "_sip._udp." + via.SentBy.Host; } else if(via.ProtocolTransport == SIP_Transport.TCP){ srvQuery = "_sip._tcp." + via.SentBy.Host; } else if(via.ProtocolTransport == SIP_Transport.UDP){ srvQuery = "_sips._tcp." + via.SentBy.Host; } DnsServerResponse dnsResponse = m_pStack.Dns.Query(srvQuery,QTYPE.SRV); if(dnsResponse.ResponseCode != RCODE.NO_ERROR){ throw new SIP_TransportException("Dns error: " + dnsResponse.ResponseCode.ToString()); } DNS_rr_SRV[] srvRecords = dnsResponse.GetSRVRecords(); // Use SRV records. if(srvRecords.Length > 0){ for(int i=0;i<srvRecords.Length;i++){ DNS_rr_SRV srv = srvRecords[i]; try{ if(m_pStack.Logger != null){ m_pStack.Logger.AddText(logID,"Starts sending response to DNS SRV record '" + srv.Target + "'."); } SendResponseToHost(logID,transactionID,localEP,srv.Target,srv.Port,via.ProtocolTransport,response); } catch{ // Generate error, all SRV records has failed. if(i == (srvRecords.Length - 1)){ if(m_pStack.Logger != null){ m_pStack.Logger.AddText(logID,"Failed to send response to DNS SRV record '" + srv.Target + "'."); } throw new SIP_TransportException("Host '" + via.SentBy.Host + "' is not accessible."); } // For loop will try next SRV record. else{ if(m_pStack.Logger != null){ m_pStack.Logger.AddText(logID,"Failed to send response to DNS SRV record '" + srv.Target + "', will try next."); } } } } } // If no SRV, use A and AAAA records. (Thats not in 3263 5., but we need to todo it so.) else{ if(m_pStack.Logger != null){ m_pStack.Logger.AddText(logID,"No DNS SRV records found, starts sending to Via: sent-by host '" + via.SentBy.Host + "'."); } SendResponseToHost(logID,transactionID,localEP,via.SentBy.Host,via.SentByPortWithDefault,via.ProtocolTransport,response); } } catch(DNS_ClientException dnsX){ throw new SIP_TransportException("Dns error: " + dnsX.ErrorCode.ToString()); } } #endregion }
/// <summary> /// Sends trying response to request maker. /// </summary> private void SendTrying() { /* RFC 3261 8.2.6.1 Sending a Provisional Response. When a 100 (Trying) response is generated, any Timestamp header field present in the request MUST be copied into this 100 (Trying) response. If there is a delay in generating the response, the UAS SHOULD add a delay value into the Timestamp value in the response. */ SIP_Response sipTryingResponse = new SIP_Response(); sipTryingResponse.StatusCode_ReasonPhrase = SIP_ResponseCodes.x100_Trying; sipTryingResponse.Via.AddToTop(m_pRequest.Via.GetTopMostValue().ToStringValue()); sipTryingResponse.To = m_pRequest.To; sipTryingResponse.From = m_pRequest.From; sipTryingResponse.CallID = m_pRequest.CallID; sipTryingResponse.CSeq = m_pRequest.CSeq; // Add time stamp, because we send Trying at once, we don't have delay. if(m_pRequest.Timestamp != null){ sipTryingResponse.Timestamp = new SIP_t_Timestamp(m_pRequest.Timestamp.Time,0); } // Send response m_pSipStack.TransportLayer.SendResponse(m_pRequest.Socket,m_pRequest.RemoteEndPoint,sipTryingResponse); }
/// <summary> /// Sends specified response back to request maker using RFC 3261 18. rules. /// </summary> /// <param name="response">SIP response.</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> /// <remarks>Use this method to send SIP responses from stateless SIP elements, like stateless proxy. /// Otherwise SIP_ServerTransaction.SendResponse method should be used.</remarks> public void SendResponse(SIP_Response response,IPEndPoint localEP) { // NOTE: all paramter / state validations are done in SendResponseInternal. SendResponseInternal(null,response,localEP); }
/// <summary> /// Processes specified request through this transaction. /// </summary> /// <param name="flow">SIP data flow.</param> /// <param name="request">SIP request.</param> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception> internal void ProcessRequest(SIP_Flow flow, SIP_Request request) { if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } lock (this.SyncRoot){ if (this.State == SIP_TransactionState.Disposed) { return; } try{ // Log if (this.Stack.Logger != null) { byte[] requestData = request.ToByteData(); this.Stack.Logger.AddRead( Guid.NewGuid().ToString(), null, 0, "Request [transactionID='" + this.ID + "'; method='" + request.RequestLine.Method + "'; cseq='" + request.CSeq.SequenceNumber + "'; " + "transport='" + flow.Transport + "'; size='" + requestData.Length + "'; received '" + flow.LocalEP + "' <- '" + flow.RemoteEP + "'.", flow.LocalEP, flow.RemoteEP, requestData ); } #region INVITE if (this.Method == SIP_Methods.INVITE) { #region INVITE if (request.RequestLine.Method == SIP_Methods.INVITE) { if (this.State == SIP_TransactionState.Proceeding) { /* RFC 3261 17.2.1. * If a request retransmission is received while in the "Proceeding" state, the most recent provisional * response that was received from the TU MUST be passed to the transport layer for retransmission. */ SIP_Response response = this.LastProvisionalResponse; if (response != null) { this.Stack.TransportLayer.SendResponse(this, response); } } else if (this.State == SIP_TransactionState.Completed) { /* RFC 3261 17.2.1. * While in the "Completed" state, if a request retransmission is received, the server SHOULD * pass the response to the transport for retransmission. */ this.Stack.TransportLayer.SendResponse(this, this.FinalResponse); } } #endregion #region ACK else if (request.RequestLine.Method == SIP_Methods.ACK) { /* RFC 3261 17.2.1 * If an ACK is received while the server transaction is in the "Completed" state, the server transaction * MUST transition to the "Confirmed" state. As Timer G is ignored in this state, any retransmissions of the * response will cease. * * When this state is entered, timer I is set to fire in T4 seconds for unreliable transports, * and zero seconds for reliable transports. */ if (this.State == SIP_TransactionState.Completed) { SetState(SIP_TransactionState.Confirmed); // Stop timers G,H if (m_pTimerG != null) { m_pTimerG.Dispose(); m_pTimerG = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer G(INVITE response(3xx - 6xx) retransmission) stoped."); } } if (m_pTimerH != null) { m_pTimerH.Dispose(); m_pTimerH = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer H(INVITE ACK wait) stoped."); } } // Start timer I. m_pTimerI = new TimerEx((flow.IsReliable ? 0 : SIP_TimerConstants.T4), false); m_pTimerI.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerI_Elapsed); // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer I(INVITE ACK retransission wait) started, will triger after " + m_pTimerI.Interval + "."); } m_pTimerI.Enabled = true; } } #endregion } #endregion #region Non-INVITE else { // Non-INVITE transaction may have only request retransmission requests. if (this.Method == request.RequestLine.Method) { if (this.State == SIP_TransactionState.Proceeding) { /* RFC 3261 17.2.2. * If a retransmission of the request is received while in the "Proceeding" state, the most * recently sent provisional response MUST be passed to the transport layer for retransmission. */ this.Stack.TransportLayer.SendResponse(this, this.LastProvisionalResponse); } else if (this.State == SIP_TransactionState.Completed) { /* RFC 3261 17.2.2. * While in the "Completed" state, the server transaction MUST pass the final response to the transport * layer for retransmission whenever a retransmission of the request is received. */ this.Stack.TransportLayer.SendResponse(this, this.FinalResponse); } } } #endregion } catch (SIP_TransportException x) { // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] transport exception: " + x.Message); } OnTransportError(x); SetState(SIP_TransactionState.Terminated); } } }
/// <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> /// Processes specified response through this transaction. /// </summary> /// <param name="flow">SIP data flow what received response.</param> /// <param name="response">SIP response to process.</param> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b>,<b>response</b> is null reference.</exception> internal void ProcessResponse(SIP_Flow flow, SIP_Response response) { if (flow == null) { throw new ArgumentNullException("flow"); } if (response == null) { throw new ArgumentNullException("response"); } lock (this.SyncRoot){ if (this.State == SIP_TransactionState.Disposed) { return; } /* RFC 3261 9.1. CANCEL. *) If provisional response, send CANCEL, we should get '478 Request terminated'. *) If final response, skip canceling, nothing to cancel. */ else if (m_IsCanceling && response.StatusCodeType == SIP_StatusCodeType.Provisional) { SendCancel(); return; } // Log if (this.Stack.Logger != null) { byte[] responseData = response.ToByteData(); this.Stack.Logger.AddRead( Guid.NewGuid().ToString(), null, 0, "Response [transactionID='" + this.ID + "'; method='" + response.CSeq.RequestMethod + "'; cseq='" + response.CSeq.SequenceNumber + "'; " + "transport='" + flow.Transport + "'; size='" + responseData.Length + "'; statusCode='" + response.StatusCode + "'; " + "reason='" + response.ReasonPhrase + "'; received '" + flow.LocalEP + "' <- '" + flow.RemoteEP + "'.", flow.LocalEP, flow.RemoteEP, responseData ); } #region INVITE /* RFC 3261 17.1.1.2. |INVITE from TU * Timer A fires |INVITE sent * Reset A, V Timer B fires * INVITE sent +-----------+ or Transport Err. +---------| |---------------+inform TU | | Calling | | +-------->| |-------------->| +-----------+ 2xx | | | 2xx to TU | | |1xx | | 300-699 +---------------+ |1xx to TU | | ACK sent | | | | resp. to TU | 1xx V | | 1xx to TU -----------+ | | +---------| | | | | |Proceeding |-------------->| | +-------->| | 2xx | | +-----------+ 2xx to TU | | 300-699 | | | ACK sent, | | | resp. to TU| | | | | NOTE: | 300-699 V | | ACK sent +-----------+Transport Err. | transitions | +---------| |Inform TU | labeled with | | | Completed |-------------->| the event | +-------->| | | over the action | +-----------+ | to take | ^ | | | | | Timer D fires | +--------------+ | - | | | | V | +-----------+ | | | | | Terminated|<--------------+ | | +-----------+ | */ if (this.Method == SIP_Methods.INVITE) { #region Calling if (this.State == SIP_TransactionState.Calling) { // Store response. AddResponse(response); // Stop timer A,B if (m_pTimerA != null) { m_pTimerA.Dispose(); m_pTimerA = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) stopped."); } } if (m_pTimerB != null) { m_pTimerB.Dispose(); m_pTimerB = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer B(INVITE calling state timeout) stopped."); } } // 1xx response. if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { OnResponseReceived(response); SetState(SIP_TransactionState.Proceeding); } // 2xx response. else if (response.StatusCodeType == SIP_StatusCodeType.Success) { OnResponseReceived(response); SetState(SIP_TransactionState.Terminated); } // 3xx - 6xx response. else { SendAck(response); OnResponseReceived(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.1.1.2. * The client transaction SHOULD start timer D when it enters the "Completed" state, * with a value of at least 32 seconds for unreliable transports, and a value of zero * seconds for reliable transports. */ m_pTimerD = new TimerEx(this.Flow.IsReliable ? 0 : 32000, false); m_pTimerD.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerD_Elapsed); // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerD.Interval + "."); } m_pTimerD.Enabled = true; } } #endregion #region Proceeding else if (this.State == SIP_TransactionState.Proceeding) { // Store response. AddResponse(response); // 1xx response. if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { OnResponseReceived(response); } // 2xx response. else if (response.StatusCodeType == SIP_StatusCodeType.Success) { OnResponseReceived(response); SetState(SIP_TransactionState.Terminated); } // 3xx - 6xx response. else { SendAck(response); OnResponseReceived(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.1.1.2. * The client transaction SHOULD start timer D when it enters the "Completed" state, * with a value of at least 32 seconds for unreliable transports, and a value of zero * seconds for reliable transports. */ m_pTimerD = new TimerEx(this.Flow.IsReliable ? 0 : 32000, false); m_pTimerD.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerD_Elapsed); // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerD.Interval + "."); } m_pTimerD.Enabled = true; } } #endregion #region Completed else if (this.State == SIP_TransactionState.Completed) { // 3xx - 6xx if (response.StatusCode >= 300) { SendAck(response); } } #endregion #region Terminated else if (this.State == SIP_TransactionState.Terminated) { // We should never reach here, but if so, do nothing. } #endregion } #endregion #region Non-INVITE /* RFC 3251 17.1.2.2 |Request from TU |send request * Timer E V * send request +-----------+ +---------| |-------------------+ | | Trying | Timer F | +-------->| | or Transport Err.| +-----------+ inform TU | | 200-699 | | | | resp. to TU | |1xx | +---------------+ |resp. to TU | | | | | Timer E V Timer F | | send req +-----------+ or Transport Err. | | +---------| | inform TU | | | |Proceeding |------------------>| | +-------->| |-----+ | | +-----------+ |1xx | | | ^ |resp to TU | | 200-699 | +--------+ | | resp. to TU | | | | | | V | | +-----------+ | | | | | | | Completed | | | | | | | +-----------+ | | ^ | | | | | Timer K | +--------------+ | - | | | | V | | NOTE: +-----------+ | | | | | transitions | Terminated|<------------------+ | labeled with | | | the event +-----------+ | over the action | to take */ else { #region Trying if (this.State == SIP_TransactionState.Trying) { // Store response. AddResponse(response); // Stop timer E if (m_pTimerE != null) { m_pTimerE.Dispose(); m_pTimerE = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(Non-INVITE request retransmission) stopped."); } } // 1xx response. if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { OnResponseReceived(response); SetState(SIP_TransactionState.Proceeding); } // 2xx - 6xx response. else { // Stop timer F if (m_pTimerF != null) { m_pTimerF.Dispose(); m_pTimerF = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stopped."); } } OnResponseReceived(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.1.2.2. * The client transaction enters the "Completed" state, it MUST set * Timer K to fire in T4 seconds for unreliable transports, and zero * seconds for reliable transports. */ m_pTimerK = new TimerEx(this.Flow.IsReliable ? 1 : SIP_TimerConstants.T4, false); m_pTimerK.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerK_Elapsed); // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerK.Interval + "."); } m_pTimerK.Enabled = true; } } #endregion #region Proceeding else if (this.State == SIP_TransactionState.Proceeding) { // Store response. AddResponse(response); // 1xx response. if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { OnResponseReceived(response); } // 2xx - 6xx response. else { // Stop timer F if (m_pTimerF != null) { m_pTimerF.Dispose(); m_pTimerF = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stopped."); } } OnResponseReceived(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.1.2.2. * The client transaction enters the "Completed" state, it MUST set * Timer K to fire in T4 seconds for unreliable transports, and zero * seconds for reliable transports. */ m_pTimerK = new TimerEx(this.Flow.IsReliable ? 0 : SIP_TimerConstants.T4, false); m_pTimerK.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerK_Elapsed); // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerK.Interval + "."); } m_pTimerK.Enabled = true; } } #endregion #region Completed else if (this.State == SIP_TransactionState.Completed) { // Eat retransmited response. } #endregion #region Terminated else if (this.State == SIP_TransactionState.Terminated) { // We should never reach here, but if so, do nothing. } #endregion } #endregion } }
/// <summary> /// Sends specified response to remote party. /// </summary> /// <param name="response">SIP response to send.</param> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception> public void SendResponse(SIP_Response response) { lock (this.SyncRoot){ if (this.State == SIP_TransactionState.Disposed) { throw new ObjectDisposedException(this.GetType().Name); } if (response == null) { throw new ArgumentNullException("response"); } try{ #region INVITE /* RFC 3261 17.2.1. |INVITE |pass INV to TU * INVITE V send 100 if TU won't in 200ms * send response+-----------+ +--------| |--------+101-199 from TU | | Proceeding| |send response +------->| |<-------+ | | Transport Err. | | Inform TU | |--------------->+ +-----------+ | | 300-699 from TU | |2xx from TU | | send response | |send response | | +------------------>+ | | | INVITE V Timer G fires | | send response+-----------+ send response | +--------| |--------+ | | | Completed | | | +------->| |<-------+ | +-----------+ | | | | | ACK | | | | - | +------------------>+ | Timer H fires | | V or Transport Err.| +-----------+ Inform TU | | | | | Confirmed | | | | | +-----------+ | | | |Timer I fires | |- | | | | V | +-----------+ | | | | | Terminated|<---------------+ | | +-----------+ */ if (this.Method == SIP_Methods.INVITE) { #region Proceeding if (this.State == SIP_TransactionState.Proceeding) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { this.Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); } // 2xx else if (response.StatusCodeType == SIP_StatusCodeType.Success) { this.Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Terminated); } // 3xx - 6xx else { this.Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.2.1. * For unreliable transports, timer G is set to fire in T1 seconds, and is not set to fire for reliable transports. */ if (!this.Flow.IsReliable) { m_pTimerG = new TimerEx(SIP_TimerConstants.T1, false); m_pTimerG.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerG_Elapsed); m_pTimerG.Enabled = true; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer G(INVITE response(3xx - 6xx) retransmission) started, will triger after " + m_pTimerG.Interval + "."); } } /* RFC 3261 17.2.1. * When the "Completed" state is entered, timer H MUST be set to fire in 64*T1 seconds for all transports. */ m_pTimerH = new TimerEx(64 * SIP_TimerConstants.T1); m_pTimerH.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerH_Elapsed); m_pTimerH.Enabled = true; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer H(INVITE ACK wait) started, will triger after " + m_pTimerH.Interval + "."); } } } #endregion #region Completed else if (this.State == SIP_TransactionState.Completed) { // We do nothing here, we just wait ACK to arrive. } #endregion #region Confirmed else if (this.State == SIP_TransactionState.Confirmed) { // We do nothing, just wait ACK retransmissions. } #endregion #region Terminated else if (this.State == SIP_TransactionState.Terminated) { // We should never rreach here, but if so, skip it. } #endregion } #endregion #region Non-INVITE /* RFC 3261 17.2.2. |Request received |pass to TU * V +-----------+ | | | Trying |-------------+ | | | +-----------+ |200-699 from TU | |send response |1xx from TU | |send response | | | | Request V 1xx from TU | | send response+-----------+send response| +--------| |--------+ | | | Proceeding| | | +------->| |<-------+ | +<--------------| | | |Trnsprt Err +-----------+ | |Inform TU | | | | | | |200-699 from TU | | |send response | | Request V | | send response+-----------+ | | +--------| | | | | | Completed |<------------+ | +------->| | +<--------------| | |Trnsprt Err +-----------+ |Inform TU | | |Timer J fires | |- | | | V | +-----------+ | | | +-------------->| Terminated| | | +-----------+ */ else { #region Trying if (this.State == SIP_TransactionState.Trying) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { this.Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Proceeding); } // 2xx - 6xx else { this.Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.2.2. * When the server transaction enters the "Completed" state, it MUST set * Timer J to fire in 64*T1 seconds for unreliable transports, and zero * seconds for reliable transports. */ m_pTimerJ = new TimerEx(64 * SIP_TimerConstants.T1, false); m_pTimerJ.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerJ_Elapsed); m_pTimerJ.Enabled = true; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer J(Non-INVITE request retransmission wait) started, will triger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Proceeding else if (this.State == SIP_TransactionState.Proceeding) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { this.Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); } // 2xx - 6xx else { this.Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.2.2. * When the server transaction enters the "Completed" state, it MUST set * Timer J to fire in 64*T1 seconds for unreliable transports, and zero * seconds for reliable transports. */ m_pTimerJ = new TimerEx(64 * SIP_TimerConstants.T1, false); m_pTimerJ.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerJ_Elapsed); m_pTimerJ.Enabled = true; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer J(Non-INVITE request retransmission wait) started, will triger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Completed else if (this.State == SIP_TransactionState.Completed) { // Do nothing. } #endregion #region Terminated else if (this.State == SIP_TransactionState.Terminated) { // Do nothing. } #endregion } #endregion } catch (SIP_TransportException x) { // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] transport exception: " + x.Message); } OnTransportError(x); SetState(SIP_TransactionState.Terminated); } } }
/// <summary> /// Initializes dialog. /// </summary> /// <param name="stack">Owner stack.</param> /// <param name="transaction">Owner transaction.</param> /// <param name="response">SIP response what caused dialog creation.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>transaction</b> or <b>response</b>.</exception> internal protected virtual void Init(SIP_Stack stack,SIP_Transaction transaction,SIP_Response response) { if(stack == null){ throw new ArgumentNullException("stack"); } if(transaction == null){ throw new ArgumentNullException("transaction"); } if(response == null){ throw new ArgumentNullException("response"); } m_pStack = stack; #region UAS /* RFC 3261 12.1.1. The UAS then constructs the state of the dialog. This state MUST be maintained for the duration of the dialog. If the request arrived over TLS, and the Request-URI contained a SIPS URI, the "secure" flag is set to TRUE. The route set MUST be set to the list of URIs in the Record-Route header field from the request, taken in order and preserving all URI parameters. If no Record-Route header field is present in the request, the route set MUST be set to the empty set. This route set, even if empty, overrides any pre-existing route set for future requests in this dialog. The remote target MUST be set to the URI from the Contact header field of the request. The remote sequence number MUST be set to the value of the sequence number in the CSeq header field of the request. The local sequence number MUST be empty. The call identifier component of the dialog ID MUST be set to the value of the Call-ID in the request. The local tag component of the dialog ID MUST be set to the tag in the To field in the response to the request (which always includes a tag), and the remote tag component of the dialog ID MUST be set to the tag from the From field in the request. A UAS MUST be prepared to receive a request without a tag in the From field, in which case the tag is considered to have a value of null. This is to maintain backwards compatibility with RFC 2543, which did not mandate From tags. The remote URI MUST be set to the URI in the From field, and the local URI MUST be set to the URI in the To field. */ if(transaction is SIP_ServerTransaction){ m_IsSecure = ((SIP_Uri)transaction.Request.RequestLine.Uri).IsSecure; m_pRouteSet = (SIP_t_AddressParam[])Net_Utils.ReverseArray(transaction.Request.RecordRoute.GetAllValues()); m_pRemoteTarget = (SIP_Uri)transaction.Request.Contact.GetTopMostValue().Address.Uri; m_RemoteSeqNo = transaction.Request.CSeq.SequenceNumber; m_LocalSeqNo = 0; m_CallID = transaction.Request.CallID; m_LocalTag = response.To.Tag; m_RemoteTag = transaction.Request.From.Tag; m_pRemoteUri = transaction.Request.From.Address.Uri; m_pLocalUri = transaction.Request.To.Address.Uri; m_pLocalContact = (SIP_Uri)response.Contact.GetTopMostValue().Address.Uri; List<string> allow = new List<string>(); foreach(SIP_t_Method m in response.Allow.GetAllValues()){ allow.Add(m.Method); } m_pRemoteAllow = allow.ToArray(); List<string> supported = new List<string>(); foreach(SIP_t_OptionTag s in response.Supported.GetAllValues()){ supported.Add(s.OptionTag); } m_pRemoteSupported = supported.ToArray(); } #endregion #region UAC /* RFC 3261 12.1.2. When a UAC receives a response that establishes a dialog, it constructs the state of the dialog. This state MUST be maintained for the duration of the dialog. If the request was sent over TLS, and the Request-URI contained a SIPS URI, the "secure" flag is set to TRUE. The route set MUST be set to the list of URIs in the Record-Route header field from the response, taken in reverse order and preserving all URI parameters. If no Record-Route header field is present in the response, the route set MUST be set to the empty set. This route set, even if empty, overrides any pre-existing route set for future requests in this dialog. The remote target MUST be set to the URI from the Contact header field of the response. The local sequence number MUST be set to the value of the sequence number in the CSeq header field of the request. The remote sequence number MUST be empty (it is established when the remote UA sends a request within the dialog). The call identifier component of the dialog ID MUST be set to the value of the Call-ID in the request. The local tag component of the dialog ID MUST be set to the tag in the From field in the request, and the remote tag component of the dialog ID MUST be set to the tag in the To field of the response. A UAC MUST be prepared to receive a response without a tag in the To field, in which case the tag is considered to have a value of null. This is to maintain backwards compatibility with RFC 2543, which did not mandate To tags. The remote URI MUST be set to the URI in the To field, and the local URI MUST be set to the URI in the From field. */ else{ // TODO: Validate request or client transaction must do it ? m_IsSecure = ((SIP_Uri)transaction.Request.RequestLine.Uri).IsSecure; m_pRouteSet = (SIP_t_AddressParam[])Net_Utils.ReverseArray(response.RecordRoute.GetAllValues()); m_pRemoteTarget = (SIP_Uri)response.Contact.GetTopMostValue().Address.Uri; m_LocalSeqNo = transaction.Request.CSeq.SequenceNumber; m_RemoteSeqNo = 0; m_CallID = transaction.Request.CallID; m_LocalTag = transaction.Request.From.Tag; m_RemoteTag = response.To.Tag; m_pRemoteUri = transaction.Request.To.Address.Uri; m_pLocalUri = transaction.Request.From.Address.Uri; m_pLocalContact = (SIP_Uri)transaction.Request.Contact.GetTopMostValue().Address.Uri; List<string> allow = new List<string>(); foreach(SIP_t_Method m in response.Allow.GetAllValues()){ allow.Add(m.Method); } m_pRemoteAllow = allow.ToArray(); List<string> supported = new List<string>(); foreach(SIP_t_OptionTag s in response.Supported.GetAllValues()){ supported.Add(s.OptionTag); } m_pRemoteSupported = supported.ToArray(); } #endregion m_pFlow = transaction.Flow; AddTransaction(transaction); }
/// <summary> /// Creates and sends ACK for final(3xx - 6xx) failure response. /// </summary> /// <param name="response">SIP response.</param> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null.</exception> private void SendAck(SIP_Response response) { if(response == null){ throw new ArgumentNullException("resposne"); } /* RFC 3261 17.1.1.3 Construction of the ACK Request. The ACK request constructed by the client transaction MUST contain values for the Call-ID, From, and Request-URI that are equal to the values of those header fields in the request passed to the transport by the client transaction (call this the "original request"). The To header field in the ACK MUST equal the To header field in the response being acknowledged, and therefore will usually differ from the To header field in the original request by the addition of the tag parameter. The ACK MUST contain a single Via header field, and this MUST be equal to the top Via header field of the original request. The CSeq header field in the ACK MUST contain the same value for the sequence number as was present in the original request, but the method parameter MUST be equal to "ACK". If the INVITE request whose response is being acknowledged had Route header fields, those header fields MUST appear in the ACK. This is to ensure that the ACK can be routed properly through any downstream stateless proxies. */ SIP_Request ackRequest = new SIP_Request(SIP_Methods.ACK); ackRequest.RequestLine.Uri = this.Request.RequestLine.Uri; ackRequest.Via.AddToTop(this.Request.Via.GetTopMostValue().ToStringValue()); ackRequest.CallID = this.Request.CallID; ackRequest.From = this.Request.From; ackRequest.To = response.To; ackRequest.CSeq = new SIP_t_CSeq(this.Request.CSeq.SequenceNumber,"ACK"); foreach(SIP_HeaderField h in response.Header.Get("Route:")){ ackRequest.Header.Add("Route:",h.Value); } ackRequest.MaxForwards = 70; try{ // Send request to target. this.Stack.TransportLayer.SendRequest(this.Flow,ackRequest,this); } catch(SIP_TransportException x){ OnTransportError(x); SetState(SIP_TransactionState.Terminated); } }
/// <summary> /// Initializes dialog. /// </summary> /// <param name="stack">Owner stack.</param> /// <param name="transaction">Owner transaction.</param> /// <param name="response">SIP response what caused dialog creation.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>transaction</b> or <b>response</b>.</exception> internal protected override void Init(SIP_Stack stack, SIP_Transaction transaction, SIP_Response response) { if (stack == null) { throw new ArgumentNullException("stack"); } if (transaction == null) { throw new ArgumentNullException("transaction"); } if (response == null) { throw new ArgumentNullException("response"); } base.Init(stack, transaction, response); if (transaction is SIP_ServerTransaction) { if (response.StatusCodeType == SIP_StatusCodeType.Success) { SetState(SIP_DialogState.Early, false); } else if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { SetState(SIP_DialogState.Early, false); m_pActiveInvite = transaction; m_pActiveInvite.StateChanged += delegate(object s, EventArgs a){ if (m_pActiveInvite != null && m_pActiveInvite.State == SIP_TransactionState.Terminated) { m_pActiveInvite = null; /* RFC 3261 13.3.1.4. * 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. */ if (this.State == SIP_DialogState.Early) { this.SetState(SIP_DialogState.Confirmed, true); Terminate("ACK was not received for initial INVITE 2xx response.", true); } else if (this.State == SIP_DialogState.Terminating) { this.SetState(SIP_DialogState.Confirmed, false); Terminate(m_TerminateReason, true); } } }; } else { throw new ArgumentException("Argument 'response' has invalid status code, 1xx - 2xx is only allowed."); } } else { if (response.StatusCodeType == SIP_StatusCodeType.Success) { SetState(SIP_DialogState.Confirmed, false); } else if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { SetState(SIP_DialogState.Early, false); m_pActiveInvite = transaction; m_pActiveInvite.StateChanged += delegate(object s, EventArgs a){ if (m_pActiveInvite != null && m_pActiveInvite.State == SIP_TransactionState.Terminated) { m_pActiveInvite = null; } }; // Once we receive 2xx response, dialog will switch to confirmed state. ((SIP_ClientTransaction)transaction).ResponseReceived += delegate(object s, SIP_ResponseReceivedEventArgs a){ if (a.Response.StatusCodeType == SIP_StatusCodeType.Success) { SetState(SIP_DialogState.Confirmed, true); } }; } else { throw new ArgumentException("Argument 'response' has invalid status code, 1xx - 2xx is only allowed."); } } }
/// <summary> /// Matches SIP response to client transaction. If not matching transaction found, returns null. /// </summary> /// <param name="response">SIP response to match.</param> internal SIP_ClientTransaction MatchClientTransaction(SIP_Response response) { /* RFC 3261 17.1.3 Matching Responses to Client Transactions. 1. If the response has the same value of the branch parameter in the top Via header field as the branch parameter in the top Via header field of the request that created the transaction. 2. If the method parameter in the CSeq header field matches the method of the request that created the transaction. The method is needed since a CANCEL request constitutes a different transaction, but shares the same value of the branch parameter. */ string transactionID = response.Via.GetTopMostValue().Branch + "-" + response.CSeq.RequestMethod; lock(m_pClientTransactions){ foreach(SIP_ClientTransaction transaction in m_pClientTransactions){ if(transactionID == transaction.ID + "-" + transaction.Request.CSeq.RequestMethod){ return transaction; } } } // m_pSipStack.Logger.AddDebug("No matching transaction for response branch='" + response.Via.GetTopMostValue().Branch + "': " + response.StatusCode_ReasonPhrase); return null; }
/// <summary> /// Processes received response. /// </summary> /// <param name="handler">Target handler what received response.</param> /// <param name="transaction">Client transaction what response it is.</param> /// <param name="response">Response received.</param> /// <exception cref="ArgumentNullException">Is raised when <b>handler</b>,<b>transaction</b> or <b>response</b> is null reference.</exception> private void ProcessResponse(TargetHandler handler,SIP_ClientTransaction transaction,SIP_Response response) { if(handler == null){ throw new ArgumentNullException("handler"); } if(transaction == null){ throw new ArgumentNullException("transaction"); } if(response == null){ throw new ArgumentNullException("response"); } /* RFC 3261 16.7 Response Processing. Steps 1 - 2 handled in TargetHandler. 3. Remove the topmost Via 4. Add the response to the response context 5. Check to see if this response should be forwarded immediately 6. When necessary, choose the best final response from the response context. If no final response has been forwarded after every client transaction associated with the response context has been terminated, the proxy must choose and forward the "best" response from those it has seen so far. The following processing MUST be performed on each response that is forwarded. It is likely that more than one response to each request will be forwarded: at least each provisional and one final response. 7. Aggregate authorization header field values if necessary 8. Optionally rewrite Record-Route header field values 9. Forward the response 10. Generate any necessary CANCEL requests */ bool forwardResponse = false; lock(m_pLock){ #region 3. Remove the topmost Via /* The proxy removes the topmost Via header field value from the response. If no Via header field values remain in the response, the response was meant for this element and MUST NOT be forwarded. The remainder of the processing described in this section is not performed on this message, the UAC processing rules described in Section 8.1.3 are followed instead (transport layer processing has already occurred). This will happen, for instance, when the element generates CANCEL requests as described in Section 10. NOTE: We MAY NOT do it for B2BUA, skip it for B2BUA */ if(!m_IsB2BUA){ response.Via.RemoveTopMostValue(); if(response.Via.GetAllValues().Length == 0){ return; } } #endregion #region 4. Add the response to the response context /* Final responses received are stored in the response context until a final response is generated on the server transaction associated with this context. The response may be a candidate for the best final response to be returned on that server transaction. Information from this response may be needed in forming the best response, even if this response is not chosen. If the proxy chooses to recurse on any contacts in a 3xx response by adding them to the target set, it MUST remove them from the response before adding the response to the response context. However, a proxy SHOULD NOT recurse to a non-SIPS URI if the Request-URI of the original request was a SIPS URI. If the proxy recurses on all of the contacts in a 3xx response, the proxy SHOULD NOT add the resulting contactless response to the response context. Removing the contact before adding the response to the response context prevents the next element upstream from retrying a location this proxy has already attempted. 3xx responses may contain a mixture of SIP, SIPS, and non-SIP URIs. A proxy may choose to recurse on the SIP and SIPS URIs and place the remainder into the response context to be returned, potentially in the final response. */ if(response.StatusCodeType == SIP_StatusCodeType.Redirection && !m_NoRecurse && !handler.IsRecursed){ // Get SIP contacts and remove them from response. SIP_t_ContactParam[] contacts = response.Contact.GetAllValues(); // Remove all contacts from response, we add non-SIP URIs back. response.Contact.RemoveAll(); foreach(SIP_t_ContactParam contact in contacts){ // SIP URI add it to fork list. if(contact.Address.IsSipOrSipsUri){ m_pTargets.Enqueue(new TargetHandler(this,null,(SIP_Uri)contact.Address.Uri,m_AddRecordRoute,true)); } // Add specified URI back to response. else{ response.Contact.Add(contact.ToStringValue()); } } // There are remaining non-SIP contacts, so we need to add the response to reponses collection. if(response.Contact.GetAllValues().Length > 0){ m_pResponses.Add(response); } // Handle forking if(m_pTargets.Count > 0){ if(m_ForkingMode == SIP_ForkingMode.Parallel){ while(m_pTargets.Count > 0){ TargetHandler h = m_pTargets.Dequeue(); m_pTargetsHandlers.Add(handler); h.Start(); } } // Just fork next. else{ TargetHandler h = m_pTargets.Dequeue(); m_pTargetsHandlers.Add(handler); h.Start(); } // Because we forked request to new target(s), we don't need to do steps 5 - 10. return; } } // Not 3xx response or recursing disabled. else{ m_pResponses.Add(response); } #endregion #region 5. Check to see if this response should be forwarded immediately /* Until a final response has been sent on the server transaction, the following responses MUST be forwarded immediately: - Any provisional response other than 100 (Trying) - Any 2xx response If a 6xx response is received, it is not immediately forwarded, but the stateful proxy SHOULD cancel all client pending transactions as described in Section 10, and it MUST NOT create any new branches in this context. After a final response has been sent on the server transaction, the following responses MUST be forwarded immediately: - Any 2xx response to an INVITE request */ if(!m_IsFinalResponseSent){ if(response.StatusCodeType == SIP_StatusCodeType.Provisional && response.StatusCode != 100){ forwardResponse = true; } else if(response.StatusCodeType == SIP_StatusCodeType.Success){ forwardResponse = true; } else if(response.StatusCodeType == SIP_StatusCodeType.GlobalFailure){ CancelAllTargets(); } } else{ if(response.StatusCodeType == SIP_StatusCodeType.Success && m_pServerTransaction.Request.RequestLine.Method == SIP_Methods.INVITE){ forwardResponse = true; } } #endregion #region x. Handle sequential forking /* Sequential Search: In a sequential search, a proxy server attempts each contact address in sequence, proceeding to the next one only after the previous has generated a final response. A 2xx or 6xx class final response always terminates a sequential search. */ if(m_ForkingMode == SIP_ForkingMode.Sequential && response.StatusCodeType != SIP_StatusCodeType.Provisional){ if(response.StatusCodeType == SIP_StatusCodeType.Success){ // Do nothing, 2xx will be always forwarded and step 10. Cancels all targets. } else if(response.StatusCodeType == SIP_StatusCodeType.GlobalFailure){ // Do nothing, 6xx is already handled in setp 5. } else if(m_pTargets.Count > 0){ TargetHandler h = m_pTargets.Dequeue(); m_pTargetsHandlers.Add(handler); h.Start(); // Skip all next steps, we will get new responses from new target. return; } } #endregion #region 6. When necessary, choose the best final response from the response context /* A stateful proxy MUST send a final response to a response context's server transaction if no final responses have been immediately forwarded by the above rules and all client transactions in this response context have been terminated. The stateful proxy MUST choose the "best" final response among those received and stored in the response context. If there are no final responses in the context, the proxy MUST send a 408 (Request Timeout) response to the server transaction. */ if(!m_IsFinalResponseSent && !forwardResponse && m_pTargets.Count == 0){ bool mustChooseBestFinalResponse = true; // Check if all transactions terminated. foreach(TargetHandler h in m_pTargetsHandlers){ if(!h.IsCompleted){ mustChooseBestFinalResponse = false; break; } } if(mustChooseBestFinalResponse){ response = GetBestFinalResponse(); if(response == null){ response = this.Proxy.Stack.CreateResponse(SIP_ResponseCodes.x408_Request_Timeout,m_pServerTransaction.Request); } forwardResponse = true; } } #endregion if(forwardResponse){ #region 7. Aggregate authorization header field values if necessary /* If the selected response is a 401 (Unauthorized) or 407 (Proxy Authentication Required), the proxy MUST collect any WWW-Authenticate and Proxy-Authenticate header field values from all other 401 (Unauthorized) and 407 (Proxy Authentication Required) responses received so far in this response context and add them to this response without modification before forwarding. The resulting 401 (Unauthorized) or 407 (Proxy Authentication Required) response could have several WWW-Authenticate AND Proxy-Authenticate header field values. This is necessary because any or all of the destinations the request was forwarded to may have requested credentials. The client needs to receive all of those challenges and supply credentials for each of them when it retries the request. */ if(response.StatusCode == 401 || response.StatusCode == 407){ foreach(SIP_Response resp in m_pResponses.ToArray()){ if(response != resp && (resp.StatusCode == 401 || resp.StatusCode == 407)){ // WWW-Authenticate foreach(SIP_HeaderField hf in resp.WWWAuthenticate.HeaderFields){ resp.WWWAuthenticate.Add(hf.Value); } // Proxy-Authenticate foreach(SIP_HeaderField hf in resp.ProxyAuthenticate.HeaderFields){ resp.ProxyAuthenticate.Add(hf.Value); } } } } #endregion #region 8. Optionally rewrite Record-Route header field values // This is optional so we currently won't do that. #endregion #region 9. Forward the response SendResponse(transaction,response); if(response.StatusCodeType != SIP_StatusCodeType.Provisional){ m_IsFinalResponseSent = true; } #endregion #region 10. Generate any necessary CANCEL requests /* If the forwarded response was a final response, the proxy MUST generate a CANCEL request for all pending client transactions associated with this response context. */ if(response.StatusCodeType != SIP_StatusCodeType.Provisional){ CancelAllTargets(); } #endregion } } }
/// <summary> /// Processes specified request through this dialog. /// </summary> /// <param name="e">Method arguments.</param> /// <returns>Returns true if this dialog processed specified request, otherwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>e</b> is null reference.</exception> internal protected override bool ProcessRequest(SIP_RequestReceivedEventArgs e) { if (e == null) { throw new ArgumentNullException("e"); } if (base.ProcessRequest(e)) { return(true); } // We must support: INVITE(re-invite),UPDATE,ACK, [BYE will be handled by base class] #region INVITE if (e.Request.RequestLine.Method == SIP_Methods.INVITE) { /* RFC 3261 14.2. * A UAS that receives a second INVITE before it sends the final * response to a first INVITE with a lower CSeq sequence number on the * same dialog MUST return a 500 (Server Internal Error) response to the * second INVITE and MUST include a Retry-After header field with a * randomly chosen value of between 0 and 10 seconds. * * A UAS that receives an INVITE on a dialog while an INVITE it had sent * on that dialog is in progress MUST return a 491 (Request Pending) * response to the received INVITE. */ if (m_pActiveInvite != null && m_pActiveInvite is SIP_ServerTransaction && ((SIP_ServerTransaction)m_pActiveInvite).Request.CSeq.SequenceNumber < e.Request.CSeq.SequenceNumber) { SIP_Response response = this.Stack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": INVITE with a lower CSeq is pending(RFC 3261 14.2).", e.Request); response.RetryAfter = new SIP_t_RetryAfter("10"); e.ServerTransaction.SendResponse(response); return(true); } if (m_pActiveInvite != null && m_pActiveInvite is SIP_ClientTransaction) { e.ServerTransaction.SendResponse(this.Stack.CreateResponse(SIP_ResponseCodes.x491_Request_Pending, e.Request)); return(true); } // Force server transaction creation and set it as active INVITE transaction. m_pActiveInvite = e.ServerTransaction; m_pActiveInvite.StateChanged += delegate(object s, EventArgs a){ if (m_pActiveInvite.State == SIP_TransactionState.Terminated) { m_pActiveInvite = null; } }; // Once we send 2xx response, we need to retransmit it while get ACK or timeout. (RFC 3261 13.3.1.4.) ((SIP_ServerTransaction)m_pActiveInvite).ResponseSent += delegate(object s, SIP_ResponseSentEventArgs a){ if (a.Response.StatusCodeType == SIP_StatusCodeType.Success) { m_pUasInvite2xxRetransmits.Add(new UasInvite2xxRetransmit(this, a.Response)); } }; OnReinvite(((SIP_ServerTransaction)m_pActiveInvite)); return(true); } #endregion #region ACK else if (e.Request.RequestLine.Method == SIP_Methods.ACK) { // Search corresponding INVITE 2xx retransmit entry and dispose it. foreach (UasInvite2xxRetransmit t in m_pUasInvite2xxRetransmits) { if (t.MatchAck(e.Request)) { t.Dispose(); if (this.State == SIP_DialogState.Early) { this.SetState(SIP_DialogState.Confirmed, true); // TODO: If Terminating } return(true); } } return(false); } #endregion #region UPDATE //else if(request.RequestLine.Method == SIP_Methods.UPDATE){ // TODO: //} #endregion // RFC 5057 5.6. Refusing New Usages. Decline(603 Decline) new dialog usages. else if (SIP_Utils.MethodCanEstablishDialog(e.Request.RequestLine.Method)) { e.ServerTransaction.SendResponse(this.Stack.CreateResponse(SIP_ResponseCodes.x603_Decline + " : New dialog usages not allowed (RFC 5057).", e.Request)); return(true); } else { return(false); } }
/// <summary> /// Processes client transaction received response. /// </summary> /// <param name="response">Response received.</param> private void ProcessResponse(SIP_Response response) { /* RFC 3261 16.7 Response Processing. 1. Find the appropriate response context 2. Update timer C for provisional responses 3. Remove the topmost Via 4. Add the response to the response context 5. Check to see if this response should be forwarded immediately 6. When necessary, choose the best final response from the response context If no final response has been forwarded after every client transaction associated with the response context has been terminated, the proxy must choose and forward the "best" response from those it has seen so far. The following processing MUST be performed on each response that is forwarded. It is likely that more than one response to each request will be forwarded: at least each provisional and one final response. 7. Aggregate authorization header field values if necessary 8. Optionally rewrite Record-Route header field values 9. Forward the response 10. Generate any necessary CANCEL requests */ lock(this){ // 1. Find the appropriate response context. // Done, "this" is it. // 2. Update timer C for provisional responses. // Our client transaction will handle it. // 3. Remove the topmost Via. If no Via header field values remain in the response, // the response was meant for this element and MUST NOT be forwarded. response.Via.RemoveTopMostValue(); if(response.Via.GetAllValues().Length == 0){ return; } // 4. Add the response to the response context. if(!m_NoRecurse && response.StausCodeType == SIP_StatusCodeType.Redirection){ /* If the proxy chooses to recurse on any contacts in a 3xx response by adding them to the target set, it MUST remove them from the response before adding the response to the response context. However, a proxy SHOULD NOT recurse to a non-SIPS URI if the Request-URI of the original request was a SIPS URI. If the proxy recurses on all of the contacts in a 3xx response, the proxy SHOULD NOT add the resulting contactless response to the response context. */ // Get SIP contacts and remove them from response. SIP_t_ContactParam[] contacts = response.Contact.GetAllValues(); // Remove all contacts from response, we add no-SIP URIs back. response.Contact.RemoveAll(); foreach(SIP_t_ContactParam contact in contacts){ // SIP URI add it to fork list. if(contact.Address.IsSipOrSipsUri){ m_pRemainingDestinations.Enqueue(new SIP_Destination(SIP_Uri.Parse(contact.Address.Uri))); } // Add specified URI back to response. else{ response.Contact.Add(contact.ToStringValue()); } } // There are remaining non-SIP contacts, so we need to add the response to reponses collection. if(response.Contact.GetAllValues().Length > 0){ m_pResponses.Add(response); } // Handle forking if(m_pRemainingDestinations.Count > 0){ if(m_ForkingMode == SIP_ForkingMode.Parallel){ while(m_pRemainingDestinations.Count > 0){ CreateClientTransaction(m_pRemainingDestinations.Dequeue()); } } // Just fork next. else{ CreateClientTransaction(m_pRemainingDestinations.Dequeue()); } // Because we forked request to new target(s), we don't need to do steps 5 - 10. return; } } // Not 3xx response or recursing disabled. else{ m_pResponses.Add(response); } // 5. Check to see if this response should be forwarded immediately. bool forwardResponse = false; if(m_FinalResponseSent){ // - Any 2xx response to an INVITE request if(response.StausCodeType == SIP_StatusCodeType.Success && m_pServerTransaction.Request.Method == SIP_Methods.INVITE){ forwardResponse = true; } } else{ // - Any provisional response other than 100 (Trying) if(response.StausCodeType == SIP_StatusCodeType.Provisional && response.StatusCode != 101){ forwardResponse = true; } // - Any 2xx response else if(response.StausCodeType == SIP_StatusCodeType.Success){ forwardResponse = true; } } /* 6. When necessary, choose the best final response from the response context. A stateful proxy MUST send a final response to a response context's server transaction if no final responses have been immediately forwarded by the above rules and all client transactions in this response context have been terminated. */ bool mustChooseBestFinalResponse = false; if(!forwardResponse && m_pRemainingDestinations.Count == 0){ mustChooseBestFinalResponse = true; foreach(SIP_ClientTransaction transaction in m_pClientTransactions.ToArray()){ // Acutally we can't relay on terminated state, thats not accurate, just see // any transaction haven't also got final response (the we can expect final response). if(transaction.GetFinalResponse() == null){ mustChooseBestFinalResponse = false; break; } } } if(mustChooseBestFinalResponse){ response = GetBestFinalResponse(); if(response == null){ /* If there are no final responses in the context, the proxy MUST send a 408 (Request Timeout) response to the server transaction. */ response = m_pServerTransaction.Request.CreateResponse(SIP_ResponseCodes.x408_Request_Timeout); } forwardResponse = true; } if(forwardResponse){ /* 7. Aggregate authorization header field values if necessary. If the selected response is a 401 (Unauthorized) or 407 (Proxy Authentication Required), the proxy MUST collect any WWW-Authenticate and Proxy-Authenticate header field values from all other 401 (Unauthorized) and 407 (Proxy Authentication Required) responses received so far in this response context and add them to this response without modification before forwarding. The resulting 401 (Unauthorized) or 407 (Proxy Authentication Required) response could have several WWW-Authenticate AND Proxy-Authenticate header field values. This is necessary because any or all of the destinations the request was forwarded to may have requested credentials. The client needs to receive all of those challenges and supply credentials for each of them when it retries the request. */ if(response.StatusCode == 401 || response.StatusCode == 407){ foreach(SIP_Response resp in m_pResponses.ToArray()){ if(response != resp && (resp.StatusCode == 401 || resp.StatusCode == 407)){ // WWW-Authenticate foreach(SIP_HeaderField hf in resp.WWWAuthenticate.HeaderFields){ resp.WWWAuthenticate.Add(hf.Value); } // Proxy-Authenticate foreach(SIP_HeaderField hf in resp.ProxyAuthenticate.HeaderFields){ resp.ProxyAuthenticate.Add(hf.Value); } } } } // 8. Optionally rewrite Record-Route header field values. // This is optional so we currently won't do that. // 9. Forward the response. m_pServerTransaction.SendResponse(response); if(response.StausCodeType != SIP_StatusCodeType.Provisional){ m_FinalResponseSent = true; } /* 10. Generate any necessary CANCEL requests. If the forwarded response was a final response, the proxy MUST generate a CANCEL request for all pending client transactions associated with this response context. */ if(response.StausCodeType != SIP_StatusCodeType.Provisional){ if(!m_NoCancel){ foreach(SIP_ClientTransaction transaction in m_pClientTransactions.ToArray()){ transaction.Cancel(); } } } } } }
/// <summary> /// Initializes dialog. /// </summary> /// <param name="stack">Owner stack.</param> /// <param name="transaction">Owner transaction.</param> /// <param name="response">SIP response what caused dialog creation.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>transaction</b> or <b>response</b>.</exception> internal protected virtual void Init(SIP_Stack stack, SIP_Transaction transaction, SIP_Response response) { if (stack == null) { throw new ArgumentNullException("stack"); } if (transaction == null) { throw new ArgumentNullException("transaction"); } if (response == null) { throw new ArgumentNullException("response"); } m_pStack = stack; m_pUsages = new List <SIP_Dialog_Usage>(); #region UAS /* RFC 3261 12.1.1. * The UAS then constructs the state of the dialog. This state MUST be * maintained for the duration of the dialog. * * If the request arrived over TLS, and the Request-URI contained a SIPS * URI, the "secure" flag is set to TRUE. * * The route set MUST be set to the list of URIs in the Record-Route * header field from the request, taken in order and preserving all URI * parameters. If no Record-Route header field is present in the * request, the route set MUST be set to the empty set. This route set, * even if empty, overrides any pre-existing route set for future * requests in this dialog. The remote target MUST be set to the URI * from the Contact header field of the request. * * The remote sequence number MUST be set to the value of the sequence * number in the CSeq header field of the request. The local sequence * number MUST be empty. The call identifier component of the dialog ID * MUST be set to the value of the Call-ID in the request. The local * tag component of the dialog ID MUST be set to the tag in the To field * in the response to the request (which always includes a tag), and the * remote tag component of the dialog ID MUST be set to the tag from the * From field in the request. A UAS MUST be prepared to receive a * request without a tag in the From field, in which case the tag is * considered to have a value of null. * * This is to maintain backwards compatibility with RFC 2543, which * did not mandate From tags. * * The remote URI MUST be set to the URI in the From field, and the * local URI MUST be set to the URI in the To field. */ if (transaction is SIP_ServerTransaction) { // TODO: Validate request or client transaction must do it ? m_IsSecure = ((SIP_Uri)transaction.Request.RequestLine.Uri).IsSecure; m_pRouteSet = (SIP_t_AddressParam[])Net_Utils.ReverseArray(transaction.Request.RecordRoute.GetAllValues()); m_pRemoteTarget = (SIP_Uri)transaction.Request.Contact.GetTopMostValue().Address.Uri; m_RemoteSeqNo = transaction.Request.CSeq.SequenceNumber; m_LocalSeqNo = 0; m_CallID = transaction.Request.CallID; m_LocalTag = response.To.Tag; m_RemoteTag = transaction.Request.From.Tag; m_pRemoteUri = transaction.Request.From.Address.Uri; m_pLocalUri = transaction.Request.To.Address.Uri; m_pLocalContact = (SIP_Uri)response.Contact.GetTopMostValue().Address.Uri; List <string> allow = new List <string>(); foreach (SIP_t_Method m in response.Allow.GetAllValues()) { allow.Add(m.Method); } m_pRemoteAllow = allow.ToArray(); List <string> supported = new List <string>(); foreach (SIP_t_OptionTag s in response.Supported.GetAllValues()) { supported.Add(s.OptionTag); } m_pRemoteSupported = supported.ToArray(); } #endregion #region UAC /* RFC 3261 12.1.2. * When a UAC receives a response that establishes a dialog, it * constructs the state of the dialog. This state MUST be maintained * for the duration of the dialog. * * If the request was sent over TLS, and the Request-URI contained a * SIPS URI, the "secure" flag is set to TRUE. * * The route set MUST be set to the list of URIs in the Record-Route * header field from the response, taken in reverse order and preserving * all URI parameters. If no Record-Route header field is present in * the response, the route set MUST be set to the empty set. This route * set, even if empty, overrides any pre-existing route set for future * requests in this dialog. The remote target MUST be set to the URI * from the Contact header field of the response. * * The local sequence number MUST be set to the value of the sequence * number in the CSeq header field of the request. The remote sequence * number MUST be empty (it is established when the remote UA sends a * request within the dialog). The call identifier component of the * dialog ID MUST be set to the value of the Call-ID in the request. * The local tag component of the dialog ID MUST be set to the tag in * the From field in the request, and the remote tag component of the * dialog ID MUST be set to the tag in the To field of the response. A * UAC MUST be prepared to receive a response without a tag in the To * field, in which case the tag is considered to have a value of null. * * This is to maintain backwards compatibility with RFC 2543, which * did not mandate To tags. * * The remote URI MUST be set to the URI in the To field, and the local * URI MUST be set to the URI in the From field. */ else { // TODO: Validate request or client transaction must do it ? m_IsSecure = ((SIP_Uri)transaction.Request.RequestLine.Uri).IsSecure; m_pRouteSet = (SIP_t_AddressParam[])Net_Utils.ReverseArray(response.RecordRoute.GetAllValues()); m_pRemoteTarget = (SIP_Uri)response.Contact.GetTopMostValue().Address.Uri; m_LocalSeqNo = transaction.Request.CSeq.SequenceNumber; m_RemoteSeqNo = 0; m_CallID = transaction.Request.CallID; m_LocalTag = transaction.Request.From.Tag; m_RemoteTag = response.To.Tag; m_pRemoteUri = transaction.Request.To.Address.Uri; m_pLocalUri = transaction.Request.From.Address.Uri; m_pLocalContact = (SIP_Uri)transaction.Request.Contact.GetTopMostValue().Address.Uri; List <string> allow = new List <string>(); foreach (SIP_t_Method m in response.Allow.GetAllValues()) { allow.Add(m.Method); } m_pRemoteAllow = allow.ToArray(); List <string> supported = new List <string>(); foreach (SIP_t_OptionTag s in response.Supported.GetAllValues()) { supported.Add(s.OptionTag); } m_pRemoteSupported = supported.ToArray(); } #endregion m_pFlow = transaction.Flow; }
/// <summary> /// Processes specified response through this transaction. /// </summary> /// <param name="flow">SIP data flow what received response.</param> /// <param name="response">SIP response to process.</param> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b>,<b>response</b> is null reference.</exception> internal void ProcessResponse(SIP_Flow flow,SIP_Response response) { if(flow == null){ throw new ArgumentNullException("flow"); } if(response == null){ throw new ArgumentNullException("response"); } lock(this.SyncRoot){ if(this.State == SIP_TransactionState.Disposed){ return; } /* RFC 3261 9.1. CANCEL. *) If provisional response, send CANCEL, we should get '478 Request terminated'. *) If final response, skip canceling, nothing to cancel. */ else if(m_IsCanceling && response.StatusCodeType == SIP_StatusCodeType.Provisional){ SendCancel(); return; } // Log if(this.Stack.Logger != null){ byte[] responseData = response.ToByteData(); this.Stack.Logger.AddRead( Guid.NewGuid().ToString(), null, 0, "Response [transactionID='" + this.ID + "'; method='" + response.CSeq.RequestMethod + "'; cseq='" + response.CSeq.SequenceNumber + "'; " + "transport='" + flow.Transport + "'; size='" + responseData.Length + "'; statusCode='" + response.StatusCode + "'; " + "reason='" + response.ReasonPhrase + "'; received '" + flow.LocalEP + "' <- '" + flow.RemoteEP + "'.", flow.LocalEP, flow.RemoteEP, responseData ); } #region INVITE /* RFC 6026 7.2. INVITE client transaction. (Udpates RFC 3261) +-----------+ +-----------+ | | | | | Calling | | Calling | | |----------->+ | |-----------+ +-----------+ 2xx | +-----------+ 2xx | 2xx to TU | 2xx to TU | | | | | | | | | +-----------+ | +-----------+ | | | | | | | |Proceeding |----------->| |Proceeding |---------->| | | 2xx | | | 2xx | +-----------+ 2xx to TU | +-----------+ 2xx to TU | | | | | | | | V | +-----------+ | | | | | Accepted | | +---| | | 2xx | +-----------+ | 2xx to TU | ^ | | | | | | +-----+ | | | | +-----------------+ | | Timer M fires | | - | V +-----------+ | +-----------+ | | | | | | Terminated|<-----------+ | Terminated| | | | | +-----------+ +-----------+ */ if(this.Method == SIP_Methods.INVITE){ #region Calling if(this.State == SIP_TransactionState.Calling){ // Store response. AddResponse(response); // Stop timer A,B if(m_pTimerA != null){ m_pTimerA.Dispose(); m_pTimerA = null; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) stopped."); } } if(m_pTimerB != null){ m_pTimerB.Dispose(); m_pTimerB = null; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer B(INVITE calling state timeout) stopped."); } } // 1xx response. if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ OnResponseReceived(response); SetState(SIP_TransactionState.Proceeding); } // 2xx response. else if(response.StatusCodeType == SIP_StatusCodeType.Success){ OnResponseReceived(response); SetState(SIP_TransactionState.Accpeted); /* RFC 6025 7.1. When the "Accepted" state is entered, timer L MUST be set to fire in 64*T1. */ m_pTimerM = new TimerEx(64 * SIP_TimerConstants.T1); m_pTimerM.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerM_Elapsed); m_pTimerM.Enabled = true; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer M(2xx retransmission wait) started, will trigger after " + m_pTimerM.Interval + "."); } } // 3xx - 6xx response. else{ SendAck(response); OnResponseReceived(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.1.1.2. The client transaction SHOULD start timer D when it enters the "Completed" state, with a value of at least 32 seconds for unreliable transports, and a value of zero seconds for reliable transports. */ m_pTimerD = new TimerEx(this.Flow.IsReliable ? 0 : 32000,false); m_pTimerD.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerD_Elapsed); // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerD.Interval + "."); } m_pTimerD.Enabled = true; } } #endregion #region Proceeding else if(this.State == SIP_TransactionState.Proceeding){ // Store response. AddResponse(response); // 1xx response. if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ OnResponseReceived(response); } // 2xx response. else if(response.StatusCodeType == SIP_StatusCodeType.Success){ OnResponseReceived(response); SetState(SIP_TransactionState.Accpeted); /* RFC 6025 7.1. When the "Accepted" state is entered, timer L MUST be set to fire in 64*T1. */ m_pTimerM = new TimerEx(64 * SIP_TimerConstants.T1); m_pTimerM.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerM_Elapsed); m_pTimerM.Enabled = true; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer M(2xx retransmission wait) started, will trigger after " + m_pTimerM.Interval + "."); } } // 3xx - 6xx response. else{ SendAck(response); OnResponseReceived(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.1.1.2. The client transaction SHOULD start timer D when it enters the "Completed" state, with a value of at least 32 seconds for unreliable transports, and a value of zero seconds for reliable transports. */ m_pTimerD = new TimerEx(this.Flow.IsReliable ? 0 : 32000,false); m_pTimerD.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerD_Elapsed); // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerD.Interval + "."); } m_pTimerD.Enabled = true; } } #endregion #region Accepted else if(this.State == SIP_TransactionState.Accpeted){ if(response.StatusCodeType == SIP_StatusCodeType.Success){ OnResponseReceived(response); } } #endregion #region Completed else if(this.State == SIP_TransactionState.Completed){ // 3xx - 6xx if(response.StatusCode >= 300){ SendAck(response); } } #endregion #region Terminated else if(this.State == SIP_TransactionState.Terminated){ // We should never reach here, but if so, do nothing. } #endregion } #endregion #region Non-INVITE /* RFC 3251 17.1.2.2 |Request from TU |send request Timer E V send request +-----------+ +---------| |-------------------+ | | Trying | Timer F | +-------->| | or Transport Err.| +-----------+ inform TU | 200-699 | | | resp. to TU | |1xx | +---------------+ |resp. to TU | | | | | Timer E V Timer F | | send req +-----------+ or Transport Err. | | +---------| | inform TU | | | |Proceeding |------------------>| | +-------->| |-----+ | | +-----------+ |1xx | | | ^ |resp to TU | | 200-699 | +--------+ | | resp. to TU | | | | | | V | | +-----------+ | | | | | | | Completed | | | | | | | +-----------+ | | ^ | | | | | Timer K | +--------------+ | - | | | V | NOTE: +-----------+ | | | | transitions | Terminated|<------------------+ labeled with | | the event +-----------+ over the action to take */ else{ #region Trying if(this.State == SIP_TransactionState.Trying){ // Store response. AddResponse(response); // Stop timer E if(m_pTimerE != null){ m_pTimerE.Dispose(); m_pTimerE = null; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(Non-INVITE request retransmission) stopped."); } } // 1xx response. if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ OnResponseReceived(response); SetState(SIP_TransactionState.Proceeding); } // 2xx - 6xx response. else{ // Stop timer F if(m_pTimerF != null){ m_pTimerF.Dispose(); m_pTimerF = null; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stopped."); } } OnResponseReceived(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.1.2.2. The client transaction enters the "Completed" state, it MUST set Timer K to fire in T4 seconds for unreliable transports, and zero seconds for reliable transports. */ m_pTimerK = new TimerEx(this.Flow.IsReliable ? 1 : SIP_TimerConstants.T4,false); m_pTimerK.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerK_Elapsed); // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerK.Interval + "."); } m_pTimerK.Enabled = true; } } #endregion #region Proceeding else if(this.State == SIP_TransactionState.Proceeding){ // Store response. AddResponse(response); // 1xx response. if(response.StatusCodeType == SIP_StatusCodeType.Provisional){ OnResponseReceived(response); } // 2xx - 6xx response. else{ // Stop timer F if(m_pTimerF != null){ m_pTimerF.Dispose(); m_pTimerF = null; // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stopped."); } } OnResponseReceived(response); SetState(SIP_TransactionState.Completed); /* RFC 3261 17.1.2.2. The client transaction enters the "Completed" state, it MUST set Timer K to fire in T4 seconds for unreliable transports, and zero seconds for reliable transports. */ m_pTimerK = new TimerEx(this.Flow.IsReliable ? 0 : SIP_TimerConstants.T4,false); m_pTimerK.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerK_Elapsed); // Log if(this.Stack.Logger != null){ this.Stack.Logger.AddText(this.ID,"Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerK.Interval + "."); } m_pTimerK.Enabled = true; } } #endregion #region Completed else if(this.State == SIP_TransactionState.Completed){ // Eat retransmited response. } #endregion #region Terminated else if(this.State == SIP_TransactionState.Terminated){ // We should never reach here, but if so, do nothing. } #endregion } #endregion } }
/// <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; }
/// <summary> /// Creates authorization for each challange in <b>response</b>. /// </summary> /// <param name="request">SIP request where to add authorization values.</param> /// <param name="response">SIP response which challanges to authorize.</param> /// <param name="credentials">Credentials for authorization.</param> /// <returns>Returns true if all challanges were authorized. If any of the challanges was not authorized, returns false.</returns> private bool Authorize(SIP_Request request,SIP_Response response,NetworkCredential[] credentials) { if(request == null){ throw new ArgumentNullException("request"); } if(response == null){ throw new ArgumentNullException("response"); } if(credentials == null){ throw new ArgumentNullException("credentials"); } bool allAuthorized = true; #region WWWAuthenticate foreach(SIP_t_Challenge challange in response.WWWAuthenticate.GetAllValues()){ Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData,request.RequestLine.Method); // Serach credential for the specified challange. NetworkCredential credential = null; foreach(NetworkCredential c in credentials){ if(c.Domain.ToLower() == authDigest.Realm.ToLower()){ credential = c; break; } } // We don't have credential for this challange. if(credential == null){ allAuthorized = false; } // Authorize challange. else{ authDigest.UserName = credential.UserName; authDigest.Password = credential.Password; authDigest.CNonce = Auth_HttpDigest.CreateNonce(); authDigest.Uri = request.RequestLine.Uri.ToString(); request.Authorization.Add(authDigest.ToAuthorization()); } } #endregion #region ProxyAuthenticate foreach(SIP_t_Challenge challange in response.ProxyAuthenticate.GetAllValues()){ Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData,request.RequestLine.Method); // Serach credential for the specified challange. NetworkCredential credential = null; foreach(NetworkCredential c in credentials){ if(c.Domain.ToLower() == authDigest.Realm.ToLower()){ credential = c; break; } } // We don't have credential for this challange. if(credential == null){ allAuthorized = false; } // Authorize challange. else{ authDigest.UserName = credential.UserName; authDigest.Password = credential.Password; authDigest.CNonce = Auth_HttpDigest.CreateNonce(); authDigest.Uri = request.RequestLine.Uri.ToString(); request.ProxyAuthorization.Add(authDigest.ToAuthorization()); } } #endregion return allAuthorized; }
/// <summary> /// Sends specified response to flow remote end point. /// </summary> /// <param name="response">SIP response to send.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null reference.</exception> public void Send(SIP_Response response) { lock(m_pLock){ if(m_IsDisposed){ throw new ObjectDisposedException(this.GetType().Name); } if(response == null){ throw new ArgumentNullException("response"); } SendInternal(response.ToByteData()); m_LastPing = DateTime.Now; } }
/// <summary> /// Sends SIP response to caller. If proxy context is in B2BUA mode, new response is generated /// as needed. /// </summary> /// <param name="transaction">Client transaction what response it is.</param> /// <param name="response">Response to send.</param> private void SendResponse(SIP_ClientTransaction transaction,SIP_Response response) { if(m_IsB2BUA){ /* draft-marjou-sipping-b2bua-00 4.1.3. When the UAC side of the B2BUA receives the downstream SIP response of a forwarded request, its associated UAS creates an upstream response (except for 100 responses). The creation of the Via, Max- Forwards, Call-Id, CSeq, Record-Route and Contact header fields follows the rules of [2]. The Record-Route header fields of the downstream response are not copied in the new upstream response, as Record-Route is meaningful for the downstream dialog. The UAS SHOULD copy other header fields and body from the downstream response into this upstream response before sending it. */ SIP_Request originalRequest = m_pServerTransaction.Request; // We need to use caller original request to construct response from proxied response. SIP_Response b2buaResponse = response.Copy(); b2buaResponse.Via.RemoveAll(); b2buaResponse.Via.AddToTop(originalRequest.Via.GetTopMostValue().ToStringValue()); b2buaResponse.CallID = originalRequest.CallID; b2buaResponse.CSeq = originalRequest.CSeq; b2buaResponse.Contact.RemoveAll(); //b2buaResponse.Contact.Add(m_pProxy.CreateContact(originalRequest.From.Address).ToStringValue()); b2buaResponse.RecordRoute.RemoveAll(); b2buaResponse.Allow.RemoveAll(); b2buaResponse.Supported.RemoveAll(); // Accept to non ACK,BYE request. if(originalRequest.RequestLine.Method != SIP_Methods.ACK && originalRequest.RequestLine.Method != SIP_Methods.BYE){ b2buaResponse.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK"); } // Supported to non ACK request. if(originalRequest.RequestLine.Method != SIP_Methods.ACK){ b2buaResponse.Supported.Add("100rel,timer"); } // Remove Require: header. b2buaResponse.Require.RemoveAll(); m_pServerTransaction.SendResponse(b2buaResponse); // If INVITE 2xx response do call here. if(response.CSeq.RequestMethod.ToUpper() == SIP_Methods.INVITE && response.StatusCodeType == SIP_StatusCodeType.Success){ m_pProxy.B2BUA.AddCall(m_pServerTransaction.Dialog,transaction.Dialog); } } else{ m_pServerTransaction.SendResponse(response); } }
/// <summary> /// Processes specified response through this dialog. /// </summary> /// <param name="response">SIP response to process.</param> /// <returns>Returns true if this dialog processed specified response, otherwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null.</exception> internal protected virtual bool ProcessResponse(SIP_Response response) { if(response == null){ throw new ArgumentNullException("response"); } return false; }
/// <summary> /// Default constructor. /// </summary> /// <param name="stack">Reference to SIP stack.</param> /// <param name="transaction">Client transaction what response it is. This value can be null if no matching client response.</param> /// <param name="response">Received response.</param> internal SIP_ResponseReceivedEventArgs(SIP_Stack stack, SIP_ClientTransaction transaction, SIP_Response response) { m_pStack = stack; m_pResponse = response; m_pTransaction = transaction; }