/// <summary> /// Default incoming call constructor. /// </summary> /// <param name="ua">Owner UA.</param> /// <param name="invite">INVITE server transaction.</param> /// <exception cref="ArgumentNullException">Is riased when <b>ua</b> or <b>invite</b> is null reference.</exception> internal SIP_UA_Call(SIP_UA ua, SIP_ServerTransaction invite) { if (ua == null) { throw new ArgumentNullException("ua"); } if (invite == null) { throw new ArgumentNullException("invite"); } m_pUA = ua; m_pInitialInviteTransaction = invite; m_pLocalUri = invite.Request.To.Address.Uri; m_pRemoteUri = invite.Request.From.Address.Uri; m_pInitialInviteTransaction.Canceled += new EventHandler(delegate(object sender, EventArgs e){ // If transaction canceled, terminate call. SetState(SIP_UA_CallState.Terminated); }); // Parse SDP if INVITE contains SDP. // RFC 3261 13.2.1. INVITE may be offerless, we must thne send offer and remote party sends sdp in ACK. if (invite.Request.ContentType != null && invite.Request.ContentType.ToLower().IndexOf("application/sdp") > -1) { m_pRemoteSDP = SDP_Message.Parse(Encoding.UTF8.GetString(invite.Request.Data)); } m_pTags = new Dictionary <string, object>(); m_State = SIP_UA_CallState.WaitingToAccept; }
/// <summary> /// This method is called when caller dialog client transaction receives response. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pCaller_ResponseReceived(object sender, SIP_ResponseReceivedEventArgs e) { SIP_ServerTransaction serverTransaction = (SIP_ServerTransaction)e.ClientTransaction.Tag; //SIP_Response response = serverTransaction.Request.CreateResponse(e.Response.StatusCode_ReasonPhrase); //CopyMessage(e.Response,response,new string[]{"Via:","Call-Id:","To:","From:","CSeq:","Contact:","Route:","Record-Route:","Allow:","Supported:"}); //serverTransaction.SendResponse(response); }
/// <summary> /// Default constructor. /// </summary> /// <param name="invite">SIP INVITE server transaction.</param> /// <exception cref="ArgumentNullException">Is raised when <b>invite</b> is null reference.</exception> public wfrm_IncomingCall(SIP_ServerTransaction invite) { if (invite == null) { throw new ArgumentNullException("invite"); } InitUI(); m_pTransaction = invite; m_pTransaction.Canceled += new EventHandler(m_pTransaction_Canceled); m_pFrom.Text = invite.Request.To.Address.ToStringValue(); }
/// <summary> /// Default incoming call constructor. /// </summary> /// <param name="ua">Owner UA.</param> /// <param name="invite">INVITE server transaction.</param> /// <exception cref="ArgumentNullException">Is riased when <b>ua</b> or <b>invite</b> is null reference.</exception> internal SIP_UA_Call(SIP_UA ua, SIP_ServerTransaction invite) { if (ua == null) { throw new ArgumentNullException("ua"); } if (invite == null) { throw new ArgumentNullException("invite"); } m_pUA = ua; m_pInitialInviteTransaction = invite; m_pLocalUri = invite.Request.To.Address.Uri; m_pRemoteUri = invite.Request.From.Address.Uri; m_pInitialInviteTransaction.Canceled += new EventHandler(delegate(object sender, EventArgs e){ // If transaction canceled, terminate call. SetState(SIP_UA_CallState.Terminated); }); m_State = SIP_UA_CallState.WaitingToAccept; }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> private void OnRequestReceived(SIP_RequestReceivedEventArgs e) { SIP_Request request = e.Request; try{ #region Statefull // Statefull if ((m_ProxyMode & SIP_ProxyMode.Statefull) != 0) { // Statefull proxy is transaction statefull proxy only, // what don't create dialogs and keep dialog state. /* RFC 3261 16.10. * StateFull proxy: * If a matching response context is found, the element MUST * immediately return a 200 (OK) response to the CANCEL request. * * If a response context is not found, the element does not have any * knowledge of the request to apply the CANCEL to. It MUST statelessly * forward the CANCEL request (it may have statelessly forwarded the * associated request previously). */ if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { // Don't do server transaction before we get CANCEL matching transaction. SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, request)); } else { ForwardRequest(false, e, true); } } // ACK never creates transaction, it's always passed directly to transport layer. else if (e.Request.RequestLine.Method == SIP_Methods.ACK) { ForwardRequest(false, e, true); } else { ForwardRequest(true, e, true); } } #endregion #region B2BUA // B2BUA else if ((m_ProxyMode & SIP_ProxyMode.B2BUA) != 0) { m_pB2BUA.OnRequestReceived(e); } #endregion #region Stateless // Stateless else if ((m_ProxyMode & SIP_ProxyMode.Stateless) != 0) { // Stateless proxy don't do transaction, just forwards all. ForwardRequest(false, e, true); } #endregion #region Proxy won't accept command else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented, request)); } #endregion } catch (Exception x) { try{ m_pStack.TransportLayer.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x.Message, e.Request)); } catch { // Skip transport layer exception if send fails. } // Don't raise OnError for transport errors. if (!(x is SIP_TransportException)) { m_pStack.OnError(x); } } }
internal SIP_ProxyContext CreateProxyContext(SIP_RequestContext requestContext, SIP_ServerTransaction transaction, SIP_Request request, bool addRecordRoute) { // Create proxy context that will be responsible for forwarding request. SIP_ProxyContext proxyContext = new SIP_ProxyContext( this, transaction, request, addRecordRoute, m_ForkingMode, (this.ProxyMode & SIP_ProxyMode.B2BUA) != 0, false, false, requestContext.Targets.ToArray() ); m_pProxyContexts.Add(proxyContext); return(proxyContext); }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> internal void OnRequestReceived(SIP_RequestReceivedEventArgs e) { SIP_Request request = e.Request; if (request.RequestLine.Method == SIP_Methods.CANCEL) { /* RFC 3261 9.2. * If the UAS did not find a matching transaction for the CANCEL * according to the procedure above, it SHOULD respond to the CANCEL * with a 481 (Call Leg/Transaction Does Not Exist). * * Regardless of the method of the original request, as long as the * CANCEL matched an existing transaction, the UAS answers the CANCEL * request itself with a 200 (OK) response. */ SIP_ServerTransaction trToCancel = m_pProxy.Stack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x200_Ok)); } else { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } } // We never should ge BYE here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.BYE) { /* RFC 3261 15.1.2. * If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481 * (Call/Transaction Does Not Exist) response and pass that to the server transaction. */ //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } // We never should ge ACK here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.ACK) { // ACK is response less request, so we may not return error to it. } // B2BUA must respond to OPTIONS request, not to forward it. else if (request.RequestLine.Method == SIP_Methods.OPTIONS) /* * SIP_Response response = e.Request.CreateResponse(SIP_ResponseCodes.x200_Ok); * // Add Allow to non ACK response. * if(e.Request.RequestLine.Method != SIP_Methods.ACK){ * response.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK,MESSAGE,UPDATE"); * } * // Add Supported to 2xx non ACK response. * if(response.StatusCodeType == SIP_StatusCodeType.Success && e.Request.RequestLine.Method != SIP_Methods.ACK){ * response.Supported.Add("100rel,timer"); * } * e.ServerTransaction.SendResponse(response);*/ { } // We never should get PRACK here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.PRACK) { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } // We never should get UPDATE here, because transport layer must match it to dialog. else if (request.RequestLine.Method == SIP_Methods.UPDATE) { //e.ServerTransaction.SendResponse(request.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist)); } else { /* draft-marjou-sipping-b2bua-00 4.1.3. * When the UAS of the B2BUA receives an upstream SIP request, its * associated UAC generates a new downstream SIP request with its new * Via, Max-Forwards, Call-Id, CSeq, and Contact header fields. Route * header fields of the upstream request are copied in the downstream * request, except the first Route header if it is under the * responsibility of the B2BUA. Record-Route header fields of the * upstream request are not copied in the new downstream request, as * Record-Route is only meaningful for the upstream dialog. The UAC * SHOULD copy other header fields and body from the upstream request * into this downstream request before sending it. */ SIP_Request b2buaRequest = e.Request.Copy(); b2buaRequest.Via.RemoveAll(); b2buaRequest.MaxForwards = 70; b2buaRequest.CallID = SIP_t_CallID.CreateCallID().CallID; b2buaRequest.CSeq.SequenceNumber = 1; b2buaRequest.Contact.RemoveAll(); // b2buaRequest.Contact.Add(m_pProxy.CreateContact(b2buaRequest.To.Address).ToStringValue()); if (b2buaRequest.Route.Count > 0 && m_pProxy.IsLocalRoute(SIP_Uri.Parse(b2buaRequest.Route.GetTopMostValue().Address.Uri.ToString()))) { b2buaRequest.Route.RemoveTopMostValue(); } b2buaRequest.RecordRoute.RemoveAll(); // Remove our Authorization header if it's there. foreach (SIP_SingleValueHF <SIP_t_Credentials> header in b2buaRequest.ProxyAuthorization.HeaderFields) { try{ Auth_HttpDigest digest = new Auth_HttpDigest(header.ValueX.AuthData, b2buaRequest.RequestLine.Method); if (m_pProxy.Stack.Realm == digest.Realm) { b2buaRequest.ProxyAuthorization.Remove(header); } } catch { // We don't care errors here. This can happen if remote server xxx auth method here and // we don't know how to parse it, so we leave it as is. } } //--- Add/replace default fields. ------------------------------------------ b2buaRequest.Allow.RemoveAll(); b2buaRequest.Supported.RemoveAll(); // Accept to non ACK,BYE request. if (request.RequestLine.Method != SIP_Methods.ACK && request.RequestLine.Method != SIP_Methods.BYE) { b2buaRequest.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK"); } // Supported to non ACK request. if (request.RequestLine.Method != SIP_Methods.ACK) { b2buaRequest.Supported.Add("100rel,timer"); } // Remove Require: header. b2buaRequest.Require.RemoveAll(); // RFC 4028 7.4. For re-INVITE and UPDATE we need to add Session-Expires and Min-SE: headers. if (request.RequestLine.Method == SIP_Methods.INVITE || request.RequestLine.Method == SIP_Methods.UPDATE) { b2buaRequest.SessionExpires = new SIP_t_SessionExpires(m_pProxy.Stack.SessionExpries, "uac"); b2buaRequest.MinSE = new SIP_t_MinSE(m_pProxy.Stack.MinimumSessionExpries); } // Forward request. //m_pProxy.ForwardRequest(true,e,b2buaRequest,false); } }
/// <summary> /// Handles REGISTER method. /// </summary> /// <param name="e">Request event arguments.</param> internal void Register(SIP_RequestReceivedEventArgs e) { /* RFC 3261 10.3 Processing REGISTER Requests. * 1. The registrar inspects the Request-URI to determine whether it * has access to bindings for the domain identified in the * Request-URI. If not, and if the server also acts as a proxy * server, the server SHOULD forward the request to the addressed * domain, following the general behavior for proxying messages * described in Section 16. * * 2. To guarantee that the registrar supports any necessary extensions, * the registrar MUST process the Require header field. * * 3. A registrar SHOULD authenticate the UAC. * * 4. The registrar SHOULD determine if the authenticated user is * authorized to modify registrations for this address-of-record. * For example, a registrar might consult an authorization * database that maps user names to a list of addresses-of-record * for which that user has authorization to modify bindings. If * the authenticated user is not authorized to modify bindings, * the registrar MUST return a 403 (Forbidden) and skip the * remaining steps. * * 5. The registrar extracts the address-of-record from the To header * field of the request. If the address-of-record is not valid * for the domain in the Request-URI, the registrar MUST send a * 404 (Not Found) response and skip the remaining steps. The URI * MUST then be converted to a canonical form. To do that, all * URI parameters MUST be removed (including the user-param), and * any escaped characters MUST be converted to their unescaped * form. The result serves as an index into the list of bindings. * * 6. The registrar checks whether the request contains the Contact * header field. If not, it skips to the last step. If the * Contact header field is present, the registrar checks if there * is one Contact field value that contains the special value "*" * and an Expires field. If the request has additional Contact * fields or an expiration time other than zero, the request is * invalid, and the server MUST return a 400 (Invalid Request) and * skip the remaining steps. If not, the registrar checks whether * the Call-ID agrees with the value stored for each binding. If * not, it MUST remove the binding. If it does agree, it MUST * remove the binding only if the CSeq in the request is higher * than the value stored for that binding. Otherwise, the update * MUST be aborted and the request fails. * * 7. The registrar now processes each contact address in the Contact * header field in turn. For each address, it determines the * expiration interval as follows: * * - If the field value has an "expires" parameter, that value * MUST be taken as the requested expiration. * * - If there is no such parameter, but the request has an * Expires header field, that value MUST be taken as the requested expiration. * * - If there is neither, a locally-configured default value MUST * be taken as the requested expiration. * * The registrar MAY choose an expiration less than the requested * expiration interval. If and only if the requested expiration * interval is greater than zero AND smaller than one hour AND * less than a registrar-configured minimum, the registrar MAY * reject the registration with a response of 423 (Interval Too * Brief). This response MUST contain a Min-Expires header field * that states the minimum expiration interval the registrar is * willing to honor. It then skips the remaining steps. * * For each address, the registrar then searches the list of * current bindings using the URI comparison rules. If the * binding does not exist, it is tentatively added. If the * binding does exist, the registrar checks the Call-ID value. If * the Call-ID value in the existing binding differs from the * Call-ID value in the request, the binding MUST be removed if * the expiration time is zero and updated otherwise. If they are * the same, the registrar compares the CSeq value. If the value * is higher than that of the existing binding, it MUST update or * remove the binding as above. If not, the update MUST be * aborted and the request fails. * * This algorithm ensures that out-of-order requests from the same * UA are ignored. * * Each binding record records the Call-ID and CSeq values from * the request. * * The binding updates MUST be committed (that is, made visible to * the proxy or redirect server) if and only if all binding * updates and additions succeed. If any one of them fails (for * example, because the back-end database commit failed), the * request MUST fail with a 500 (Server Error) response and all * tentative binding updates MUST be removed. * * 8. The registrar returns a 200 (OK) response. The response MUST * contain Contact header field values enumerating all current * bindings. Each Contact value MUST feature an "expires" * parameter indicating its expiration interval chosen by the * registrar. The response SHOULD include a Date header field. */ SIP_ServerTransaction transaction = e.ServerTransaction; SIP_Request request = e.Request; SIP_Uri to = null; string userName = ""; // Probably we need to do validate in SIP stack. #region Validate request if (SIP_Utils.IsSipOrSipsUri(request.To.Address.Uri.ToString())) { to = (SIP_Uri)request.To.Address.Uri; } else { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": To: value must be SIP or SIPS URI.", request)); return; } #endregion #region 1. Check if we are responsible for Request-URI domain // if(m_pProxy.OnIsLocalUri(e.Request.Uri)){ // } // TODO: #endregion #region 2. Check that all required extentions supported #endregion #region 3. Authenticate request if (!m_pProxy.AuthenticateRequest(e, out userName)) { return; } #endregion #region 4. Check if user user is authorized to modify registrations // We do this in next step(5.). #endregion #region 5. Check if address of record exists if (!m_pProxy.OnAddressExists(to.Address)) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x404_Not_Found, request)); return; } else if (!OnCanRegister(userName, to.Address)) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x403_Forbidden, request)); return; } #endregion #region 6. Process * Contact if exists // Check if we have star contact. SIP_t_ContactParam starContact = null; foreach (SIP_t_ContactParam c in request.Contact.GetAllValues()) { if (c.IsStarContact) { starContact = c; break; } } // We have star contact. if (starContact != null) { if (request.Contact.GetAllValues().Length > 1) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": RFC 3261 10.3.6 -> If star(*) present, only 1 contact allowed.", request)); return; } else if (starContact.Expires != 0) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": RFC 3261 10.3.6 -> star(*) contact parameter 'expires' value must be always '0'.", request)); return; } // Remove bindings. SIP_Registration reg = m_pRegistrations[to.Address]; if (reg != null) { foreach (SIP_RegistrationBinding b in reg.Bindings) { if (request.CallID != b.CallID || request.CSeq.SequenceNumber > b.CSeqNo) { b.Remove(); } } } } #endregion #region 7. Process Contact values if (starContact == null) { SIP_Registration reg = m_pRegistrations[to.Address]; if (reg == null) { reg = new SIP_Registration(userName, to.Address); m_pRegistrations.Add(reg); } // We may do updates in batch only. // We just validate all values then do update(this ensures that update doesn't fail). // Check expires and CSeq. foreach (SIP_t_ContactParam c in request.Contact.GetAllValues()) { if (c.Expires == -1) { c.Expires = request.Expires; } if (c.Expires == -1) { c.Expires = m_pProxy.Stack.MinimumExpireTime; } // We must accept 0 values - means remove contact. if (c.Expires != 0 && c.Expires < m_pProxy.Stack.MinimumExpireTime) { SIP_Response resp = m_pStack.CreateResponse( SIP_ResponseCodes.x423_Interval_Too_Brief, request); resp.MinExpires = m_pProxy.Stack.MinimumExpireTime; transaction.SendResponse(resp); return; } SIP_RegistrationBinding currentBinding = reg.GetBinding(c.Address.Uri); if (currentBinding != null && currentBinding.CallID == request.CallID && request.CSeq.SequenceNumber < currentBinding.CSeqNo) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": CSeq value out of order.", request)); return; } } // Do binding updates. reg.AddOrUpdateBindings(e.ServerTransaction.Flow, request.CallID, request.CSeq.SequenceNumber, request.Contact.GetAllValues()); } #endregion #region 8. Create 200 OK response and return all current bindings SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, request); response.Date = DateTime.Now; SIP_Registration registration = m_pRegistrations[to.Address]; if (registration != null) { foreach (SIP_RegistrationBinding b in registration.Bindings) { // Don't list expired bindings what wait to be disposed. if (b.TTL > 1) { response.Header.Add("Contact:", b.ToContactValue()); } } } // Add Authentication-Info:, then client knows next nonce. response.AuthenticationInfo.Add("qop=\"auth\",nextnonce=\"" + m_pStack.DigestNonceManager.CreateNonce() + "\""); transaction.SendResponse(response); #endregion }
/// <summary> /// This method is called when SIP stack received new message. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">Event data.</param> private void m_pStack_RequestReceived(object sender, SIP_RequestReceivedEventArgs e) { // TODO: Performance: rise events on thread pool or see if this method called on pool aready, then we may not keep lock for events ? if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { /* RFC 3261 9.2. * If the UAS did not find a matching transaction for the CANCEL * according to the procedure above, it SHOULD respond to the CANCEL * with a 481 (Call Leg/Transaction Does Not Exist). * * Regardless of the method of the original request, as long as the * CANCEL matched an existing transaction, the UAS answers the CANCEL * request itself with a 200 (OK) response. */ SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); } else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request)); } } else if (e.Request.RequestLine.Method == SIP_Methods.BYE) { /* RFC 3261 15.1.2. * If the BYE does not match an existing dialog, the UAS core SHOULD generate a 481 * (Call/Transaction Does Not Exist) response and pass that to the server transaction. */ // TODO: SIP_Dialog dialog = m_pStack.TransactionLayer.MatchDialog(e.Request); if (dialog != null) { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, e.Request)); dialog.Terminate(); } else { e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist, e.Request)); } } else if (e.Request.RequestLine.Method == SIP_Methods.INVITE) { // Supress INVITE retransmissions. e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x100_Trying, e.Request)); // Create call. SIP_UA_Call call = new SIP_UA_Call(this, e.ServerTransaction); call.StateChanged += new EventHandler(Call_StateChanged); m_pCalls.Add(call); OnIncomingCall(call); } else { OnRequestReceived(e); } }
/// <summary> /// This method is called when new request is received. /// </summary> /// <param name="e">Request event arguments.</param> private void OnRequestReceived(SIP_RequestReceivedEventArgs e) { /* RFC 3261 16.12. ????????? Forward does all thse steps. * 1. The proxy will inspect the Request-URI. If it indicates a * resource owned by this proxy, the proxy will replace it with * the results of running a location service. Otherwise, the * proxy will not change the Request-URI. * * 2. The proxy will inspect the URI in the topmost Route header * field value. If it indicates this proxy, the proxy removes it * from the Route header field (this route node has been reached). * * 3. The proxy will forward the request to the resource indicated * by the URI in the topmost Route header field value or in the * Request-URI if no Route header field is present. The proxy * determines the address, port and transport to use when * forwarding the request by applying the procedures in [4] to that URI. */ SIP_Request request = e.Request; try { #region Registrar // Registrar if ((m_ProxyMode & SIP_ProxyMode.Registrar) != 0 && request.RequestLine.Method == SIP_Methods.REGISTER) { m_pRegistrar.Register(e); } #endregion #region Presence /* * // Presence * else if((m_ProxyMode & SIP_ProxyMode.Presence) != 0 && (request.Method == "SUBSCRIBE" || request.Method == "NOTIFY")){ * * } */ #endregion #region Statefull // Statefull else if ((m_ProxyMode & SIP_ProxyMode.Statefull) != 0) { // Statefull proxy is transaction statefull proxy only, // what don't create dialogs and keep dialog state. /* RFC 3261 16.10. * StateFull proxy: * If a matching response context is found, the element MUST * immediately return a 200 (OK) response to the CANCEL request. * * If a response context is not found, the element does not have any * knowledge of the request to apply the CANCEL to. It MUST statelessly * forward the CANCEL request (it may have statelessly forwarded the * associated request previously). */ if (e.Request.RequestLine.Method == SIP_Methods.CANCEL) { // Don't do server transaction before we get CANCEL matching transaction. SIP_ServerTransaction trToCancel = m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request); if (trToCancel != null) { trToCancel.Cancel(); e.ServerTransaction.SendResponse(m_pStack.CreateResponse( SIP_ResponseCodes.x200_Ok, request)); } else { ForwardRequest(false, e); } } // ACK never creates transaction, it's always passed directly to transport layer. else if (e.Request.RequestLine.Method == SIP_Methods.ACK) { ForwardRequest(false, e); } else { ForwardRequest(true, e); } } #endregion #region B2BUA // B2BUA else if ((m_ProxyMode & SIP_ProxyMode.B2BUA) != 0) { m_pB2BUA.OnRequestReceived(e); } #endregion #region Stateless // Stateless else if ((m_ProxyMode & SIP_ProxyMode.Stateless) != 0) { // Stateless proxy don't do transaction, just forwards all. ForwardRequest(false, e); } #endregion #region Proxy won't accept command else { e.ServerTransaction.SendResponse( m_pStack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented, request)); } #endregion } catch (Exception x) { try { m_pStack.TransportLayer.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x500_Server_Internal_Error + ": " + x.Message, e.Request)); } catch { // Skip transport layer exception if send fails. } // Don't raise OnError for transport errors. if (!(x is SIP_TransportException)) { m_pStack.OnError(x); } } }
/// <summary> /// Default incoming call constructor. /// </summary> /// <param name="ua">Owner UA.</param> /// <param name="invite">INVITE server transaction.</param> /// <exception cref="ArgumentNullException">Is riased when <b>ua</b> or <b>invite</b> is null reference.</exception> internal SIP_UA_Call(SIP_UA ua, SIP_ServerTransaction invite) { if (ua == null) { throw new ArgumentNullException("ua"); } if (invite == null) { throw new ArgumentNullException("invite"); } m_pUA = ua; m_pInitialInviteTransaction = invite; m_pLocalUri = invite.Request.To.Address.Uri; m_pRemoteUri = invite.Request.From.Address.Uri; m_pInitialInviteTransaction.Canceled += delegate { // If transaction canceled, terminate call. SetState(SIP_UA_CallState.Terminated); }; m_State = SIP_UA_CallState.WaitingToAccept; }
/// <summary> /// Cleans up any resources being used. /// </summary> public void Dispose() { lock (m_pLock) { if (m_IsDisposed) { return; } m_IsDisposed = true; m_pProxy.Stack.Logger.AddText("ProxyContext(id='" + m_ID + "') disposed."); m_pProxy.m_pProxyContexts.Remove(this); m_pProxy = null; m_pServerTransaction = null; m_pTargetsHandlers = null; m_pResponses = null; m_pTargets = null; } }
/// <summary> /// Default constructor. /// </summary> /// <param name="proxy">Owner proxy.</param> /// <param name="transaction">Server transaction what is used to send SIP responses back to caller.</param> /// <param name="request">Request to forward.</param> /// <param name="addRecordRoute">If true, Record-Route header field will be added.</param> /// <param name="forkingMode">Specifies how proxy context must handle forking.</param> /// <param name="isB2BUA">Specifies if proxy context is in B2BUA or just transaction satefull mode.</param> /// <param name="noCancel">Specifies if proxy should not send Cancel to forked requests.</param> /// <param name="noRecurse">Specifies what proxy server does when it gets 3xx response. If true proxy will forward /// request to new specified address if false, proxy will return 3xx response to caller.</param> /// <param name="targets">Possible remote targets. NOTE: These values must be in priority order !</param> /// <param name="credentials">Target set credentials.</param> /// <exception cref="ArgumentNullException">Is raised when any of the reference type prameters is null.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> internal SIP_ProxyContext(SIP_ProxyCore proxy, SIP_ServerTransaction transaction, SIP_Request request, bool addRecordRoute, SIP_ForkingMode forkingMode, bool isB2BUA, bool noCancel, bool noRecurse, SIP_ProxyTarget[] targets, NetworkCredential[] credentials) { if (proxy == null) { throw new ArgumentNullException("proxy"); } if (transaction == null) { throw new ArgumentNullException("transaction"); } if (request == null) { throw new ArgumentNullException("request"); } if (targets == null) { throw new ArgumentNullException("targets"); } if (targets.Length == 0) { throw new ArgumentException("Argumnet 'targets' must contain at least 1 value."); } m_pProxy = proxy; m_pServerTransaction = transaction; m_pServerTransaction.Canceled += m_pServerTransaction_Canceled; m_pServerTransaction.Disposed += m_pServerTransaction_Disposed; m_pRequest = request; m_AddRecordRoute = addRecordRoute; m_ForkingMode = forkingMode; m_IsB2BUA = isB2BUA; m_NoCancel = noCancel; m_NoRecurse = noRecurse; m_pTargetsHandlers = new List<TargetHandler>(); m_pResponses = new List<SIP_Response>(); m_ID = Guid.NewGuid().ToString(); m_CreateTime = DateTime.Now; // Queue targets up, higest to lowest. m_pTargets = new Queue<TargetHandler>(); foreach (SIP_ProxyTarget target in targets) { m_pTargets.Enqueue(new TargetHandler(this, target.Flow, target.TargetUri, m_AddRecordRoute, false)); } m_pCredentials = new List<NetworkCredential>(); m_pCredentials.AddRange(credentials); /* RFC 3841 9.1. The Request-Disposition header field specifies caller preferences for how a server should process a request. Override SIP proxy default value. */ foreach (SIP_t_Directive directive in request.RequestDisposition.GetAllValues()) { if (directive.Directive == SIP_t_Directive.DirectiveType.NoFork) { m_ForkingMode = SIP_ForkingMode.None; } else if (directive.Directive == SIP_t_Directive.DirectiveType.Parallel) { m_ForkingMode = SIP_ForkingMode.Parallel; } else if (directive.Directive == SIP_t_Directive.DirectiveType.Sequential) { m_ForkingMode = SIP_ForkingMode.Sequential; } else if (directive.Directive == SIP_t_Directive.DirectiveType.NoCancel) { m_NoCancel = true; } else if (directive.Directive == SIP_t_Directive.DirectiveType.NoRecurse) { m_NoRecurse = true; } } m_pProxy.Stack.Logger.AddText("ProxyContext(id='" + m_ID + "') created."); }