/// <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> /// 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> protected internal virtual bool ProcessResponse(SIP_Response response) { if (response == null) { throw new ArgumentNullException("response"); } return(false); }
/// <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> /// 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[] { ' ' }, 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], 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> /// 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 { 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> /// 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> /// 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> /// 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(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, ElapsedEventArgs e) { lock (SyncRoot) { // RFC 3261 17.2.1. TU didn't generate response in 200 ms, send '100 Trying' to stop request retransmission. if (State == SIP_TransactionState.Proceeding && 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 = Stack.CreateResponse(SIP_ResponseCodes.x100_Trying, Request); if (Request.Timestamp != null) { tryingResponse.Timestamp = new SIP_t_Timestamp(Request.Timestamp.Time, (DateTime.Now - CreateTime).Seconds); } try { 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> protected internal 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 += m_pTimer_Elapsed; m_pTimer.Enabled = true; }
/// <summary> /// Cancels current transaction processing and sends '487 Request Terminated'. /// </summary> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when final response is sent and Cancel method is called after it.</exception> public override void Cancel() { lock (SyncRoot) { if (State == SIP_TransactionState.Disposed) { throw new ObjectDisposedException(GetType().Name); } if (FinalResponse != null) { throw new InvalidOperationException("Final response is already sent, CANCEL not allowed."); } try { SIP_Response response = Stack.CreateResponse(SIP_ResponseCodes.x487_Request_Terminated, Request); Stack.TransportLayer.SendResponse(this, response); OnCanceled(); } catch (SIP_TransportException x) { // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] transport exception: " + x.Message); } OnTransportError(x); SetState(SIP_TransactionState.Terminated); } } }
/// <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 (SyncRoot) { if (State == SIP_TransactionState.Disposed) { throw new ObjectDisposedException(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 (Method == SIP_Methods.INVITE) { #region Proceeding if (State == SIP_TransactionState.Proceeding) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); } // 2xx else if (response.StatusCodeType == SIP_StatusCodeType.Success) { Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Terminated); } // 3xx - 6xx else { 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 (!Flow.IsReliable) { m_pTimerG = new TimerEx(SIP_TimerConstants.T1, false); m_pTimerG.Elapsed += m_pTimerG_Elapsed; m_pTimerG.Enabled = true; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + 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 += m_pTimerH_Elapsed; m_pTimerH.Enabled = true; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] timer H(INVITE ACK wait) started, will triger after " + m_pTimerH.Interval + "."); } } } #endregion #region Completed else if (State == SIP_TransactionState.Completed) { // We do nothing here, we just wait ACK to arrive. } #endregion #region Confirmed else if (State == SIP_TransactionState.Confirmed) { // We do nothing, just wait ACK retransmissions. } #endregion #region Terminated else if (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 (State == SIP_TransactionState.Trying) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Proceeding); } // 2xx - 6xx else { 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 += m_pTimerJ_Elapsed; m_pTimerJ.Enabled = true; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] timer J(Non-INVITE request retransmission wait) started, will triger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Proceeding else if (State == SIP_TransactionState.Proceeding) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); } // 2xx - 6xx else { 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 += m_pTimerJ_Elapsed; m_pTimerJ.Enabled = true; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] timer J(Non-INVITE request retransmission wait) started, will triger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Completed else if (State == SIP_TransactionState.Completed) { // Do nothing. } #endregion #region Terminated else if (State == SIP_TransactionState.Terminated) { // Do nothing. } #endregion } #endregion } catch (SIP_TransportException x) { // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] transport exception: " + x.Message); } OnTransportError(x); SetState(SIP_TransactionState.Terminated); } } }
/// <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); } 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); } } #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> protected internal 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 && (m_pActiveInvite).Request.CSeq.SequenceNumber < e.Request.CSeq.SequenceNumber) { SIP_Response response = 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( 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 { 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 (State == SIP_DialogState.Early) { 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( Stack.CreateResponse( SIP_ResponseCodes.x603_Decline + " : New dialog usages not allowed (RFC 5057).", e.Request)); return(true); } else { return(false); } }
/// <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> /// 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> protected internal virtual bool ProcessResponse(SIP_Response response) { if (response == null) { throw new ArgumentNullException("response"); } return false; }
/// <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.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); } }
/// <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[] {' '}, 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], 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> /// 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> /// Clones this request. /// </summary> /// <returns>Returns new cloned request.</returns> public SIP_Response Copy() { SIP_Response retVal = Parse(ToByteData()); return(retVal); }
/// <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 = Request.RequestLine.Uri; ackRequest.Via.AddToTop(Request.Via.GetTopMostValue().ToStringValue()); ackRequest.CallID = Request.CallID; ackRequest.From = Request.From; ackRequest.To = response.To; ackRequest.CSeq = new SIP_t_CSeq(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. Stack.TransportLayer.SendRequest(Flow, ackRequest, this); } catch (SIP_TransportException x) { OnTransportError(x); SetState(SIP_TransactionState.Terminated); } }
/// <summary> /// Creates response for the specified request. /// </summary> /// <param name="statusCode_reasonText">Status-code reasontext.</param> /// <param name="request">SIP request.</param> /// <param name="flow">Data flow what sends response. This value is used to construct Contact: header value. /// This value can be null, but then adding Contact: header is response sender responsibility.</param> /// <returns>Returns created response.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>statusCode_reasonText</b> or <b>request</b> is null reference.</exception> /// <exception cref="InvalidOperationException">Is raised when request is ACK-request. ACK request is response less.</exception> public SIP_Response CreateResponse(string statusCode_reasonText, SIP_Request request, SIP_Flow flow) { if (request == null) { throw new ArgumentNullException("request"); } if (request.RequestLine.Method == SIP_Methods.ACK) { throw new InvalidOperationException("ACK is responseless request !"); } /* RFC 3261 8.2.6.1. When a 100 (Trying) response is generated, any Timestamp header field present in the request MUST be copied into this 100 (Trying) response. RFC 3261 8.2.6.2. The From field of the response MUST equal the From header field of the request. The Call-ID header field of the response MUST equal the Call-ID header field of the request. The CSeq header field of the response MUST equal the CSeq field of the request. The Via header field values in the response MUST equal the Via header field values in the request and MUST maintain the same ordering. If a request contained a To tag in the request, the To header field in the response MUST equal that of the request. However, if the To header field in the request did not contain a tag, the URI in the To header field in the response MUST equal the URI in the To header field; additionally, the UAS MUST add a tag to the To header field in the response (with the exception of the 100 (Trying) response, in which a tag MAY be present). This serves to identify the UAS that is responding, possibly resulting in a component of a dialog ID. The same tag MUST be used for all responses to that request, both final and provisional (again excepting the 100 (Trying)). Procedures for the generation of tags are defined in Section 19.3. RFC 3261 12.1.1. When a UAS responds to a request with a response that establishes a dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route header field values from the request into the response (including the URIs, URI parameters, and any Record-Route header field parameters, whether they are known or unknown to the UAS) and MUST maintain the order of those values. */ SIP_Response response = new SIP_Response(request); response.StatusCode_ReasonPhrase = statusCode_reasonText; foreach (SIP_t_ViaParm via in request.Via.GetAllValues()) { response.Via.Add(via.ToStringValue()); } response.From = request.From; response.To = request.To; if (request.To.Tag == null) { response.To.Tag = SIP_Utils.CreateTag(); } response.CallID = request.CallID; response.CSeq = request.CSeq; // TODO: Allow: / Supported: if (SIP_Utils.MethodCanEstablishDialog(request.RequestLine.Method)) { foreach (SIP_t_AddressParam route in request.RecordRoute.GetAllValues()) { response.RecordRoute.Add(route.ToStringValue()); } if (response.StatusCodeType == SIP_StatusCodeType.Success && response.Contact.GetTopMostValue() == null && flow != null) { try { string user = ((SIP_Uri) response.To.Address.Uri).User; response.Contact.Add((flow.IsSecure ? "sips:" : "sip:") + user + "@" + TransportLayer.Resolve(flow)); } catch { // TODO: Log } } } return response; }
/// <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 { 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> /// Raises ResponseReceived event. /// </summary> /// <param name="response">SIP response received.</param> private void OnResponseReceived(SIP_Response response) { if (ResponseReceived != null) { ResponseReceived(this, new SIP_ResponseReceivedEventArgs(m_pStack, m_pTransaction, 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> protected internal 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) { // TODO: Validate request or client transaction must do it ? m_IsSecure = ((SIP_Uri) transaction.Request.RequestLine.Uri).IsSecure; m_pRouteSet = (SIP_t_AddressParam[]) Core.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; } #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[]) Core.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; } #endregion m_pFlow = transaction.Flow; }
/// <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(); 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(); request.ProxyAuthorization.Add(authDigest.ToAuthorization()); } } #endregion return allAuthorized; }
/// <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> /// 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> /// 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 += m_pTimer_Elapsed; m_pTimer.Enabled = true; }
/// <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> protected internal 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 { 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 { 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> /// 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> protected internal 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 { 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 { 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> /// 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(); 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(); request.ProxyAuthorization.Add(authDigest.ToAuthorization()); } } #endregion return(allAuthorized); }
/// <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 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> /// Raises <b>ResponseSent</b> event. /// </summary> /// <param name="response">SIP response.</param> private void OnResponseSent(SIP_Response response) { if (ResponseSent != null) { ResponseSent(this, new SIP_ResponseSentEventArgs(this, response)); } }
/// <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 (SyncRoot) { if (State == SIP_TransactionState.Disposed) { return; } try { // Log if (Stack.Logger != null) { byte[] requestData = request.ToByteData(); Stack.Logger.AddRead(Guid.NewGuid().ToString(), null, 0, "Request [transactionID='" + 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 (Method == SIP_Methods.INVITE) { #region INVITE if (request.RequestLine.Method == SIP_Methods.INVITE) { if (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 = LastProvisionalResponse; if (response != null) { Stack.TransportLayer.SendResponse(this, response); } } else if (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. */ Stack.TransportLayer.SendResponse(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 (State == SIP_TransactionState.Completed) { SetState(SIP_TransactionState.Confirmed); // Stop timers G,H if (m_pTimerG != null) { m_pTimerG.Dispose(); m_pTimerG = null; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] timer G(INVITE response(3xx - 6xx) retransmission) stoped."); } } if (m_pTimerH != null) { m_pTimerH.Dispose(); m_pTimerH = null; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + 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 += m_pTimerI_Elapsed; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + 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 (Method == request.RequestLine.Method) { if (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. */ Stack.TransportLayer.SendResponse(this, LastProvisionalResponse); } else if (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. */ Stack.TransportLayer.SendResponse(this, FinalResponse); } } } #endregion } catch (SIP_TransportException x) { // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] transport exception: " + x.Message); } OnTransportError(x); SetState(SIP_TransactionState.Terminated); } } }
/// <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> protected internal 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> /// 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 (SyncRoot) { if (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 (Stack.Logger != null) { byte[] responseData = response.ToByteData(); Stack.Logger.AddRead(Guid.NewGuid().ToString(), null, 0, "Response [transactionID='" + 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 (Method == SIP_Methods.INVITE) { #region Calling if (State == SIP_TransactionState.Calling) { // Store response. AddResponse(response); // Stop timer A,B if (m_pTimerA != null) { m_pTimerA.Dispose(); m_pTimerA = null; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer A(INVITE request retransmission) stoped."); } } if (m_pTimerB != null) { m_pTimerB.Dispose(); m_pTimerB = null; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer B(INVITE calling state timeout) stoped."); } } // 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(Flow.IsReliable ? 0 : Workaround.Definitions.MaxStreamLineLength, false); m_pTimerD.Elapsed += m_pTimerD_Elapsed; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will triger after " + m_pTimerD.Interval + "."); } m_pTimerD.Enabled = true; } } #endregion #region Proceeding else if (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(Flow.IsReliable ? 0 : Workaround.Definitions.MaxStreamLineLength, false); m_pTimerD.Elapsed += m_pTimerD_Elapsed; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will triger after " + m_pTimerD.Interval + "."); } m_pTimerD.Enabled = true; } } #endregion #region Completed else if (State == SIP_TransactionState.Completed) { // 3xx - 6xx if (response.StatusCode >= 300) { SendAck(response); } } #endregion #region Terminated else if (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 (State == SIP_TransactionState.Trying) { // Store response. AddResponse(response); // Stop timer E if (m_pTimerE != null) { m_pTimerE.Dispose(); m_pTimerE = null; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer E(Non-INVITE request retransmission) stoped."); } } // 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 (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stoped."); } } 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(Flow.IsReliable ? 1 : SIP_TimerConstants.T4, false); m_pTimerK.Elapsed += m_pTimerK_Elapsed; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will triger after " + m_pTimerK.Interval + "."); } m_pTimerK.Enabled = true; } } #endregion #region Proceeding else if (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 (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stoped."); } } 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(Flow.IsReliable ? 0 : SIP_TimerConstants.T4, false); m_pTimerK.Elapsed += m_pTimerK_Elapsed; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will triger after " + m_pTimerK.Interval + "."); } m_pTimerK.Enabled = true; } } #endregion #region Completed else if (State == SIP_TransactionState.Completed) { // Eat retransmited response. } #endregion #region Terminated else if (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 (SyncRoot) { if (State == SIP_TransactionState.Disposed) { throw new ObjectDisposedException(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 (Method == SIP_Methods.INVITE) { #region Proceeding if (State == SIP_TransactionState.Proceeding) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); } // 2xx else if (response.StatusCodeType == SIP_StatusCodeType.Success) { Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Terminated); } // 3xx - 6xx else { 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 (!Flow.IsReliable) { m_pTimerG = new TimerEx(SIP_TimerConstants.T1, false); m_pTimerG.Elapsed += m_pTimerG_Elapsed; m_pTimerG.Enabled = true; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + 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 += m_pTimerH_Elapsed; m_pTimerH.Enabled = true; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] timer H(INVITE ACK wait) started, will triger after " + m_pTimerH.Interval + "."); } } } #endregion #region Completed else if (State == SIP_TransactionState.Completed) { // We do nothing here, we just wait ACK to arrive. } #endregion #region Confirmed else if (State == SIP_TransactionState.Confirmed) { // We do nothing, just wait ACK retransmissions. } #endregion #region Terminated else if (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 (State == SIP_TransactionState.Trying) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); SetState(SIP_TransactionState.Proceeding); } // 2xx - 6xx else { 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 += m_pTimerJ_Elapsed; m_pTimerJ.Enabled = true; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] timer J(Non-INVITE request retransmission wait) started, will triger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Proceeding else if (State == SIP_TransactionState.Proceeding) { AddResponse(response); // 1xx if (response.StatusCodeType == SIP_StatusCodeType.Provisional) { Stack.TransportLayer.SendResponse(this, response); OnResponseSent(response); } // 2xx - 6xx else { 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 += m_pTimerJ_Elapsed; m_pTimerJ.Enabled = true; // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=true] timer J(Non-INVITE request retransmission wait) started, will triger after " + m_pTimerJ.Interval + "."); } } } #endregion #region Completed else if (State == SIP_TransactionState.Completed) { // Do nothing. } #endregion #region Terminated else if (State == SIP_TransactionState.Terminated) { // Do nothing. } #endregion } #endregion } catch (SIP_TransportException x) { // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + 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(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> /// 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> protected internal 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) { // TODO: Validate request or client transaction must do it ? m_IsSecure = ((SIP_Uri)transaction.Request.RequestLine.Uri).IsSecure; m_pRouteSet = (SIP_t_AddressParam[])Core.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; } #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[])Core.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; } #endregion m_pFlow = transaction.Flow; }
// FIX ME: /// <summary> /// Raises ResponseReceived event. /// </summary> /// <param name="response">SIP response received.</param> private void OnResponseReceived(SIP_Response response) { if (ResponseReceived != null) { ResponseReceived(this, new SIP_ResponseReceivedEventArgs(Stack, this, response)); } }