/// <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> /// 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(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 (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> /// 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(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(delegate { 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; } //uri.Param_Transport = "TCP"; // 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); } if (m_pHops.Count == 0) { OnTransportError( new SIP_TransportException("No hops 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(); } } }); } }
/// <summary> /// Sends request to the specified flow. /// </summary> /// <param name="flow">Data flow.</param> /// <param name="request">SIP request.</param> /// <param name="transaction">Owner client transaction or null if stateless sending.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments contains invalid value.</exception> internal void SendRequest(SIP_Flow flow, SIP_Request request, SIP_ClientTransaction transaction) { if (m_IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } if (request.Via.GetTopMostValue() == null) { throw new ArgumentException("Argument 'request' doesn't contain required Via: header field."); } // Set sent-by SIP_t_ViaParm via = request.Via.GetTopMostValue(); via.ProtocolTransport = flow.Transport; // Via sent-by is used only to send responses when request maker data flow is not active. // Normally this never used, so just report first local listening point as sent-by. HostEndPoint sentBy = null; foreach (IPBindInfo bind in BindInfo) { if (flow.Transport == SIP_Transport.UDP && bind.Protocol == BindInfoProtocol.UDP) { if (!string.IsNullOrEmpty(bind.HostName)) { sentBy = new HostEndPoint(bind.HostName, bind.Port); } else { sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(), bind.Port); } break; } else if (flow.Transport == SIP_Transport.TLS && bind.Protocol == BindInfoProtocol.TCP && bind.SslMode == SslMode.SSL) { if (!string.IsNullOrEmpty(bind.HostName)) { sentBy = new HostEndPoint(bind.HostName, bind.Port); } else { sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(), bind.Port); } break; } else if (flow.Transport == SIP_Transport.TCP && bind.Protocol == BindInfoProtocol.TCP) { if (!string.IsNullOrEmpty(bind.HostName)) { sentBy = new HostEndPoint(bind.HostName, bind.Port); } else { sentBy = new HostEndPoint(flow.LocalEP.Address.ToString(), bind.Port); } break; } } // No local end point for sent-by, just use flow local end point for it. if (sentBy == null) { via.SentBy = new HostEndPoint(flow.LocalEP); } else { via.SentBy = sentBy; } // Send request. flow.Send(request); // Log. if (m_pStack.Logger != null) { byte[] requestData = request.ToByteData(); m_pStack.Logger.AddWrite(Guid.NewGuid().ToString(), null, 0, "Request [" + (transaction == null ? "" : "transactionID='" + transaction.ID + "';") + "method='" + request.RequestLine.Method + "'; cseq='" + request.CSeq.SequenceNumber + "'; " + "transport='" + flow.Transport + "'; size='" + requestData.Length + "'; sent '" + flow.LocalEP + "' -> '" + flow.RemoteEP + "'.", flow.LocalEP, flow.RemoteEP, requestData); } }
/// <summary> /// Sends request using methods as described in RFC 3261 [4](RFC 3263). /// </summary> /// <param name="request">SIP request to send.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>request</b> is null.</exception> /// <exception cref="SIP_TransportException">Is raised when transport error happens.</exception> public void SendRequest(SIP_Request request) { if (m_IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (request == null) { throw new ArgumentNullException("request"); } SIP_Hop[] hops = m_pStack.GetHops((SIP_Uri) request.RequestLine.Uri, request.ToByteData().Length, false); if (hops.Length == 0) { throw new SIP_TransportException("No target hops for URI '" + request.RequestLine.Uri + "'."); } SIP_TransportException lastException = null; foreach (SIP_Hop hop in hops) { try { SendRequest(request, null, hop); return; } catch (SIP_TransportException x) { lastException = x; } } // If we reach so far, send failed, return last error. throw lastException; }
/// <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); } } }