Example #1
0
        //---------------------------------------------------------------------
        // 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);
        }
Example #2
0
        /// <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);
        }
Example #3
0
 /// <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;
 }
Example #4
0
 /// <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;
 }
Example #5
0
 /// <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;
 }
Example #6
0
 /// <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;
 }
Example #7
0
        /// <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;
        }
Example #8
0
        /// <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;
        }
Example #9
0
        /// <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));
        }
Example #10
0
        /// <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);
        }
Example #11
0
        /// <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);
                }
            }
        }
Example #12
0
        /// <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);
        }
Example #13
0
        /// <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));
        }
Example #14
0
        /// <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);
                }
            }
        }
Example #15
0
        /// <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);
                }
            }
        }
Example #16
0
        /// <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();
        }
Example #17
0
        /// <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);
        }
Example #18
0
        /// <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
            {
            }
        }