/// <summary> /// Constructor. /// </summary> /// <param name="request">The initiating <see cref="SipRequest" />.</param> /// <param name="dialog">The <see cref="SipDialog" /> for requests that initiate a dialog (or <c>null</c>).</param> /// <param name="agent">The <see cref="ISipAgent" /> that sent the request and received the response.</param> /// <param name="response">The final <see cref="SipResponse" />.</param> public SipResult(SipRequest request, SipDialog dialog, ISipAgent agent, SipResponse response) { this.Request = request; this.Response = response; this.Status = response.Status; this.Dialog = dialog; this.Agent = agent; }
/// <summary> /// Constructor. /// </summary> /// <param name="request">The initiating <see cref="SipRequest" />.</param> /// <param name="dialog">The <see cref="SipDialog" /> for requests that initiate a dialog (or <c>null</c>).</param> /// <param name="agent">The <see cref="ISipAgent" /> that sent the request and received the response.</param> /// <param name="status">The final operation status.</param> public SipResult(SipRequest request, SipDialog dialog, ISipAgent agent, SipStatus status) { this.Request = request; this.Response = null; this.Status = status; this.Dialog = dialog; this.Agent = agent; }
/// <summary> /// Constuctor. /// </summary> /// <param name="status">The <see cref="SipStatus" />.</param> /// <param name="response">The received <see cref="SipResponse" /> (or <c>null</c>).</param> /// <param name="transaction">The <see cref="SipClientTransaction" />.</param> /// <param name="dialog">The associated <see cref="SipDialog" /> (or <c>null</c>).</param> /// <param name="agent">The <see cref="SipClientAgent" /> that processed the response.</param> /// <param name="core">The <see cref="SipCore" /> that raised the event.</param> internal SipResponseEventArgs(SipStatus status, SipResponse response, SipClientTransaction transaction, SipDialog dialog, SipClientAgent agent, SipCore core) { this.Status = status; this.Response = response; this.Transaction = transaction; this.Dialog = dialog; this.Agent = agent; this.Core = core; }
/// <summary> /// Constructor. /// </summary> /// <param name="statusCode">The SIP response status code.</param> /// <param name="reasonPhrase">The reason phrase (or <c>null</c>).</param> /// <param name="sipVersion">The SIP version string (or <c>null</c>).</param> public SipResponse(int statusCode, string reasonPhrase, string sipVersion) : base(false, sipVersion) { if (statusCode < 0) { throw new SipException("Cannot assign stack specific error codes to a SIP response."); } if (statusCode < 100 || statusCode >= 700) { throw new SipException("Invalid status code [{0}].", statusCode); } this.status = (SipStatus)statusCode; this.reasonPhrase = reasonPhrase != null ? reasonPhrase : SipHelper.GetReasonPhrase(statusCode); }
/// <summary> /// Called when an INVITE transaction completes. /// </summary> /// <param name="transaction">The source <see cref="SipClientTransaction" />.</param> /// <param name="status">The completion status.</param> /// <param name="response">The final response (or <c>null</c>).</param> /// <remarks> /// <para> /// The <paramref name="response"/> parameter will be passed as <c>null</c> if /// the transaction was completed without receiving a final message (such /// as a timeout). In this case, the agent should look to the <paramref name="status"/> /// property for the final disposition of the transaction. /// </para> /// <para> /// This method also handles the resubmission of the request with additional /// authentication information if necessary. /// </para> /// <para> /// The method may create a custom ACK <see cref="SipResponse" /> to be delivered /// back to the server by saving the response in the <paramref name="response"/> parameter. /// Otherwise, if this value is left as <c>null</c>, the client transaction will /// generate a default ACK response and send it. /// </para> /// </remarks> internal void OnInviteComplete(SipClientTransaction transaction, SipStatus status, SipResponse response) { var arClient = (ClientAsyncResult)transaction.AgentState; var args = new SipResponseEventArgs(status, response, transaction, arClient.Dialog, this, this.core); if (response == null) { // The operation has completed without receiving a final response (probably // due to a time out or some kind of transport related problem). arClient.SipResult = new SipResult(arClient.Request, arClient.Dialog, this, status); arClient.Notify(); core.OnResponseReceived(args); core.OnInviteFailed(args, status); return; } // We have the final response. Compute the dialog ID from the response's // Call-ID, and To/From tags and assign it to the dialog. arClient.Dialog.ID = SipDialog.GetDialogID(response); // Call the core's OnInviteConfirmed() method so it can perform // any dialog specific activities. if (response.IsSuccess) { core.OnInviteConfirmed(args); } else { core.OnInviteFailed(args, status); } // Signal completion of the async operation. arClient.SipResult = new SipResult(arClient.Request, arClient.Dialog, this, response); arClient.Notify(); }
/// <summary> /// Called when a non-INVITE transaction completes. /// </summary> /// <param name="transaction">The source <see cref="SipClientTransaction" />.</param> /// <param name="status">The completion status.</param> /// <param name="response">The final response (or <c>null</c>).</param> /// <remarks> /// <para> /// The <paramref name="response"/> parameter will be passed as <c>null</c> if /// the transaction was completed without receiving a final message (such /// as a timeout). In this case, the agent should look to the <paramref name="status"/> /// property for the final disposition of the transaction. /// </para> /// <para> /// This method also handles the resubmission of the request with additional /// authentication information if necessary. /// </para> /// </remarks> internal void OnComplete(SipClientTransaction transaction, SipStatus status, SipResponse response) { var arClient = (ClientAsyncResult)transaction.AgentState; if (response == null) { // The operation has completed without receiving a final response (probably // due to a time out or some kind of transport related problem). arClient.SipResult = new SipResult(arClient.Request, arClient.Dialog, this, status); arClient.Notify(); core.OnResponseReceived(new SipResponseEventArgs(status, response, transaction, arClient.Dialog, this, this.core)); return; } // We have the final response. arClient.SipResult = new SipResult(arClient.Request, arClient.Dialog, this, response); arClient.Notify(); core.OnResponseReceived(new SipResponseEventArgs(response.Status, response, transaction, arClient.Dialog, this, this.core)); }
/// <summary> /// Creates a <see cref="SipResponse" /> for this request, copying the minimum /// required headers from the request to the response. /// </summary> /// <param name="status">The status code.</param> /// <param name="reasonPhrase">The reason phrase (or <c>null</c>).</param> /// <returns>The <see cref="SipResponse" />.</returns> /// <remarks> /// <para> /// The procedure for doing this is described in RFC 3261 on page 50. /// </para> /// <note> /// This method assumes that the <b>tag</b> parameter on the <b>To</b> /// header has already been added (if necessary). /// </note> /// </remarks> public SipResponse CreateResponse(SipStatus status, string reasonPhrase) { SipResponse response = new SipResponse(status, reasonPhrase, this.SipVersion); SipHeader header; header = this[SipHeader.Via]; if (header != null) { response.Headers.Add(SipHeader.Via, header.Clone()); } header = this[SipHeader.To]; if (header != null) { response.Headers.Add(SipHeader.To, header.Clone()); } header = this[SipHeader.From]; if (header != null) { response.Headers.Add(SipHeader.From, header.Clone()); } header = this[SipHeader.CallID]; if (header != null) { response.Headers.Add(SipHeader.CallID, header.Clone()); } header = this[SipHeader.CSeq]; if (header != null) { response.Headers.Add(SipHeader.CSeq, header.Clone()); } return(response); }
/// <summary> /// This method will be called periodically on a background thread to handle /// message resending as well as timeout related state transitions. /// </summary> public override void OnBkTask() { bool callOnComplete = false; bool callOnProceeding = false; SipResponse callbackMsg = null; SipStatus status = SipStatus.OK; try { using (TimedLock.Lock(agent)) { switch (base.State) { default: case SipTransactionState.Unknown: break; case SipTransactionState.InviteCalling: if (base.TimerB.HasFired) { // Request has timed out. SetState(SipTransactionState.Terminated); // Setup to call the agent's completion method callOnComplete = true; callbackMsg = null; status = SipStatus.RequestTimeout; return; } if (base.TimerA.HasFired) { // Retransmit the request and reset the // timer for exponential backoff transport.Send(remoteEP, request); base.TimerA.Interval = new TimeSpan(base.TimerA.Interval.Ticks * 2); } break; case SipTransactionState.InviteProceeding: if (base.TimerB.HasFired) { // Request has timed out. SetState(SipTransactionState.Terminated); // Setup to call the agent's completion method callOnComplete = true; callbackMsg = null; status = SipStatus.RequestTimeout; return; } break; case SipTransactionState.InviteCompleted: if (base.TimerD.HasFired) { SetState(SipTransactionState.Terminated); } break; case SipTransactionState.Trying: if (base.TimerF.HasFired) { // Request has timed out. SetState(SipTransactionState.Terminated); // Setup to call the agent's completion method callOnComplete = true; callbackMsg = null; status = SipStatus.RequestTimeout; return; } if (isUdp && base.TimerE.HasFired) { // Retransmit for UDP transport.Send(remoteEP, request); base.TimerE.Interval = Helper.Min(base.BaseTimers.T2, Helper.Multiply(base.TimerE.Interval, 2)); } break; case SipTransactionState.Proceeding: if (base.TimerF.HasFired) { // Request has timed out. SetState(SipTransactionState.Terminated); // Setup to call the agent's completion method callOnComplete = true; callbackMsg = null; status = SipStatus.RequestTimeout; return; } if (base.TimerE.HasFired) { // Retransmit for all transports. transport.Send(remoteEP, request); base.TimerE.Interval = base.BaseTimers.T2; } break; case SipTransactionState.Completed: if (base.TimerK.HasFired) { SetState(SipTransactionState.Terminated); } break; case SipTransactionState.Terminated: break; } } } finally { // Handle the agent callbacks outside of the lock to avoid // deadlock issues. if (callOnComplete) { agent.OnComplete(this, status, callbackMsg); } if (callOnProceeding) { agent.OnProceeding(this, (SipResponse)callbackMsg); } } }
/// <summary> /// The managing <see cref="ISipAgent" /> is responsible for calling this /// method whenever it receives responses correlated to this transaction. /// </summary> /// <param name="transport">The source <see cref="ISipTransport" />.</param> /// <param name="response">The received <see cref="SipResponse" />.</param> public void OnResponse(ISipTransport transport, SipResponse response) { bool callOnComplete = false; bool callOnProceeding = false; bool callOnInviteComplete = false; SipResponse callbackMsg = null; SipStatus status = SipStatus.OK; SipCSeqValue vCSeq; this.transport = transport; try { response.SourceTransaction = this; using (TimedLock.Lock(agent)) { // Ignore messages without a sequence number // // $todo(jeff.lill): Probably should check the method too vCSeq = response.GetHeader <SipCSeqValue>(SipHeader.CSeq); if (vCSeq == null) { return; } // Handle state specific processing switch (base.State) { default: case SipTransactionState.Unknown: SysLog.LogError("Unexpected SIP transaction state."); SetState(SipTransactionState.Terminated); // Setup to call the agent's completion method callOnComplete = true; callbackMsg = null; status = SipStatus.Stack_ProtocolError; return; case SipTransactionState.InviteCalling: if (!request.MatchCSeq(response)) { return; // Ignore responses whose CSeq header doesn't match the request } if (response.IsProvisional) { // Provisional response. SetState(SipTransactionState.InviteProceeding); // Setup to call the agent's proceeding method callOnProceeding = true; callbackMsg = response; status = response.Status; return; } if (response.IsNonSuccessFinal) { // Final response non-2xx response. Generate and // send the ACK request to the server to squelch // any further responses and then enter the // InviteCompleted state to absorb any responses // that do make it through. ackRequest = CreateAckRequest(request, response); transport.Send(remoteEP, ackRequest); SetState(SipTransactionState.InviteCompleted); // Setup to call the agent's invite completed method callOnInviteComplete = true; callbackMsg = response; status = response.Status; return; } // Must be a 2xx response. Setup to call the agent's // completed method and enter the terminated state // without sending an ACK request. // // Note that the agent is required to do this as // described in RFC 3261 on pages 128-129. SetState(SipTransactionState.Terminated); callOnInviteComplete = true; callbackMsg = response; status = response.Status; break; case SipTransactionState.InviteProceeding: if (!request.MatchCSeq(response)) { return; // Ignore responses whose CSeq header doesn't match the request } if (response.IsProvisional) { // Setup to call the agent's proceeding method callOnProceeding = true; callbackMsg = response; status = response.Status; return; } if (response.IsNonSuccessFinal) { // Final response non-2xx response. Generate and // send the ACK request to the server to squelch // any further responses and then enter the // InviteCompleted state to absorb any responses // that do make it through. // $todo(jeff.lill): // // I need to figure out a way to // map to the dialog so that it // can generate the ACK rather than // doing this locally. ackRequest = CreateAckRequest(request, response); transport.Send(remoteEP, ackRequest); SetState(SipTransactionState.InviteCompleted); // Setup to call the agent's invite completed method callOnInviteComplete = true; callbackMsg = response; status = response.Status; return; } // Must be a 2xx response. Setup to call the agent's // completed method and enter the terminated state // without sending an ACK request. // // Note that the agent is required to do this as // described in RFC 3261 on pages 128-129. SetState(SipTransactionState.Terminated); callOnInviteComplete = true; callbackMsg = response; status = response.Status; break; case SipTransactionState.InviteCompleted: // Retransmit the ACK if we get another final response if (response.IsFinal) { transport.Send(remoteEP, ackRequest); } break; case SipTransactionState.Trying: if (!request.MatchCSeq(response)) { return; // Ignore responses whose CSeq header doesn't match the request } if (response.IsProvisional) { // Provisional response. SetState(SipTransactionState.Proceeding); // Setup to call the agent's proceeding method callOnProceeding = true; callbackMsg = response; status = response.Status; return; } else { // Final response SetState(SipTransactionState.Completed); // Setup to call the agent's completion method callOnComplete = true; callbackMsg = response; status = response.Status; return; } case SipTransactionState.Proceeding: if (!request.MatchCSeq(response)) { return; // Ignore responses whose CSeq header doesn't match the request } if (response.IsProvisional) { // Setup to call the agent's proceeding method callOnProceeding = true; callbackMsg = response; status = response.Status; return; } // Final response. SetState(SipTransactionState.Completed); // Setup to call the agent's completion method callOnComplete = true; callbackMsg = response; status = response.Status; return; case SipTransactionState.Completed: break; case SipTransactionState.Terminated: break; } } } finally { // Handle the agent callbacks outside of the lock to avoid // deadlock issues. if (callOnProceeding) { agent.OnProceeding(this, callbackMsg); } if (callOnComplete) { agent.OnComplete(this, status, callbackMsg); } if (callOnInviteComplete) { agent.OnInviteComplete(this, status, response); } } }
/// <summary> /// Returns <c>true</c> if <see cref="SipStatus" /> code is in /// the range of 100-199, indicating a provisional response. /// </summary> /// <param name="status">The <see cref="SipStatus" /> code to check.</param> public static bool IsProvisional(SipStatus status) { return((int)status <= 199); }
/// <summary> /// Returns <c>true</c> if <see cref="SipStatus" /> code is in /// the range of 400-699, indicating an error response. /// </summary> /// <param name="status">The <see cref="SipStatus" /> code to check.</param> public static bool IsError(SipStatus status) { return((int)status >= 400); }
/// <summary> /// Returns <c>true</c> if <see cref="SipStatus" /> code is in /// the range of 200-299, indicating a successful response. /// </summary> /// <param name="status">The <see cref="SipStatus" /> code to check.</param> public static bool IsSuccess(SipStatus status) { return(200 <= (int)status && (int)status <= 299); }
/// <summary> /// Returns <c>true</c> if <see cref="SipStatus" /> code is in /// the range of 200-699, indicating a final response. /// </summary> /// <param name="status">The <see cref="SipStatus" /> code to check.</param> public static bool IsFinal(SipStatus status) { return((int)status >= 200); }
/// <summary> /// Returns the textual reason phrase for a <see cref="SipStatus" /> value. /// </summary> /// <param name="status">The status.</param> /// <returns>The phrase.</returns> public static string GetReasonPhrase(SipStatus status) { return(GetReasonPhrase((int)status)); }