/// <summary> /// Sends specified request to flow remote end point. /// </summary> /// <param name="request">SIP request 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>request</b> is null reference.</exception> public void Send(SIP_Request request) { lock (m_pLock){ if (m_IsDisposed) { throw new ObjectDisposedException(this.GetType().Name); } if (request == null) { throw new ArgumentNullException("request"); } SendInternal(request.ToByteData()); } }
/// <summary> /// Processes specified request through this transaction. /// </summary> /// <param name="flow">SIP data flow.</param> /// <param name="request">SIP request.</param> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception> internal void ProcessRequest(SIP_Flow flow, SIP_Request request) { if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } lock (this.SyncRoot){ if (this.State == SIP_TransactionState.Disposed) { return; } try{ // Log if (this.Stack.Logger != null) { byte[] requestData = request.ToByteData(); this.Stack.Logger.AddRead( Guid.NewGuid().ToString(), null, 0, "Request [transactionID='" + this.ID + "'; method='" + request.RequestLine.Method + "'; cseq='" + request.CSeq.SequenceNumber + "'; " + "transport='" + flow.Transport + "'; size='" + requestData.Length + "'; received '" + flow.LocalEP + "' <- '" + flow.RemoteEP + "'.", flow.LocalEP, flow.RemoteEP, requestData ); } #region INVITE if (this.Method == SIP_Methods.INVITE) { #region INVITE if (request.RequestLine.Method == SIP_Methods.INVITE) { if (this.State == SIP_TransactionState.Proceeding) { /* RFC 3261 17.2.1. * If a request retransmission is received while in the "Proceeding" state, the most recent provisional * response that was received from the TU MUST be passed to the transport layer for retransmission. */ SIP_Response response = this.LastProvisionalResponse; if (response != null) { this.Stack.TransportLayer.SendResponse(this, response); } } else if (this.State == SIP_TransactionState.Completed) { /* RFC 3261 17.2.1. * While in the "Completed" state, if a request retransmission is received, the server SHOULD * pass the response to the transport for retransmission. */ this.Stack.TransportLayer.SendResponse(this, this.FinalResponse); } } #endregion #region ACK else if (request.RequestLine.Method == SIP_Methods.ACK) { #region Accepeted if (this.State == SIP_TransactionState.Accpeted) { } #endregion #region Completed else if (this.State == SIP_TransactionState.Completed) { /* 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. */ SetState(SIP_TransactionState.Confirmed); // Stop timers G,H if (m_pTimerG != null) { m_pTimerG.Dispose(); m_pTimerG = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer G(INVITE response(3xx - 6xx) retransmission) stopped."); } } if (m_pTimerH != null) { m_pTimerH.Dispose(); m_pTimerH = null; // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer H(INVITE ACK wait) stopped."); } } // Start timer I. m_pTimerI = new TimerEx((flow.IsReliable ? 0 : SIP_TimerConstants.T4), false); m_pTimerI.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerI_Elapsed); // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer I(INVITE ACK retransission wait) started, will trigger after " + m_pTimerI.Interval + "."); } m_pTimerI.Enabled = true; } #endregion } #endregion } #endregion #region Non-INVITE else { // Non-INVITE transaction may have only request retransmission requests. if (this.Method == request.RequestLine.Method) { if (this.State == SIP_TransactionState.Proceeding) { /* RFC 3261 17.2.2. * If a retransmission of the request is received while in the "Proceeding" state, the most * recently sent provisional response MUST be passed to the transport layer for retransmission. */ this.Stack.TransportLayer.SendResponse(this, this.LastProvisionalResponse); } else if (this.State == SIP_TransactionState.Completed) { /* RFC 3261 17.2.2. * While in the "Completed" state, the server transaction MUST pass the final response to the transport * layer for retransmission whenever a retransmission of the request is received. */ this.Stack.TransportLayer.SendResponse(this, this.FinalResponse); } } } #endregion } catch (SIP_TransportException x) { // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] transport exception: " + x.Message); } OnTransportError(x); } } }
/// <summary> /// Starts sending request. /// </summary> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when <b>Start</b> method has alredy called.</exception> /// <exception cref="SIP_TransportException">Is raised when no transport hop(s) for request.</exception> public void Start() { lock (m_pLock){ if (m_State == SIP_RequestSenderState.Disposed) { throw new ObjectDisposedException(this.GetType().Name); } if (m_IsStarted) { throw new InvalidOperationException("Start method has been already called."); } m_IsStarted = true; m_State = SIP_RequestSenderState.Starting; // Start may take so, process it on thread pool. ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object state){ lock (m_pLock){ if (m_State == SIP_RequestSenderState.Disposed) { return; } /* RFC 3261 8.1.2 Sending the Request * The destination for the request is then computed. Unless there is * local policy specifying otherwise, the destination MUST be determined * by applying the DNS procedures described in [4] as follows. If the * first element in the route set indicated a strict router (resulting * in forming the request as described in Section 12.2.1.1), the * procedures MUST be applied to the Request-URI of the request. * Otherwise, the procedures are applied to the first Route header field * value in the request (if one exists), or to the request's Request-URI * if there is no Route header field present. These procedures yield an * ordered set of address, port, and transports to attempt. Independent * of which URI is used as input to the procedures of [4], if the * Request-URI specifies a SIPS resource, the UAC MUST follow the * procedures of [4] as if the input URI were a SIPS URI. * * The UAC SHOULD follow the procedures defined in [4] for stateful * elements, trying each address until a server is contacted. Each try * constitutes a new transaction, and therefore each carries a different * topmost Via header field value with a new branch parameter. * Furthermore, the transport value in the Via header field is set to * whatever transport was determined for the target server. */ // We never use strict, only loose route. bool isStrictRoute = false; SIP_Uri uri = null; if (isStrictRoute) { uri = (SIP_Uri)m_pRequest.RequestLine.Uri; } else if (m_pRequest.Route.GetTopMostValue() != null) { uri = (SIP_Uri)m_pRequest.Route.GetTopMostValue().Address.Uri; } else { uri = (SIP_Uri)m_pRequest.RequestLine.Uri; } try{ // Queue hops. foreach (SIP_Hop hop in m_pStack.GetHops(uri, m_pRequest.ToByteData().Length, ((SIP_Uri)m_pRequest.RequestLine.Uri).IsSecure)) { m_pHops.Enqueue(hop); } } catch (Exception x) { OnTransportError(new SIP_TransportException("SIP hops resolving failed '" + x.Message + "'.")); OnCompleted(); return; } if (m_pHops.Count == 0) { OnTransportError(new SIP_TransportException("No target hops resolved for '" + uri + "'.")); OnCompleted(); } else { m_State = SIP_RequestSenderState.Started; try{ if (m_pFlow != null) { SendToFlow(m_pFlow, m_pRequest.Copy()); return; } } catch { // Sending to specified flow failed, probably disposed, just try send to first hop. } SendToNextHop(); } } })); } }