/// <summary> /// Sends specified request to the specified data flow. /// </summary> /// <param name="flow">SIP data flow.</param> /// <param name="request">SIP request to send.</param> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception> private void SendToFlow(SIP_Flow flow, SIP_Request request) { if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } #region Contact (RFC 3261 8.1.1.8) /* * The Contact header field provides a SIP or SIPS URI that can be used * to contact that specific instance of the UA for subsequent requests. * The Contact header field MUST be present and contain exactly one SIP * or SIPS URI in any request that can result in the establishment of a * dialog. For the methods defined in this specification, that includes * only the INVITE request. For these requests, the scope of the * Contact is global. That is, the Contact header field value contains * the URI at which the UA would like to receive requests, and this URI * MUST be valid even if used in subsequent requests outside of any * dialogs. * * If the Request-URI or top Route header field value contains a SIPS * URI, the Contact header field MUST contain a SIPS URI as well. */ SIP_t_ContactParam contact = request.Contact.GetTopMostValue(); // Add contact header If request-Method can establish dialog and contact header not present. if (SIP_Utils.MethodCanEstablishDialog(request.RequestLine.Method) && contact == null) { SIP_Uri from = (SIP_Uri)request.From.Address.Uri; request.Contact.Add((flow.IsSecure ? "sips:" : "sip:") + from.User + "@" + flow.LocalPublicEP.ToString()); // REMOVE ME: 22.10.2010 //request.Contact.Add((flow.IsSecure ? "sips:" : "sip:" ) + from.User + "@" + m_pStack.TransportLayer.GetContactHost(flow).ToString()); } // If contact SIP URI and host = auto-allocate, allocate it as needed. else if (contact != null && contact.Address.Uri is SIP_Uri && ((SIP_Uri)contact.Address.Uri).Host == "auto-allocate") { ((SIP_Uri)contact.Address.Uri).Host = flow.LocalPublicEP.ToString(); // REMOVE ME: 22.10.2010 //((SIP_Uri)contact.Address.Uri).Host = m_pStack.TransportLayer.GetContactHost(flow).ToString(); } #endregion m_pTransaction = m_pStack.TransactionLayer.CreateClientTransaction(flow, request, true); m_pTransaction.ResponseReceived += new EventHandler <SIP_ResponseReceivedEventArgs>(ClientTransaction_ResponseReceived); m_pTransaction.TimedOut += new EventHandler(ClientTransaction_TimedOut); m_pTransaction.TransportError += new EventHandler <ExceptionEventArgs>(ClientTransaction_TransportError); // Start transaction processing. m_pTransaction.Start(); }
/// <summary> /// Sends notify request to remote end point. /// </summary> /// <param name="notify">SIP NOTIFY request.</param> public void Notify(SIP_Request notify) { if (notify == null) { throw new ArgumentNullException("notify"); } // TODO: }
/// <summary> /// Default constructor. /// </summary> /// <param name="stack">Owner SIP stack.</param> /// <param name="flow">Transaction data flow.</param> /// <param name="request">SIP request that transaction will handle.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>flow</b> or <b>request</b> is null reference.</exception> public SIP_Transaction(SIP_Stack stack, SIP_Flow flow, SIP_Request request) { if (stack == null) { throw new ArgumentNullException("stack"); } if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } m_pStack = stack; m_pFlow = flow; m_pRequest = request; m_Method = request.RequestLine.Method; m_CreateTime = DateTime.Now; m_pResponses = new List <SIP_Response>(); // Validate Via: SIP_t_ViaParm via = request.Via.GetTopMostValue(); if (via == null) { throw new ArgumentException("Via: header is missing !"); } if (via.Branch == null) { throw new ArgumentException("Via: header 'branch' parameter is missing !"); } m_ID = via.Branch; if (this is SIP_ServerTransaction) { /* * We use branch and sent-by as indexing key for transaction, the only special what we need to * do is to handle CANCEL, because it has same branch as transaction to be canceled. * For avoiding key collision, we add branch + '-' + 'sent-by' + CANCEL for cancel index key. * ACK has also same branch, but we won't do transaction for ACK, so it isn't problem. */ string key = request.Via.GetTopMostValue().Branch + '-' + request.Via.GetTopMostValue().SentBy; if (request.RequestLine.Method == SIP_Methods.CANCEL) { key += "-CANCEL"; } m_Key = key; } else { m_Key = m_ID + "-" + request.RequestLine.Method; } }
/// <summary> /// Clones this request. /// </summary> /// <returns>Returns new cloned request.</returns> public SIP_Request Copy() { SIP_Request retVal = SIP_Request.Parse(this.ToByteData()); retVal.Flow = m_pFlow; retVal.LocalEndPoint = m_pLocalEP; retVal.RemoteEndPoint = m_pRemoteEP; return(retVal); }
/// <summary> /// Default constructor. /// </summary> /// <param name="stack">Owner SIP stack.</param> /// <param name="flow">SIP data flow which received request.</param> /// <param name="request">SIP request that transaction will handle.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>flow</b> or <b>request</b> is null reference.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> public SIP_ServerTransaction(SIP_Stack stack, SIP_Flow flow, SIP_Request request) : base(stack, flow, request) { // Log if (this.Stack.Logger != null) { this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] created."); } Start(); }
/// <summary> /// Cleans up any resources being used. /// </summary> public virtual void Dispose() { SetState(SIP_TransactionState.Disposed); OnDisposed(); m_pStack = null; m_pFlow = null; m_pRequest = null; this.StateChanged = null; this.Disposed = null; this.TimedOut = null; this.TransportError = null; }
/// <summary> /// Creates new client transaction. /// </summary> /// <param name="flow">SIP data flow which is used to send request.</param> /// <param name="request">SIP request that transaction will handle.</param> /// <param name="addVia">If true, transaction will add <b>Via:</b> header, otherwise it's user responsibility.</param> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception> /// <returns>Returns created transaction.</returns> public SIP_ClientTransaction CreateClientTransaction(SIP_Flow flow, SIP_Request request, bool addVia) { if (m_IsDisposed) { throw new ObjectDisposedException(this.GetType().Name); } if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } // Add Via: if (addVia) { SIP_t_ViaParm via = new SIP_t_ViaParm(); via.ProtocolName = "SIP"; via.ProtocolVersion = "2.0"; via.ProtocolTransport = flow.Transport; via.SentBy = new HostEndPoint("transport_layer_will_replace_it", -1); via.Branch = SIP_t_ViaParm.CreateBranch(); via.RPort = 0; request.Via.AddToTop(via.ToStringValue()); } lock (m_pClientTransactions){ SIP_ClientTransaction transaction = new SIP_ClientTransaction(m_pStack, flow, request); m_pClientTransactions.Add(transaction.Key, transaction); transaction.StateChanged += new EventHandler(delegate(object s, EventArgs e){ if (transaction.State == SIP_TransactionState.Terminated) { lock (m_pClientTransactions){ m_pClientTransactions.Remove(transaction.Key); } } }); SIP_Dialog dialog = MatchDialog(request); if (dialog != null) { dialog.AddTransaction(transaction); } return(transaction); } }
/// <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> /// Matches SIP request to server transaction. If not matching transaction found, returns null. /// </summary> /// <param name="request">SIP request to match.</param> /// <returns>Returns matching transaction or null if no match.</returns> internal SIP_ServerTransaction MatchServerTransaction(SIP_Request request) { /* RFC 3261 17.2.3 Matching Requests to Server Transactions. * This matching rule applies to both INVITE and non-INVITE transactions. * * 1. the branch parameter in the request is equal to the one in the top Via header * field of the request that created the transaction, and * * 2. the sent-by value in the top Via of the request is equal to the * one in the request that created the transaction, and * * 3. the method of the request matches the one that created the transaction, except * for ACK, where the method of the request that created the transaction is INVITE. * * Internal implementation notes: * Inernally we use branch + '-' + sent-by for non-CANCEL and for CANCEL * branch + '-' + sent-by + '-' CANCEL. This is because method matching is actually * needed for CANCEL only (CANCEL shares cancelable transaction branch ID). */ SIP_ServerTransaction retVal = null; /* * We use branch and sent-by as indexing key for transaction, the only special what we need to * do is to handle CANCEL, because it has same branch as transaction to be canceled. * For avoiding key collision, we add branch + '-' + 'sent-by' + CANCEL for cancel index key. * ACK has also same branch, but we won't do transaction for ACK, so it isn't problem. */ string key = request.Via.GetTopMostValue().Branch + '-' + request.Via.GetTopMostValue().SentBy; if (request.RequestLine.Method == SIP_Methods.CANCEL) { key += "-CANCEL"; } lock (m_pServerTransactions){ m_pServerTransactions.TryGetValue(key, out retVal); } // Don't match ACK for terminated transaction, in that case ACK must be passed to "core". if (retVal != null && request.RequestLine.Method == SIP_Methods.ACK && retVal.State == SIP_TransactionState.Terminated) { retVal = null; } return(retVal); }
/// <summary> /// Default constructor. /// </summary> /// <param name="stack">Owner stack.</param> /// <param name="request">SIP request.</param> /// <param name="flow">Active data flow what to try before RFC 3261 [4](RFC 3263) methods to use to send request. /// This value can be null.</param> /// <exception cref="ArgumentNullException">Is raised when <b>stack</b> or <b>request</b> is null.</exception> internal SIP_RequestSender(SIP_Stack stack, SIP_Request request, SIP_Flow flow) { if (stack == null) { throw new ArgumentNullException("stack"); } if (request == null) { throw new ArgumentNullException("request"); } m_pStack = stack; m_pRequest = request; m_pFlow = flow; m_pCredentials = new List <NetworkCredential>(); m_pHops = new Queue <SIP_Hop>(); }
/// <summary> /// Creates SIP request sender for the specified request. /// </summary> /// <remarks>All requests sent through this dialog SHOULD use this request sender to send out requests.</remarks> /// <param name="request">SIP request.</param> /// <returns>Returns created sender.</returns> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>request</b> is null.</exception> public SIP_RequestSender CreateRequestSender(SIP_Request request) { lock (m_pLock){ if (this.State == SIP_DialogState.Terminated) { throw new ObjectDisposedException(this.GetType().Name); } if (request == null) { throw new ArgumentNullException("request"); } // TODO: Request sender must use dialog sequence numbering if authentication done. SIP_RequestSender sender = m_pStack.CreateRequestSender(request, this.Flow); return(sender); } }
/// <summary> /// Starts unregistering. /// </summary> /// <param name="dispose">If true, registration will be disposed after unregister.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> public void BeginUnregister(bool dispose) { if (m_IsDisposed) { throw new ObjectDisposedException(this.GetType().Name); } m_AutoDispose = dispose; // Stop register timer, otherwise we may get register and unregister race condition. m_pTimer.Enabled = false; if (m_State == SIP_UA_RegistrationState.Registered) { /* RFC 3261 10.1 Constructing the REGISTER Request. * Request-URI: The Request-URI names the domain of the location service for which the registration is meant (for example, * "sip:chicago.com"). The "userinfo" and "@" components of the SIP URI MUST NOT be present. */ SIP_Request unregister = m_pStack.CreateRequest(SIP_Methods.REGISTER, new SIP_t_NameAddress(m_pServer.Scheme + ":" + m_AOR), new SIP_t_NameAddress(m_pServer.Scheme + ":" + m_AOR)); unregister.RequestLine.Uri = SIP_Uri.Parse(m_pServer.Scheme + ":" + m_AOR.Substring(m_AOR.IndexOf('@') + 1)); unregister.Route.Add(m_pServer.ToString()); unregister.Contact.Add("<" + this.Contact + ">;expires=0"); m_pUnregisterSender = m_pStack.CreateRequestSender(unregister, m_pFlow); m_pUnregisterSender.ResponseReceived += new EventHandler <SIP_ResponseReceivedEventArgs>(m_pUnregisterSender_ResponseReceived); m_pUnregisterSender.Start(); } else { SetState(SIP_UA_RegistrationState.Unregistered); OnUnregistered(); if (m_AutoDispose) { Dispose(); } m_pUnregisterSender = null; } }
/// <summary> /// Ensures that specified request has matching server transaction. If server transaction doesn't exist, /// it will be created, otherwise existing transaction will be returned. /// </summary> /// <param name="flow">SIP data flow which is used to receive request.</param> /// <param name="request">SIP request.</param> /// <returns>Returns matching transaction.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null.</exception> /// <exception cref="InvalidOperationException">Is raised when request.Method is ACK request.</exception> public SIP_ServerTransaction EnsureServerTransaction(SIP_Flow flow, SIP_Request request) { if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } if (request.RequestLine.Method == SIP_Methods.ACK) { throw new InvalidOperationException("ACK request is transaction less request, can't create transaction for it."); } /* * We use branch and sent-by as indexing key for transaction, the only special what we need to * do is to handle CANCEL, because it has same branch as transaction to be canceled. * For avoiding key collision, we add branch + '-' + 'sent-by' + CANCEL for cancel index key. * ACK has also same branch, but we won't do transaction for ACK, so it isn't problem. */ string key = request.Via.GetTopMostValue().Branch + '-' + request.Via.GetTopMostValue().SentBy; if (request.RequestLine.Method == SIP_Methods.CANCEL) { key += "-CANCEL"; } lock (m_pServerTransactions){ SIP_ServerTransaction retVal = null; m_pServerTransactions.TryGetValue(key, out retVal); // We don't have transaction, create it. if (retVal == null) { retVal = CreateServerTransaction(flow, request); } return(retVal); } }
/// <summary> /// Cleans up any resources being used. /// </summary> public void Dispose() { lock (m_pLock){ if (m_State == SIP_RequestSenderState.Disposed) { return; } m_State = SIP_RequestSenderState.Disposed; OnDisposed(); this.ResponseReceived = null; this.Completed = null; this.Disposed = null; m_pStack = null; m_pRequest = null; m_pCredentials = null; m_pHops = null; m_pTransaction = null; m_pLock = null; } }
/// <summary> /// Matches CANCEL requst to SIP server non-CANCEL transaction. Returns null if no match. /// </summary> /// <param name="cancelRequest">SIP CANCEL request.</param> /// <returns>Returns CANCEL matching server transaction or null if no match.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>cancelTransaction</b> is null.</exception> /// <exception cref="ArgumentException">Is raised when <b>cancelTransaction</b> has invalid.</exception> public SIP_ServerTransaction MatchCancelToTransaction(SIP_Request cancelRequest) { if (cancelRequest == null) { throw new ArgumentNullException("cancelRequest"); } if (cancelRequest.RequestLine.Method != SIP_Methods.CANCEL) { throw new ArgumentException("Argument 'cancelRequest' is not SIP CANCEL request."); } SIP_ServerTransaction retVal = null; // NOTE: There we don't add '-CANCEL' because we want to get CANCEL matching transaction, not CANCEL // transaction itself. string key = cancelRequest.Via.GetTopMostValue().Branch + '-' + cancelRequest.Via.GetTopMostValue().SentBy; lock (m_pServerTransactions){ m_pServerTransactions.TryGetValue(key, out retVal); } return(retVal); }
/// <summary> /// Creates new SIP server transaction for specified request. /// </summary> /// <param name="flow">SIP data flow which is used to receive request.</param> /// <param name="request">SIP request.</param> /// <returns>Returns added server transaction.</returns> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception> public SIP_ServerTransaction CreateServerTransaction(SIP_Flow flow, SIP_Request request) { if (m_IsDisposed) { throw new ObjectDisposedException(this.GetType().Name); } if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } lock (m_pServerTransactions){ SIP_ServerTransaction transaction = new SIP_ServerTransaction(m_pStack, flow, request); m_pServerTransactions.Add(transaction.Key, transaction); transaction.StateChanged += new EventHandler(delegate(object s, EventArgs e){ if (transaction.State == SIP_TransactionState.Terminated) { lock (m_pClientTransactions){ m_pServerTransactions.Remove(transaction.Key); } } }); SIP_Dialog dialog = MatchDialog(request); if (dialog != null) { dialog.AddTransaction(transaction); } return(transaction); } }
/// <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> /// Default constructor. /// </summary> /// <param name="request">Incoming SIP request.</param> /// <param name="remoteEndpoint">IP end point what made request.</param> public SIP_ValidateRequestEventArgs(SIP_Request request, IPEndPoint remoteEndpoint) { m_pRequest = request; m_pRemoteEndPoint = remoteEndpoint; }
/// <summary> /// Starts terminating dialog. /// </summary> /// <param name="reason">Termination reason. This value may be null.</param> /// <param name="sendBye">If true BYE is sent to remote party.</param> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> public void Terminate(string reason, bool sendBye) { lock (this.SyncRoot){ if (this.State == SIP_DialogState.Disposed) { throw new ObjectDisposedException(this.GetType().Name); } if (this.State == SIP_DialogState.Terminating || this.State == SIP_DialogState.Terminated) { return; } m_TerminateReason = reason; /* RFC 3261 15. * The caller's UA MAY send a BYE for either confirmed or early dialogs, and the callee's UA MAY send a BYE on * confirmed dialogs, but MUST NOT send a BYE on early dialogs. * * RFC 3261 15.1. * Once the BYE is constructed, the UAC core creates a new non-INVITE client transaction, and passes it the BYE request. * The UAC MUST consider the session terminated (and therefore stop sending or listening for media) as soon as the BYE * request is passed to the client transaction. If the response for the BYE is a 481 (Call/Transaction Does Not Exist) * or a 408 (Request Timeout) or no response at all is received for the BYE (that is, a timeout is returned by the * client transaction), the UAC MUST consider the session and the dialog terminated. */ if (sendBye) { if ((this.State == SIP_DialogState.Early && m_pActiveInvite is SIP_ClientTransaction) || this.State == SIP_DialogState.Confirmed) { this.SetState(SIP_DialogState.Terminating, true); SIP_Request bye = CreateRequest(SIP_Methods.BYE); if (!string.IsNullOrEmpty(reason)) { SIP_t_ReasonValue r = new SIP_t_ReasonValue(); r.Protocol = "SIP"; r.Text = reason; bye.Reason.Add(r.ToStringValue()); } // Send BYE, just wait BYE to complete, we don't care about response code. SIP_RequestSender sender = CreateRequestSender(bye); sender.Completed += delegate(object s, EventArgs a){ this.SetState(SIP_DialogState.Terminated, true); }; sender.Start(); } else { /* We are "early" UAS dialog, we need todo follwoing: *) If we havent sent final response, send '408 Request terminated' and we are done. *) We have sen't final response, we need to wait ACK to arrive or timeout. * If will ACK arrives or timeout, send BYE. */ if (m_pActiveInvite != null && m_pActiveInvite.FinalResponse == null) { this.Stack.CreateResponse(SIP_ResponseCodes.x408_Request_Timeout, m_pActiveInvite.Request); this.SetState(SIP_DialogState.Terminated, true); } else { // Wait ACK to arrive or timeout. this.SetState(SIP_DialogState.Terminating, true); } } } else { this.SetState(SIP_DialogState.Terminated, true); } } }
/// <summary> /// Creates new SIP request using this dialog info. /// </summary> /// <param name="method">SIP method.</param> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>method</b> is null reference.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> /// <returns>Returns created request.</returns> public SIP_Request CreateRequest(string method) { if (this.State == SIP_DialogState.Disposed) { throw new ObjectDisposedException(this.GetType().Name); } if (method == null) { throw new ArgumentNullException("method"); } if (method == string.Empty) { throw new ArgumentException("Argument 'method' value must be specified."); } /* RFC 3261 12.2.1.1. * A request within a dialog is constructed by using many of the * components of the state stored as part of the dialog. * * The URI in the To field of the request MUST be set to the remote URI * from the dialog state. The tag in the To header field of the request * MUST be set to the remote tag of the dialog ID. The From URI of the * request MUST be set to the local URI from the dialog state. The tag * in the From header field of the request MUST be set to the local tag * of the dialog ID. If the value of the remote or local tags is null, * the tag parameter MUST be omitted from the To or From header fields, * respectively. * * The Call-ID of the request MUST be set to the Call-ID of the dialog. * Requests within a dialog MUST contain strictly monotonically * increasing and contiguous CSeq sequence numbers (increasing-by-one) * in each direction (excepting ACK and CANCEL of course, whose numbers * equal the requests being acknowledged or cancelled). Therefore, if * the local sequence number is not empty, the value of the local * sequence number MUST be incremented by one, and this value MUST be * placed into the CSeq header field. If the local sequence number is * empty, an initial value MUST be chosen using the guidelines of * Section 8.1.1.5. The method field in the CSeq header field value * MUST match the method of the request. * * With a length of 32 bits, a client could generate, within a single * call, one request a second for about 136 years before needing to * wrap around. The initial value of the sequence number is chosen * so that subsequent requests within the same call will not wrap * around. A non-zero initial value allows clients to use a time- * based initial sequence number. A client could, for example, * choose the 31 most significant bits of a 32-bit second clock as an * initial sequence number. * * The UAC uses the remote target and route set to build the Request-URI * and Route header field of the request. * * If the route set is empty, the UAC MUST place the remote target URI * into the Request-URI. The UAC MUST NOT add a Route header field to * the request. * * If the route set is not empty, and the first URI in the route set * contains the lr parameter (see Section 19.1.1), the UAC MUST place * the remote target URI into the Request-URI and MUST include a Route * header field containing the route set values in order, including all * parameters. * * If the route set is not empty, and its first URI does not contain the * lr parameter, the UAC MUST place the first URI from the route set * into the Request-URI, stripping any parameters that are not allowed * in a Request-URI. The UAC MUST add a Route header field containing * the remainder of the route set values in order, including all * parameters. The UAC MUST then place the remote target URI into the * Route header field as the last value. * * For example, if the remote target is sip:user@remoteua and the route * set contains: * <sip:proxy1>,<sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4> * * The request will be formed with the following Request-URI and Route * header field: * METHOD sip:proxy1 * Route: <sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>,<sip:user@remoteua> * * If the first URI of the route set does not contain the lr * parameter, the proxy indicated does not understand the routing * mechanisms described in this document and will act as specified in * RFC 2543, replacing the Request-URI with the first Route header * field value it receives while forwarding the message. Placing the * Request-URI at the end of the Route header field preserves the * information in that Request-URI across the strict router (it will * be returned to the Request-URI when the request reaches a loose- * router). * * A UAC SHOULD include a Contact header field in any target refresh * requests within a dialog, and unless there is a need to change it, * the URI SHOULD be the same as used in previous requests within the * dialog. If the "secure" flag is true, that URI MUST be a SIPS URI. * As discussed in Section 12.2.2, a Contact header field in a target * refresh request updates the remote target URI. This allows a UA to * provide a new contact address, should its address change during the * duration of the dialog. * * However, requests that are not target refresh requests do not affect * the remote target URI for the dialog. * * The rest of the request is formed as described in Section 8.1.1. */ lock (m_pLock){ SIP_Request request = m_pStack.CreateRequest(method, new SIP_t_NameAddress("", m_pRemoteUri), new SIP_t_NameAddress("", m_pLocalUri)); request.Route.RemoveAll(); if (m_pRouteSet.Length == 0) { request.RequestLine.Uri = m_pRemoteTarget; } else { SIP_Uri topmostRoute = ((SIP_Uri)m_pRouteSet[0].Address.Uri); if (topmostRoute.Param_Lr) { request.RequestLine.Uri = m_pRemoteTarget; for (int i = 0; i < m_pRouteSet.Length; i++) { request.Route.Add(m_pRouteSet[i].ToStringValue()); } } else { request.RequestLine.Uri = SIP_Utils.UriToRequestUri(topmostRoute); for (int i = 1; i < m_pRouteSet.Length; i++) { request.Route.Add(m_pRouteSet[i].ToStringValue()); } } } request.To.Tag = m_RemoteTag; request.From.Tag = m_LocalTag; request.CallID = m_CallID; // ACK won't increase sequence. if (method != SIP_Methods.ACK) { request.CSeq.SequenceNumber = ++m_LocalSeqNo; } request.Contact.Add(m_pLocalContact.ToString()); return(request); } }
/// <summary> /// Creates authorization for each challange in <b>response</b>. /// </summary> /// <param name="request">SIP request where to add authorization values.</param> /// <param name="response">SIP response which challanges to authorize.</param> /// <param name="credentials">Credentials for authorization.</param> /// <returns>Returns true if all challanges were authorized. If any of the challanges was not authorized, returns false.</returns> private bool Authorize(SIP_Request request, SIP_Response response, NetworkCredential[] credentials) { if (request == null) { throw new ArgumentNullException("request"); } if (response == null) { throw new ArgumentNullException("response"); } if (credentials == null) { throw new ArgumentNullException("credentials"); } bool allAuthorized = true; #region WWWAuthenticate foreach (SIP_t_Challenge challange in response.WWWAuthenticate.GetAllValues()) { Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData, request.RequestLine.Method); // Serach credential for the specified challange. NetworkCredential credential = null; foreach (NetworkCredential c in credentials) { if (c.Domain.ToLower() == authDigest.Realm.ToLower()) { credential = c; break; } } // We don't have credential for this challange. if (credential == null) { allAuthorized = false; } // Authorize challange. else { authDigest.UserName = credential.UserName; authDigest.Password = credential.Password; authDigest.CNonce = Auth_HttpDigest.CreateNonce(); authDigest.Uri = request.RequestLine.Uri.ToString(); request.Authorization.Add(authDigest.ToAuthorization()); } } #endregion #region ProxyAuthenticate foreach (SIP_t_Challenge challange in response.ProxyAuthenticate.GetAllValues()) { Auth_HttpDigest authDigest = new Auth_HttpDigest(challange.AuthData, request.RequestLine.Method); // Serach credential for the specified challange. NetworkCredential credential = null; foreach (NetworkCredential c in credentials) { if (c.Domain.ToLower() == authDigest.Realm.ToLower()) { credential = c; break; } } // We don't have credential for this challange. if (credential == null) { allAuthorized = false; } // Authorize challange. else { authDigest.UserName = credential.UserName; authDigest.Password = credential.Password; authDigest.CNonce = Auth_HttpDigest.CreateNonce(); authDigest.Uri = request.RequestLine.Uri.ToString(); request.ProxyAuthorization.Add(authDigest.ToAuthorization()); } } #endregion return(allAuthorized); }
/// <summary> /// SIP_Request.CreateResponse constructor. /// </summary> /// <param name="request">Owner request.</param> internal SIP_Response(SIP_Request request) { m_pRequest = request; }
/// <summary> /// Default constructor. /// </summary> /// <param name="stack">Reference to SIP stack.</param> /// <param name="flow">SIP data flow.</param> /// <param name="request">Recieved request.</param> internal SIP_RequestReceivedEventArgs(SIP_Stack stack, SIP_Flow flow, SIP_Request request) : this(stack, flow, request, null) { }
/// <summary> /// Is called when client transactions receives response. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void ClientTransaction_ResponseReceived(object sender, SIP_ResponseReceivedEventArgs e) { lock (m_pLock){ m_pFlow = e.ClientTransaction.Request.Flow; if (e.Response.StatusCode == 401 || e.Response.StatusCode == 407) { // Check if authentication failed(We sent authorization data and it's challenged again, // probably user name or password inccorect) bool hasFailedAuthorization = false; foreach (SIP_t_Challenge challange in e.Response.WWWAuthenticate.GetAllValues()) { foreach (SIP_t_Credentials credentials in m_pTransaction.Request.Authorization.GetAllValues()) { if (new Auth_HttpDigest(challange.AuthData, "").Realm == new Auth_HttpDigest(credentials.AuthData, "").Realm) { hasFailedAuthorization = true; break; } } } foreach (SIP_t_Challenge challange in e.Response.ProxyAuthenticate.GetAllValues()) { foreach (SIP_t_Credentials credentials in m_pTransaction.Request.ProxyAuthorization.GetAllValues()) { if (new Auth_HttpDigest(challange.AuthData, "").Realm == new Auth_HttpDigest(credentials.AuthData, "").Realm) { hasFailedAuthorization = true; break; } } } // Authorization failed, pass response to UA. if (hasFailedAuthorization) { OnResponseReceived(e.Response); } // Try to authorize challanges. else { SIP_Request request = m_pRequest.Copy(); /* RFC 3261 22.2. * When a UAC resubmits a request with its credentials after receiving a * 401 (Unauthorized) or 407 (Proxy Authentication Required) response, * it MUST increment the CSeq header field value as it would normally * when sending an updated request. */ request.CSeq = new SIP_t_CSeq(m_pStack.ConsumeCSeq(), request.CSeq.RequestMethod); // All challanges authorized, resend request. if (Authorize(request, e.Response, this.Credentials.ToArray())) { SIP_Flow flow = m_pTransaction.Flow; CleanUpActiveTransaction(); SendToFlow(flow, request); } // We don't have credentials for one or more challenges. else { OnResponseReceived(e.Response); } } } else { OnResponseReceived(e.Response); if (e.Response.StatusCodeType != SIP_StatusCodeType.Provisional) { OnCompleted(); } } } }