/// <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> /// Matches specified SIP request to SIP dialog. If no matching dialog found, returns null. /// </summary> /// <param name="request">SIP request.</param> /// <returns>Returns matched SIP dialog or null in no match found.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>request</b> is null.</exception> internal SIP_Dialog MatchDialog(SIP_Request request) { if (request == null) { throw new ArgumentNullException("request"); } SIP_Dialog dialog = null; try { string callID = request.CallID; string localTag = request.To.Tag; string remoteTag = request.From.Tag; if (callID != null && localTag != null && remoteTag != null) { string dialogID = callID + "-" + localTag + "-" + remoteTag; lock (m_pDialogs) { m_pDialogs.TryGetValue(dialogID, out dialog); } } } catch {} return(dialog); }
/// <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(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 += delegate { if (transaction.State == SIP_TransactionState.Terminated) { lock (m_pClientTransactions) { m_pServerTransactions.Remove(transaction.Key); } } }; return(transaction); } }
/// <summary> /// Parses SIP_Request from stream. /// </summary> /// <param name="stream">Stream what contains valid SIP request.</param> /// <returns>Returns parsed SIP_Request 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_Request Parse(Stream stream) { /* Syntax: * SIP-Method SIP-URI SIP-Version * SIP-Message */ if (stream == null) { throw new ArgumentNullException("stream"); } // Parse Response-line StreamLineReader r = new StreamLineReader(stream); r.Encoding = "utf-8"; string[] method_uri_version = r.ReadLineString().Split(' '); if (method_uri_version.Length != 3) { throw new Exception( "Invalid SIP request data ! Method line doesn't contain: SIP-Method SIP-URI SIP-Version."); } SIP_Request retVal = new SIP_Request(method_uri_version[0]); retVal.RequestLine.Uri = AbsoluteUri.Parse(method_uri_version[1]); retVal.RequestLine.Version = method_uri_version[2]; // Parse SIP message retVal.InternalParse(stream); return(retVal); }
/// <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 + "@" + m_pStack.TransportLayer.GetContactHost(flow)); } // 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 = m_pStack.TransportLayer.GetContactHost(flow).ToString(); } #endregion m_pTransaction = m_pStack.TransactionLayer.CreateClientTransaction(flow, request, true); m_pTransaction.ResponseReceived += ClientTransaction_ResponseReceived; m_pTransaction.TimedOut += ClientTransaction_TimedOut; m_pTransaction.TransportError += 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> /// 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> /// 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 = Parse(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">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> /// Terminates 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 virtual void Terminate(string reason, bool sendBye) { lock (m_pLock) { if (State == SIP_DialogState.Disposed) { throw new ObjectDisposedException(GetType().Name); } if (State == SIP_DialogState.Terminating || State == SIP_DialogState.Terminated) { return; } /* 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. */ SetState(SIP_DialogState.Terminating, true); if (sendBye) { // TODO: UAS early if (State == SIP_DialogState.Confirmed) { 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 { SetState(SIP_DialogState.Terminated, true); }; sender.Start(); } } else { SetState(SIP_DialogState.Terminated, true); } } }
/// <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> /// <param name="dialog">SIP dialog which received request.</param> /// <param name="transaction">SIP server transaction which must be used to send response back to request maker.</param> internal SIP_RequestReceivedEventArgs(SIP_Stack stack, SIP_Flow flow, SIP_Request request, SIP_Dialog dialog, SIP_ServerTransaction transaction) { m_pStack = stack; m_pFlow = flow; m_pRequest = request; m_pDialog = dialog; m_pTransaction = transaction; }
/// <summary> /// Default constructor. /// </summary> /// <param name="stack">Owner SIP stack.</param> /// <param name="flow">SIP data flow which is used to send 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> internal SIP_ClientTransaction(SIP_Stack stack, SIP_Flow flow, SIP_Request request) : base(stack, flow, request) { // Log if (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + Method + "';IsServer=false] created."); } SetState(SIP_TransactionState.WaitingToStart); }
/// <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 (Stack.Logger != null) { Stack.Logger.AddText(ID, "Transaction [branch='" + ID + "';method='" + 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; StateChanged = null; Disposed = null; TimedOut = null; TransportError = null; }
/// <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> /// 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> /// Checks if specified ACK request matches this 2xx response retransmission. /// </summary> /// <param name="ackRequest">ACK request.</param> /// <returns>Returns true if ACK matches, othwerwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>ackRequest</b> is null reference value.</exception> public bool MatchAck(SIP_Request ackRequest) { if (ackRequest == null) { throw new ArgumentNullException("ackRequest"); } if (ackRequest.CSeq.SequenceNumber == m_pResponse.CSeq.SequenceNumber) { return(true); } else { return(false); } }
/// <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(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 += delegate { if (transaction.State == SIP_TransactionState.Terminated) { lock (m_pClientTransactions) { m_pClientTransactions.Remove(transaction.Key); } } }; return(transaction); } }
/// <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(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("<" + Contact + ">;expires=0"); m_pUnregisterSender = m_pStack.CreateRequestSender(unregister, m_pFlow); m_pUnregisterSender.ResponseReceived += m_pUnregisterSender_ResponseReceived; m_pUnregisterSender.Start(); } else { SetState(SIP_UA_RegistrationState.Unregistered); OnUnregistered(); if (m_AutoDispose) { Dispose(); } m_pUnregisterSender = null; } }
/// <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 (State == SIP_DialogState.Terminated) { throw new ObjectDisposedException(GetType().Name); } if (request == null) { throw new ArgumentNullException("request"); } SIP_RequestSender sender = m_pStack.CreateRequestSender(request, Flow); return(sender); } }
/// <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> /// 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_IsDisposed) { return; } m_IsDisposed = true; m_pDialog.m_pUacInvite2xxRetransmitWaits.Remove(this); m_pDialog = null; m_pInvite = null; if (m_pTimer != null) { m_pTimer.Dispose(); m_pTimer = null; } } }
/// <summary> /// Default constructor. /// </summary> /// <param name="dialog">Owner INVITE dialog.</param> /// <param name="invite">INVITE request which 2xx retransmission to wait.</param> /// <exception cref="ArgumentNullException">Is raised when <b>dialog</b> or <b>invite</b> is null reference.</exception> public UacInvite2xxRetransmissionWaiter(SIP_Dialog_Invite dialog, SIP_Request invite) { if (dialog == null) { throw new ArgumentNullException("dialog"); } if (invite == null) { throw new ArgumentNullException("invite"); } m_pDialog = dialog; m_pInvite = invite; /* RFC 3261 13.2.2.4. * The UAC core considers the INVITE transaction completed 64*T1 seconds * after the reception of the first 2xx response. */ m_pTimer = new TimerEx(64 * SIP_TimerConstants.T1, false); m_pTimer.Elapsed += m_pTimer_Elapsed; m_pTimer.Enabled = true; }
/// <summary> /// Default constructor. /// </summary> /// <param name="dialog">Owner INVITE dialog.</param> /// <param name="invite">INVITE request which 2xx retransmission to wait.</param> /// <exception cref="ArgumentNullException">Is raised when <b>dialog</b> or <b>invite</b> is null reference.</exception> public UacInvite2xxRetransmissionWaiter(SIP_Dialog_Invite dialog, SIP_Request invite) { if (dialog == null) { throw new ArgumentNullException("dialog"); } if (invite == null) { throw new ArgumentNullException("invite"); } m_pDialog = dialog; m_pInvite = invite; /* RFC 3261 13.2.2.4. The UAC core considers the INVITE transaction completed 64*T1 seconds after the reception of the first 2xx response. */ m_pTimer = new TimerEx(64*SIP_TimerConstants.T1, false); m_pTimer.Elapsed += m_pTimer_Elapsed; m_pTimer.Enabled = true; }
/// <summary> /// Re-invites remote party. /// </summary> /// <param name="contact">New contact value. Value null means current contact value used.</param> /// <param name="sdp">SDP media offer.</param> /// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when there is pending invite and this method is called or dialog is in invalid state.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>sdp</b> is null reference.</exception> public void ReInvite(SIP_Uri contact, SDP_Message sdp) { if (State == SIP_DialogState.Disposed) { throw new ObjectDisposedException(GetType().Name); } if (HasPendingInvite) { throw new InvalidOperationException("There is pending INVITE."); } if (State != SIP_DialogState.Confirmed) { throw new InvalidOperationException("ReInvite is only available in Confirmed state."); } if (sdp == null) { throw new ArgumentNullException("sdp"); } lock (SyncRoot) { // TODO: SIP_Request reinvite = CreateRequest(SIP_Methods.INVITE); if (contact != null) { reinvite.Contact.RemoveAll(); reinvite.Contact.Add(contact.ToString()); } reinvite.ContentType = "application/sdp"; // reinvite.Data = sdp.ToStringData(); // TODO: Create request sender // TODO: Try to reuse existing data flow //SIP_RequestSender sender = this.Stack.CreateRequestSender(reinvite); //sender.Start(); } }
/// <summary> /// Starts registering. /// </summary> /// <param name="autoRefresh">If true, registration takes care of refreshing itself to registrar server.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> public void BeginRegister(bool autoRefresh) { if (m_IsDisposed) { throw new ObjectDisposedException(GetType().Name); } // Fix ME: Stack not running, try register on next step. // In ideal solution we need to start registering when stack starts. if (!m_pStack.IsRunning) { m_pTimer.Enabled = true; return; } m_AutoRefresh = autoRefresh; SetState(SIP_UA_RegistrationState.Registering); /* 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 register = m_pStack.CreateRequest(SIP_Methods.REGISTER, new SIP_t_NameAddress(m_pServer.Scheme + ":" + m_AOR), new SIP_t_NameAddress(m_pServer.Scheme + ":" + m_AOR)); register.RequestLine.Uri = SIP_Uri.Parse(m_pServer.Scheme + ":" + m_AOR.Substring(m_AOR.IndexOf('@') + 1)); register.Route.Add(m_pServer.ToString()); register.Contact.Add("<" + Contact + ">;expires=" + m_RefreshInterval); m_pRegisterSender = m_pStack.CreateRequestSender(register, m_pFlow); m_pRegisterSender.ResponseReceived += m_pRegisterSender_ResponseReceived; m_pRegisterSender.Start(); }
/// <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(); ResponseReceived = null; Completed = null; Disposed = null; m_pStack = null; m_pRequest = null; m_pCredentials = null; m_pHops = null; m_pTransaction = null; m_pLock = null; } }
/// <summary> /// Matches specified SIP request to SIP dialog. If no matching dialog found, returns null. /// </summary> /// <param name="request">SIP request.</param> /// <returns>Returns matched SIP dialog or null in no match found.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>request</b> is null.</exception> internal SIP_Dialog MatchDialog(SIP_Request request) { if (request == null) { throw new ArgumentNullException("request"); } SIP_Dialog dialog = null; try { string callID = request.CallID; string localTag = request.To.Tag; string remoteTag = request.From.Tag; if (callID != null && localTag != null && remoteTag != null) { string dialogID = callID + "-" + localTag + "-" + remoteTag; lock (m_pDialogs) { m_pDialogs.TryGetValue(dialogID, out dialog); } } } catch {} return dialog; }
/// <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> /// 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> /// 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> /// Is called by Transport layer when new incoming SIP request is received. /// </summary> /// <param name="request">Incoming SIP request.</param> /// <param name="remoteEndPoint">Request maker IP end point.</param> /// <returns></returns> internal SIP_ValidateRequestEventArgs OnValidateRequest(SIP_Request request, IPEndPoint remoteEndPoint) { SIP_ValidateRequestEventArgs eArgs = new SIP_ValidateRequestEventArgs(request, remoteEndPoint); if (ValidateRequest != null) { ValidateRequest(this, eArgs); } return eArgs; }
/// <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 (State == SIP_DialogState.Disposed) { throw new ObjectDisposedException(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)); 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; request.CSeq.SequenceNumber = ++m_LocalSeqNo; request.Contact.Add(m_pLocalContact.ToString()); return(request); } }
/// <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> /// Creates SIP request sender for the specified request. /// </summary> /// <param name="request">SIP request.</param> /// <param name="flow">Data flow.</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 reference.</exception> internal SIP_RequestSender CreateRequestSender(SIP_Request request, SIP_Flow flow) { if (m_IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (request == null) { throw new ArgumentNullException("request"); } SIP_RequestSender sender = new SIP_RequestSender(this, request, flow); sender.Credentials.AddRange(m_pCredentials); return sender; }
/// <summary> /// Creates new out-off dialog SIP request. /// </summary> /// <param name="method">SIP request-method.</param> /// <param name="to">Recipient address. For example: sip:[email protected]</param> /// <param name="from">Senders address. For example: sip:[email protected]</param> /// <returns>Returns created request.</returns> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>method</b>,<b>to</b> or <b>from</b> is null.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> public SIP_Request CreateRequest(string method, SIP_t_NameAddress to, SIP_t_NameAddress from) { if (m_IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (method == null) { throw new ArgumentNullException("method"); } if (method == "") { throw new ArgumentException("Argument 'method' value must be specified."); } if (to == null) { throw new ArgumentNullException("to"); } if (from == null) { throw new ArgumentNullException("from"); } method = method.ToUpper(); /* RFC 3261 8.1.1 Generating the Request A valid SIP request formulated by a UAC MUST, at a minimum, contain the following header fields: To, From, CSeq, Call-ID, Max-Forwards, and Via; all of these header fields are mandatory in all SIP requests. These six header fields are the fundamental building blocks of a SIP message, as they jointly provide for most of the critical message routing services including the addressing of messages, the routing of responses, limiting message propagation, ordering of messages, and the unique identification of transactions. These header fields are in addition to the mandatory request line, which contains the method, Request-URI, and SIP version. */ SIP_Request request = new SIP_Request(method); #region Request-URI (section 8.1.1.1) /* The initial Request-URI of the message SHOULD be set to the value of the URI in the To field. One notable exception is the REGISTER method; behavior for setting the Request-URI of REGISTER is given in Section 10. */ request.RequestLine.Uri = to.Uri; #endregion #region To (section 8.1.1.2) /* The To header field first and foremost specifies the desired "logical" recipient of the request, or the address-of-record of the user or resource that is the target of this request. This may or may not be the ultimate recipient of the request. The To header field MAY contain a SIP or SIPS URI, but it may also make use of other URI schemes (the tel URL (RFC 2806 [9]), for example) when appropriate. */ SIP_t_To t = new SIP_t_To(to); request.To = t; #endregion #region From (section 8.1.1.3) /* The From header field indicates the logical identity of the initiator of the request, possibly the user's address-of-record. Like the To header field, it contains a URI and optionally a display name. It is used by SIP elements to determine which processing rules to apply to a request (for example, automatic call rejection). As such, it is very important that the From URI not contain IP addresses or the FQDN of the host on which the UA is running, since these are not logical names. The From header field allows for a display name. A UAC SHOULD use the display name "Anonymous", along with a syntactically correct, but otherwise meaningless URI (like sip:[email protected]), if the identity of the client is to remain hidden. The From field MUST contain a new "tag" parameter, chosen by the UAC. See Section 19.3 for details on choosing a tag. */ SIP_t_From f = new SIP_t_From(from); f.Tag = SIP_Utils.CreateTag(); request.From = f; #endregion #region CallID (section 8.1.1.4) /* The Call-ID header field acts as a unique identifier to group together a series of messages. It MUST be the same for all requests and responses sent by either UA in a dialog. It SHOULD be the same in each registration from a UA. */ if (method == SIP_Methods.REGISTER) { request.CallID = m_RegisterCallID.ToStringValue(); } else { request.CallID = SIP_t_CallID.CreateCallID().ToStringValue(); } #endregion #region CSeq (section 8.1.1.5) /* The CSeq header field serves as a way to identify and order transactions. It consists of a sequence number and a method. The method MUST match that of the request. For non-REGISTER requests outside of a dialog, the sequence number value is arbitrary. The sequence number value MUST be expressible as a 32-bit unsigned integer and MUST be less than 2**31. As long as it follows the above guidelines, a client may use any mechanism it would like to select CSeq header field values. */ request.CSeq = new SIP_t_CSeq(ConsumeCSeq(), method); #endregion #region Max-Forwards (section 8.1.1.6) request.MaxForwards = m_MaxForwards; #endregion #region Pre-configured route (proxy server) // section 8.1.2 suggests to use pre-configured route for proxy. foreach (SIP_Uri proxy in m_pProxyServers) { request.Route.Add(proxy.ToString()); } #endregion // TODO: RFC 3261 13.2.1 INVITE special headers // INVITE should have // Allow,Supported // TODO: Subscribe special headers. // Expires is mandatory header. return request; }
/// <summary> /// Creates SIP request sender for the specified request. /// </summary> /// <param name="request">SIP request.</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 reference.</exception> public SIP_RequestSender CreateRequestSender(SIP_Request request) { return CreateRequestSender(request, null); }
/// <summary> /// Sends request to the specified flow. /// </summary> /// <param name="flow">Data flow.</param> /// <param name="request">SIP request.</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> public void SendRequest(SIP_Flow flow, SIP_Request request) { SendRequest(flow, request, null); }
/// <summary> /// Sends request to the specified hop. /// </summary> /// <param name="request">SIP request.</param> /// <param name="localEP">Local end point. Value null means system will allocate it.</param> /// <param name="hop">Target hop.</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> or <b>hop</b> is null reference.</exception> public void SendRequest(SIP_Request request, IPEndPoint localEP, SIP_Hop hop) { if (m_IsDisposed) { throw new ObjectDisposedException(GetType().Name); } if (request == null) { throw new ArgumentNullException("request"); } if (hop == null) { throw new ArgumentNullException("hop"); } SendRequest(GetOrCreateFlow(hop.Transport, localEP, hop.EndPoint), request); }
/// <summary> /// Checks if specified ACK request matches this 2xx response retransmission. /// </summary> /// <param name="ackRequest">ACK request.</param> /// <returns>Returns true if ACK matches, othwerwise false.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>ackRequest</b> is null reference value.</exception> public bool MatchAck(SIP_Request ackRequest) { if (ackRequest == null) { throw new ArgumentNullException("ackRequest"); } if (ackRequest.CSeq.SequenceNumber == m_pResponse.CSeq.SequenceNumber) { return true; } else { return false; } }
/// <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 override void Terminate(string reason, bool sendBye) { lock (SyncRoot) { if (State == SIP_DialogState.Disposed) { throw new ObjectDisposedException(GetType().Name); } if (State == SIP_DialogState.Terminating || State == SIP_DialogState.Terminated) { return; } /* 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 ((State == SIP_DialogState.Early && m_pActiveInvite is SIP_ClientTransaction) || State == SIP_DialogState.Confirmed) { 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 { 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) { Stack.CreateResponse(SIP_ResponseCodes.x408_Request_Timeout, m_pActiveInvite.Request); SetState(SIP_DialogState.Terminated, true); } else { // Wait ACK to arrive or timeout. SetState(SIP_DialogState.Terminating, true); } } } else { SetState(SIP_DialogState.Terminated, true); } } }
/// <summary> /// SIP_Request.CreateResponse constructor. /// </summary> /// <param name="request">Owner request.</param> internal SIP_Response(SIP_Request request) { m_pRequest = request; }
/// <summary> /// Creates response for the specified request. /// </summary> /// <param name="statusCode_reasonText">Status-code reasontext.</param> /// <param name="request">SIP request.</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) { return CreateResponse(statusCode_reasonText, request, null); }
/// <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); } } }
/// <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, 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> /// 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(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 += delegate { if (transaction.State == SIP_TransactionState.Terminated) { lock (m_pClientTransactions) { m_pClientTransactions.Remove(transaction.Key); } } }; return transaction; } }
/// <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> /// Parses SIP_Request from stream. /// </summary> /// <param name="stream">Stream what contains valid SIP request.</param> /// <returns>Returns parsed SIP_Request 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_Request Parse(Stream stream) { /* Syntax: SIP-Method SIP-URI SIP-Version SIP-Message */ if (stream == null) { throw new ArgumentNullException("stream"); } // Parse Response-line StreamLineReader r = new StreamLineReader(stream); r.Encoding = "utf-8"; string[] method_uri_version = r.ReadLineString().Split(' '); if (method_uri_version.Length != 3) { throw new Exception( "Invalid SIP request data ! Method line doesn't contain: SIP-Method SIP-URI SIP-Version."); } SIP_Request retVal = new SIP_Request(method_uri_version[0]); retVal.RequestLine.Uri = AbsoluteUri.Parse(method_uri_version[1]); retVal.RequestLine.Version = method_uri_version[2]; // Parse SIP message retVal.InternalParse(stream); 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(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 += delegate { if (transaction.State == SIP_TransactionState.Terminated) { lock (m_pClientTransactions) { m_pServerTransactions.Remove(transaction.Key); } } }; return transaction; } }