//--------------------------------------------------------------------- // Event handling methods called by SipClientTransaction. /// <summary> /// Handles messages received by a transport to be processed by this agent. /// </summary> /// <param name="transport">The source transport.</param> /// <param name="message">The received message.</param> public void OnReceive(ISipTransport transport, SipMessage message) { SipResponse response = (SipResponse)message; SipClientTransaction transaction; string transactionID; if (response == null) { return; // Ignore any requests } // Route the message to the correct transaction. if (!response.TryGetTransactionID(out transactionID)) { return; } using (TimedLock.Lock(this)) { if (!transactions.TryGetValue(transactionID, out transaction)) { // The response doesn't map to an existing transaction. // We're going to pass this to the core's OnUncorrelatedResponse() // method. core.OnUncorrelatedResponse(this, response); return; } } transaction.OnResponse(transport, response); }
/// <summary> /// Returns a deep clone of the response. /// </summary> /// <returns>The cloned <see cref="SipResponse" />.</returns> public SipResponse Clone() { var clone = new SipResponse(status, reasonPhrase, base.SipVersion); clone.CopyFrom(this); return(clone); }
/// <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="dialog">The <see cref="SipDialog" />.</param> /// <param name="transaction">The <see cref="SipTransaction" /> associated with the event (or <c>null</c>).</param> /// <param name="core">The <see cref="SipCore" /> that raised the event.</param> /// <param name="clientRequest">Pass the <see cref="SipRequest" /> received for server side dialog creation (or <c>null</c>).</param> /// <param name="serverResult">The <see cref="SipResult" /> returned by the server, completing its side of the dialog creation (or <c>null</c>).</param> internal SipDialogEventArgs(SipDialog dialog, SipTransaction transaction, SipCore core, SipRequest clientRequest, SipResult serverResult) { this.Dialog = dialog; this.Transaction = transaction; this.Core = core; this.ClientRequest = clientRequest; this.ServerResult = serverResult; this.Response = null; }
/// <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="agent">The <see cref="ISipAgent" /> that owns this transaction.</param> /// <param name="id">The globally unique transaction ID.</param> /// <param name="transport">The <see cref="ISipTransport" /> to be used for this transaction.</param> /// <param name="ttd">(Time-to-die) The time (SYS) where the transaction should terminate itself regardless of its current state.</param> public SipServerTransaction(SipServerAgent agent, string id, ISipTransport transport, DateTime ttd) : base(agent, id, transport) { this.ttd = ttd; this.agent = (SipServerAgent)agent; this.transport = transport; this.isUdp = !transport.IsStreaming; this.request = null; this.remoteEP = null; this.provisionalResponse = null; this.finalResponse = null; }
/// <summary> /// Constructs B2BUA event arguments for a received <see cref="SipResponse" />. /// </summary> /// <param name="core">The <see cref="SipCore" /> that raised the event.</param> /// <param name="b2bUserAgent">The <see cref="SipB2BUserAgent{TState}" /> that raised the event.</param> /// <param name="session">The <see cref="SipB2BUASession{TState}" /> associated with the event.</param> /// <param name="receivedResponse">The received <see cref="SipResponse" />.</param> /// <param name="response">The proposed <see cref="SipResponse" /> to be forwarded.</param> internal SipB2BUAEventArgs(SipCore core, SipB2BUserAgent <TState> b2bUserAgent, SipB2BUASession <TState> session, SipResponse receivedResponse, SipResponse response) { Assertion.Test(receivedResponse != null); Assertion.Test(response != null); this.Core = core; this.B2BUserAgent = b2bUserAgent; this.Session = session; this.ReceivedResponse = receivedResponse; this.Response = response; }
/// <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> /// The managing <see cref="ISipAgent" /> is responsible for calling this /// method whenever it receives requests correlated to this transaction. /// </summary> /// <param name="request">The received <see cref="SipRequest" />.</param> public void OnRequest(SipRequest request) { SipRequest callbackMsg = null; bool callOnRequest = false; bool callOnInviteBegin = false; bool callOnInviteComplete = false; try { request.SourceTransaction = this; using (TimedLock.Lock(agent)) { if (this.request == null) { SipViaValue viaValue; SipContactValue toValue; NetworkBinding sentBy; IPAddress address; // This is the initial transaction request. this.request = request; // Handle the Via "received" and "rport" header parameters (mostly) as described on page // RFC 3261 (page 145) and RFC 3581 (page 4). viaValue = request.GetHeader <SipViaValue>(SipHeader.Via); if (viaValue == null) { // Illegal request SetState(SipTransactionState.Terminated); return; } sentBy = viaValue.SentByBinding; if (sentBy == null || sentBy.IsHost || sentBy.Address != request.RemoteEndpoint.Address) { viaValue.Received = request.RemoteEndpoint.Address.ToString(); } if (viaValue.RPort != null) { viaValue.RPort = request.RemoteEndpoint.Port.ToString(); } // Determine the destination network endpoint based on the // rules described on RFC 3261 (page 146). if (request.SourceTransport.IsStreaming) { // $todo(jeff.lill): // // This implementation is incomplete. To be fully // compliant with the RFC, I'd have to check to // see if the connection is still present in the // transport and if not, use the received and // rport values as described. remoteEP = request.RemoteEndpoint; } else { if (viaValue.MAddr != null) { if (!IPAddress.TryParse(viaValue.MAddr, out address)) { SipException e; // Illegal request SetState(SipTransactionState.Terminated); e = new SipException("Illegal request: Invalid [Via: maddr]."); e.Transport = transport.Name; e.SourceEndpoint = request.RemoteEndpoint; e.BadMessage = request; throw e; } remoteEP = new NetworkBinding(address, viaValue.SentByBinding.Port); } else { remoteEP = request.RemoteEndpoint; } } // INVITE and non-INVITE requests have different state machines. if (request.Method == SipMethod.Invite) { // Start an INVITE transaction SetState(SipTransactionState.InviteProceeding); // If the request has a "To" header without a "tag" parameter then // generate a tag. Note that this code will cause provisional INVITE // responses to include a generated tag which the RFC indicates // SHOULD NOT be done. But, it's much safer to do this once here // for all transaction types, avoiding special cases, and besides, // I've noticed that Asterisk includes a tag in its provisional // INVITE responses. toValue = request.GetHeader <SipContactValue>(SipHeader.To); if (toValue != null) { if (toValue["tag"] == null) { toValue["tag"] = SipHelper.GenerateTagID(); } request.SetHeader(SipHeader.To, toValue); } // Always send an initial provisional trying response. provisionalResponse = request.CreateResponse(SipStatus.Trying, null); SendResponse(provisionalResponse); // Setup to call the agent's OnInviteBegin() method. callOnInviteBegin = true; callbackMsg = request; } else if (request.Method == SipMethod.Ack) { // Allow an ACK request to drop through to the state machine. } else { // Start a non-INVITE transaction SetState(SipTransactionState.Trying); // Setup to call the agent's OnRequest() method. callOnRequest = true; callbackMsg = request; } return; } // Handle state specific processing switch (base.State) { default: case SipTransactionState.Unknown: SysLog.LogError("Unexpected SIP transaction state."); SetState(SipTransactionState.Terminated); return; case SipTransactionState.InviteCalling: break; case SipTransactionState.InviteProceeding: if (provisionalResponse != null) { transport.Send(remoteEP, provisionalResponse); } break; case SipTransactionState.InviteCompleted: if (request.Method == SipMethod.Ack) { SetState(SipTransactionState.InviteConfirmed); // Setup to call OnInviteComplete(ack); callOnInviteComplete = true; callbackMsg = request; return; } Assertion.Test(finalResponse != null); transport.Send(remoteEP, finalResponse); break; case SipTransactionState.InviteConfirmed: break; case SipTransactionState.Trying: break; case SipTransactionState.Proceeding: Assertion.Test(provisionalResponse != null); transport.Send(remoteEP, provisionalResponse); break; case SipTransactionState.Completed: Assertion.Test(finalResponse != null); transport.Send(remoteEP, finalResponse); break; case SipTransactionState.Terminated: break; } } } finally { // Handle the agent callbacks outside of the lock to avoid // deadlock issues. if (callOnRequest) { agent.OnRequest(this, callbackMsg); } if (callOnInviteBegin) { agent.OnInviteBegin(this, request); } if (callOnInviteComplete) { agent.OnInviteComplete(this, this.request, finalResponse, callbackMsg); } } }
/// <summary> /// Creates the proper ACK request based on the original INVITE request sent /// to the server and the 2xx response received. /// </summary> /// <param name="inviteRequest">The INVITE <see cref="SipRequest" /> sent to the server.</param> /// <param name="response">The 2xx <see cref="SipResponse" /> received from the server.</param> /// <returns>The created ACK <see cref="SipRequest" />.</returns> private SipRequest CreateAckRequest(SipRequest inviteRequest, SipResponse response) { SipRequest ackRequest; SipHeader callID; SipHeader to; SipHeader from; SipHeader via; SipHeader contact; SipHeader route; SipCSeqValue vCSeq; if (inviteRequest.Method != SipMethod.Invite) { throw new ArgumentException("INVITE request expected.", "inviteRequest"); } if (response.IsProvisional) { throw new ArgumentException("Non-provisional response expected.", "response"); } ackRequest = new SipRequest(SipMethod.Ack, inviteRequest.Uri, null); callID = inviteRequest[SipHeader.CallID]; to = response[SipHeader.To]; from = inviteRequest[SipHeader.From]; via = inviteRequest[SipHeader.Via]; contact = inviteRequest[SipHeader.Contact]; route = inviteRequest[SipHeader.Route]; if (callID == null) { throw new SipException("INVITE request is missing header: [Call-ID]"); } if (to == null) { throw new SipException("INVITE response is missing header: [To]"); } if (from == null) { throw new SipException("INVITE request is missing header: [From]"); } if (contact == null) { throw new SipException("INVITE request is missing header: [Contact]"); } if (via == null) { throw new SipException("INVITE request is missing header: [Via]"); } vCSeq = inviteRequest.GetHeader <SipCSeqValue>(SipHeader.CSeq); if (vCSeq == null) { throw new SipException("INVITE request is missing header: [CSeq]"); } ackRequest.AddHeader(SipHeader.Via, via.Text); ackRequest.AddHeader(SipHeader.To, to.Text); ackRequest.AddHeader(SipHeader.From, from.Text); ackRequest.AddHeader(SipHeader.Contact, contact.Text); ackRequest.AddHeader(SipHeader.CallID, callID.Text); ackRequest.AddHeader(SipHeader.CSeq, new SipCSeqValue(vCSeq.Number, "ACK")); ackRequest.AddHeader(SipHeader.MaxForwards, SipHelper.MaxForwards); ackRequest.AddHeader(SipHeader.UserAgent, agent.Core.Settings.UserAgent); if (route != null) { ackRequest.Headers.Add(SipHeader.Route, route.Clone()); } return(ackRequest); }
/// <summary> /// Called when a transaction receives a 1xx response. /// </summary> /// <param name="transaction">The source <see cref="SipClientTransaction" />.</param> /// <param name="response">The <see cref="SipResponse" /> received.</param> internal void OnProceeding(SipClientTransaction transaction, SipResponse response) { var arClient = (ClientAsyncResult)transaction.AgentState; core.OnResponseReceived(new SipResponseEventArgs(response.Status, response, transaction, arClient.Dialog, this, this.core)); }
/// <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> /// 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> /// Parses a <see cref="SipRequest" /> or <see cref="SipResponse" /> from the /// <paramref name="buffer"/> byte array passed. /// </summary> /// <param name="buffer">The UTF-8 encoded SIP message data.</param> /// <param name="isCompletePacket"><c>true</c> if <paramref name="buffer" /> includes both the message header and payload.</param> /// <returns>A <see cref="SipRequest" /> or <see cref="SipResponse" />.</returns> /// <remarks> /// <para> /// This method is used internally by <see cref="ISipTransport" /> implementations to /// convert raw data read from the transport into SIP messages. This method supports /// two basic situations: /// </para> /// <list type="bullet"> /// <item> /// The transport is packet oriented (aka UDP) and the buffer passed includes /// both the SIP message header and content information. In this case, you /// need to pass <paramref name="isCompletePacket"/> as <c>true</c>, and the /// SIP message returned will be complete, with the <see cref="Contents" /> /// property set to the payload bytes. /// </item> /// <item> /// The transport is stream oriented (aka TCP or TLS) and the buffer passed /// includes only the message header information from the message start line /// up to and including the empty line terminating the header section. In this /// case, the <see cref="Contents" /> property of the SIP message returned will /// be set to <c>null</c> and <see cref="ContentLength" /> will be set to the /// parsed value. The transport is then responsible for extracting the message /// payload from the stream and explictly setting the <see cref="Contents" /> /// property. /// </item> /// </list> /// </remarks> public static SipMessage Parse(byte[] buffer, bool isCompletePacket) { string text; int dataPos; int p, pEnd, pColon; string firstLine; string line; string reasonPhrase; string[] fields; SipMessage message; SipHeader header; int cbContents; dataPos = Helper.IndexOf(buffer, CRLFCRLF); if (dataPos == -1) { throw new SipException("Malformed SIP message: header termination missing."); } dataPos += 4; text = Helper.FromUTF8(buffer, 0, dataPos); // Look at the first line of text to determine whether we have a // request or a response. p = 0; pEnd = text.IndexOf("\r\n"); firstLine = text.Substring(p, pEnd - p); fields = firstLine.Split(' '); if (fields.Length < 3) { throw new SipException("Malformed SIP message: invalid first line."); } p = firstLine.IndexOf(' '); p = firstLine.IndexOf(' ', p + 1); reasonPhrase = firstLine.Substring(p + 1); if (fields[0].ToUpper().StartsWith("SIP/")) { int statusCode; if (!int.TryParse(fields[1], out statusCode)) { throw new SipException("Malformed SIP message: invalid response status code."); } if (statusCode < 100 || statusCode >= 700) { throw new SipException("Invalid status code [{0}].", statusCode); } message = new SipResponse(statusCode, reasonPhrase, fields[0]); } else { message = new SipRequest(fields[0], fields[1], reasonPhrase); } // Parse the headers header = null; p = pEnd + 2; while (true) { pEnd = text.IndexOf("\r\n", p); line = text.Substring(p, pEnd - p); if (line.Length == 0) { break; } if (line[0] == ' ' || line[0] == '\t') { // Header folding if (header == null) { throw new SipException("Malformed SIP message: invalid header folding."); } header.FullText += " " + line.Trim(); } else { string name; string value; // Parse a normal header: <field-name> ":" <field-value> pColon = line.IndexOf(':'); if (pColon == -1) { throw new SipException("Malformed SIP message: header missing a colon."); } name = line.Substring(0, pColon).Trim(); value = line.Substring(pColon + 1).Trim(); header = message.headers.Add(name, value); } p = pEnd + 2; } // Handle the payload if (isCompletePacket) { // Extract the contents from the buffer. If we have a Content-Length header // then use that to determine how much data we have, otherwise extract the // data from the end of the headers to the end of the buffer. header = message[SipHeader.ContentLength]; if (header != null) { if (!int.TryParse(header.Text, out cbContents) || cbContents < 0 || cbContents > buffer.Length - dataPos) { throw new SipException("Malformed SIP message: invalid Content-Length."); } message.contents = Helper.Extract(buffer, dataPos, cbContents); } else { message.contents = Helper.Extract(buffer, dataPos); } } else { // Messages received from streaming transports must have a valid // Content-Length header. header = message[SipHeader.ContentLength]; if (header == null) { throw new SipException("Content-Length required for messages received on stream transports."); } if (!int.TryParse(header.Text, out cbContents) || cbContents < 0) { throw new SipException("Malformed SIP message: invalid Content-Length."); } } return(message); }
/// <summary> /// The managing <see cref="ISipAgent" /> is responsible for calling this /// method whenever it needs to send a response for the transaction. /// </summary> /// <param name="response">The <see cref="SipResponse" /> (or <c>null</c> to abort).</param> /// <remarks> /// You may pass <paramref name="response"/> as <c>null</c> to abort the transaction /// without sending a response. This is equivalent to calling <see cref="Abort" />. /// </remarks> public void SendResponse(SipResponse response) { try { using (TimedLock.Lock(agent)) { if (response == null) { // Handle aborting by transitioning to the completed state so // request retransmits will continue to be absorbed by the // transaction. SetState(SipTransactionState.Completed); return; } // Handle state specific processing switch (base.State) { default: case SipTransactionState.Unknown: SysLog.LogError("Unexpected SIP transaction state."); SetState(SipTransactionState.Terminated); return; case SipTransactionState.InviteCalling: break; case SipTransactionState.InviteProceeding: if (response.IsProvisional) { // Provisional provisionalResponse = response; transport.Send(remoteEP, provisionalResponse); return; } if (response.IsSuccess) { // Final response (success) finalResponse = response; transport.Send(remoteEP, finalResponse); SetState(SipTransactionState.Terminated); return; } // Final response (error) finalResponse = response; transport.Send(remoteEP, finalResponse); SetState(SipTransactionState.InviteCompleted); break; case SipTransactionState.InviteCompleted: break; case SipTransactionState.InviteConfirmed: break; case SipTransactionState.Trying: if (response.IsProvisional) { // Provisional provisionalResponse = response; transport.Send(remoteEP, provisionalResponse); SetState(SipTransactionState.Proceeding); return; } // Final response finalResponse = response; transport.Send(remoteEP, finalResponse); SetState(SipTransactionState.Completed); break; case SipTransactionState.Proceeding: if (response.IsProvisional) { // Provisional provisionalResponse = response; transport.Send(remoteEP, provisionalResponse); return; } // Final response finalResponse = response; transport.Send(remoteEP, finalResponse); SetState(SipTransactionState.Completed); return; case SipTransactionState.Completed: break; case SipTransactionState.Terminated: break; } } } finally { } }